From 2259dbebf7e38d8a2cbb59f747cea837a7df75ae Mon Sep 17 00:00:00 2001 From: Jinqing Kuang Date: Thu, 26 Sep 2024 13:27:24 +0800 Subject: [PATCH 1/7] fix(query)[TS-5487]. Fix slow cleanup of metacahe on exit - Change the storage structure of the suid-related tagFilter list in the metacache to a hash set - During cleanup on exit, first clear the suid tagFilter list before cleaning the LRU cache to avoid excessive overhead. These modifications ensure that the metacache cleanup process is efficient and maintains good LRU invalidation performance. --- source/dnode/vnode/src/meta/metaCache.c | 188 ++++++++---------------- 1 file changed, 63 insertions(+), 125 deletions(-) diff --git a/source/dnode/vnode/src/meta/metaCache.c b/source/dnode/vnode/src/meta/metaCache.c index 91aa513aa6..36068d1447 100644 --- a/source/dnode/vnode/src/meta/metaCache.c +++ b/source/dnode/vnode/src/meta/metaCache.c @@ -40,7 +40,7 @@ typedef struct SMetaStbStatsEntry { } SMetaStbStatsEntry; typedef struct STagFilterResEntry { - SList list; // the linked list of md5 digest, extracted from the serialized tag query condition + SHashObj *set; // the set of md5 digest, extracted from the serialized tag query condition uint32_t hitTimes; // queried times for current super table } STagFilterResEntry; @@ -112,7 +112,7 @@ static void statsCacheClose(SMeta* pMeta) { static void freeCacheEntryFp(void* param) { STagFilterResEntry** p = param; - tdListEmpty(&(*p)->list); + taosHashCleanup((*p)->set); taosMemoryFreeClear(*p); } @@ -200,10 +200,12 @@ void metaCacheClose(SMeta* pMeta) { entryCacheClose(pMeta); statsCacheClose(pMeta); + taosHashClear(pMeta->pCache->sTagFilterResCache.pTableEntry); taosLRUCacheCleanup(pMeta->pCache->sTagFilterResCache.pUidResCache); (void)taosThreadMutexDestroy(&pMeta->pCache->sTagFilterResCache.lock); taosHashCleanup(pMeta->pCache->sTagFilterResCache.pTableEntry); + taosHashClear(pMeta->pCache->STbGroupResCache.pTableEntry); taosLRUCacheCleanup(pMeta->pCache->STbGroupResCache.pResCache); (void)taosThreadMutexDestroy(&pMeta->pCache->STbGroupResCache.lock); taosHashCleanup(pMeta->pCache->STbGroupResCache.pTableEntry); @@ -471,34 +473,6 @@ int32_t metaStatsCacheGet(SMeta* pMeta, int64_t uid, SMetaStbStats* pInfo) { return code; } -static int checkAllEntriesInCache(const STagFilterResEntry* pEntry, SArray* pInvalidRes, int32_t keyLen, - SLRUCache* pCache, uint64_t suid) { - SListIter iter = {0}; - tdListInitIter((SList*)&(pEntry->list), &iter, TD_LIST_FORWARD); - - SListNode* pNode = NULL; - uint64_t buf[3]; - buf[0] = suid; - - int32_t len = sizeof(uint64_t) * tListLen(buf); - - while ((pNode = tdListNext(&iter)) != NULL) { - memcpy(&buf[1], pNode->data, keyLen); - - // check whether it is existed in LRU cache, and remove it from linked list if not. - LRUHandle* pRes = taosLRUCacheLookup(pCache, buf, len); - if (pRes == NULL) { // remove the item in the linked list - if (taosArrayPush(pInvalidRes, &pNode) == NULL) { - return terrno; - } - } else { - bool ret = taosLRUCacheRelease(pCache, pRes, false); - } - } - - return 0; -} - static FORCE_INLINE void setMD5DigestInKey(uint64_t* pBuf, const char* key, int32_t keyLen) { memcpy(&pBuf[2], key, keyLen); } @@ -584,22 +558,11 @@ static void freeUidCachePayload(const void* key, size_t keyLen, void* value, voi if (pEntry != NULL && (*pEntry) != NULL) { int64_t st = taosGetTimestampUs(); - - SListIter iter = {0}; - tdListInitIter((SList*)&((*pEntry)->list), &iter, TD_LIST_FORWARD); - - SListNode* pNode = NULL; - while ((pNode = tdListNext(&iter)) != NULL) { - uint64_t* digest = (uint64_t*)pNode->data; - if (digest[0] == p[2] && digest[1] == p[3]) { - void* tmp = tdListPopNode(&((*pEntry)->list), pNode); - taosMemoryFree(tmp); - - double el = (taosGetTimestampUs() - st) / 1000.0; - metaInfo("clear items in meta-cache, remain cached item:%d, elapsed time:%.2fms", listNEles(&((*pEntry)->list)), - el); - break; - } + int32_t code = taosHashRemove((*pEntry)->set, &p[2], sizeof(uint64_t) * 2); + if (code == TSDB_CODE_SUCCESS) { + double el = (taosGetTimestampUs() - st) / 1000.0; + metaInfo("clear items in meta-cache, remain cached item:%d, elapsed time:%.2fms", taosHashGetSize((*pEntry)->set), + el); } } @@ -607,16 +570,30 @@ static void freeUidCachePayload(const void* key, size_t keyLen, void* value, voi } static int32_t addNewEntry(SHashObj* pTableEntry, const void* pKey, int32_t keyLen, uint64_t suid) { + int32_t code = TSDB_CODE_SUCCESS; + int32_t lino = 0; STagFilterResEntry* p = taosMemoryMalloc(sizeof(STagFilterResEntry)); - if (p == NULL) { - return terrno; - } + TSDB_CHECK_NULL(p, code, lino, _end, terrno); p->hitTimes = 0; - tdListInit(&p->list, keyLen); - TAOS_CHECK_RETURN(taosHashPut(pTableEntry, &suid, sizeof(uint64_t), &p, POINTER_BYTES)); - TAOS_CHECK_RETURN(tdListAppend(&p->list, pKey)); - return 0; + p->set = taosHashInit(1024, taosGetDefaultHashFunction(TSDB_DATA_TYPE_BINARY), false, HASH_NO_LOCK); + TSDB_CHECK_NULL(p->set, code, lino, _end, terrno); + code = taosHashPut(p->set, pKey, keyLen, NULL, 0); + TSDB_CHECK_CODE(code, lino, _end); + code = taosHashPut(pTableEntry, &suid, sizeof(uint64_t), &p, POINTER_BYTES); + TSDB_CHECK_CODE(code, lino, _end); + +_end: + if (code != TSDB_CODE_SUCCESS) { + metaError("%s failed at line %d since %s", __func__, lino, tstrerror(code)); + if (p != NULL) { + if (p->set != NULL) { + taosHashCleanup(p->set); + } + taosMemoryFree(p); + } + } + return code; } // check both the payload size and selectivity ratio @@ -657,25 +634,14 @@ int32_t metaUidFilterCachePut(void* pVnode, uint64_t suid, const void* pKey, int goto _end; } } else { // check if it exists or not - size_t size = listNEles(&(*pEntry)->list); - if (size == 0) { - code = tdListAppend(&(*pEntry)->list, pKey); - if (code) { - goto _end; - } - } else { - SListNode* pNode = listHead(&(*pEntry)->list); - uint64_t* p = (uint64_t*)pNode->data; - if (p[1] == ((uint64_t*)pKey)[1] && p[0] == ((uint64_t*)pKey)[0]) { - // we have already found the existed items, no need to added to cache anymore. - (void)taosThreadMutexUnlock(pLock); - return TSDB_CODE_SUCCESS; - } else { // not equal, append it - code = tdListAppend(&(*pEntry)->list, pKey); - if (code) { - goto _end; - } - } + code = taosHashPut((*pEntry)->set, pKey, keyLen, NULL, 0); + if (code == TSDB_CODE_DUP_KEY) { + // we have already found the existed items, no need to added to cache anymore. + (void)taosThreadMutexUnlock(pLock); + return TSDB_CODE_SUCCESS; + } + if (code != TSDB_CODE_SUCCESS) { + goto _end; } } @@ -703,23 +669,20 @@ int32_t metaUidCacheClear(SMeta* pMeta, uint64_t suid) { (void)taosThreadMutexLock(pLock); STagFilterResEntry** pEntry = taosHashGet(pEntryHashMap, &suid, sizeof(uint64_t)); - if (pEntry == NULL || listNEles(&(*pEntry)->list) == 0) { + if (pEntry == NULL || taosHashGetSize((*pEntry)->set) == 0) { (void)taosThreadMutexUnlock(pLock); return TSDB_CODE_SUCCESS; } (*pEntry)->hitTimes = 0; - SListIter iter = {0}; - tdListInitIter(&(*pEntry)->list, &iter, TD_LIST_FORWARD); - - SListNode* pNode = NULL; - while ((pNode = tdListNext(&iter)) != NULL) { - setMD5DigestInKey(p, pNode->data, 2 * sizeof(uint64_t)); + char *iter = taosHashIterate((*pEntry)->set, NULL); + while (iter != NULL) { + setMD5DigestInKey(p, iter, 2 * sizeof(uint64_t)); taosLRUCacheErase(pMeta->pCache->sTagFilterResCache.pUidResCache, p, TAG_FILTER_RES_KEY_LEN); + iter = taosHashIterate((*pEntry)->set, iter); } - - tdListEmpty(&(*pEntry)->list); + taosHashClear((*pEntry)->set); (void)taosThreadMutexUnlock(pLock); metaDebug("vgId:%d suid:%" PRId64 " cached related tag filter uid list cleared", vgId, suid); @@ -789,22 +752,11 @@ static void freeTbGroupCachePayload(const void* key, size_t keyLen, void* value, if (pEntry != NULL && (*pEntry) != NULL) { int64_t st = taosGetTimestampUs(); - - SListIter iter = {0}; - tdListInitIter((SList*)&((*pEntry)->list), &iter, TD_LIST_FORWARD); - - SListNode* pNode = NULL; - while ((pNode = tdListNext(&iter)) != NULL) { - uint64_t* digest = (uint64_t*)pNode->data; - if (digest[0] == p[2] && digest[1] == p[3]) { - void* tmp = tdListPopNode(&((*pEntry)->list), pNode); - taosMemoryFree(tmp); - - double el = (taosGetTimestampUs() - st) / 1000.0; - metaDebug("clear one item in tb group cache, remain cached item:%d, elapsed time:%.2fms", - listNEles(&((*pEntry)->list)), el); - break; - } + int32_t code = taosHashRemove((*pEntry)->set, &p[2], sizeof(uint64_t) * 2); + if (code == TSDB_CODE_SUCCESS) { + double el = (taosGetTimestampUs() - st) / 1000.0; + metaDebug("clear one item in tb group cache, remain cached item:%d, elapsed time:%.2fms", + taosHashGetSize((*pEntry)->set), el); } } @@ -840,25 +792,14 @@ int32_t metaPutTbGroupToCache(void* pVnode, uint64_t suid, const void* pKey, int goto _end; } } else { // check if it exists or not - size_t size = listNEles(&(*pEntry)->list); - if (size == 0) { - code = tdListAppend(&(*pEntry)->list, pKey); - if (code) { - goto _end; - } - } else { - SListNode* pNode = listHead(&(*pEntry)->list); - uint64_t* p = (uint64_t*)pNode->data; - if (p[1] == ((uint64_t*)pKey)[1] && p[0] == ((uint64_t*)pKey)[0]) { - // we have already found the existed items, no need to added to cache anymore. - (void)taosThreadMutexUnlock(pLock); - return TSDB_CODE_SUCCESS; - } else { // not equal, append it - code = tdListAppend(&(*pEntry)->list, pKey); - if (code) { - goto _end; - } - } + code = taosHashPut((*pEntry)->set, pKey, keyLen, NULL, 0); + if (code == TSDB_CODE_DUP_KEY) { + // we have already found the existed items, no need to added to cache anymore. + (void)taosThreadMutexUnlock(pLock); + return TSDB_CODE_SUCCESS; + } + if (code != TSDB_CODE_SUCCESS) { + goto _end; } } @@ -886,23 +827,20 @@ int32_t metaTbGroupCacheClear(SMeta* pMeta, uint64_t suid) { (void)taosThreadMutexLock(pLock); STagFilterResEntry** pEntry = taosHashGet(pEntryHashMap, &suid, sizeof(uint64_t)); - if (pEntry == NULL || listNEles(&(*pEntry)->list) == 0) { + if (pEntry == NULL || taosHashGetSize((*pEntry)->set) == 0) { (void)taosThreadMutexUnlock(pLock); return TSDB_CODE_SUCCESS; } (*pEntry)->hitTimes = 0; - SListIter iter = {0}; - tdListInitIter(&(*pEntry)->list, &iter, TD_LIST_FORWARD); - - SListNode* pNode = NULL; - while ((pNode = tdListNext(&iter)) != NULL) { - setMD5DigestInKey(p, pNode->data, 2 * sizeof(uint64_t)); + char *iter = taosHashIterate((*pEntry)->set, NULL); + while (iter != NULL) { + setMD5DigestInKey(p, iter, 2 * sizeof(uint64_t)); taosLRUCacheErase(pMeta->pCache->STbGroupResCache.pResCache, p, TAG_FILTER_RES_KEY_LEN); + iter = taosHashIterate((*pEntry)->set, iter); } - - tdListEmpty(&(*pEntry)->list); + taosHashClear((*pEntry)->set); (void)taosThreadMutexUnlock(pLock); metaDebug("vgId:%d suid:%" PRId64 " cached related tb group cleared", vgId, suid); From af5558b4b2222d89b400aa10954c43bfc40532e2 Mon Sep 17 00:00:00 2001 From: yihaoDeng Date: Thu, 26 Sep 2024 13:30:09 +0800 Subject: [PATCH 2/7] remove unused code --- source/os/src/osSocket.c | 762 ++------------------------------------- 1 file changed, 21 insertions(+), 741 deletions(-) diff --git a/source/os/src/osSocket.c b/source/os/src/osSocket.c index 2065ba68fe..09a5579e13 100644 --- a/source/os/src/osSocket.c +++ b/source/os/src/osSocket.c @@ -55,7 +55,7 @@ typedef struct TdSocket { #endif int refId; SocketFd fd; -} *TdSocketPtr, TdSocket; +} * TdSocketPtr, TdSocket; typedef struct TdSocketServer { #if SOCKET_WITH_LOCK @@ -63,7 +63,7 @@ typedef struct TdSocketServer { #endif int refId; SocketFd fd; -} *TdSocketServerPtr, TdSocketServer; +} * TdSocketServerPtr, TdSocketServer; typedef struct TdEpoll { #if SOCKET_WITH_LOCK @@ -71,52 +71,7 @@ typedef struct TdEpoll { #endif int refId; EpollFd fd; -} *TdEpollPtr, TdEpoll; - -#if 0 -int32_t taosSendto(TdSocketPtr pSocket, void *buf, int len, unsigned int flags, const struct sockaddr *dest_addr, - int addrlen) { - if (pSocket == NULL || pSocket->fd < 0) { - return -1; - } -#ifdef WINDOWS - return sendto(pSocket->fd, buf, len, flags, dest_addr, addrlen); -#else - return sendto(pSocket->fd, buf, len, flags, dest_addr, addrlen); -#endif -} - -int32_t taosWriteSocket(TdSocketPtr pSocket, void *buf, int len) { - if (pSocket == NULL || pSocket->fd < 0) { - return -1; - } -#ifdef WINDOWS - return send(pSocket->fd, buf, len, 0); - ; -#else - return write(pSocket->fd, buf, len); -#endif -} -int32_t taosReadSocket(TdSocketPtr pSocket, void *buf, int len) { - if (pSocket == NULL || pSocket->fd < 0) { - return -1; - } -#ifdef WINDOWS - return recv(pSocket->fd, buf, len, 0); - ; -#else - return read(pSocket->fd, buf, len); -#endif -} - -int32_t taosReadFromSocket(TdSocketPtr pSocket, void *buf, int32_t len, int32_t flags, struct sockaddr *destAddr, - int *addrLen) { - if (pSocket == NULL || pSocket->fd < 0) { - return -1; - } - return recvfrom(pSocket->fd, buf, len, flags, destAddr, addrLen); -} -#endif // endif 0 +} * TdEpollPtr, TdEpoll; int32_t taosCloseSocketNoCheck1(SocketFd fd) { #ifdef WINDOWS @@ -145,136 +100,16 @@ int32_t taosCloseSocket(TdSocketPtr *ppSocket) { code = taosCloseSocketNoCheck1((*ppSocket)->fd); (*ppSocket)->fd = -1; taosMemoryFree(*ppSocket); - + return code; } -#if 0 -int32_t taosCloseSocketServer(TdSocketServerPtr *ppSocketServer) { - int32_t code; - if (ppSocketServer == NULL || *ppSocketServer == NULL || (*ppSocketServer)->fd < 0) { - return -1; - } - code = taosCloseSocketNoCheck1((*ppSocketServer)->fd); - (*ppSocketServer)->fd = -1; - taosMemoryFree(*ppSocketServer); - return code; -} - -int32_t taosShutDownSocketRD(TdSocketPtr pSocket) { - if (pSocket == NULL || pSocket->fd < 0) { - return -1; - } -#ifdef WINDOWS - return closesocket(pSocket->fd); -#elif __APPLE__ - return close(pSocket->fd); -#else - return shutdown(pSocket->fd, SHUT_RD); -#endif -} -int32_t taosShutDownSocketServerRD(TdSocketServerPtr pSocketServer) { - if (pSocketServer == NULL || pSocketServer->fd < 0) { - return -1; - } -#ifdef WINDOWS - return closesocket(pSocketServer->fd); -#elif __APPLE__ - return close(pSocketServer->fd); -#else - return shutdown(pSocketServer->fd, SHUT_RD); -#endif -} - -int32_t taosShutDownSocketWR(TdSocketPtr pSocket) { - if (pSocket == NULL || pSocket->fd < 0) { - return -1; - } -#ifdef WINDOWS - return closesocket(pSocket->fd); -#elif __APPLE__ - return close(pSocket->fd); -#else - return shutdown(pSocket->fd, SHUT_WR); -#endif -} -int32_t taosShutDownSocketServerWR(TdSocketServerPtr pSocketServer) { - if (pSocketServer == NULL || pSocketServer->fd < 0) { - return -1; - } -#ifdef WINDOWS - return closesocket(pSocketServer->fd); -#elif __APPLE__ - return close(pSocketServer->fd); -#else - return shutdown(pSocketServer->fd, SHUT_WR); -#endif -} -int32_t taosShutDownSocketRDWR(TdSocketPtr pSocket) { - if (pSocket == NULL || pSocket->fd < 0) { - return -1; - } -#ifdef WINDOWS - return closesocket(pSocket->fd); -#elif __APPLE__ - return close(pSocket->fd); -#else - return shutdown(pSocket->fd, SHUT_RDWR); -#endif -} -int32_t taosShutDownSocketServerRDWR(TdSocketServerPtr pSocketServer) { - if (pSocketServer == NULL || pSocketServer->fd < 0) { - return -1; - } -#ifdef WINDOWS - return closesocket(pSocketServer->fd); -#elif __APPLE__ - return close(pSocketServer->fd); -#else - return shutdown(pSocketServer->fd, SHUT_RDWR); -#endif -} - -int32_t taosSetNonblocking(TdSocketPtr pSocket, int32_t on) { - if (pSocket == NULL || pSocket->fd < 0) { - return -1; - } -#ifdef WINDOWS - u_long mode; - if (on) { - mode = 1; - ioctlsocket(pSocket->fd, FIONBIO, &mode); - } else { - mode = 0; - ioctlsocket(pSocket->fd, FIONBIO, &mode); - } -#else - int32_t flags = 0; - if ((flags = fcntl(pSocket->fd, F_GETFL, 0)) < 0) { - // printf("fcntl(F_GETFL) error: %d (%s)\n", errno, strerror(errno)); - return 1; - } - - if (on) - flags |= O_NONBLOCK; - else - flags &= ~O_NONBLOCK; - - if ((flags = fcntl(pSocket->fd, F_SETFL, flags)) < 0) { - // printf("fcntl(F_SETFL) error: %d (%s)\n", errno, strerror(errno)); - return 1; - } -#endif - return 0; -} -#endif // endif 0 - int32_t taosSetSockOpt(TdSocketPtr pSocket, int32_t level, int32_t optname, void *optval, int32_t optlen) { if (pSocket == NULL || pSocket->fd < 0) { terrno = TSDB_CODE_INVALID_PARA; return terrno; } - + #ifdef WINDOWS #ifdef TCP_KEEPCNT if (level == SOL_SOCKET && optname == TCP_KEEPCNT) { @@ -300,11 +135,11 @@ int32_t taosSetSockOpt(TdSocketPtr pSocket, int32_t level, int32_t optname, void } #endif -int ret = setsockopt(pSocket->fd, level, optname, optval, optlen); -if (ret == SOCKET_ERROR) { - int errorCode = WSAGetLastError(); - return terrno = TAOS_SYSTEM_WINSOCKET_ERROR(errorCode); -} + int ret = setsockopt(pSocket->fd, level, optname, optval, optlen); + if (ret == SOCKET_ERROR) { + int errorCode = WSAGetLastError(); + return terrno = TAOS_SYSTEM_WINSOCKET_ERROR(errorCode); + } #else int32_t code = setsockopt(pSocket->fd, level, optname, optval, (int)optlen); if (-1 == code) { @@ -315,22 +150,8 @@ if (ret == SOCKET_ERROR) { #endif } -#if 0 -int32_t taosGetSockOpt(TdSocketPtr pSocket, int32_t level, int32_t optname, void *optval, int32_t *optlen) { - if (pSocket == NULL || pSocket->fd < 0) { - return -1; - } -#ifdef WINDOWS - return -1; -#else - return getsockopt(pSocket->fd, level, optname, optval, (int *)optlen); -#endif -} - -#endif - const char *taosInetNtoa(struct in_addr ipInt, char *dstStr, int32_t len) { - const char* r = inet_ntop(AF_INET, &ipInt, dstStr, len); + const char *r = inet_ntop(AF_INET, &ipInt, dstStr, len); if (NULL == r) { terrno = TAOS_SYSTEM_ERROR(errno); } @@ -344,403 +165,6 @@ const char *taosInetNtoa(struct in_addr ipInt, char *dstStr, int32_t len) { #define TCP_CONN_TIMEOUT 3000 // conn timeout -#if 0 -int32_t taosWriteMsg(TdSocketPtr pSocket, void *buf, int32_t nbytes) { - if (pSocket == NULL || pSocket->fd < 0) { - return -1; - } - int32_t nleft, nwritten; - char *ptr = (char *)buf; - - nleft = nbytes; - - while (nleft > 0) { - nwritten = taosWriteSocket(pSocket, (char *)ptr, (size_t)nleft); - if (nwritten <= 0) { - if (errno == EINTR /* || errno == EAGAIN || errno == EWOULDBLOCK */) - continue; - else - return -1; - } else { - nleft -= nwritten; - ptr += nwritten; - } - - if (errno == SIGPIPE || errno == EPIPE) { - return -1; - } - } - - return (nbytes - nleft); -} - -int32_t taosReadMsg(TdSocketPtr pSocket, void *buf, int32_t nbytes) { - if (pSocket == NULL || pSocket->fd < 0) { - return -1; - } - int32_t nleft, nread; - char *ptr = (char *)buf; - - nleft = nbytes; - - while (nleft > 0) { - nread = taosReadSocket(pSocket, ptr, (size_t)nleft); - if (nread == 0) { - break; - } else if (nread < 0) { - if (errno == EINTR /* || errno == EAGAIN || errno == EWOULDBLOCK*/) { - continue; - } else { - return -1; - } - } else { - nleft -= nread; - ptr += nread; - } - - if (errno == SIGPIPE || errno == EPIPE) { - return -1; - } - } - - return (nbytes - nleft); -} - -int32_t taosNonblockwrite(TdSocketPtr pSocket, char *ptr, int32_t nbytes) { - if (pSocket == NULL || pSocket->fd < 0) { - return -1; - } - taosSetNonblocking(pSocket, 1); - - int32_t nleft, nwritten, nready; - fd_set fset; - struct timeval tv; - - nleft = nbytes; - while (nleft > 0) { - tv.tv_sec = 30; - tv.tv_usec = 0; - FD_ZERO(&fset); - FD_SET(pSocket->fd, &fset); - if ((nready = select((SocketFd)(pSocket->fd + 1), NULL, &fset, NULL, &tv)) == 0) { - errno = ETIMEDOUT; - // printf("fd %d timeout, no enough space to write", fd); - break; - - } else if (nready < 0) { - if (errno == EINTR) continue; - - // printf("select error, %d (%s)", errno, strerror(errno)); - return -1; - } - - nwritten = (int32_t)send(pSocket->fd, ptr, (size_t)nleft, MSG_NOSIGNAL); - if (nwritten <= 0) { - if (errno == EAGAIN || errno == EINTR) continue; - - // printf("write error, %d (%s)", errno, strerror(errno)); - return -1; - } - - nleft -= nwritten; - ptr += nwritten; - } - - taosSetNonblocking(pSocket, 0); - - return (nbytes - nleft); -} - -TdSocketPtr taosOpenUdpSocket(uint32_t ip, uint16_t port) { - struct sockaddr_in localAddr; - SocketFd fd; - int32_t bufSize = 1024000; - - // printf("open udp socket:0x%x:%hu", ip, port); - - memset((char *)&localAddr, 0, sizeof(localAddr)); - localAddr.sin_family = AF_INET; - localAddr.sin_addr.s_addr = ip; - localAddr.sin_port = (uint16_t)htons(port); - - if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) <= 2) { - // printf("failed to open udp socket: %d (%s)", errno, strerror(errno)); - taosCloseSocketNoCheck1(fd); - return NULL; - } - - TdSocketPtr pSocket = (TdSocketPtr)taosMemoryMalloc(sizeof(TdSocket)); - if (pSocket == NULL) { - taosCloseSocketNoCheck1(fd); - return NULL; - } - pSocket->fd = fd; - pSocket->refId = 0; - - if (taosSetSockOpt(pSocket, SOL_SOCKET, SO_SNDBUF, (void *)&bufSize, sizeof(bufSize)) != 0) { - // printf("failed to set the send buffer size for UDP socket\n"); - taosCloseSocket(&pSocket); - return NULL; - } - - if (taosSetSockOpt(pSocket, SOL_SOCKET, SO_RCVBUF, (void *)&bufSize, sizeof(bufSize)) != 0) { - // printf("failed to set the receive buffer size for UDP socket\n"); - taosCloseSocket(&pSocket); - return NULL; - } - - /* bind socket to local address */ - if (bind(pSocket->fd, (struct sockaddr *)&localAddr, sizeof(localAddr)) < 0) { - // printf("failed to bind udp socket: %d (%s), 0x%x:%hu", errno, strerror(errno), ip, port); - taosCloseSocket(&pSocket); - return NULL; - } - - return pSocket; -} - -TdSocketPtr taosOpenTcpClientSocket(uint32_t destIp, uint16_t destPort, uint32_t clientIp) { - SocketFd fd = -1; - int32_t ret; - struct sockaddr_in serverAddr, clientAddr; - int32_t bufSize = 1024 * 1024; - - fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); - - if (fd <= 2) { - // printf("failed to open the socket: %d (%s)", errno, strerror(errno)); - if (fd >= 0) taosCloseSocketNoCheck1(fd); - return NULL; - } - - TdSocketPtr pSocket = (TdSocketPtr)taosMemoryMalloc(sizeof(TdSocket)); - if (pSocket == NULL) { - taosCloseSocketNoCheck1(fd); - return NULL; - } - pSocket->fd = fd; - pSocket->refId = 0; - - /* set REUSEADDR option, so the portnumber can be re-used */ - int32_t reuse = 1; - if (taosSetSockOpt(pSocket, SOL_SOCKET, SO_REUSEADDR, (void *)&reuse, sizeof(reuse)) < 0) { - // printf("setsockopt SO_REUSEADDR failed: %d (%s)", errno, strerror(errno)); - taosCloseSocket(&pSocket); - return NULL; - } - - if (taosSetSockOpt(pSocket, SOL_SOCKET, SO_SNDBUF, (void *)&bufSize, sizeof(bufSize)) != 0) { - // printf("failed to set the send buffer size for TCP socket\n"); - taosCloseSocket(&pSocket); - return NULL; - } - - if (taosSetSockOpt(pSocket, SOL_SOCKET, SO_RCVBUF, (void *)&bufSize, sizeof(bufSize)) != 0) { - // printf("failed to set the receive buffer size for TCP socket\n"); - taosCloseSocket(&pSocket); - return NULL; - } - - if (clientIp != 0) { - memset((char *)&clientAddr, 0, sizeof(clientAddr)); - clientAddr.sin_family = AF_INET; - clientAddr.sin_addr.s_addr = clientIp; - clientAddr.sin_port = 0; - - /* bind socket to client address */ - if (bind(pSocket->fd, (struct sockaddr *)&clientAddr, sizeof(clientAddr)) < 0) { - // printf("bind tcp client socket failed, client(0x%x:0), dest(0x%x:%d), reason:(%s)", clientIp, destIp, destPort, - // strerror(errno)); - taosCloseSocket(&pSocket); - return NULL; - } - } - - memset((char *)&serverAddr, 0, sizeof(serverAddr)); - serverAddr.sin_family = AF_INET; - serverAddr.sin_addr.s_addr = destIp; - serverAddr.sin_port = (uint16_t)htons((uint16_t)destPort); - -#ifdef _TD_LINUX - taosSetNonblocking(pSocket, 1); - ret = connect(pSocket->fd, (struct sockaddr *)&serverAddr, sizeof(serverAddr)); - if (ret == -1) { - if (errno == EHOSTUNREACH) { - // printf("failed to connect socket, ip:0x%x, port:%hu(%s)", destIp, destPort, strerror(errno)); - taosCloseSocket(&pSocket); - return -1; - } else if (errno == EINPROGRESS || errno == EAGAIN || errno == EWOULDBLOCK) { - struct pollfd wfd[1]; - - wfd[0].fd = pSocket->fd; - wfd[0].events = POLLOUT; - - int res = poll(wfd, 1, TCP_CONN_TIMEOUT); - if (res == -1 || res == 0) { - // printf("failed to connect socket, ip:0x%x, port:%hu(poll error/conn timeout)", destIp, destPort); - taosCloseSocket(&pSocket); // - return -1; - } - int optVal = -1, optLen = sizeof(int); - if ((0 != taosGetSockOpt(pSocket, SOL_SOCKET, SO_ERROR, &optVal, &optLen)) || (optVal != 0)) { - // printf("failed to connect socket, ip:0x%x, port:%hu(connect host error)", destIp, destPort); - taosCloseSocket(&pSocket); // - return -1; - } - ret = 0; - } else { // Other error - // printf("failed to connect socket, ip:0x%x, port:%hu(target host cannot be reached)", destIp, destPort); - taosCloseSocket(&pSocket); // - return -1; - } - } - taosSetNonblocking(pSocket, 0); - -#else - ret = connect(pSocket->fd, (struct sockaddr *)&serverAddr, sizeof(serverAddr)); -#endif - - if (ret != 0) { - // printf("failed to connect socket, ip:0x%x, port:%hu(%s)", destIp, destPort, strerror(errno)); - taosCloseSocket(&pSocket); - return NULL; - } else { - if (taosKeepTcpAlive(pSocket) == -1) { - return NULL; - } - } - - return pSocket; -} - -int32_t taosKeepTcpAlive(TdSocketPtr pSocket) { - if (pSocket == NULL || pSocket->fd < 0) { - return -1; - } - int32_t alive = 1; - if (taosSetSockOpt(pSocket, SOL_SOCKET, SO_KEEPALIVE, (void *)&alive, sizeof(alive)) < 0) { - // printf("fd:%d setsockopt SO_KEEPALIVE failed: %d (%s)", sockFd, errno, strerror(errno)); - taosCloseSocket(&pSocket); - return -1; - } - -#ifndef __APPLE__ - // all fails on macosx -#ifdef TCP_KEEPCNT - int32_t probes = 3; - if (taosSetSockOpt(pSocket, SOL_TCP, TCP_KEEPCNT, (void *)&probes, sizeof(probes)) < 0) { - // printf("fd:%d setsockopt SO_KEEPCNT failed: %d (%s)", sockFd, errno, strerror(errno)); - taosCloseSocket(&pSocket); - return -1; - } -#endif - -#ifdef TCP_KEEPIDLE - int32_t alivetime = 10; - if (taosSetSockOpt(pSocket, SOL_TCP, TCP_KEEPIDLE, (void *)&alivetime, sizeof(alivetime)) < 0) { - // printf("fd:%d setsockopt SO_KEEPIDLE failed: %d (%s)", sockFd, errno, strerror(errno)); - taosCloseSocket(&pSocket); - return -1; - } -#endif - -#ifdef TCP_KEEPINTVL - int32_t interval = 3; - if (taosSetSockOpt(pSocket, SOL_TCP, TCP_KEEPINTVL, (void *)&interval, sizeof(interval)) < 0) { - // printf("fd:%d setsockopt SO_KEEPINTVL failed: %d (%s)", sockFd, errno, strerror(errno)); - taosCloseSocket(&pSocket); - return -1; - } -#endif -#endif // __APPLE__ - - int32_t nodelay = 1; - if (taosSetSockOpt(pSocket, IPPROTO_TCP, TCP_NODELAY, (void *)&nodelay, sizeof(nodelay)) < 0) { - // printf("fd:%d setsockopt TCP_NODELAY failed %d (%s)", sockFd, errno, strerror(errno)); - taosCloseSocket(&pSocket); - return -1; - } - - struct linger linger = {0}; - linger.l_onoff = 1; - linger.l_linger = 3; - if (taosSetSockOpt(pSocket, SOL_SOCKET, SO_LINGER, (void *)&linger, sizeof(linger)) < 0) { - // printf("setsockopt SO_LINGER failed: %d (%s)", errno, strerror(errno)); - taosCloseSocket(&pSocket); - return -1; - } - - return 0; -} - -int taosGetLocalIp(const char *eth, char *ip) { -#if defined(WINDOWS) - // DO NOTHAING - return -1; -#else - int fd; - struct ifreq ifr; - struct sockaddr_in sin; - - fd = socket(AF_INET, SOCK_DGRAM, 0); - if (-1 == fd) { - return -1; - } - strncpy(ifr.ifr_name, eth, IFNAMSIZ); - ifr.ifr_name[IFNAMSIZ - 1] = 0; - - if (ioctl(fd, SIOCGIFADDR, &ifr) < 0) { - taosCloseSocketNoCheck1(fd); - return -1; - } - memcpy(&sin, &ifr.ifr_addr, sizeof(sin)); - taosInetNtoa(sin.sin_addr, ip, 64); - taosCloseSocketNoCheck1(fd); -#endif - return 0; -} -int taosValidIp(uint32_t ip) { -#if defined(WINDOWS) - // DO NOTHAING - return -1; -#else - int ret = -1; - int fd; - - struct ifconf ifconf; - - char buf[512] = {0}; - ifconf.ifc_len = 512; - ifconf.ifc_buf = buf; - - if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { - return -1; - } - - ioctl(fd, SIOCGIFCONF, &ifconf); - struct ifreq *ifreq = (struct ifreq *)ifconf.ifc_buf; - for (int i = (ifconf.ifc_len / sizeof(struct ifreq)); i > 0; i--) { - char ip_str[64] = {0}; - if (ifreq->ifr_flags == AF_INET) { - ret = taosGetLocalIp(ifreq->ifr_name, ip_str); - if (ret != 0) { - break; - } - ret = -1; - if (ip == (uint32_t)taosInetAddr(ip_str)) { - ret = 0; - break; - } - ifreq++; - } - } - taosCloseSocketNoCheck1(fd); - return ret; -#endif - return 0; -} -#endif // endif 0 - bool taosValidIpAndPort(uint32_t ip, uint16_t port) { struct sockaddr_in serverAdd; SocketFd fd; @@ -778,138 +202,19 @@ bool taosValidIpAndPort(uint32_t ip, uint16_t port) { TAOS_SKIP_ERROR(taosCloseSocket(&pSocket)); return false; } - + /* bind socket to server address */ if (-1 == bind(pSocket->fd, (struct sockaddr *)&serverAdd, sizeof(serverAdd))) { terrno = TAOS_SYSTEM_ERROR(errno); TAOS_SKIP_ERROR(taosCloseSocket(&pSocket)); return false; } - + TAOS_SKIP_ERROR(taosCloseSocket(&pSocket)); - + return true; } -#if 0 -TdSocketServerPtr taosOpenTcpServerSocket(uint32_t ip, uint16_t port) { - struct sockaddr_in serverAdd; - SocketFd fd; - int32_t reuse; - - // printf("open tcp server socket:0x%x:%hu", ip, port); - - bzero((char *)&serverAdd, sizeof(serverAdd)); - serverAdd.sin_family = AF_INET; - serverAdd.sin_addr.s_addr = ip; - serverAdd.sin_port = (uint16_t)htons(port); - - if ((fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) <= 2) { - // printf("failed to open TCP socket: %d (%s)", errno, strerror(errno)); - taosCloseSocketNoCheck1(fd); - return NULL; - } - - TdSocketPtr pSocket = (TdSocketPtr)taosMemoryMalloc(sizeof(TdSocket)); - if (pSocket == NULL) { - taosCloseSocketNoCheck1(fd); - return NULL; - } - pSocket->refId = 0; - pSocket->fd = fd; - - /* set REUSEADDR option, so the portnumber can be re-used */ - reuse = 1; - if (taosSetSockOpt(pSocket, SOL_SOCKET, SO_REUSEADDR, (void *)&reuse, sizeof(reuse)) < 0) { - // printf("setsockopt SO_REUSEADDR failed: %d (%s)", errno, strerror(errno)); - taosCloseSocket(&pSocket); - return NULL; - } - - /* bind socket to server address */ - if (bind(pSocket->fd, (struct sockaddr *)&serverAdd, sizeof(serverAdd)) < 0) { - // printf("bind tcp server socket failed, 0x%x:%hu(%s)", ip, port, strerror(errno)); - taosCloseSocket(&pSocket); - return NULL; - } - - if (taosKeepTcpAlive(pSocket) < 0) { - // printf("failed to set tcp server keep-alive option, 0x%x:%hu(%s)", ip, port, strerror(errno)); - return NULL; - } - - if (listen(pSocket->fd, 1024) < 0) { - // printf("listen tcp server socket failed, 0x%x:%hu(%s)", ip, port, strerror(errno)); - taosCloseSocket(&pSocket); - return NULL; - } - - return (TdSocketServerPtr)pSocket; -} - -TdSocketPtr taosAcceptTcpConnectSocket(TdSocketServerPtr pServerSocket, struct sockaddr *destAddr, int *addrLen) { - if (pServerSocket == NULL || pServerSocket->fd < 0) { - return NULL; - } - SocketFd fd = accept(pServerSocket->fd, destAddr, addrLen); - if (fd == -1) { - // tError("TCP accept failure(%s)", strerror(errno)); - return NULL; - } - - TdSocketPtr pSocket = (TdSocketPtr)taosMemoryMalloc(sizeof(TdSocket)); - if (pSocket == NULL) { - taosCloseSocketNoCheck1(fd); - return NULL; - } - pSocket->fd = fd; - pSocket->refId = 0; - return pSocket; -} -#define COPY_SIZE 32768 -// sendfile shall be used - -int64_t taosCopyFds(TdSocketPtr pSrcSocket, TdSocketPtr pDestSocket, int64_t len) { - if (pSrcSocket == NULL || pSrcSocket->fd < 0 || pDestSocket == NULL || pDestSocket->fd < 0) { - return -1; - } - int64_t leftLen; - int64_t readLen, writeLen; - char temp[COPY_SIZE]; - - leftLen = len; - - while (leftLen > 0) { - if (leftLen < COPY_SIZE) - readLen = leftLen; - else - readLen = COPY_SIZE; // 4K - - int64_t retLen = taosReadMsg(pSrcSocket, temp, (int32_t)readLen); - if (readLen != retLen) { - // printf("read error, readLen:%" PRId64 " retLen:%" PRId64 " len:%" PRId64 " leftLen:%" PRId64 ", reason:%s", - // readLen, retLen, len, leftLen, strerror(errno)); - return -1; - } - - writeLen = taosWriteMsg(pDestSocket, temp, (int32_t)readLen); - - if (readLen != writeLen) { - // printf("copy error, readLen:%" PRId64 " writeLen:%" PRId64 " len:%" PRId64 " leftLen:%" PRId64 ", reason:%s", - // readLen, writeLen, len, leftLen, strerror(errno)); - return -1; - } - - leftLen -= readLen; - } - - return len; -} - -// Function converting an IP address string to an uint32_t. - -#endif // endif 0 - int32_t taosBlockSIGPIPE() { #ifdef WINDOWS return 0; @@ -1029,7 +334,7 @@ int32_t taosGetFqdn(char *fqdn) { // hints.ai_family = AF_INET; strcpy(fqdn, hostname); strcpy(fqdn + strlen(hostname), ".local"); -#else // linux +#else // linux #endif // linux @@ -1048,7 +353,7 @@ int32_t taosGetFqdn(char *fqdn) { terrno = TAOS_SYSTEM_ERROR(errno); return terrno; } - + terrno = TAOS_SYSTEM_ERROR(ret); return terrno; } @@ -1067,7 +372,7 @@ int32_t taosGetFqdn(char *fqdn) { int32_t ret = getaddrinfo(hostname, NULL, &hints, &result); if (!result) { - //fprintf(stderr, "failed to get fqdn, code:%d, hostname:%s, reason:%s\n", ret, hostname, gai_strerror(ret)); + // fprintf(stderr, "failed to get fqdn, code:%d, hostname:%s, reason:%s\n", ret, hostname, gai_strerror(ret)); return TAOS_SYSTEM_WINSOCKET_ERROR(WSAGetLastError()); } strcpy(fqdn, result->ai_canonname); @@ -1082,41 +387,16 @@ void tinet_ntoa(char *ipstr, uint32_t ip) { (void)sprintf(ipstr, "%d.%d.%d.%d", ip & 0xFF, (ip >> 8) & 0xFF, (ip >> 16) & 0xFF, ip >> 24); } -int32_t taosIgnSIGPIPE() { - sighandler_t h = signal(SIGPIPE, SIG_IGN); +int32_t taosIgnSIGPIPE() { + sighandler_t h = signal(SIGPIPE, SIG_IGN); if (SIG_ERR == h) { terrno = TAOS_SYSTEM_ERROR(errno); return terrno; } - return 0; + return 0; } -#if 0 - -int32_t taosSetMaskSIGPIPE() { -#ifdef WINDOWS - return -1; -#else - sigset_t signal_mask; - (void)sigemptyset(&signal_mask); - (void)sigaddset(&signal_mask, SIGPIPE); - - int32_t rc = pthread_sigmask(SIG_SETMASK, &signal_mask, NULL); - if (rc != 0) { - // printf("failed to setmask SIGPIPE"); - } -#endif -} - -int32_t taosGetSocketName(TdSocketPtr pSocket, struct sockaddr *destAddr, int *addrLen) { - if (pSocket == NULL || pSocket->fd < 0) { - return -1; - } - return getsockname(pSocket->fd, destAddr, addrLen); -} -#endif // endif 0 - /* * Set TCP connection timeout per-socket level. * ref [https://github.com/libuv/help/issues/54] @@ -1132,7 +412,7 @@ int32_t taosCreateSocketWithTimeout(uint32_t timeout) { terrno = TAOS_SYSTEM_ERROR(errno); return terrno; } - + #if defined(WINDOWS) if (0 != setsockopt(fd, IPPROTO_TCP, TCP_MAXRT, (char *)&timeout, sizeof(timeout))) { taosCloseSocketNoCheck1(fd); From 9f0686f35a58e9a795b8dec0a500073e95fdfa51 Mon Sep 17 00:00:00 2001 From: charles Date: Thu, 26 Sep 2024 13:34:11 +0800 Subject: [PATCH 3/7] add CI test case for ts-5111 by charles --- tests/army/cluster/test_drop_table_by_uid.py | 368 +++++++++++++++++++ tests/parallel_test/cases.task | 1 + 2 files changed, 369 insertions(+) create mode 100644 tests/army/cluster/test_drop_table_by_uid.py diff --git a/tests/army/cluster/test_drop_table_by_uid.py b/tests/army/cluster/test_drop_table_by_uid.py new file mode 100644 index 0000000000..f09006b37b --- /dev/null +++ b/tests/army/cluster/test_drop_table_by_uid.py @@ -0,0 +1,368 @@ +import time +import random +import taos +from enum import Enum + +from frame.log import * +from frame.cases import * +from frame.sql import * +from frame.caseBase import * +from frame import * +from frame.srvCtl import * + + +class TDTestCase(TBase): + """ + Description: This class is used to verify the feature of 'drop table by uid' for task TS-5111 + FS: https://taosdata.feishu.cn/wiki/JgeDwZkH3iTNv2ksVkWcHenKnTf + TS: https://taosdata.feishu.cn/wiki/DX3FwopwGiXCeRkBNXFcj0MBnnb + create: + 2024-09-23 created by Charles + update: + None + """ + + class TableType(Enum): + STABLE = 0 + CHILD_TABLE = 1 + REGULAR_TABLE = 2 + + def init(self, conn, logSql, replicaVar=1): + """Initialize the TDengine cluster environment + """ + super(TDTestCase, self).init(conn, logSql, replicaVar, db="db") + tdSql.init(conn.cursor(), logSql) + + def get_uid_by_db_table_name(self, db_name, table_name, table_type=TableType.STABLE): + """Get table uid with db name and table name from system table + :param db_name: database name + :param table_name: table name + :param table_type: table type, default is stable + :return: table uid + """ + if table_type == self.TableType.STABLE: + tdSql.query(f"select * from information_schema.ins_stables where db_name='{db_name}' and stable_name like '%{table_name}%';") + elif table_type == self.TableType.CHILD_TABLE: + tdSql.query(f"select * from information_schema.ins_tables where db_name='{db_name}' and table_name like '%{table_name}%' and stable_name is not null;") + else: + tdSql.query(f"select * from information_schema.ins_tables where db_name='{db_name}' and table_name like '%{table_name}%' and stable_name is null;") + # check whether the table uid is empty + if len(tdSql.res) == 0: + tdLog.debug(f"Can't get table uid with db name: {db_name} and table name: {table_name}") + return None + # get table uid list + if table_type == self.TableType.STABLE: + return [item[10] for item in tdSql.res] + else: + return [item[5] for item in tdSql.res] + + def get_uid_by_db_name(self, db_name, table_type=TableType.STABLE): + """Get table uid with db name and table type from system table + :param db_name: database name + :param table_type: table type, default is stable + :return: table uid list + """ + if table_type == self.TableType.STABLE: + tdSql.query(f"select * from information_schema.ins_stables where db_name='{db_name}';") + elif table_type == self.TableType.CHILD_TABLE: + tdSql.query(f"select * from information_schema.ins_tables where db_name='{db_name}' and stable_name is not null;") + else: + tdSql.query(f"select * from information_schema.ins_tables where db_name='{db_name}' and stable_name is null;") + # check whether the table uid is empty + if len(tdSql.res) == 0: + tdLog.debug(f"Can't get table uid with db name: {db_name}") + return None + # get table uid list + if table_type == self.TableType.STABLE: + return [item[10] for item in tdSql.res] + else: + return [item[5] for item in tdSql.res] + + def drop_table_by_uid(self, uid_list, table_type=TableType.STABLE, exist_ops=False): + """Drop the specified tables by uid list + :db_name: database name + :param uid_list: table uid list to be dropped + :param exist_ops: whether to use exist option, default is False + :return: None + """ + # check whether the uid list is empty + if len(uid_list) == 0: + return + # drop table by uid + if exist_ops and table_type == self.TableType.STABLE: + for uid in uid_list: + tdSql.execute(f"drop stable with if exists `{uid}`;") + else: + uids = ','.join(["`" + str(item) + "`" for item in uid_list]) + tdSql.execute(f"drop table with {uids};") + + def test_drop_single_table_by_uid(self): + """Verify the feature of dropping a single stable/child table/regular table by uid with root user + """ + db_name = "test_drop_single_table_by_uid" + tdLog.info("Start test case: test_drop_single_table_by_uid") + # data for case test_drop_single_table_by_uid + tdLog.info("Prepare data for test case test_drop_single_table_by_uid") + tdSql.execute(f"create database {db_name};") + tdSql.execute(f"use {db_name};") + # table with normal characters + tdSql.execute("create stable st1 (ts timestamp, c1 int) tags (t1 int);") + tdSql.execute("create table ct1_1 using st1 tags(1);") + tdSql.execute("create table t1 (ts timestamp, c1 int, c2 float);") + tdLog.info("Finish preparing data for test case test_drop_single_table_by_uid of normal characters") + # get table uid + uid_st1 = self.get_uid_by_db_table_name(db_name, "st1") + tdLog.debug(f"uid_st1: {uid_st1}") + uid_ct1_1 = self.get_uid_by_db_table_name(db_name, "ct1_1", self.TableType.CHILD_TABLE) + tdLog.debug(f"uid_ct1_1: {uid_ct1_1}") + uid_t1 = self.get_uid_by_db_table_name(db_name, "t1", self.TableType.REGULAR_TABLE) + tdLog.debug(f"uid_t1: {uid_t1}") + # drop table by uid + self.drop_table_by_uid(uid_t1, self.TableType.REGULAR_TABLE) + self.drop_table_by_uid(uid_ct1_1, self.TableType.CHILD_TABLE) + self.drop_table_by_uid(uid_st1, self.TableType.STABLE, True) + + # table with special characters + tdSql.execute("create stable `st2\u00bf\u200bfnn1` (ts timestamp, c1 int) tags (t1 int);") + tdSql.execute("create table `ct2_1\u00cf\u00ff` using `st2\u00bf\u200bfnn1` tags(1);") + tdSql.execute("create table `t2\u00ef\u00fa` (ts timestamp, c1 int, c2 float);") + tdLog.info("Finish preparing data for test case test_drop_single_table_by_uid of special characters") + # get table uid + uid_st2 = self.get_uid_by_db_table_name(db_name, "st2") + tdLog.debug(f"uid_st2: {uid_st2}") + uid_ct2_1 = self.get_uid_by_db_table_name(db_name, "ct2_1", self.TableType.CHILD_TABLE) + tdLog.debug(f"uid_ct2_1: {uid_ct2_1}") + uid_t2 = self.get_uid_by_db_table_name(db_name, "t2", self.TableType.REGULAR_TABLE) + tdLog.debug(f"uid_t2: {uid_t2}") + # drop table by uid + self.drop_table_by_uid(uid_t2, self.TableType.REGULAR_TABLE) + self.drop_table_by_uid(uid_ct2_1, self.TableType.CHILD_TABLE) + self.drop_table_by_uid(uid_st2, self.TableType.STABLE, True) + tdSql.execute(f"drop database {db_name};") + tdLog.info("Finish test case: test_drop_single_table_by_uid") + + def test_drop_multiple_tables_by_uid(self): + """Verify the feature of dropping multiple tables by uid with root user + """ + db_name = "test_drop_multiple_tables_by_uid" + table_number = 100 + tdLog.info("Start test case: test_drop_multiple_tables_by_uid") + # data for case test_drop_multiple_tables_by_uid + tdLog.info("Prepare data for test case test_drop_multiple_tables_by_uid") + tdSql.execute(f"create database {db_name};") + tdSql.execute(f"use {db_name};") + # table with normal characters + for i in range(table_number): + tdSql.execute(f"create stable st{i} (ts timestamp, c1 int) tags (t1 int);") + tdSql.execute(f"create table ct{i}_{i} using st{i} tags({i+1});") + tdSql.execute(f"create table t{i} (ts timestamp, c1 int, c2 float);") + tdLog.info("Finish preparing data for test case test_drop_multiple_tables_by_uid of normal characters") + # get table uid + uid_st = self.get_uid_by_db_table_name(db_name, "st") + # tdLog.debug(f"Get multiple stable uid list: {uid_st}") + uid_ct = self.get_uid_by_db_table_name(db_name, "ct", self.TableType.CHILD_TABLE) + # tdLog.debug(f"Get multiple child table uid list: {uid_ct}") + uid_t = self.get_uid_by_db_table_name(db_name, "t", self.TableType.REGULAR_TABLE) + # tdLog.debug(f"Get multiple regular table uid list: {uid_t}") + # drop table by uid + self.drop_table_by_uid(uid_t, self.TableType.REGULAR_TABLE) + self.drop_table_by_uid(uid_ct, self.TableType.CHILD_TABLE) + self.drop_table_by_uid(uid_st, self.TableType.STABLE, True) + + # table with special characters + for i in range(table_number): + tdSql.execute(f"create stable `st{i}\u00bf\u200bfnn1` (ts timestamp, c1 int) tags (t1 int);") + tdSql.execute(f"create table `ct{i}_{i}\u00cf\u00ff` using `st{i}\u00bf\u200bfnn1` tags(1);") + tdSql.execute(f"create table `t{i}\u00ef\u00fa` (ts timestamp, c1 int, c2 float);") + # get table uid + uid_st = self.get_uid_by_db_table_name(db_name, "st") + # tdLog.debug(f"Get multiple stable uid list: {uid_st}") + uid_ct = self.get_uid_by_db_table_name(db_name, "ct", self.TableType.CHILD_TABLE) + # tdLog.debug(f"Get multiple child table uid list: {uid_ct}") + uid_t = self.get_uid_by_db_table_name(db_name, "t", self.TableType.REGULAR_TABLE) + # tdLog.debug(f"Get multiple regular table uid list: {uid_t}") + # drop table by uid + self.drop_table_by_uid(uid_t, self.TableType.REGULAR_TABLE) + self.drop_table_by_uid(uid_ct, self.TableType.CHILD_TABLE) + self.drop_table_by_uid(uid_st, self.TableType.STABLE, True) + tdSql.execute(f"drop database {db_name};") + tdLog.info("Finish test case: test_drop_multiple_tables_by_uid") + + def test_uid_as_table_name(self): + """Verify using uid as table name, drop table with uid doesn't affect other tables + """ + db_name = "test_uid_as_table_name" + tdLog.info("Start test case: test_uid_as_table_name") + # data for case test_uid_as_table_name + tdLog.info("Prepare data for test case test_uid_as_table_name") + tdSql.execute(f"create database {db_name};") + tdSql.execute(f"use {db_name};") + # super table + tdSql.execute(f"create stable `st1\u00bf\u200bfnn1` (ts timestamp, c1 int) tags (t1 int);") + uid_st = self.get_uid_by_db_table_name(db_name, "st") + tdSql.execute(f"create stable `{uid_st[0]}` (ts timestamp, c1 int) tags (t1 int);") + self.drop_table_by_uid(uid_st, self.TableType.STABLE, True) + uid_st = self.get_uid_by_db_table_name(db_name, str(uid_st[0])) + assert uid_st is not None + tdLog.info(f"Drop stable with special characters with uid {uid_st[0]}, stable named as {uid_st[0]} doesn't be affected") + # child table + tdSql.execute(f"create stable `st2\u00bf\u200bfnn1` (ts timestamp, c1 int) tags (t1 int);") + tdSql.execute(f"create table `ct2_1\u00cf\u00ff` using `st2\u00bf\u200bfnn1` tags(1);") + uid_ct = self.get_uid_by_db_table_name(db_name, "ct", self.TableType.CHILD_TABLE) + tdSql.execute(f"create table `{uid_ct[0]}` using `st2\u00bf\u200bfnn1` tags(2);") + self.drop_table_by_uid(uid_ct, self.TableType.CHILD_TABLE) + uid_ct = self.get_uid_by_db_table_name(db_name, str(uid_ct[0]), self.TableType.CHILD_TABLE) + assert uid_ct is not None + tdLog.info(f"Drop child table with special characters with uid {uid_ct[0]}, child table named as {uid_ct[0]} doesn't be affected") + # regular table + tdSql.execute(f"create table `t2\u00bf\u200bfnn1` (ts timestamp, c1 int);") + uid_t = self.get_uid_by_db_table_name(db_name, "t2", self.TableType.REGULAR_TABLE) + tdSql.execute(f"create table `{uid_t[0]}` (ts timestamp, c1 int);") + self.drop_table_by_uid(uid_t, self.TableType.REGULAR_TABLE) + uid_t = self.get_uid_by_db_table_name(db_name, str(uid_t[0]), self.TableType.REGULAR_TABLE) + assert uid_t is not None + tdLog.info(f"Drop regular table with special characters with uid {uid_t[0]}, regular table named as {uid_t[0]} doesn't be affected") + tdSql.execute(f"drop database {db_name};") + tdLog.info("Finish test case: test_uid_as_table_name") + + def test_abnormal_non_exist_uid(self): + """Verify dropping table with non-exist uid + """ + db_name = "test_abnormal_non_exist_uid" + tdLog.info("Start test case: test_abnormal_non_exist_uid") + # data for case test_abnormal_non_exist_uid + tdLog.info("Prepare data for test case test_abnormal_non_exist_uid") + tdSql.execute(f"create database {db_name};") + tdSql.execute(f"use {db_name};") + # drop table with non-exist uid + tdSql.error(f"drop stable with if exists `1234567890`;", expectErrInfo="STable not exist:") + tdSql.error(f"drop table with `1234567890`;", expectErrInfo="Table does not exist:") + tdSql.execute(f"drop database {db_name};") + tdLog.info("Finish test case: test_abnormal_non_exist_uid") + + def test_abnormal_incorrect_table_type(self): + """Verify dropping table with incorrect sql, like drop stable sql with table or child table uid + """ + try: + db_name = "test_abnormal_incorrect_table_type" + tdLog.info("Start test case: test_abnormal_incorrect_table_type") + # data for case test_abnormal_incorrect_table_type + tdLog.info("Prepare data for test case test_abnormal_incorrect_table_type") + tdSql.execute(f"create database {db_name};") + tdSql.execute(f"use {db_name};") + tdSql.execute("create stable `st3\u00bf\u200bfnn1` (ts timestamp, c1 int) tags (t1 int);") + tdSql.execute("create table `ct3_1\u00cf\u00ff` using `st3\u00bf\u200bfnn1` tags(1);") + tdSql.execute("create table `t3\u00ef\u00fa` (ts timestamp, c1 int, c2 float);") + tdLog.info("Finish preparing data for test case test_abnormal_incorrect_table_type of special characters") + # get table uid + uid_st = self.get_uid_by_db_table_name(db_name, "st") + uid_ct = self.get_uid_by_db_table_name(db_name, "ct", self.TableType.CHILD_TABLE) + uid_t = self.get_uid_by_db_table_name(db_name, "t", self.TableType.REGULAR_TABLE) + # drop table with incorrect sql + tdSql.error(f"drop stable with `{uid_ct[0]}`;", expectErrInfo="STable not exist") + tdSql.error(f"drop stable with `{uid_t[0]}`;", expectErrInfo="STable not exist") + tdLog.info("Finish test case: test_abnormal_incorrect_table_type") + except Exception as e: + tdLog.exit("Failed to run test case test_abnormal_incorrect_table_type with msg: %s" % str(e)) + finally: + tdSql.execute(f"drop database {db_name};") + + def test_abnormal_mixed_uid(self): + """Verify dropping table with mixed uid + """ + db_name = "test_abnormal_mixed_uid" + tdLog.info("Start test case: test_abnormal_mixed_uid") + # data for case test_abnormal_mixed_uidF + tdLog.info("Prepare data for test case test_abnormal_mixed_uid") + tdSql.execute(f"create database {db_name};") + tdSql.execute(f"use {db_name};") + tdSql.execute("create stable `st3\u00bf\u200bfnn1` (ts timestamp, c1 int) tags (t1 int);") + tdSql.execute("create table `ct3_1\u00cf\u00ff` using `st3\u00bf\u200bfnn1` tags(1);") + tdSql.execute("create table `t3\u00ef\u00fa` (ts timestamp, c1 int, c2 float);") + tdLog.info("Finish preparing data for test case test_abnormal_mixed_uid of special characters") + # get table uid + uid_st = self.get_uid_by_db_table_name(db_name, "st") + uid_ct = self.get_uid_by_db_table_name(db_name, "ct", self.TableType.CHILD_TABLE) + uid_t = self.get_uid_by_db_table_name(db_name, "t", self.TableType.REGULAR_TABLE) + # drop table with incorrect sql + tdSql.error(f"drop stable with `{uid_st[0]}`,`{uid_ct[0]}`;", expectErrInfo="syntax error") + tdSql.error(f"drop table with `{uid_st[0]}`,`{uid_ct[0]}`,`{uid_t[0]}`;", expectErrInfo="Cannot drop super table in batch") + tdSql.execute(f"drop database {db_name};") + tdLog.info("Finish test case: test_abnormal_mixed_uid") + + def test_abnormal_system_tables(self): + """Verify dropping system tables + """ + try: + uid_list = self.get_uid_by_db_name("information_schema", self.TableType.REGULAR_TABLE) + uid = random.choice(uid_list) + assert uid is None + except Exception as e: + tdLog.exit("Failed to run test case test_abnormal_system_tables with msg: %s" % str(e)) + + def test_abnormal_drop_table_with_non_root_user(self): + """Verify dropping table with non-root user + """ + try: + # create new user and grant create database priviledge + tdSql.execute("create user test pass 'test';") + tdSql.execute("alter user test createdb 1;") + conn = taos.connect(user="test", password="test") + cursor = conn.cursor() + # create database and tables with new user + tdLog.info("Prepare data for test case test_abnormal_drop_table_with_non_root_user") + db_name = "test_abnormal_drop_table_with_non_root_user" + cursor.execute(f"create database {db_name};") + cursor.execute(f"use {db_name};") + time.sleep(3) + cursor.execute("create stable `st4\u00bf\u200bfnn1` (ts timestamp, c1 int) tags (t1 int);") + cursor.execute("create table `ct4_1\u00cf\u00ff` using `st4\u00bf\u200bfnn1` tags(1);") + cursor.execute("create table `t4\u00ef\u00fa` (ts timestamp, c1 int, c2 float);") + tdLog.info("Finish preparing data for test case test_abnormal_drop_table_with_non_root_user of special characters") + # get table uid + uid_st = self.get_uid_by_db_table_name(db_name, "st") + uid_ct = self.get_uid_by_db_table_name(db_name, "ct", self.TableType.CHILD_TABLE) + uid_t = self.get_uid_by_db_table_name(db_name, "t", self.TableType.REGULAR_TABLE) + # drop stable with sql by non-root user + try: + cursor.execute(f"drop stable with `{uid_st[0]}`;") + except Exception as e: + assert "Permission denied or target object not exist" in str(e) + tdLog.info("Drop stable with non-root user failed as expected") + # drop child table with sql by non-root user + try: + cursor.execute(f"drop table with `{uid_ct[0]}`;") + except Exception as e: + assert "Permission denied or target object not exist" in str(e) + tdLog.info("Drop child table with non-root user failed as expected") + # drop regular table with sql by non-root user + try: + cursor.execute(f"drop table with `{uid_t[0]}`;") + except Exception as e: + assert "Permission denied or target object not exist" in str(e) + tdLog.info("Drop regular table with non-root user failed as expected") + tdLog.info("Finish test case: test_abnormal_drop_table_with_non_root_user") + except Exception as e: + tdLog.exit("Failed to run test case test_abnormal_drop_table_with_non_root_user with msg: %s" % str(e)) + finally: + tdSql.execute(f"drop database {db_name};") + tdSql.execute("drop user test;") + + def run(self): + # normal cases + self.test_drop_single_table_by_uid() + self.test_drop_multiple_tables_by_uid() + self.test_uid_as_table_name() + # abnormal cases + self.test_abnormal_non_exist_uid() + self.test_abnormal_incorrect_table_type() + self.test_abnormal_mixed_uid() + self.test_abnormal_system_tables() + self.test_abnormal_drop_table_with_non_root_user() + + def stop(self): + tdSql.close() + tdLog.success(f"{__file__} successfully executed") + +tdCases.addLinux(__file__, TDTestCase()) +tdCases.addWindows(__file__, TDTestCase()) diff --git a/tests/parallel_test/cases.task b/tests/parallel_test/cases.task index 602ac9ad66..9921588a77 100644 --- a/tests/parallel_test/cases.task +++ b/tests/parallel_test/cases.task @@ -23,6 +23,7 @@ ,,y,army,./pytest.sh python3 ./test.py -f insert/test_column_tag_boundary.py ,,y,army,./pytest.sh python3 ./test.py -f query/fill/fill_desc.py -N 3 -L 3 -D 2 ,,y,army,./pytest.sh python3 ./test.py -f query/fill/fill_null.py +,,y,army,./pytest.sh python3 ./test.py -f cluster/test_drop_table_by_uid.py -N 3 ,,y,army,./pytest.sh python3 ./test.py -f cluster/incSnapshot.py -N 3 ,,y,army,./pytest.sh python3 ./test.py -f cluster/clusterBasic.py -N 5 ,,y,army,./pytest.sh python3 ./test.py -f query/query_basic.py -N 3 From 7d8857d2575d711961abdb2df44712a23b804ce5 Mon Sep 17 00:00:00 2001 From: dmchen Date: Thu, 26 Sep 2024 06:12:54 +0000 Subject: [PATCH 4/7] fix/check-memalloc-result --- source/dnode/mnode/impl/src/mndDb.c | 4 ++++ source/dnode/mnode/impl/src/mndFunc.c | 20 ++++++++++++++++++++ source/dnode/mnode/impl/src/mndMnode.c | 16 ++++++++++++++++ source/dnode/mnode/impl/src/mndStb.c | 10 ++++++++++ 4 files changed, 50 insertions(+) diff --git a/source/dnode/mnode/impl/src/mndDb.c b/source/dnode/mnode/impl/src/mndDb.c index 374dff8d0c..0403029f74 100644 --- a/source/dnode/mnode/impl/src/mndDb.c +++ b/source/dnode/mnode/impl/src/mndDb.c @@ -2281,6 +2281,10 @@ static void mndDumpDbInfoData(SMnode *pMnode, SSDataBlock *pBlock, SDbObj *pDb, int32_t cols = 0; int32_t bytes = pShow->pMeta->pSchemas[cols].bytes; char *buf = taosMemoryMalloc(bytes); + if (buf == NULL) { + mError("db:%s, failed to malloc buffer", pDb->name); + return; + } int32_t code = 0; int32_t lino = 0; diff --git a/source/dnode/mnode/impl/src/mndFunc.c b/source/dnode/mnode/impl/src/mndFunc.c index 4c5a695402..db4d842662 100644 --- a/source/dnode/mnode/impl/src/mndFunc.c +++ b/source/dnode/mnode/impl/src/mndFunc.c @@ -184,6 +184,7 @@ static int32_t mndFuncActionDelete(SSdb *pSdb, SFuncObj *pFunc) { } static int32_t mndFuncActionUpdate(SSdb *pSdb, SFuncObj *pOld, SFuncObj *pNew) { + int32_t code = 0; mTrace("func:%s, perform update action, old row:%p new row:%p", pOld->name, pOld, pNew); taosWLockLatch(&pOld->lock); @@ -205,6 +206,11 @@ static int32_t mndFuncActionUpdate(SSdb *pSdb, SFuncObj *pOld, SFuncObj *pNew) { if (pNew->commentSize > 0 && pNew->pComment != NULL) { pOld->commentSize = pNew->commentSize; pOld->pComment = taosMemoryMalloc(pOld->commentSize); + if (pOld->pComment == NULL) { + code = terrno; + taosWUnLockLatch(&pOld->lock); + return code; + } (void)memcpy(pOld->pComment, pNew->pComment, pOld->commentSize); } @@ -215,6 +221,11 @@ static int32_t mndFuncActionUpdate(SSdb *pSdb, SFuncObj *pOld, SFuncObj *pNew) { if (pNew->codeSize > 0 && pNew->pCode != NULL) { pOld->codeSize = pNew->codeSize; pOld->pCode = taosMemoryMalloc(pOld->codeSize); + if (pOld->pCode == NULL) { + code = terrno; + taosWUnLockLatch(&pOld->lock); + return code; + } (void)memcpy(pOld->pCode, pNew->pCode, pOld->codeSize); } @@ -261,6 +272,10 @@ static int32_t mndCreateFunc(SMnode *pMnode, SRpcMsg *pReq, SCreateFuncReq *pCre if (NULL != pCreate->pComment) { func.commentSize = strlen(pCreate->pComment) + 1; func.pComment = taosMemoryMalloc(func.commentSize); + if (func.pComment == NULL) { + code = terrno; + goto _OVER; + } } func.codeSize = pCreate->codeLen; func.pCode = taosMemoryMalloc(func.codeSize); @@ -716,6 +731,11 @@ static int32_t mndRetrieveFuncs(SRpcMsg *pReq, SShowObj *pShow, SSDataBlock *pBl ? TSDB_MAX_BINARY_LEN : pFunc->codeSize + VARSTR_HEADER_SIZE; char *b4 = taosMemoryMalloc(varCodeLen); + if (b4 == NULL) { + code = terrno; + sdbRelease(pSdb, pFunc); + TAOS_RETURN(code); + } (void)memcpy(varDataVal(b4), pFunc->pCode, varCodeLen - VARSTR_HEADER_SIZE); varDataSetLen(b4, varCodeLen - VARSTR_HEADER_SIZE); code = colDataSetVal(pColInfo, numOfRows, (const char *)b4, false); diff --git a/source/dnode/mnode/impl/src/mndMnode.c b/source/dnode/mnode/impl/src/mndMnode.c index c00c88c4f9..6b1c97b399 100644 --- a/source/dnode/mnode/impl/src/mndMnode.c +++ b/source/dnode/mnode/impl/src/mndMnode.c @@ -343,6 +343,10 @@ static int32_t mndBuildCreateMnodeRedoAction(STrans *pTrans, SDCreateMnodeReq *p int32_t code = 0; int32_t contLen = tSerializeSDCreateMnodeReq(NULL, 0, pCreateReq); void *pReq = taosMemoryMalloc(contLen); + if (pReq == NULL) { + code = terrno; + return code; + } code = tSerializeSDCreateMnodeReq(pReq, contLen, pCreateReq); if (code < 0) { taosMemoryFree(pReq); @@ -369,6 +373,10 @@ static int32_t mndBuildAlterMnodeTypeRedoAction(STrans *pTrans, SDAlterMnodeType int32_t code = 0; int32_t contLen = tSerializeSDCreateMnodeReq(NULL, 0, pAlterMnodeTypeReq); void *pReq = taosMemoryMalloc(contLen); + if (pReq == NULL) { + code = terrno; + return code; + } code = tSerializeSDCreateMnodeReq(pReq, contLen, pAlterMnodeTypeReq); if (code < 0) { taosMemoryFree(pReq); @@ -395,6 +403,10 @@ static int32_t mndBuildAlterMnodeRedoAction(STrans *pTrans, SDCreateMnodeReq *pA int32_t code = 0; int32_t contLen = tSerializeSDCreateMnodeReq(NULL, 0, pAlterReq); void *pReq = taosMemoryMalloc(contLen); + if (pReq == NULL) { + code = terrno; + return code; + } code = tSerializeSDCreateMnodeReq(pReq, contLen, pAlterReq); if (code < 0) { taosMemoryFree(pReq); @@ -420,6 +432,10 @@ static int32_t mndBuildDropMnodeRedoAction(STrans *pTrans, SDDropMnodeReq *pDrop int32_t code = 0; int32_t contLen = tSerializeSCreateDropMQSNodeReq(NULL, 0, pDropReq); void *pReq = taosMemoryMalloc(contLen); + if (pReq == NULL) { + code = terrno; + return code; + } code = tSerializeSCreateDropMQSNodeReq(pReq, contLen, pDropReq); if (code < 0) { taosMemoryFree(pReq); diff --git a/source/dnode/mnode/impl/src/mndStb.c b/source/dnode/mnode/impl/src/mndStb.c index fa9e4fa8fa..56461e9cfd 100644 --- a/source/dnode/mnode/impl/src/mndStb.c +++ b/source/dnode/mnode/impl/src/mndStb.c @@ -2352,6 +2352,11 @@ static int32_t mndBuildSMAlterStbRsp(SDbObj *pDb, SStbObj *pObj, void **pCont, i } void *cont = taosMemoryMalloc(contLen); + if (NULL == cont) { + code = terrno; + tFreeSMAlterStbRsp(&alterRsp); + TAOS_RETURN(code); + } tEncoderInit(&ec, cont, contLen); code = tEncodeSMAlterStbRsp(&ec, &alterRsp); tEncoderClear(&ec); @@ -2407,6 +2412,11 @@ int32_t mndBuildSMCreateStbRsp(SMnode *pMnode, char *dbFName, char *stbFName, vo } void *cont = taosMemoryMalloc(contLen); + if (NULL == cont) { + code = terrno; + tFreeSMCreateStbRsp(&stbRsp); + goto _OVER; + } tEncoderInit(&ec, cont, contLen); TAOS_CHECK_GOTO(tEncodeSMCreateStbRsp(&ec, &stbRsp), NULL, _OVER); tEncoderClear(&ec); From dfd4fcbf6623ef493e656fe1acedb479b4d7fdc9 Mon Sep 17 00:00:00 2001 From: dmchen Date: Thu, 26 Sep 2024 06:19:20 +0000 Subject: [PATCH 5/7] fix/check-memalloc-result --- source/dnode/mnode/impl/src/mndTrans.c | 1 + 1 file changed, 1 insertion(+) diff --git a/source/dnode/mnode/impl/src/mndTrans.c b/source/dnode/mnode/impl/src/mndTrans.c index 623400869e..3e77a208ba 100644 --- a/source/dnode/mnode/impl/src/mndTrans.c +++ b/source/dnode/mnode/impl/src/mndTrans.c @@ -367,6 +367,7 @@ SSdbRow *mndTransDecode(SSdbRaw *pRaw) { SDB_GET_INT32(pRaw, dataPos, &pTrans->paramLen, _OVER) if (pTrans->paramLen != 0) { pTrans->param = taosMemoryMalloc(pTrans->paramLen); + if (pTrans->param == NULL) goto _OVER; SDB_GET_BINARY(pRaw, dataPos, pTrans->param, pTrans->paramLen, _OVER); } From 9f28140fc5842d3c4530e991d8990dfb0018b3bd Mon Sep 17 00:00:00 2001 From: dmchen Date: Thu, 26 Sep 2024 06:59:41 +0000 Subject: [PATCH 6/7] fix/check-memalloc-result --- .../libs/monitorfw/src/taos_metric_formatter_custom.c | 11 +++++++++++ source/libs/monitorfw/src/taos_monitor_util.c | 1 + 2 files changed, 12 insertions(+) diff --git a/source/libs/monitorfw/src/taos_metric_formatter_custom.c b/source/libs/monitorfw/src/taos_metric_formatter_custom.c index 6e7ded62bb..5c384c0421 100644 --- a/source/libs/monitorfw/src/taos_metric_formatter_custom.c +++ b/source/libs/monitorfw/src/taos_metric_formatter_custom.c @@ -40,16 +40,26 @@ int taos_metric_formatter_load_sample_new(taos_metric_formatter_t *self, taos_me int32_t len = end -start; char* keyvalues = taosMemoryMalloc(len); + if (keyvalues == NULL) return 1; memset(keyvalues, 0, len); memcpy(keyvalues, start + 1, len - 1); int32_t count = taos_monitor_count_occurrences(keyvalues, ","); char** keyvalue = taosMemoryMalloc(sizeof(char*) * (count + 1)); + if (keyvalue == NULL) { + taosMemoryFreeClear(keyvalues); + return 1; + } memset(keyvalue, 0, sizeof(char*) * (count + 1)); taos_monitor_split_str(keyvalue, keyvalues, ","); char** arr = taosMemoryMalloc(sizeof(char*) * (count + 1) * 2); + if (arr == NULL) { + taosMemoryFreeClear(keyvalue); + taosMemoryFreeClear(keyvalues); + return 1; + } memset(arr, 0, sizeof(char*) * (count + 1) * 2); bool isfound = true; @@ -165,6 +175,7 @@ int taos_metric_formatter_load_metric_new(taos_metric_formatter_t *self, taos_me int32_t size = strlen(metric->name); char* name = taosMemoryMalloc(size + 1); + if (name == NULL) return 1; memset(name, 0, size + 1); memcpy(name, metric->name, size); char* arr[2] = {0}; //arr[0] is table name, arr[1] is metric name diff --git a/source/libs/monitorfw/src/taos_monitor_util.c b/source/libs/monitorfw/src/taos_monitor_util.c index 06ae4993c5..b0eff27507 100644 --- a/source/libs/monitorfw/src/taos_monitor_util.c +++ b/source/libs/monitorfw/src/taos_monitor_util.c @@ -37,6 +37,7 @@ void taos_monitor_split_str(char** arr, char* str, const char* del) { void taos_monitor_split_str_metric(char** arr, taos_metric_t* metric, const char* del, char** buf) { int32_t size = strlen(metric->name); char* name = taosMemoryMalloc(size + 1); + if (name == NULL) return; memset(name, 0, size + 1); memcpy(name, metric->name, size); From 4bf368fe4a767b4bf2168c670b3e3ab9d55dc129 Mon Sep 17 00:00:00 2001 From: Linhe Huo Date: Thu, 26 Sep 2024 15:00:26 +0800 Subject: [PATCH 7/7] docs(datain:kafka): improve documents for kafka sasl auth Close [TD-32325](https://jira.taosdata.com:18080/browse/TD-32325) --- docs/zh/06-advanced/05-data-in/08-kafka.md | 44 +++++++++++++++++- .../05-data-in/kafka-04-sasl-gssapi.png | Bin 0 -> 44365 bytes .../05-data-in/kafka-04-sasl-plain.png | Bin 0 -> 20354 bytes 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 docs/zh/06-advanced/05-data-in/kafka-04-sasl-gssapi.png create mode 100644 docs/zh/06-advanced/05-data-in/kafka-04-sasl-plain.png diff --git a/docs/zh/06-advanced/05-data-in/08-kafka.md b/docs/zh/06-advanced/05-data-in/08-kafka.md index e05c205f6e..837aa8d8fb 100644 --- a/docs/zh/06-advanced/05-data-in/08-kafka.md +++ b/docs/zh/06-advanced/05-data-in/08-kafka.md @@ -44,8 +44,50 @@ TDengine 可以高效地从 Kafka 读取数据并将其写入 TDengine,以实 如果服务端开启了 SASL 认证机制,此处需要启用 SASL 并配置相关内容,目前支持 PLAIN/SCRAM-SHA-256/GSSAPI 三种认证机制,请按实际情况进行选择。 +#### 4.1. PLAIN 认证 + +选择 `PLAIN` 认证机制,输入用户名和密码: + +![kafka-04-sasl-plain.png](./kafka-04-sasl-plain.png) + +#### 4.1. SCRAM(SCRAM-SHA-256) 认证 + +选择 `SCRAM-SHA-256` 认证机制,输入用户名和密码: + ![kafka-04.png](./kafka-04.png) +#### 4.3. GSSAPI 认证 + +选择 `GSSAPI` ,将通过 [RDkafka 客户端](https://github.com/confluentinc/librdkafka) 调用 GSSAPI 应用 Kerberos 认证机制: + +![kafka-04-sasl-gssapi.png](./kafka-04-sasl-gssapi.png) + +需要输入的信息有: + +- Kerberos 服务名,一般是 `kafka`; +- Kerberos 认证主体,即认证用户名,例如 `kafkaclient`; +- Kerberos 初始化命令(可选,一般不用填写); +- Kerberos 密钥表,需提供文件并上传; + +以上信息均需由 Kafka 服务管理者提供。 + +除此之外,在服务器上需要配置 [Kerberos](https://web.mit.edu/kerberos/) 认证服务。在 Ubuntu 下使用 `apt install krb5-user` ;在 CentOS 下,使用 `yum install krb5-workstation`;即可。 + +配置完成后,可以使用 [kcat](https://github.com/edenhill/kcat) 工具进行 Kafka 主题消费验证: + +```bash +kcat \ + -b \ + -G kcat \ + -X security.protocol=SASL_PLAINTEXT \ + -X sasl.mechanism=GSSAPI \ + -X sasl.kerberos.keytab= \ + -X sasl.kerberos.principal= \ + -X sasl.kerberos.service.name=kafka +``` + +如果出现错误:“Server xxxx not found in kerberos database”,则需要配置 Kafka 节点对应的域名并在 Kerberos 客户端配置文件 `/etc/krb5.conf` 中配置反向域名解析 `rdns = true`。 + ### 5. 配置 SSL 证书 如果服务端开启了 SSL 加密认证,此处需要启用 SSL 并配置相关内容。 @@ -160,4 +202,4 @@ json 数据支持 JSONObject 或者 JSONArray,使用 json 解析器可以解 ### 9. 创建完成 -点击 **提交** 按钮,完成创建 Kafka 到 TDengine 的数据同步任务,回到**数据源列表**页面可查看任务执行情况。 \ No newline at end of file +点击 **提交** 按钮,完成创建 Kafka 到 TDengine 的数据同步任务,回到**数据源列表**页面可查看任务执行情况。 diff --git a/docs/zh/06-advanced/05-data-in/kafka-04-sasl-gssapi.png b/docs/zh/06-advanced/05-data-in/kafka-04-sasl-gssapi.png new file mode 100644 index 0000000000000000000000000000000000000000..051efda8abfcd1750e388a3e9d02c6a1e7b57c10 GIT binary patch literal 44365 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 UF!zfD@N7K>Pgg&ebxsLQ0DkcaJOBUy literal 0 HcmV?d00001