github.com/m3db/m3@v1.5.0/src/query/api/v1/handler/namespace/schema_test.go (about) 1 // Copyright (c) 2018 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package namespace 22 23 import ( 24 "fmt" 25 "io/ioutil" 26 "net/http" 27 "net/http/httptest" 28 "os" 29 "os/exec" 30 "path/filepath" 31 "strings" 32 "testing" 33 34 "github.com/m3db/m3/src/cluster/kv" 35 "github.com/m3db/m3/src/cluster/placementhandler/handleroptions" 36 nsproto "github.com/m3db/m3/src/dbnode/generated/proto/namespace" 37 "github.com/m3db/m3/src/dbnode/namespace" 38 "github.com/m3db/m3/src/dbnode/namespace/kvadmin" 39 "github.com/m3db/m3/src/x/instrument" 40 xjson "github.com/m3db/m3/src/x/json" 41 42 "github.com/golang/mock/gomock" 43 "github.com/stretchr/testify/assert" 44 "github.com/stretchr/testify/require" 45 ) 46 47 const ( 48 mainProtoStr = `syntax = "proto3"; 49 50 package mainpkg; 51 52 import "mainpkg/imported.proto"; 53 54 message TestMessage { 55 double latitude = 1; 56 double longitude = 2; 57 int64 epoch = 3; 58 bytes deliveryID = 4; 59 map<string, string> attributes = 5; 60 ImportedMessage an_imported_message = 6; 61 } 62 ` 63 importedProtoStr = ` 64 syntax = "proto3"; 65 66 package mainpkg; 67 68 message ImportedMessage { 69 double latitude = 1; 70 double longitude = 2; 71 int64 epoch = 3; 72 bytes deliveryID = 4; 73 } 74 ` 75 testSchemaJQ = ` 76 { 77 name: "testNamespace", 78 msgName: "mainpkg.TestMessage", 79 protoName: "mainpkg/test.proto", 80 protoMap: 81 { 82 "mainpkg/test.proto": $file1, 83 "mainpkg/imported.proto": $file2 84 } 85 } 86 ` 87 testSchemaJSON = ` 88 { 89 "name": "testNamespace", 90 "msgName": "mainpkg.TestMessage", 91 "protoName": "mainpkg/test.proto", 92 "protoMap": { 93 "mainpkg/test.proto": "syntax = \"proto3\";\n\npackage mainpkg;\n\nimport \"mainpkg/imported.proto\";\n\nmessage TestMessage {\n double latitude = 1;\n double longitude = 2;\n int64 epoch = 3;\n bytes deliveryID = 4;\n map<string, string> attributes = 5;\n ImportedMessage an_imported_message = 6;\n}", 94 "mainpkg/imported.proto": "\nsyntax = \"proto3\";\n\npackage mainpkg;\n\nmessage ImportedMessage {\n double latitude = 1;\n double longitude = 2;\n int64 epoch = 3;\n bytes deliveryID = 4;\n}" 95 } 96 } 97 ` 98 ) 99 100 var ( 101 svcDefaults = handleroptions.ServiceNameAndDefaults{ 102 ServiceName: "m3db", 103 } 104 ) 105 106 func genTestJSON(t *testing.T) string { 107 tempDir, err := ioutil.TempDir("", "schema_deploy_test") 108 require.NoError(t, err) 109 defer os.RemoveAll(tempDir) 110 111 file1 := filepath.Join(tempDir, "test.proto") 112 file2 := filepath.Join(tempDir, "imported.proto") 113 require.NoError(t, ioutil.WriteFile(file1, []byte(mainProtoStr), 0644)) 114 require.NoError(t, ioutil.WriteFile(file2, []byte(importedProtoStr), 0644)) 115 116 jqfile := filepath.Join(tempDir, "test.jq") 117 require.NoError(t, ioutil.WriteFile(jqfile, []byte(testSchemaJQ), 0644)) 118 119 file1var := "\"$(<" + file1 + ")\"" 120 file2var := "\"$(<" + file2 + ")\"" 121 cmd := exec.Command("sh", "-c", "jq -n --arg file1 "+file1var+ 122 " --arg file2 "+file2var+" -f "+jqfile) 123 t.Logf("cmd args are %v\n", cmd.Args) 124 out, err := cmd.CombinedOutput() 125 require.NoError(t, err) 126 t.Logf("generated json is \n%s\n", string(out)) 127 return string(out) 128 } 129 130 func TestGenTestJson(t *testing.T) { 131 t.Skip("skip for now as jq is not installed on buildkite") 132 genTestJSON(t) 133 } 134 135 func TestSchemaDeploy_KVKeyNotFound(t *testing.T) { 136 ctrl := gomock.NewController(t) 137 defer ctrl.Finish() 138 139 mockClient, mockKV := setupNamespaceTest(t, ctrl) 140 addHandler := NewSchemaHandler(mockClient, instrument.NewOptions()) 141 mockClient.EXPECT().Store(gomock.Any()).Return(mockKV, nil) 142 143 // Error case where required fields are not set 144 w := httptest.NewRecorder() 145 146 jsonInput := xjson.Map{ 147 "name": "testNamespace", 148 } 149 150 req := httptest.NewRequest("POST", "/schema", 151 xjson.MustNewTestReader(t, jsonInput)) 152 require.NotNil(t, req) 153 154 mockKV.EXPECT().Get(M3DBNodeNamespacesKey).Return(nil, kv.ErrNotFound) 155 addHandler.ServeHTTP(svcDefaults, w, req) 156 157 resp := w.Result() 158 body, err := ioutil.ReadAll(resp.Body) 159 assert.NoError(t, err) 160 assert.Equal(t, http.StatusNotFound, resp.StatusCode) 161 assert.JSONEq(t, `{"status":"error","error":"namespace is not found"}`, string(body)) 162 } 163 164 func TestSchemaDeploy(t *testing.T) { 165 ctrl := gomock.NewController(t) 166 defer ctrl.Finish() 167 168 mockClient, mockKV := setupNamespaceTest(t, ctrl) 169 schemaHandler := NewSchemaHandler(mockClient, instrument.NewOptions()) 170 mockClient.EXPECT().Store(gomock.Any()).Return(mockKV, nil) 171 172 mockAdminSvc := kvadmin.NewMockNamespaceMetadataAdminService(ctrl) 173 newAdminService = func(kv.Store, string, func() string) kvadmin.NamespaceMetadataAdminService { return mockAdminSvc } 174 defer func() { newAdminService = kvadmin.NewAdminService }() 175 176 mockAdminSvc.EXPECT().DeploySchema("testNamespace", "mainpkg/test.proto", 177 "mainpkg.TestMessage", gomock.Any()).Do( 178 func(name, file, msg string, protos map[string]string) { 179 schemaOpt, err := namespace.AppendSchemaOptions(nil, file, msg, protos, "first") 180 require.NoError(t, err) 181 require.Equal(t, "mainpkg.TestMessage", schemaOpt.DefaultMessageName) 182 sh, err := namespace.LoadSchemaHistory(schemaOpt) 183 require.NoError(t, err) 184 descr, ok := sh.GetLatest() 185 require.True(t, ok) 186 require.NotNil(t, descr) 187 require.Equal(t, "first", descr.DeployId()) 188 }).Return("first", nil) 189 190 w := httptest.NewRecorder() 191 192 // TODO [haijun] buildkite does not have jq, so use the pre-generated json string. 193 //testSchemaJson := genTestJson(t) 194 req := httptest.NewRequest("POST", "/schema", strings.NewReader(testSchemaJSON)) 195 require.NotNil(t, req) 196 197 schemaHandler.ServeHTTP(svcDefaults, w, req) 198 199 resp := w.Result() 200 body, _ := ioutil.ReadAll(resp.Body) 201 assert.Equal(t, http.StatusOK, resp.StatusCode) 202 assert.Equal(t, "{\"deployID\":\"first\"}", string(body)) 203 } 204 205 func TestSchemaDeploy_NamespaceNotFound(t *testing.T) { 206 ctrl := gomock.NewController(t) 207 defer ctrl.Finish() 208 209 mockClient, mockKV := setupNamespaceTest(t, ctrl) 210 schemaHandler := NewSchemaHandler(mockClient, instrument.NewOptions()) 211 mockClient.EXPECT().Store(gomock.Any()).Return(mockKV, nil) 212 213 jsonInput := xjson.Map{ 214 "name": "no-such-namespace", 215 } 216 217 // Ensure adding to an non-existing namespace returns 404 218 req := httptest.NewRequest("POST", "/namespace", 219 xjson.MustNewTestReader(t, jsonInput)) 220 require.NotNil(t, req) 221 222 registry := nsproto.Registry{ 223 Namespaces: map[string]*nsproto.NamespaceOptions{ 224 "testNamespace": { 225 BootstrapEnabled: true, 226 FlushEnabled: true, 227 SnapshotEnabled: true, 228 WritesToCommitLog: true, 229 CleanupEnabled: false, 230 RepairEnabled: false, 231 RetentionOptions: &nsproto.RetentionOptions{ 232 RetentionPeriodNanos: 172800000000000, 233 BlockSizeNanos: 7200000000000, 234 BufferFutureNanos: 600000000000, 235 BufferPastNanos: 600000000000, 236 BlockDataExpiry: true, 237 BlockDataExpiryAfterNotAccessPeriodNanos: 3600000000000, 238 }, 239 }, 240 }, 241 } 242 243 mockValue := kv.NewMockValue(ctrl) 244 mockValue.EXPECT().Unmarshal(gomock.Any()).Return(nil).SetArg(0, registry) 245 mockValue.EXPECT().Version().Return(0) 246 mockKV.EXPECT().Get(M3DBNodeNamespacesKey).Return(mockValue, nil) 247 248 w := httptest.NewRecorder() 249 schemaHandler.ServeHTTP(svcDefaults, w, req) 250 resp := w.Result() 251 body, _ := ioutil.ReadAll(resp.Body) 252 assert.Equal(t, http.StatusNotFound, resp.StatusCode) 253 assert.JSONEq(t, `{"status":"error","error":"namespace is not found"}`, string(body)) 254 } 255 256 func TestSchemaReset(t *testing.T) { 257 ctrl := gomock.NewController(t) 258 defer ctrl.Finish() 259 260 mockClient, mockKV := setupNamespaceTest(t, ctrl) 261 schemaHandler := NewSchemaResetHandler(mockClient, instrument.NewOptions()) 262 mockClient.EXPECT().Store(gomock.Any()).Return(mockKV, nil) 263 264 mockAdminSvc := kvadmin.NewMockNamespaceMetadataAdminService(ctrl) 265 newAdminService = func(kv.Store, string, func() string) kvadmin.NamespaceMetadataAdminService { return mockAdminSvc } 266 defer func() { newAdminService = kvadmin.NewAdminService }() 267 268 mockAdminSvc.EXPECT().ResetSchema("testNamespace").Return(nil) 269 270 w := httptest.NewRecorder() 271 272 jsonInput := xjson.Map{ 273 "name": "testNamespace", 274 } 275 276 req := httptest.NewRequest("DELETE", "/schema", 277 xjson.MustNewTestReader(t, jsonInput)) 278 require.NotNil(t, req) 279 280 schemaHandler.ServeHTTP(svcDefaults, w, req) 281 282 resp := w.Result() 283 body, _ := ioutil.ReadAll(resp.Body) 284 assert.Equal(t, http.StatusBadRequest, resp.StatusCode, 285 fmt.Sprintf("response: %s", body)) 286 287 w = httptest.NewRecorder() 288 req = httptest.NewRequest("DELETE", "/schema", 289 xjson.MustNewTestReader(t, jsonInput)) 290 require.NotNil(t, req) 291 req.Header.Add("Force", "true") 292 293 schemaHandler.ServeHTTP(svcDefaults, w, req) 294 295 resp = w.Result() 296 body, _ = ioutil.ReadAll(resp.Body) 297 assert.Equal(t, http.StatusOK, resp.StatusCode, 298 fmt.Sprintf("response: %s", body)) 299 assert.Equal(t, "{}", string(body)) 300 }