github.com/m3db/m3@v1.5.0/src/cluster/placementhandler/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 placementhandler
    22  
    23  import (
    24  	"errors"
    25  	"io/ioutil"
    26  	"net/http"
    27  	"net/http/httptest"
    28  	"strings"
    29  	"testing"
    30  	"time"
    31  
    32  	"github.com/m3db/m3/src/cluster/placement"
    33  	"github.com/m3db/m3/src/cluster/placementhandler/handleroptions"
    34  	"github.com/m3db/m3/src/x/instrument"
    35  
    36  	"github.com/golang/mock/gomock"
    37  	"github.com/stretchr/testify/assert"
    38  	"github.com/stretchr/testify/require"
    39  )
    40  
    41  func TestPlacementAddHandler_Force(t *testing.T) {
    42  	runForAllAllowedServices(func(serviceName string) {
    43  		ctrl := gomock.NewController(t)
    44  		defer ctrl.Finish()
    45  
    46  		mockClient, mockPlacementService := SetupPlacementTest(t, ctrl)
    47  
    48  		handlerOpts, err := NewHandlerOptions(
    49  			mockClient, placement.Configuration{}, nil, instrument.NewOptions())
    50  		require.NoError(t, err)
    51  
    52  		handler := NewAddHandler(handlerOpts)
    53  		handler.nowFn = func() time.Time { return time.Unix(0, 0) }
    54  
    55  		// Test add failure
    56  		var (
    57  			w   = httptest.NewRecorder()
    58  			req *http.Request
    59  		)
    60  		if serviceName == handleroptions.M3AggregatorServiceName {
    61  			req = httptest.NewRequest(AddHTTPMethod, M3DBAddURL,
    62  				strings.NewReader(`{"force": true, "instances":[]}`))
    63  		} else {
    64  			req = httptest.NewRequest(AddHTTPMethod, M3DBAddURL,
    65  				strings.NewReader(`{"force": true, "instances":[]}`))
    66  		}
    67  		require.NotNil(t, req)
    68  
    69  		svcDefaults := handleroptions.ServiceNameAndDefaults{
    70  			ServiceName: serviceName,
    71  		}
    72  		mockPlacementService.EXPECT().AddInstances(gomock.Any()).Return(
    73  			placement.NewPlacement(),
    74  			nil,
    75  			errors.New("no new instances found in the valid zone"))
    76  		handler.ServeHTTP(svcDefaults, w, req)
    77  
    78  		resp := w.Result()
    79  		body, _ := ioutil.ReadAll(resp.Body)
    80  		assert.JSONEq(t,
    81  			`{"status":"error","error":"no new instances found in the valid zone"}`,
    82  			string(body))
    83  		assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
    84  
    85  		// Test add success
    86  		w = httptest.NewRecorder()
    87  		if serviceName == handleroptions.M3AggregatorServiceName {
    88  			req = httptest.NewRequest(AddHTTPMethod, M3DBAddURL,
    89  				strings.NewReader(`{"force": true, "instances":[{"id": "host1","isolation_group": "rack1","zone": "test","weight": 1,"endpoint": "http://host1:1234","hostname": "host1","port": 1234}]}`))
    90  		} else {
    91  			req = httptest.NewRequest(AddHTTPMethod, M3DBAddURL,
    92  				strings.NewReader(`{"force": true, "instances":[{"id": "host1","isolation_group": "rack1","zone": "test","weight": 1,"endpoint": "http://host1:1234","hostname": "host1","port": 1234}]}`))
    93  		}
    94  		require.NotNil(t, req)
    95  
    96  		mockPlacementService.EXPECT().AddInstances(gomock.Not(nil)).
    97  			Return(placement.NewPlacement(), nil, nil)
    98  		handler.ServeHTTP(svcDefaults, w, req)
    99  
   100  		resp = w.Result()
   101  		body, _ = ioutil.ReadAll(resp.Body)
   102  		assert.Equal(t, `{"placement":{"instances":{},"replicaFactor":0,"numShards":0,"isSharded":false,"cutoverTime":"0","isMirrored":false,"maxShardSetId":0},"version":0}`, string(body))
   103  		assert.Equal(t, http.StatusOK, resp.StatusCode)
   104  
   105  	})
   106  }
   107  
   108  func TestPlacementAddHandler_SafeErr_NoNewInstance(t *testing.T) {
   109  	runForAllAllowedServices(func(serviceName string) {
   110  		ctrl := gomock.NewController(t)
   111  		defer ctrl.Finish()
   112  
   113  		mockClient := setupPlacementTest(t, ctrl, newValidAvailPlacement())
   114  		handlerOpts, err := NewHandlerOptions(
   115  			mockClient, placement.Configuration{}, nil, instrument.NewOptions())
   116  		require.NoError(t, err)
   117  		handler := NewAddHandler(handlerOpts)
   118  
   119  		// Test add failure
   120  		var (
   121  			w   = httptest.NewRecorder()
   122  			req *http.Request
   123  		)
   124  
   125  		if serviceName == handleroptions.M3AggregatorServiceName {
   126  			req = httptest.NewRequest(AddHTTPMethod, M3AggAddURL, strings.NewReader(`{"instances":[]}`))
   127  		} else {
   128  			req = httptest.NewRequest(AddHTTPMethod, M3DBAddURL, strings.NewReader(`{"instances":[]}`))
   129  		}
   130  
   131  		require.NotNil(t, req)
   132  		svcDefaults := handleroptions.ServiceNameAndDefaults{
   133  			ServiceName: serviceName,
   134  		}
   135  
   136  		handler.ServeHTTP(svcDefaults, w, req)
   137  
   138  		resp := w.Result()
   139  		body, _ := ioutil.ReadAll(resp.Body)
   140  		assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
   141  		assert.JSONEq(t,
   142  			`{"status":"error","error":"no new instances found in the valid zone"}`,
   143  			string(body))
   144  	})
   145  }
   146  
   147  func TestPlacementAddHandler_SafeErr_NotAllAvailable(t *testing.T) {
   148  	runForAllAllowedServices(func(serviceName string) {
   149  		ctrl := gomock.NewController(t)
   150  		defer ctrl.Finish()
   151  
   152  		mockClient := setupPlacementTest(t, ctrl, newValidInitPlacement())
   153  		handlerOpts, err := NewHandlerOptions(
   154  			mockClient, placement.Configuration{}, nil, instrument.NewOptions())
   155  		require.NoError(t, err)
   156  		handler := NewAddHandler(handlerOpts)
   157  
   158  		// Test add failure
   159  		var (
   160  			w   = httptest.NewRecorder()
   161  			req *http.Request
   162  		)
   163  		if serviceName == handleroptions.M3AggregatorServiceName {
   164  			req = httptest.NewRequest(AddHTTPMethod, M3AggAddURL,
   165  				strings.NewReader(`{"instances":[{"id": "host1","isolation_group": "rack1","zone": "test","weight": 1,"endpoint": "http://host1:1234","hostname": "host1","port": 1234}]}`))
   166  		} else {
   167  			req = httptest.NewRequest(AddHTTPMethod, M3DBAddURL,
   168  				strings.NewReader(`{"instances":[{"id": "host1","isolation_group": "rack1","zone": "test","weight": 1,"endpoint": "http://host1:1234","hostname": "host1","port": 1234}]}`))
   169  		}
   170  
   171  		require.NotNil(t, req)
   172  		svcDefaults := handleroptions.ServiceNameAndDefaults{
   173  			ServiceName: serviceName,
   174  		}
   175  
   176  		handler.ServeHTTP(svcDefaults, w, req)
   177  		resp := w.Result()
   178  		body, _ := ioutil.ReadAll(resp.Body)
   179  		assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
   180  		assert.JSONEq(t,
   181  			`{"status":"error","error":"instances do not have all shards available: [A, B]"}`,
   182  			string(body))
   183  	})
   184  }
   185  
   186  func TestPlacementAddHandler_SafeOK(t *testing.T) {
   187  	runForAllAllowedServices(func(serviceName string) {
   188  		ctrl := gomock.NewController(t)
   189  		defer ctrl.Finish()
   190  
   191  		mockClient, mockPlacementService := SetupPlacementTest(t, ctrl)
   192  		handlerOpts, err := NewHandlerOptions(
   193  			mockClient, placement.Configuration{}, nil, instrument.NewOptions())
   194  		require.NoError(t, err)
   195  		handler := NewAddHandler(handlerOpts)
   196  		handler.nowFn = func() time.Time { return time.Unix(0, 0) }
   197  
   198  		// Test add error
   199  		var (
   200  			w   = httptest.NewRecorder()
   201  			req *http.Request
   202  		)
   203  
   204  		switch serviceName {
   205  		case handleroptions.M3AggregatorServiceName:
   206  			req = httptest.NewRequest(AddHTTPMethod, M3AggAddURL,
   207  				strings.NewReader(`{"instances":[{"id": "host1","isolation_group": "rack1","zone": "test","weight": 1,"endpoint": "http://host1:1234","hostname": "host1","port": 1234}]}`))
   208  		default:
   209  			req = httptest.NewRequest(AddHTTPMethod, M3DBAddURL,
   210  				strings.NewReader(`{"instances":[{"id": "host1","isolation_group": "rack1","zone": "test","weight": 1,"endpoint": "http://host1:1234","hostname": "host1","port": 1234}]}`))
   211  		}
   212  
   213  		require.NotNil(t, req)
   214  		var (
   215  			existingPlacement = placement.NewPlacement().
   216  						SetIsSharded(true)
   217  			newPlacement = placement.NewPlacement().
   218  					SetIsSharded(true)
   219  		)
   220  
   221  		switch serviceName {
   222  		case handleroptions.M3CoordinatorServiceName:
   223  			existingPlacement = existingPlacement.
   224  				SetIsSharded(false).
   225  				SetReplicaFactor(1)
   226  			newPlacement = existingPlacement.
   227  				SetIsSharded(false).
   228  				SetReplicaFactor(1)
   229  		case handleroptions.M3AggregatorServiceName:
   230  			existingPlacement = existingPlacement.
   231  				SetIsMirrored(true).
   232  				SetReplicaFactor(1)
   233  			newPlacement = newPlacement.
   234  				SetIsMirrored(true).
   235  				SetReplicaFactor(1)
   236  		}
   237  
   238  		mockPlacementService.EXPECT().AddInstances(gomock.Any()).
   239  			Return(nil, nil, errors.New("test err"))
   240  		svcDefaults := handleroptions.ServiceNameAndDefaults{
   241  			ServiceName: serviceName,
   242  		}
   243  
   244  		handler.ServeHTTP(svcDefaults, w, req)
   245  
   246  		resp := w.Result()
   247  		body, _ := ioutil.ReadAll(resp.Body)
   248  		require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
   249  		require.JSONEq(t, `{"status":"error","error":"test err"}`, string(body))
   250  
   251  		w = httptest.NewRecorder()
   252  		if serviceName == handleroptions.M3AggregatorServiceName {
   253  			req = httptest.NewRequest(AddHTTPMethod, M3AggAddURL,
   254  				strings.NewReader(`{"instances":[{"id": "host1","isolation_group": "rack1","zone": "test","weight": 1,"endpoint": "http://host1:1234","hostname": "host1","port": 1234}]}`))
   255  		} else {
   256  			req = httptest.NewRequest(AddHTTPMethod, M3DBAddURL,
   257  				strings.NewReader(`{"instances":[{"id": "host1","isolation_group": "rack1","zone": "test","weight": 1,"endpoint": "http://host1:1234","hostname": "host1","port": 1234}]}`))
   258  		}
   259  
   260  		require.NotNil(t, req)
   261  		newInst := placement.NewInstance().
   262  			SetID("host1").
   263  			SetIsolationGroup("rack1").
   264  			SetZone("test").
   265  			SetWeight(1).
   266  			SetEndpoint("http://host1:1234").
   267  			SetHostname("host1").
   268  			SetPort(1234)
   269  
   270  		returnPlacement := newPlacement.Clone().SetInstances([]placement.Instance{
   271  			newInst,
   272  		})
   273  
   274  		if serviceName == handleroptions.M3CoordinatorServiceName {
   275  			mockPlacementService.EXPECT().AddInstances(gomock.Any()).
   276  				Return(returnPlacement.SetVersion(1), nil, nil)
   277  		} else {
   278  			mockPlacementService.EXPECT().AddInstances(gomock.Any()).
   279  				Return(existingPlacement.Clone().SetVersion(1), nil, nil)
   280  		}
   281  		handler.ServeHTTP(svcDefaults, w, req)
   282  
   283  		resp = w.Result()
   284  		body, _ = ioutil.ReadAll(resp.Body)
   285  
   286  		switch serviceName {
   287  		case handleroptions.M3CoordinatorServiceName:
   288  			require.Equal(t, `{"placement":{"instances":{"host1":{"id":"host1","isolationGroup":"rack1","zone":"test","weight":1,"endpoint":"http://host1:1234","shards":[],"shardSetId":0,"hostname":"host1","port":1234,"metadata":{"debugPort":0}}},"replicaFactor":1,"numShards":0,"isSharded":false,"cutoverTime":"0","isMirrored":false,"maxShardSetId":0},"version":1}`, string(body))
   289  		case handleroptions.M3AggregatorServiceName:
   290  			require.Equal(t, `{"placement":{"instances":{},"replicaFactor":1,"numShards":0,"isSharded":true,"cutoverTime":"0","isMirrored":true,"maxShardSetId":0},"version":1}`, string(body))
   291  		default:
   292  			require.Equal(t, `{"placement":{"instances":{},"replicaFactor":0,"numShards":0,"isSharded":true,"cutoverTime":"0","isMirrored":false,"maxShardSetId":0},"version":1}`, string(body))
   293  		}
   294  
   295  		require.Equal(t, http.StatusOK, resp.StatusCode)
   296  	})
   297  }