diff --git a/tests/restful_client_v2/api/milvus.py b/tests/restful_client_v2/api/milvus.py index 9cbddf9295ee6..b50dc4d5e9608 100644 --- a/tests/restful_client_v2/api/milvus.py +++ b/tests/restful_client_v2/api/milvus.py @@ -338,6 +338,17 @@ def __init__(self, endpoint, token): self.name_list = [] self.headers = self.update_headers() + def wait_load_completed(self, collection_name, db_name="default", timeout=5): + t0 = time.time() + while True and time.time() - t0 < timeout: + rsp = self.collection_describe(collection_name, db_name=db_name) + if "data" in rsp and "load" in rsp["data"] and rsp["data"]["load"] == "LoadStateLoaded": + logger.info(f"collection {collection_name} load completed in {time.time() - t0} seconds") + break + else: + time.sleep(1) + + @classmethod def update_headers(cls, headers=None): if headers is not None: @@ -479,6 +490,92 @@ def collection_drop(self, payload, db_name="default"): response = self.post(url, headers=self.update_headers(), data=payload) return response.json() + def refresh_load(self, collection_name, db_name="default"): + """Refresh load collection""" + url = f"{self.endpoint}/v2/vectordb/collections/refresh_load" + payload = { + "collectionName": collection_name + } + if self.db_name is not None: + payload["dbName"] = self.db_name + if db_name != "default": + payload["dbName"] = db_name + response = self.post(url, headers=self.update_headers(), data=payload) + return response.json() + + def alter_collection_properties(self, collection_name, properties, db_name="default"): + """Alter collection properties""" + url = f"{self.endpoint}/v2/vectordb/collections/alter_properties" + payload = { + "collectionName": collection_name, + "properties": properties + } + if self.db_name is not None: + payload["dbName"] = self.db_name + if db_name != "default": + payload["dbName"] = db_name + response = self.post(url, headers=self.update_headers(), data=payload) + return response.json() + + def drop_collection_properties(self, collection_name, delete_keys, db_name="default"): + """Drop collection properties""" + url = f"{self.endpoint}/v2/vectordb/collections/drop_properties" + payload = { + "collectionName": collection_name, + "deleteKeys": delete_keys + } + if self.db_name is not None: + payload["dbName"] = self.db_name + if db_name != "default": + payload["dbName"] = db_name + response = self.post(url, headers=self.update_headers(), data=payload) + return response.json() + + def alter_field_properties(self, collection_name, field_name, field_params, db_name="default"): + """Alter field properties""" + url = f"{self.endpoint}/v2/vectordb/collections/fields/alter_properties" + payload = { + "collectionName": collection_name, + "fieldName": field_name, + "fieldParams": field_params + } + if self.db_name is not None: + payload["dbName"] = self.db_name + if db_name != "default": + payload["dbName"] = db_name + response = self.post(url, headers=self.update_headers(), data=payload) + return response.json() + + def alter_index_properties(self, collection_name, index_name, properties, db_name="default"): + """Alter index properties""" + url = f"{self.endpoint}/v2/vectordb/indexes/alter_properties" + payload = { + "collectionName": collection_name, + "indexName": index_name, + "properties": properties + } + if self.db_name is not None: + payload["dbName"] = self.db_name + if db_name != "default": + payload["dbName"] = db_name + response = self.post(url, headers=self.update_headers(), data=payload) + return response.json() + + def drop_index_properties(self, collection_name, index_name, delete_keys, db_name="default"): + """Drop index properties""" + url = f"{self.endpoint}/v2/vectordb/indexes/drop_properties" + payload = { + "collectionName": collection_name, + "indexName": index_name, + "deleteKeys": delete_keys + } + if self.db_name is not None: + payload["dbName"] = self.db_name + if db_name != "default": + payload["dbName"] = db_name + response = self.post(url, headers=self.update_headers(), data=payload) + return response.json() + class PartitionClient(Requests): @@ -748,7 +845,7 @@ def index_create(self, payload, db_name="default"): res = response.json() return res - def index_describe(self, db_name="default", collection_name=None, index_name=None): + def index_describe(self, collection_name=None, index_name=None, db_name="default",): url = f'{self.endpoint}/v2/vectordb/indexes/describe' if self.db_name is not None: db_name = self.db_name diff --git a/tests/restful_client_v2/testcases/test_collection_operations.py b/tests/restful_client_v2/testcases/test_collection_operations.py index c054d2b2ed47f..4b28f5cfcdab2 100644 --- a/tests/restful_client_v2/testcases/test_collection_operations.py +++ b/tests/restful_client_v2/testcases/test_collection_operations.py @@ -1406,3 +1406,155 @@ def test_create_collections_with_invalid_api_key(self): } rsp = client.collection_create(payload) assert rsp['code'] == 1800 + + +@pytest.mark.L0 +class TestCollectionProperties(TestBase): + """Test collection property operations""" + + def test_refresh_load_collection(self): + """ + target: test refresh load collection + method: create collection, refresh load + expected: refresh load success + """ + name = gen_collection_name() + dim = 128 + client = self.collection_client + payload = { + "collectionName": name, + "dimension": dim, + } + rsp = client.collection_create(payload) + assert rsp['code'] == 0 + + # release collection + client.collection_release(collection_name=name) + # load collection + client.collection_load(collection_name=name) + client.wait_load_completed(collection_name=name) + # refresh load + rsp = client.refresh_load(collection_name=name) + + assert rsp['code'] == 0 + + def test_alter_collection_properties(self): + """ + target: test alter collection properties + method: create collection, alter properties + expected: alter properties success + """ + name = gen_collection_name() + dim = 128 + client = self.collection_client + payload = { + "collectionName": name, + "dimension": dim, + } + rsp = client.collection_create(payload) + assert rsp['code'] == 0 + client.collection_release(collection_name=name) + # alter properties + properties = {"mmap.enabled": "true"} + rsp = client.alter_collection_properties(name, properties) + assert rsp['code'] == 0 + rsp = client.collection_describe(name) + enabled_mmap = False + for prop in rsp['data']['properties']: + if prop['key'] == "mmap.enabled": + assert prop['value'] == "true" + enabled_mmap = True + assert enabled_mmap + + def test_drop_collection_properties(self): + """ + target: test drop collection properties + method: create collection, alter properties, drop properties + expected: drop properties success + """ + name = gen_collection_name() + dim = 128 + client = self.collection_client + payload = { + "collectionName": name, + "dimension": dim, + } + rsp = client.collection_create(payload) + assert rsp['code'] == 0 + client.collection_release(collection_name=name) + + # alter properties + properties = {"mmap.enabled": "true"} + rsp = client.alter_collection_properties(name, properties) + assert rsp['code'] == 0 + rsp = client.collection_describe(name) + enabled_mmap = False + for prop in rsp['data']['properties']: + if prop['key'] == "mmap.enabled": + assert prop['value'] == "true" + enabled_mmap = True + assert enabled_mmap + + # drop properties + delete_keys = ["mmap.enabled"] + rsp = client.drop_collection_properties(name, delete_keys) + assert rsp['code'] == 0 + rsp = client.collection_describe(name) + enabled_mmap = False + for prop in rsp['data']['properties']: + if prop['key'] == "mmap.enabled": + enabled_mmap = True + assert not enabled_mmap + + def test_alter_field_properties(self): + """ + target: test alter field properties + method: create collection with varchar field, alter field properties + expected: alter field properties success + """ + name = gen_collection_name() + dim = 128 + client = self.collection_client + payload = { + "collectionName": name, + "schema": { + "autoId": True, + "enableDynamicField": True, + "fields": [ + {"fieldName": "book_id", "dataType": "Int64", "isPrimary": True, "elementTypeParams": {}}, + {"fieldName": "user_id", "dataType": "Int64", "isPartitionKey": True, + "elementTypeParams": {}}, + {"fieldName": "word_count", "dataType": "Int64", "elementTypeParams": {}}, + {"fieldName": "book_describe", "dataType": "VarChar", "elementTypeParams": {"max_length": "256"}}, + {"fieldName": "book_intro", "dataType": "FloatVector", "elementTypeParams": {"dim": f"{dim}"}}, + {"fieldName": "image_intro", "dataType": "FloatVector", "elementTypeParams": {"dim": f"{dim}"}}, + ] + } + } + rsp = client.collection_create(payload) + assert rsp['code'] == 0 + # release collection + client.collection_release(collection_name=name) + + # describe collection + rsp = client.collection_describe(name) + for field in rsp['data']['fields']: + if field['name'] == "book_describe": + for p in field['params']: + if p['key'] == "max_length": + assert p['value'] == "256" + + # alter field properties + field_params = {"max_length": "100"} + rsp = client.alter_field_properties(name, "book_describe", field_params) + assert rsp['code'] == 0 + + # describe collection + rsp = client.collection_describe(name) + for field in rsp['data']['fields']: + if field['name'] == "book_describe": + for p in field['params']: + if p['key'] == "max_length": + assert p['value'] == "100" + +