github.com/m3db/m3@v1.5.0/src/cluster/placementhandler/remove_test.go (about)

     1  // Copyright (c) 2021 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/golang/mock/gomock"
    33  	"github.com/stretchr/testify/assert"
    34  	"github.com/stretchr/testify/require"
    35  
    36  	"github.com/m3db/m3/src/cluster/placement"
    37  	"github.com/m3db/m3/src/cluster/placementhandler/handleroptions"
    38  	"github.com/m3db/m3/src/x/instrument"
    39  )
    40  
    41  func TestPlacementRemoveHandler_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 := NewRemoveHandler(handlerOpts)
    53  		handler.nowFn = func() time.Time { return time.Unix(0, 0) }
    54  
    55  		// Test remove failure
    56  		w := httptest.NewRecorder()
    57  		req := httptest.NewRequest(RemoveHTTPMethod, M3DBRemoveURL,
    58  			strings.NewReader(`{"force": true, "instanceIds":[]}`))
    59  		require.NotNil(t, req)
    60  
    61  		svcDefaults := handleroptions.ServiceNameAndDefaults{
    62  			ServiceName: serviceName,
    63  		}
    64  		mockPlacementService.EXPECT().RemoveInstances(gomock.Any()).Return(
    65  			placement.NewPlacement(),
    66  			errors.New("no new instances found in the valid zone"))
    67  		handler.ServeHTTP(svcDefaults, w, req)
    68  
    69  		resp := w.Result()
    70  		defer resp.Body.Close()
    71  
    72  		body, _ := ioutil.ReadAll(resp.Body)
    73  		assert.JSONEq(t,
    74  			`{"status":"error","error":"no new instances found in the valid zone"}`,
    75  			string(body))
    76  		assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
    77  
    78  		// Test remove success
    79  		w = httptest.NewRecorder()
    80  		req = httptest.NewRequest(RemoveHTTPMethod, M3DBRemoveURL,
    81  			strings.NewReader(`{"force": true, "instanceIds":["host2"]}`))
    82  		require.NotNil(t, req)
    83  
    84  		mockPlacementService.EXPECT().RemoveInstances(gomock.Not(nil)).
    85  			Return(placement.NewPlacement(), nil)
    86  		handler.ServeHTTP(svcDefaults, w, req)
    87  
    88  		resp = w.Result()
    89  		defer resp.Body.Close()
    90  
    91  		body, _ = ioutil.ReadAll(resp.Body)
    92  		//nolint: lll
    93  		assert.Equal(t, `{"placement":{"instances":{},"replicaFactor":0,"numShards":0,"isSharded":false,"cutoverTime":"0","isMirrored":false,"maxShardSetId":0},"version":0}`, string(body))
    94  		assert.Equal(t, http.StatusOK, resp.StatusCode)
    95  	})
    96  }
    97  
    98  func TestPlacementRemoveHandler_SafeOK(t *testing.T) {
    99  	runForAllAllowedServices(func(serviceName string) {
   100  		ctrl := gomock.NewController(t)
   101  		defer ctrl.Finish()
   102  
   103  		mockClient, mockPlacementService := SetupPlacementTest(t, ctrl)
   104  		handlerOpts, err := NewHandlerOptions(
   105  			mockClient, placement.Configuration{}, nil, instrument.NewOptions())
   106  		require.NoError(t, err)
   107  		handler := NewRemoveHandler(handlerOpts)
   108  		handler.nowFn = func() time.Time { return time.Unix(0, 0) }
   109  
   110  		// Test remove error
   111  		w := httptest.NewRecorder()
   112  		req := httptest.NewRequest(RemoveHTTPMethod, M3DBRemoveURL,
   113  			strings.NewReader(`{"instanceIds":["host2"]}`))
   114  
   115  		require.NotNil(t, req)
   116  		var (
   117  			existingPlacement = placement.NewPlacement().
   118  						SetIsSharded(true)
   119  			newPlacement = placement.NewPlacement().
   120  					SetIsSharded(true)
   121  		)
   122  
   123  		switch serviceName {
   124  		case handleroptions.M3CoordinatorServiceName:
   125  			existingPlacement = existingPlacement.
   126  				SetIsSharded(false).
   127  				SetReplicaFactor(1)
   128  			newPlacement = existingPlacement.
   129  				SetIsSharded(false).
   130  				SetReplicaFactor(1)
   131  		case handleroptions.M3AggregatorServiceName:
   132  			existingPlacement = existingPlacement.
   133  				SetIsMirrored(true).
   134  				SetReplicaFactor(1)
   135  			newPlacement = newPlacement.
   136  				SetIsMirrored(true).
   137  				SetReplicaFactor(1)
   138  		}
   139  
   140  		mockPlacementService.EXPECT().RemoveInstances(gomock.Any()).
   141  			Return(nil, errors.New("test err"))
   142  		svcDefaults := handleroptions.ServiceNameAndDefaults{
   143  			ServiceName: serviceName,
   144  		}
   145  
   146  		handler.ServeHTTP(svcDefaults, w, req)
   147  
   148  		resp := w.Result()
   149  		defer resp.Body.Close()
   150  
   151  		body, _ := ioutil.ReadAll(resp.Body)
   152  		require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
   153  		require.JSONEq(t, `{"status":"error","error":"test err"}`, string(body))
   154  
   155  		w = httptest.NewRecorder()
   156  		if serviceName == handleroptions.M3AggregatorServiceName {
   157  			req = httptest.NewRequest(RemoveHTTPMethod, M3AggRemoveURL,
   158  				strings.NewReader(`{"instanceIds":["host2"]}`))
   159  		} else {
   160  			req = httptest.NewRequest(RemoveHTTPMethod, M3DBRemoveURL,
   161  				strings.NewReader(`{"instanceIds":["host2"]}`))
   162  		}
   163  
   164  		require.NotNil(t, req)
   165  		newInst := placement.NewInstance().
   166  			SetID("host1").
   167  			SetIsolationGroup("rack1").
   168  			SetZone("test").
   169  			SetWeight(1).
   170  			SetEndpoint("http://host1:1234").
   171  			SetHostname("host1").
   172  			SetPort(1234)
   173  
   174  		returnPlacement := newPlacement.Clone().SetInstances([]placement.Instance{
   175  			newInst,
   176  		})
   177  
   178  		if serviceName == handleroptions.M3CoordinatorServiceName {
   179  			mockPlacementService.EXPECT().RemoveInstances(gomock.Any()).
   180  				Return(returnPlacement.SetVersion(1), nil)
   181  		} else {
   182  			mockPlacementService.EXPECT().RemoveInstances(gomock.Any()).
   183  				Return(existingPlacement.Clone().SetVersion(1), nil)
   184  		}
   185  		handler.ServeHTTP(svcDefaults, w, req)
   186  
   187  		resp = w.Result()
   188  		defer resp.Body.Close()
   189  
   190  		body, _ = ioutil.ReadAll(resp.Body)
   191  
   192  		switch serviceName {
   193  		case handleroptions.M3CoordinatorServiceName:
   194  			//nolint: lll
   195  			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))
   196  		case handleroptions.M3AggregatorServiceName:
   197  			//nolint: lll
   198  			require.Equal(t, `{"placement":{"instances":{},"replicaFactor":1,"numShards":0,"isSharded":true,"cutoverTime":"0","isMirrored":true,"maxShardSetId":0},"version":1}`, string(body))
   199  		default:
   200  			//nolint: lll
   201  			require.Equal(t, `{"placement":{"instances":{},"replicaFactor":0,"numShards":0,"isSharded":true,"cutoverTime":"0","isMirrored":false,"maxShardSetId":0},"version":1}`, string(body))
   202  		}
   203  
   204  		require.Equal(t, http.StatusOK, resp.StatusCode)
   205  	})
   206  }