fix: restful v2 (#32162)

issue: #31176
master pr: #32144
2.4 pr: #32160

1. cannot get dbName correctly while describe alias [Bug]: [restful
v2]The dbName parameter of the describe alias interface did not take
effect #31978
2. return a valid json string even if the user doesn't have the whole
privileges to describe collection [Bug]: [restful v2]When accessing the
interface with a user without permission, the response will contain
multiple results, some of which will report an error of lack of
permission, while one will return the correct result #31635
3. rename IndexParam.IndexConfig to IndexParam.Params
4. FieldSchema.ElementTypeParams, IndexParam.Params can not only accept
string

Signed-off-by: PowderLi <min.li@zilliz.com>
This commit is contained in:
PowderLi 2024-04-23 16:07:09 +08:00 committed by GitHub
parent 8da82afb59
commit 0f118e7083
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 117 additions and 42 deletions

View File

@ -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))

View File

@ -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 + `",` +

View File

@ -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
}

View File

@ -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