github.com/m3db/m3@v1.5.0/src/query/api/v1/handler/namespace/add_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 "errors" 25 "io/ioutil" 26 "net/http" 27 "net/http/httptest" 28 "strings" 29 "testing" 30 31 "github.com/m3db/m3/src/cluster/kv" 32 nsproto "github.com/m3db/m3/src/dbnode/generated/proto/namespace" 33 "github.com/m3db/m3/src/dbnode/namespace" 34 "github.com/m3db/m3/src/query/api/v1/validators" 35 "github.com/m3db/m3/src/x/instrument" 36 xjson "github.com/m3db/m3/src/x/json" 37 xtest "github.com/m3db/m3/src/x/test" 38 39 "github.com/golang/mock/gomock" 40 "github.com/stretchr/testify/assert" 41 "github.com/stretchr/testify/require" 42 ) 43 44 const testAddJSON = ` 45 { 46 "name": "testNamespace", 47 "options": { 48 "bootstrapEnabled": true, 49 "flushEnabled": true, 50 "writesToCommitLog": true, 51 "cleanupEnabled": true, 52 "repairEnabled": true, 53 "retentionOptions": { 54 "retentionPeriodNanos": 172800000000000, 55 "blockSizeNanos": 7200000000000, 56 "bufferFutureNanos": 600000000000, 57 "bufferPastNanos": 600000000000, 58 "blockDataExpiry": true, 59 "blockDataExpiryAfterNotAccessPeriodNanos": 300000000000 60 }, 61 "snapshotEnabled": true, 62 "indexOptions": { 63 "enabled": true, 64 "blockSizeNanos": 7200000000000 65 }, 66 "stagingState": { 67 "status": "INITIALIZING" 68 }, 69 "extendedOptions": { 70 "type": "testExtendedOptions", 71 "options": { 72 "value": "foo" 73 } 74 } 75 } 76 } 77 ` 78 79 func TestNamespaceAddHandler(t *testing.T) { 80 ctrl := gomock.NewController(t) 81 defer ctrl.Finish() 82 83 mockClient, mockKV := setupNamespaceTest(t, ctrl) 84 addHandler := NewAddHandler(mockClient, instrument.NewOptions(), validators.NamespaceValidator) 85 mockClient.EXPECT().Store(gomock.Any()).Return(mockKV, nil) 86 87 // Error case where required fields are not set 88 w := httptest.NewRecorder() 89 90 jsonInput := xjson.Map{ 91 "name": "testNamespace", 92 "options": xjson.Map{}, 93 } 94 95 req := httptest.NewRequest("POST", "/namespace", 96 xjson.MustNewTestReader(t, jsonInput)) 97 require.NotNil(t, req) 98 99 addHandler.ServeHTTP(svcDefaults, w, req) 100 101 resp := w.Result() 102 body, err := ioutil.ReadAll(resp.Body) 103 assert.NoError(t, err) 104 assert.Equal(t, http.StatusBadRequest, resp.StatusCode) 105 assert.JSONEq(t, 106 `{"status":"error","error":"bad namespace metadata: retention options must be set"}`, 107 string(body)) 108 109 // Test good case. Note: there is no way to tell the difference between a boolean 110 // being false and it not being set by a user. 111 w = httptest.NewRecorder() 112 113 req = httptest.NewRequest("POST", "/namespace", strings.NewReader(testAddJSON)) 114 require.NotNil(t, req) 115 116 mockKV.EXPECT().Get(M3DBNodeNamespacesKey).Return(nil, kv.ErrNotFound) 117 mockKV.EXPECT().CheckAndSet(M3DBNodeNamespacesKey, gomock.Any(), gomock.Not(nil)).Return(1, nil) 118 addHandler.ServeHTTP(svcDefaults, w, req) 119 120 resp = w.Result() 121 body, _ = ioutil.ReadAll(resp.Body) 122 assert.Equal(t, http.StatusOK, resp.StatusCode) 123 124 expected := xtest.MustPrettyJSONMap(t, 125 xjson.Map{ 126 "registry": xjson.Map{ 127 "namespaces": xjson.Map{ 128 "testNamespace": xjson.Map{ 129 "aggregationOptions": nil, 130 "bootstrapEnabled": true, 131 "cacheBlocksOnRetrieve": false, 132 "flushEnabled": true, 133 "writesToCommitLog": true, 134 "cleanupEnabled": true, 135 "repairEnabled": true, 136 "retentionOptions": xjson.Map{ 137 "retentionPeriodNanos": "172800000000000", 138 "blockSizeNanos": "7200000000000", 139 "bufferFutureNanos": "600000000000", 140 "bufferPastNanos": "600000000000", 141 "blockDataExpiry": true, 142 "blockDataExpiryAfterNotAccessPeriodNanos": "300000000000", 143 "futureRetentionPeriodNanos": "0", 144 }, 145 "snapshotEnabled": true, 146 "stagingState": xjson.Map{"status": "INITIALIZING"}, 147 "indexOptions": xjson.Map{ 148 "enabled": true, 149 "blockSizeNanos": "7200000000000", 150 }, 151 "runtimeOptions": nil, 152 "schemaOptions": nil, 153 "coldWritesEnabled": false, 154 "extendedOptions": xtest.NewTestExtendedOptionsJSON("foo"), 155 }, 156 }, 157 }, 158 }) 159 160 actual := xtest.MustPrettyJSONString(t, string(body)) 161 162 assert.Equal(t, expected, actual, 163 xtest.Diff(expected, actual)) 164 } 165 166 func TestNamespaceAddHandler_Conflict(t *testing.T) { 167 ctrl := gomock.NewController(t) 168 defer ctrl.Finish() 169 170 mockClient, mockKV := setupNamespaceTest(t, ctrl) 171 addHandler := NewAddHandler(mockClient, instrument.NewOptions(), validators.NamespaceValidator) 172 mockClient.EXPECT().Store(gomock.Any()).Return(mockKV, nil) 173 174 // Ensure adding an existing namespace returns 409 175 req := httptest.NewRequest("POST", "/namespace", strings.NewReader(testAddJSON)) 176 require.NotNil(t, req) 177 178 registry := nsproto.Registry{ 179 Namespaces: map[string]*nsproto.NamespaceOptions{ 180 "testNamespace": { 181 BootstrapEnabled: true, 182 FlushEnabled: true, 183 SnapshotEnabled: true, 184 WritesToCommitLog: true, 185 CleanupEnabled: false, 186 RepairEnabled: false, 187 RetentionOptions: &nsproto.RetentionOptions{ 188 RetentionPeriodNanos: 172800000000000, 189 BlockSizeNanos: 7200000000000, 190 BufferFutureNanos: 600000000000, 191 BufferPastNanos: 600000000000, 192 BlockDataExpiry: true, 193 BlockDataExpiryAfterNotAccessPeriodNanos: 3600000000000, 194 }, 195 }, 196 }, 197 } 198 199 mockValue := kv.NewMockValue(ctrl) 200 mockValue.EXPECT().Unmarshal(gomock.Any()).Return(nil).SetArg(0, registry) 201 mockValue.EXPECT().Version().Return(0) 202 mockKV.EXPECT().Get(M3DBNodeNamespacesKey).Return(mockValue, nil) 203 204 w := httptest.NewRecorder() 205 addHandler.ServeHTTP(svcDefaults, w, req) 206 resp := w.Result() 207 assert.Equal(t, http.StatusConflict, resp.StatusCode) 208 } 209 210 func TestNamespaceAddHandler_InvokesNewNamespaceValidator(t *testing.T) { 211 ctrl := gomock.NewController(t) 212 defer ctrl.Finish() 213 214 mockClient, mockKV := setupNamespaceTest(t, ctrl) 215 validator := &testNamespaceValidator{} 216 addHandler := NewAddHandler(mockClient, instrument.NewOptions(), validator) 217 mockClient.EXPECT().Store(gomock.Any()).Return(mockKV, nil) 218 219 req := httptest.NewRequest("POST", "/namespace", strings.NewReader(testAddJSON)) 220 require.NotNil(t, req) 221 222 registry := nsproto.Registry{ 223 Namespaces: map[string]*nsproto.NamespaceOptions{ 224 "firstNamespace": { 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 addHandler.ServeHTTP(svcDefaults, w, req) 250 resp := w.Result() 251 assert.Equal(t, http.StatusBadRequest, resp.StatusCode) 252 assert.Equal(t, 1, validator.invocationCount) 253 } 254 255 type testNamespaceValidator struct { 256 invocationCount int 257 } 258 259 func (v *testNamespaceValidator) ValidateNewNamespace(namespace.Metadata, []namespace.Metadata) error { 260 v.invocationCount++ 261 return errors.New("expected validation error") 262 }