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  }