diff --git a/internal/distributed/proxy/httpserver/handler_v2.go b/internal/distributed/proxy/httpserver/handler_v2.go index 3005c4901e..77f8f8b416 100644 --- a/internal/distributed/proxy/httpserver/handler_v2.go +++ b/internal/distributed/proxy/httpserver/handler_v2.go @@ -3,6 +3,7 @@ package httpserver import ( "context" "encoding/json" + "fmt" "io" "net/http" "strconv" @@ -10,7 +11,7 @@ import ( "github.com/cockroachdb/errors" "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" - "github.com/go-playground/validator/v10" + validator "github.com/go-playground/validator/v10" "github.com/golang/protobuf/proto" "github.com/tidwall/gjson" "go.opentelemetry.io/otel" @@ -205,13 +206,32 @@ func wrapperTraceLog(v2 handlerFuncV2) handlerFuncV2 { } } +func checkAuthorizationV2(ctx context.Context, c *gin.Context, ignoreErr bool, req interface{}) error { + username, ok := c.Get(ContextUsername) + if !ok || username.(string) == "" { + if !ignoreErr { + c.JSON(http.StatusUnauthorized, gin.H{HTTPReturnCode: merr.Code(merr.ErrNeedAuthenticate), HTTPReturnMessage: merr.ErrNeedAuthenticate.Error()}) + } + return merr.ErrNeedAuthenticate + } + _, authErr := proxy.PrivilegeInterceptor(ctx, req) + if authErr != nil { + if !ignoreErr { + c.JSON(http.StatusForbidden, gin.H{HTTPReturnCode: merr.Code(authErr), HTTPReturnMessage: authErr.Error()}) + } + return authErr + } + + return nil +} + func wrapperProxy(ctx context.Context, c *gin.Context, req any, checkAuth bool, ignoreErr bool, handler func(reqCtx context.Context, req any) (any, error)) (interface{}, error) { if baseGetter, ok := req.(BaseGetter); ok { span := trace.SpanFromContext(ctx) span.AddEvent(baseGetter.GetBase().GetMsgType().String()) } if checkAuth { - err := checkAuthorization(ctx, c, req) + err := checkAuthorizationV2(ctx, c, ignoreErr, req) if err != nil { return nil, err } @@ -314,6 +334,7 @@ func (h *HandlersV2) getCollectionDetails(ctx context.Context, c *gin.Context, a } else { autoID = primaryField.AutoID } + errMessage := "" loadStateReq := &milvuspb.GetLoadStateRequest{ DbName: dbName, CollectionName: collectionName, @@ -324,6 +345,8 @@ func (h *HandlersV2) getCollectionDetails(ctx context.Context, c *gin.Context, a collLoadState := "" if err == nil { collLoadState = stateResp.(*milvuspb.GetLoadStateResponse).State.String() + } else { + errMessage += err.Error() + ";" } vectorField := "" for _, field := range coll.Schema.Fields { @@ -338,22 +361,26 @@ func (h *HandlersV2) getCollectionDetails(ctx context.Context, c *gin.Context, a CollectionName: collectionName, FieldName: vectorField, } - indexResp, err := wrapperProxy(ctx, c, descIndexReq, false, true, func(reqCtx context.Context, req any) (any, error) { + indexResp, err := wrapperProxy(ctx, c, descIndexReq, h.checkAuth, true, func(reqCtx context.Context, req any) (any, error) { return h.proxy.DescribeIndex(reqCtx, req.(*milvuspb.DescribeIndexRequest)) }) if err == nil { indexDesc = printIndexes(indexResp.(*milvuspb.DescribeIndexResponse).IndexDescriptions) + } else { + errMessage += err.Error() + ";" } var aliases []string aliasReq := &milvuspb.ListAliasesRequest{ DbName: dbName, CollectionName: collectionName, } - aliasResp, err := wrapperProxy(ctx, c, aliasReq, h.checkAuth, false, func(reqCtx context.Context, req any) (interface{}, error) { + aliasResp, err := wrapperProxy(ctx, c, aliasReq, h.checkAuth, true, func(reqCtx context.Context, req any) (interface{}, error) { return h.proxy.ListAliases(reqCtx, req.(*milvuspb.ListAliasesRequest)) }) if err == nil { aliases = aliasResp.(*milvuspb.ListAliasesResponse).GetAliases() + } else { + errMessage += err.Error() + "." } if aliases == nil { aliases = []string{} @@ -375,7 +402,7 @@ func (h *HandlersV2) getCollectionDetails(ctx context.Context, c *gin.Context, a "consistencyLevel": commonpb.ConsistencyLevel_name[int32(coll.ConsistencyLevel)], "enableDynamicField": coll.Schema.EnableDynamicField, "properties": coll.Properties, - }}) + }, HTTPReturnMessage: errMessage}) return resp, nil } @@ -426,8 +453,11 @@ func (h *HandlersV2) getCollectionLoadState(ctx context.Context, c *gin.Context, return h.proxy.GetLoadingProgress(reqCtx, req.(*milvuspb.GetLoadingProgressRequest)) }) progress := int64(-1) + errMessage := "" if err == nil { progress = progressResp.(*milvuspb.GetLoadingProgressResponse).Progress + } else { + errMessage += err.Error() + "." } state := commonpb.LoadState_LoadStateLoading.String() if progress >= 100 { @@ -436,7 +466,7 @@ func (h *HandlersV2) getCollectionLoadState(ctx context.Context, c *gin.Context, c.JSON(http.StatusOK, gin.H{HTTPReturnCode: http.StatusOK, HTTPReturnData: gin.H{ HTTPReturnLoadState: state, HTTPReturnLoadProgress: progress, - }}) + }, HTTPReturnMessage: errMessage}) return resp, err } @@ -985,7 +1015,7 @@ func (h *HandlersV2) createCollection(ctx context.Context, c *gin.Context, anyRe } } for key, fieldParam := range field.ElementTypeParams { - fieldSchema.TypeParams = append(fieldSchema.TypeParams, &commonpb.KeyValuePair{Key: key, Value: fieldParam}) + fieldSchema.TypeParams = append(fieldSchema.TypeParams, &commonpb.KeyValuePair{Key: key, Value: fmt.Sprintf("%v", fieldParam)}) } collSchema.Fields = append(collSchema.Fields, &fieldSchema) fieldNames[field.FieldName] = true @@ -1079,8 +1109,8 @@ func (h *HandlersV2) createCollection(ctx context.Context, c *gin.Context, anyRe IndexName: indexParam.IndexName, ExtraParams: []*commonpb.KeyValuePair{{Key: common.MetricTypeKey, Value: indexParam.MetricType}}, } - for key, value := range indexParam.IndexConfig { - createIndexReq.ExtraParams = append(createIndexReq.ExtraParams, &commonpb.KeyValuePair{Key: key, Value: value}) + for key, value := range indexParam.Params { + createIndexReq.ExtraParams = append(createIndexReq.ExtraParams, &commonpb.KeyValuePair{Key: key, Value: fmt.Sprintf("%v", value)}) } statusResponse, err := wrapperProxy(ctx, c, createIndexReq, h.checkAuth, false, func(reqCtx context.Context, req any) (interface{}, error) { return h.proxy.CreateIndex(ctx, req.(*milvuspb.CreateIndexRequest)) @@ -1507,8 +1537,8 @@ func (h *HandlersV2) createIndex(ctx context.Context, c *gin.Context, anyReq any {Key: common.MetricTypeKey, Value: indexParam.MetricType}, }, } - for key, value := range indexParam.IndexConfig { - req.ExtraParams = append(req.ExtraParams, &commonpb.KeyValuePair{Key: key, Value: value}) + for key, value := range indexParam.Params { + req.ExtraParams = append(req.ExtraParams, &commonpb.KeyValuePair{Key: key, Value: fmt.Sprintf("%v", value)}) } resp, err := wrapperProxy(ctx, c, req, false, false, func(reqCtx context.Context, req any) (interface{}, error) { return h.proxy.CreateIndex(reqCtx, req.(*milvuspb.CreateIndexRequest)) diff --git a/internal/distributed/proxy/httpserver/handler_v2_test.go b/internal/distributed/proxy/httpserver/handler_v2_test.go index 2ab3695fbb..e47e8aa959 100644 --- a/internal/distributed/proxy/httpserver/handler_v2_test.go +++ b/internal/distributed/proxy/httpserver/handler_v2_test.go @@ -283,6 +283,39 @@ func TestGrpcWrapper(t *testing.T) { fmt.Println(w.Body.String()) }) } + + path = "/wrapper/grpc/auth" + app.GET(path, func(c *gin.Context) { + wrapperProxy(context.Background(), c, &milvuspb.DescribeCollectionRequest{}, true, false, handle) + }) + appNeedAuth.GET(path, func(c *gin.Context) { + ctx := proxy.NewContextWithMetadata(c, "test", DefaultDbName) + wrapperProxy(ctx, c, &milvuspb.LoadCollectionRequest{}, true, false, handle) + }) + t.Run("check authorization", func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, path, nil) + w := httptest.NewRecorder() + ginHandler.ServeHTTP(w, req) + assert.Equal(t, http.StatusUnauthorized, w.Code) + returnBody := &ReturnErrMsg{} + err := json.Unmarshal(w.Body.Bytes(), returnBody) + assert.Nil(t, err) + assert.Equal(t, int32(1800), returnBody.Code) + assert.Equal(t, "user hasn't authenticated", returnBody.Message) + fmt.Println(w.Body.String()) + + paramtable.Get().Save(proxy.Params.CommonCfg.AuthorizationEnabled.Key, "true") + req = httptest.NewRequest(http.MethodGet, needAuthPrefix+path, nil) + req.SetBasicAuth("test", util.DefaultRootPassword) + w = httptest.NewRecorder() + ginHandler.ServeHTTP(w, req) + assert.Equal(t, http.StatusForbidden, w.Code) + err = json.Unmarshal(w.Body.Bytes(), returnBody) + assert.Nil(t, err) + assert.Equal(t, int32(2), returnBody.Code) + assert.Equal(t, "service unavailable: internal: Milvus Proxy is not ready yet. please wait", returnBody.Message) + fmt.Println(w.Body.String()) + }) } type headerTestCase struct { @@ -441,8 +474,8 @@ func TestCreateCollection(t *testing.T) { "fields": [ {"fieldName": "book_id", "dataType": "Int64", "isPrimary": true, "elementTypeParams": {}}, {"fieldName": "word_count", "dataType": "Int64", "isPartitionKey": false, "elementTypeParams": {}}, - {"fieldName": "partition_field", "dataType": "VarChar", "isPartitionKey": true, "elementTypeParams": {"max_length": "256"}}, - {"fieldName": "book_intro", "dataType": "FloatVector", "elementTypeParams": {"dim": "2"}} + {"fieldName": "partition_field", "dataType": "VarChar", "isPartitionKey": true, "elementTypeParams": {"max_length": 256}}, + {"fieldName": "book_intro", "dataType": "FloatVector", "elementTypeParams": {"dim": 2}} ] }, "params": {"partitionsNum": "32"}}`), }) @@ -452,7 +485,7 @@ func TestCreateCollection(t *testing.T) { "fields": [ {"fieldName": "book_id", "dataType": "Int64", "isPrimary": true, "elementTypeParams": {}}, {"fieldName": "word_count", "dataType": "Int64", "elementTypeParams": {}}, - {"fieldName": "book_intro", "dataType": "FloatVector", "elementTypeParams": {"dim": "2"}} + {"fieldName": "book_intro", "dataType": "FloatVector", "elementTypeParams": {"dim": 2}} ] }, "indexParams": [{"fieldName": "book_intro", "indexName": "book_intro_vector", "metricType": "L2"}]}`), }) @@ -462,7 +495,7 @@ func TestCreateCollection(t *testing.T) { "fields": [ {"fieldName": "book_id", "dataType": "int64", "isPrimary": true, "elementTypeParams": {}}, {"fieldName": "word_count", "dataType": "Int64", "elementTypeParams": {}}, - {"fieldName": "book_intro", "dataType": "FloatVector", "elementTypeParams": {"dim": "2"}} + {"fieldName": "book_intro", "dataType": "FloatVector", "elementTypeParams": {"dim": 2}} ] }}`), errMsg: "invalid parameter, data type int64 is invalid(case sensitive).", @@ -473,8 +506,8 @@ func TestCreateCollection(t *testing.T) { requestBody: []byte(`{"collectionName": "` + DefaultCollectionName + `", "schema": { "fields": [ {"fieldName": "book_id", "dataType": "Int64", "isPrimary": true, "elementTypeParams": {}}, - {"fieldName": "word_count", "dataType": "Array", "elementDataType": "Int64", "elementTypeParams": {"max_capacity": "2"}}, - {"fieldName": "book_intro", "dataType": "FloatVector", "elementTypeParams": {"dim": "2"}} + {"fieldName": "word_count", "dataType": "Array", "elementDataType": "Int64", "elementTypeParams": {"max_capacity": 2}}, + {"fieldName": "book_intro", "dataType": "FloatVector", "elementTypeParams": {"dim": 2}} ] }}`), }) @@ -484,7 +517,7 @@ func TestCreateCollection(t *testing.T) { "fields": [ {"fieldName": "book_id", "dataType": "Int64", "isPrimary": true, "elementTypeParams": {}}, {"fieldName": "word_count", "dataType": "Array", "elementDataType": "int64", "elementTypeParams": {}}, - {"fieldName": "book_intro", "dataType": "FloatVector", "elementTypeParams": {"dim": "2"}} + {"fieldName": "book_intro", "dataType": "FloatVector", "elementTypeParams": {"dim": 2}} ] }}`), errMsg: "invalid parameter, element data type int64 is invalid(case sensitive).", @@ -496,7 +529,7 @@ func TestCreateCollection(t *testing.T) { "fields": [ {"fieldName": "book_id", "dataType": "Int64", "isPrimary": true, "elementTypeParams": {}}, {"fieldName": "word_count", "dataType": "Int64", "elementTypeParams": {}}, - {"fieldName": "book_intro", "dataType": "FloatVector", "elementTypeParams": {"dim": "2"}} + {"fieldName": "book_intro", "dataType": "FloatVector", "elementTypeParams": {"dim": 2}} ] }, "indexParams": [{"fieldName": "book_xxx", "indexName": "book_intro_vector", "metricType": "L2"}]}`), errMsg: "missing required parameters, error: `book_xxx` hasn't defined in schema", @@ -514,7 +547,7 @@ func TestCreateCollection(t *testing.T) { "fields": [ {"fieldName": "book_id", "dataType": "Int64", "isPrimary": true, "elementTypeParams": {}}, {"fieldName": "word_count", "dataType": "Int64", "elementTypeParams": {}}, - {"fieldName": "book_intro", "dataType": "FloatVector", "elementTypeParams": {"dim": "2"}} + {"fieldName": "book_intro", "dataType": "FloatVector", "elementTypeParams": {"dim": 2}} ] }, "indexParams": [{"fieldName": "book_intro", "indexName": "book_intro_vector", "metricType": "L2"}]}`), errMsg: "", @@ -629,9 +662,11 @@ func TestMethodGet(t *testing.T) { Schema: generateCollectionSchema(schemapb.DataType_Int64), ShardsNum: ShardNumDefault, Status: &StatusSuccess, - }, nil).Once() + }, nil).Twice() mp.EXPECT().DescribeCollection(mock.Anything, mock.Anything).Return(&milvuspb.DescribeCollectionResponse{Status: commonErrorStatus}, nil).Once() - mp.EXPECT().GetLoadState(mock.Anything, mock.Anything).Return(&DefaultLoadStateResp, nil).Twice() + mp.EXPECT().GetLoadState(mock.Anything, mock.Anything).Return(&milvuspb.GetLoadStateResponse{Status: commonErrorStatus}, nil).Once() + mp.EXPECT().GetLoadState(mock.Anything, mock.Anything).Return(&DefaultLoadStateResp, nil).Times(3) + mp.EXPECT().DescribeIndex(mock.Anything, mock.Anything).Return(&milvuspb.DescribeIndexResponse{Status: commonErrorStatus}, nil).Once() mp.EXPECT().DescribeIndex(mock.Anything, mock.Anything).Return(&DefaultDescIndexesReqp, nil).Times(3) mp.EXPECT().DescribeIndex(mock.Anything, mock.Anything).Return(nil, merr.WrapErrIndexNotFoundForCollection(DefaultCollectionName)).Once() mp.EXPECT().DescribeIndex(mock.Anything, mock.Anything).Return(&milvuspb.DescribeIndexResponse{ @@ -653,6 +688,7 @@ func TestMethodGet(t *testing.T) { Status: commonSuccessStatus, Progress: int64(77), }, nil).Once() + mp.EXPECT().GetLoadingProgress(mock.Anything, mock.Anything).Return(&milvuspb.GetLoadingProgressResponse{Status: commonErrorStatus}, nil).Once() mp.EXPECT().ShowPartitions(mock.Anything, mock.Anything).Return(&milvuspb.ShowPartitionsResponse{ Status: &StatusSuccess, PartitionNames: []string{DefaultPartitionName}, @@ -700,6 +736,7 @@ func TestMethodGet(t *testing.T) { }, }, }, nil).Once() + mp.EXPECT().ListAliases(mock.Anything, mock.Anything).Return(&milvuspb.ListAliasesResponse{Status: commonErrorStatus}, nil).Once() mp.EXPECT().ListAliases(mock.Anything, mock.Anything).Return(&milvuspb.ListAliasesResponse{ Status: &StatusSuccess, }, nil).Once() @@ -731,6 +768,9 @@ func TestMethodGet(t *testing.T) { queryTestCases = append(queryTestCases, rawTestCase{ path: versionalV2(CollectionCategory, DescribeAction), }) + queryTestCases = append(queryTestCases, rawTestCase{ + path: versionalV2(CollectionCategory, DescribeAction), + }) queryTestCases = append(queryTestCases, rawTestCase{ path: versionalV2(CollectionCategory, DescribeAction), errMsg: "", @@ -745,6 +785,9 @@ func TestMethodGet(t *testing.T) { queryTestCases = append(queryTestCases, rawTestCase{ path: versionalV2(CollectionCategory, LoadStateAction), }) + queryTestCases = append(queryTestCases, rawTestCase{ + path: versionalV2(CollectionCategory, LoadStateAction), + }) queryTestCases = append(queryTestCases, rawTestCase{ path: versionalV2(PartitionCategory, ListAction), }) @@ -958,8 +1001,8 @@ func TestMethodPost(t *testing.T) { bodyReader := bytes.NewReader([]byte(`{` + `"collectionName": "` + DefaultCollectionName + `", "newCollectionName": "test", "newDbName": "",` + `"partitionName": "` + DefaultPartitionName + `", "partitionNames": ["` + DefaultPartitionName + `"],` + - `"schema": {"fields": [{"fieldName": "book_id", "dataType": "Int64", "elementTypeParams": {}}, {"fieldName": "book_intro", "dataType": "FloatVector", "elementTypeParams": {"dim": "2"}}]},` + - `"indexParams": [{"indexName": "` + DefaultIndexName + `", "fieldName": "book_intro", "metricType": "L2", "indexConfig": {"nlist": "30", "index_type": "IVF_FLAT"}}],` + + `"schema": {"fields": [{"fieldName": "book_id", "dataType": "Int64", "elementTypeParams": {}}, {"fieldName": "book_intro", "dataType": "FloatVector", "elementTypeParams": {"dim": 2}}]},` + + `"indexParams": [{"indexName": "` + DefaultIndexName + `", "fieldName": "book_intro", "metricType": "L2", "params": {"nlist": 30, "index_type": "IVF_FLAT"}}],` + `"userName": "` + util.UserRoot + `", "password": "Milvus", "newPassword": "milvus", "roleName": "` + util.RoleAdmin + `",` + `"roleName": "` + util.RoleAdmin + `", "objectType": "Global", "objectName": "*", "privilege": "*",` + `"aliasName": "` + DefaultAliasName + `",` + diff --git a/internal/distributed/proxy/httpserver/request_v2.go b/internal/distributed/proxy/httpserver/request_v2.go index de1b749ef1..a71c716fcc 100644 --- a/internal/distributed/proxy/httpserver/request_v2.go +++ b/internal/distributed/proxy/httpserver/request_v2.go @@ -205,10 +205,10 @@ type GrantReq struct { } type IndexParam struct { - FieldName string `json:"fieldName" binding:"required"` - IndexName string `json:"indexName" binding:"required"` - MetricType string `json:"metricType" binding:"required"` - IndexConfig map[string]string `json:"indexConfig"` + FieldName string `json:"fieldName" binding:"required"` + IndexName string `json:"indexName" binding:"required"` + MetricType string `json:"metricType" binding:"required"` + Params map[string]interface{} `json:"params"` } type IndexParamReq struct { @@ -235,12 +235,12 @@ func (req *IndexReq) GetIndexName() string { } type FieldSchema struct { - FieldName string `json:"fieldName" binding:"required"` - DataType string `json:"dataType" binding:"required"` - ElementDataType string `json:"elementDataType"` - IsPrimary bool `json:"isPrimary"` - IsPartitionKey bool `json:"isPartitionKey"` - ElementTypeParams map[string]string `json:"elementTypeParams" binding:"required"` + FieldName string `json:"fieldName" binding:"required"` + DataType string `json:"dataType" binding:"required"` + ElementDataType string `json:"elementDataType"` + IsPrimary bool `json:"isPrimary"` + IsPartitionKey bool `json:"isPartitionKey"` + ElementTypeParams map[string]interface{} `json:"elementTypeParams" binding:"required"` } type CollectionSchema struct { @@ -270,6 +270,8 @@ type AliasReq struct { AliasName string `json:"aliasName" binding:"required"` } +func (req *AliasReq) GetDbName() string { return req.DbName } + func (req *AliasReq) GetAliasName() string { return req.AliasName } diff --git a/tests/restful_client_v2/testcases/test_index_operation.py b/tests/restful_client_v2/testcases/test_index_operation.py index 30d15503bc..b6515ece4b 100644 --- a/tests/restful_client_v2/testcases/test_index_operation.py +++ b/tests/restful_client_v2/testcases/test_index_operation.py @@ -72,9 +72,9 @@ class TestCreateIndex(TestBase): "metricType": f"{metric_type}"}] } if index_type == "HNSW": - payload["indexParams"][0]["indexConfig"] = {"index_type": "HNSW", "M": "16", "efConstruction": "200"} + payload["indexParams"][0]["params"] = {"index_type": "HNSW", "M": "16", "efConstruction": "200"} if index_type == "AUTOINDEX": - payload["indexParams"][0]["indexConfig"] = {"index_type": "AUTOINDEX"} + payload["indexParams"][0]["params"] = {"index_type": "AUTOINDEX"} rsp = self.index_client.index_create(payload) assert rsp['code'] == 200 time.sleep(10) @@ -90,7 +90,7 @@ class TestCreateIndex(TestBase): assert expected_index[i]['fieldName'] == actual_index[i]['fieldName'] assert expected_index[i]['indexName'] == actual_index[i]['indexName'] assert expected_index[i]['metricType'] == actual_index[i]['metricType'] - assert expected_index[i]["indexConfig"]['index_type'] == actual_index[i]['indexType'] + assert expected_index[i]["params"]['index_type'] == actual_index[i]['indexType'] # drop index for i in range(len(actual_index)): @@ -154,10 +154,10 @@ class TestCreateIndex(TestBase): payload = { "collectionName": name, "indexParams": [{"fieldName": "binary_vector", "indexName": index_name, "metricType": metric_type, - "indexConfig": {"index_type": index_type}}] + "params": {"index_type": index_type}}] } if index_type == "BIN_IVF_FLAT": - payload["indexParams"][0]["indexConfig"]["nlist"] = "16384" + payload["indexParams"][0]["params"]["nlist"] = "16384" rsp = self.index_client.index_create(payload) assert rsp['code'] == 200 time.sleep(10) @@ -172,7 +172,7 @@ class TestCreateIndex(TestBase): for i in range(len(expected_index)): assert expected_index[i]['fieldName'] == actual_index[i]['fieldName'] assert expected_index[i]['indexName'] == actual_index[i]['indexName'] - assert expected_index[i]['indexConfig']['index_type'] == actual_index[i]['indexType'] + assert expected_index[i]['params']['index_type'] == actual_index[i]['indexType'] @pytest.mark.L1 @@ -228,9 +228,9 @@ class TestCreateIndexNegative(TestBase): payload = { "collectionName": name, "indexParams": [{"fieldName": "binary_vector", "indexName": index_name, "metricType": metric_type, - "indexConfig": {"index_type": index_type}}] + "params": {"index_type": index_type}}] } if index_type == "BIN_IVF_FLAT": - payload["indexParams"][0]["indexConfig"]["nlist"] = "16384" + payload["indexParams"][0]["params"]["nlist"] = "16384" rsp = self.index_client.index_create(payload) assert rsp['code'] == 65535