github.com/m3db/m3@v1.5.0/src/cluster/placementhandler/init_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  
    31  	"github.com/m3db/m3/src/cluster/generated/proto/placementpb"
    32  	"github.com/m3db/m3/src/cluster/kv"
    33  	"github.com/m3db/m3/src/cluster/placement"
    34  	"github.com/m3db/m3/src/cluster/placementhandler/handleroptions"
    35  	"github.com/m3db/m3/src/x/instrument"
    36  
    37  	"github.com/golang/mock/gomock"
    38  	"github.com/stretchr/testify/assert"
    39  	"github.com/stretchr/testify/require"
    40  )
    41  
    42  var (
    43  	initTestPlacementProto = &placementpb.Placement{
    44  		Instances: map[string]*placementpb.Instance{
    45  			"host1": &placementpb.Instance{
    46  				Id:             "host1",
    47  				IsolationGroup: "rack1",
    48  				Zone:           "test",
    49  				Weight:         1,
    50  				Endpoint:       "http://host1:1234",
    51  				Hostname:       "host1",
    52  				Port:           1234,
    53  				Metadata: &placementpb.InstanceMetadata{
    54  					DebugPort: 0,
    55  				},
    56  			},
    57  			"host2": &placementpb.Instance{
    58  				Id:             "host2",
    59  				IsolationGroup: "rack1",
    60  				Zone:           "test",
    61  				Weight:         1,
    62  				Endpoint:       "http://host2:1234",
    63  				Hostname:       "host2",
    64  				Port:           1234,
    65  				Metadata: &placementpb.InstanceMetadata{
    66  					DebugPort: 0,
    67  				},
    68  			},
    69  		},
    70  	}
    71  )
    72  
    73  func TestPlacementInitHandler(t *testing.T) {
    74  	runForAllAllowedServices(func(serviceName string) {
    75  		ctrl := gomock.NewController(t)
    76  		defer ctrl.Finish()
    77  
    78  		mockClient, mockPlacementService := SetupPlacementTest(t, ctrl)
    79  		handlerOpts, err := NewHandlerOptions(
    80  			mockClient, placement.Configuration{}, nil, instrument.NewOptions())
    81  		require.NoError(t, err)
    82  		handler := NewInitHandler(handlerOpts)
    83  
    84  		// Test placement init success
    85  		var (
    86  			w   = httptest.NewRecorder()
    87  			req *http.Request
    88  		)
    89  		if serviceName == handleroptions.M3AggregatorServiceName {
    90  			req = httptest.NewRequest(InitHTTPMethod, M3DBInitURL, strings.NewReader(`{"instances": [{"id": "host1","isolation_group": "rack1","zone": "test","weight": 1,"endpoint": "http://host1:1234","hostname": "host1","port": 1234, "metadata": {"debugPort":0}},{"id": "host2","isolation_group": "rack1","zone": "test","weight": 1,"endpoint": "http://host2:1234","hostname": "host2","port": 1234, "metadata": {"debugPort":0}}],"num_shards": 16,"replication_factor": 1}`))
    91  		} else {
    92  			req = httptest.NewRequest(InitHTTPMethod, M3DBInitURL, strings.NewReader(`{"instances": [{"id": "host1","isolation_group": "rack1","zone": "test","weight": 1,"endpoint": "http://host1:1234","hostname": "host1","port": 1234, "metadata": {"debugPort":0}},{"id": "host2","isolation_group": "rack1","zone": "test","weight": 1,"endpoint": "http://host2:1234","hostname": "host2","port": 1234, "metadata": {"debugPort":0}}],"num_shards": 16,"replication_factor": 1}`))
    93  		}
    94  
    95  		require.NotNil(t, req)
    96  
    97  		newPlacement, err := placement.NewPlacementFromProto(initTestPlacementProto)
    98  		require.NoError(t, err)
    99  
   100  		svcDefaults := handleroptions.ServiceNameAndDefaults{
   101  			ServiceName: serviceName,
   102  		}
   103  
   104  		mockPlacementService.EXPECT().BuildInitialPlacement(gomock.Not(nil), 16, 1).Return(newPlacement, nil)
   105  		handler.ServeHTTP(svcDefaults, w, req)
   106  		resp := w.Result()
   107  		body, err := ioutil.ReadAll(resp.Body)
   108  		require.NoError(t, err)
   109  		assert.Equal(t, http.StatusOK, resp.StatusCode)
   110  		assert.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}},"host2":{"id":"host2","isolationGroup":"rack1","zone":"test","weight":1,"endpoint":"http://host2:1234","shards":[],"shardSetId":0,"hostname":"host2","port":1234,"metadata":{"debugPort":0}}},"replicaFactor":0,"numShards":0,"isSharded":false,"cutoverTime":"0","isMirrored":false,"maxShardSetId":0},"version":0}`, string(body))
   111  
   112  		// Test error response
   113  		w = httptest.NewRecorder()
   114  		if serviceName == handleroptions.M3AggregatorServiceName {
   115  			req = httptest.NewRequest(InitHTTPMethod, M3DBInitURL, strings.NewReader(`{"instances": [{"id": "host1","isolation_group": "rack1","zone": "test","weight": 1,"endpoint": "host1:1234","hostname": "host1","port": 1234, "metadata": {"debugPort":0}},{"id": "host2","isolation_group": "rack1","zone": "test","weight": 1,"endpoint": "http://host2:1234","hostname": "host2","port": 1234, "metadata": {"debugPort":0}}],"num_shards": 64,"replication_factor": 2}`))
   116  		} else {
   117  			req = httptest.NewRequest(InitHTTPMethod, M3DBInitURL, strings.NewReader(`{"instances": [{"id": "host1","isolation_group": "rack1","zone": "test","weight": 1,"endpoint": "host1:1234","hostname": "host1","port": 1234, "metadata": {"debugPort":0}},{"id": "host2","isolation_group": "rack1","zone": "test","weight": 1,"endpoint": "http://host2:1234","hostname": "host2","port": 1234, "metadata": {"debugPort":0}}],"num_shards": 64,"replication_factor": 2}`))
   118  		}
   119  		require.NotNil(t, req)
   120  
   121  		switch serviceName {
   122  		case handleroptions.M3CoordinatorServiceName:
   123  			mockPlacementService.EXPECT().
   124  				BuildInitialPlacement(gomock.Not(nil), 64, 1).
   125  				Return(nil, errors.New("unable to build initial placement"))
   126  		default:
   127  			mockPlacementService.EXPECT().
   128  				BuildInitialPlacement(gomock.Not(nil), 64, 2).
   129  				Return(nil, errors.New("unable to build initial placement"))
   130  		}
   131  
   132  		handler.ServeHTTP(svcDefaults, w, req)
   133  		resp = w.Result()
   134  		body, err = ioutil.ReadAll(resp.Body)
   135  		require.NoError(t, err)
   136  		assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
   137  		assert.JSONEq(t,
   138  			`{"status":"error","error":"unable to build initial placement"}`,
   139  			string(body))
   140  
   141  		// Test error response
   142  		w = httptest.NewRecorder()
   143  		if serviceName == handleroptions.M3AggregatorServiceName {
   144  			req = httptest.NewRequest(InitHTTPMethod, M3DBInitURL, strings.NewReader(`{"instances": [{"id": "host1","isolation_group": "rack1","zone": "test","weight": 1,"endpoint": "host1:1234","hostname": "host1","port": 1234, "metadata": {"debugPort":0}},{"id": "host2","isolation_group": "rack1","zone": "test","weight": 1,"endpoint": "http://host2:1234","hostname": "host2","port": 1234, "metadata": {"debugPort":0}}],"num_shards": 64,"replication_factor": 2}`))
   145  		} else {
   146  			req = httptest.NewRequest(InitHTTPMethod, M3DBInitURL, strings.NewReader(`{"instances": [{"id": "host1","isolation_group": "rack1","zone": "test","weight": 1,"endpoint": "host1:1234","hostname": "host1","port": 1234, "metadata": {"debugPort":0}},{"id": "host2","isolation_group": "rack1","zone": "test","weight": 1,"endpoint": "http://host2:1234","hostname": "host2","port": 1234, "metadata": {"debugPort":0}}],"num_shards": 64,"replication_factor": 2}`))
   147  		}
   148  
   149  		require.NotNil(t, req)
   150  		switch serviceName {
   151  		case handleroptions.M3CoordinatorServiceName:
   152  			mockPlacementService.EXPECT().
   153  				BuildInitialPlacement(gomock.Not(nil), 64, 1).
   154  				Return(nil, kv.ErrAlreadyExists)
   155  		default:
   156  			mockPlacementService.EXPECT().
   157  				BuildInitialPlacement(gomock.Not(nil), 64, 2).
   158  				Return(nil, kv.ErrAlreadyExists)
   159  		}
   160  
   161  		handler.ServeHTTP(svcDefaults, w, req)
   162  		resp = w.Result()
   163  		_, err = ioutil.ReadAll(resp.Body)
   164  		require.NoError(t, err)
   165  		assert.Equal(t, http.StatusConflict, resp.StatusCode)
   166  	})
   167  }