diff --git a/.gitignore b/.gitignore index 6152560af8..412e72f6f2 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ *.cov *.html *.tmp +.DS_Store test.log # glide manages this @@ -46,4 +47,3 @@ site/ !m3db.io/**/vendor # Automatically populated from asset sources m3db.io/openapi - diff --git a/README.md b/README.md index f8df9515e9..61dd9107d3 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ produce a lightweight production image from a single Dockerfile. Accordingly, it 17.05 or later to build. ``` -docker build -t m3dbnode:$(git rev-parse head) . +docker build -f docker/m3dbnode/Dockerfile -t m3dbnode:$(git rev-parse head) . docker run --name m3dbnode m3dbnode:$(git rev-parse head) ``` @@ -98,7 +98,7 @@ If you wish to build an image with the source code included you can stop the bui `builder` stage: ``` -docker build -t m3dbnode:$(git rev-parse head) --target builder . +docker build -f docker/m3dbnode/Dockerfile -t m3dbnode:$(git rev-parse head) --target builder . ``` ## Configuration diff --git a/docs/operational_guide/placement_configuration.md b/docs/operational_guide/placement_configuration.md index f63e4ace87..f296590e0f 100644 --- a/docs/operational_guide/placement_configuration.md +++ b/docs/operational_guide/placement_configuration.md @@ -64,10 +64,10 @@ The instructions below all contain sample curl commands, but you can always revi #### Placement Initialization -Send a POST request to the `/api/v1/placement/init` endpoint +Send a POST request to the `/api/v1/services/m3db/placement/init` endpoint ```bash -curl -X POST localhost:7201/api/v1/placement/init -d '{ +curl -X POST localhost:7201/api/v1/services/m3db/placement/init -d '{ "num_shards": , "replication_factor": (recommended 3), "instances": [ @@ -104,10 +104,10 @@ curl -X POST localhost:7201/api/v1/placement/init -d '{ #### Adding a Node -Send a POST request to the `/api/v1/placement` endpoint +Send a POST request to the `/api/v1/services/m3db/placement` endpoint ```bash -curl -X POST :/api/v1/placement -d '{ +curl -X POST :/api/v1/services/m3db/placement -d '{ "instances": [ { "id": "", @@ -126,16 +126,31 @@ After sending the add command you will need to wait for the M3DB cluster to reac #### Removing a Node -Send a DELETE request to the `/api/v1/placement/` endpoint. +Send a DELETE request to the `/api/v1/services/m3db/placement/` endpoint. ```bash -curl -X DELETE :/api/v1/placement/ +curl -X DELETE :/api/v1/services/m3db/placement/ ``` After sending the delete command you will need to wait for the M3DB cluster to reach the new desired state. You'll know that this has been achieved when the placement shows that all shards for all hosts are in the `Available` state. #### Replacing a Node -Currently, the best way to replace a node (due to hardware failure or any other reason) is to perform a node remove followed by a node add. We will eventually support node replacement as a single operation, but that is currently not implemented. - +Send a POST request to the `/api/v1/services/m3db/placement/replace` endpoint containing hosts to replace and candidates to replace it with. +```bash +curl -X POST :/api/v1/services/m3db/placement/replace -d '{ + "leavingInstanceIDs": [""], + "candidates": [ + { + "id": "", + "isolationGroup": "", + "zone": "", + "weight": , + "endpoint": ":(default 9000)", + "hostname": "", + "port": + } + ] +}' +``` diff --git a/src/dbnode/example/README.md b/src/dbnode/example/README.md deleted file mode 100644 index 4ce26d63e6..0000000000 --- a/src/dbnode/example/README.md +++ /dev/null @@ -1,20 +0,0 @@ -## WARNING: This is Alpha software and not intended for use until a stable release. - -# Running a single node of M3DB on GCP - -Setup GCP for m3db: - - 1. Download necessary packages - $ sudo apt-get install golang golint make git golang-glide - 2. Set GOPATH: - $ export GOPATH=$HOME/code - 3. Create m3db directory - $ mkdir -p $HOME/code/src/github.com/m3db - 4. cd into m3db directory and git clone m3db - $ git clone https://github.com/m3db/m3 - 5. Build m3db - $ git submodule update --init --recursive - $ glide install - $ make services - 8. Run m3db: - $ sudo ./bin/m3dbnode -f example/m3db-node-config.yaml diff --git a/src/dbnode/example/m3db-node-config.yaml b/src/dbnode/example/m3db-node-config.yaml deleted file mode 100644 index 10276cda72..0000000000 --- a/src/dbnode/example/m3db-node-config.yaml +++ /dev/null @@ -1,195 +0,0 @@ -logging: - level: info - -metrics: - prometheus: - handlerPath: /metrics - sanitization: prometheus - samplingRate: 1.0 - extended: detailed - -listenAddress: 0.0.0.0:9000 -clusterListenAddress: 0.0.0.0:9001 -httpNodeListenAddress: 0.0.0.0:9002 -httpClusterListenAddress: 0.0.0.0:9003 -debugListenAddress: 0.0.0.0:9004 - -hostID: - resolver: config - value: m3db_server - -client: - writeConsistencyLevel: majority - readConsistencyLevel: unstrict_majority - writeTimeout: 10s - fetchTimeout: 15s - connectTimeout: 20s - writeRetry: - initialBackoff: 500ms - backoffFactor: 3 - maxRetries: 2 - jitter: true - fetchRetry: - initialBackoff: 500ms - backoffFactor: 2 - maxRetries: 3 - jitter: true - backgroundHealthCheckFailLimit: 4 - backgroundHealthCheckFailThrottleFactor: 0.5 - -gcPercentage: 100 - -writeNewSeriesAsync: true -writeNewSeriesLimitPerSecond: 1048576 -writeNewSeriesBackoffDuration: 2ms - -bootstrap: - bootstrappers: - - filesystem - - commitlog - - peers - - uninitialized_topology - fs: - numProcessorsPerCPU: 0.125 - -commitlog: - flushMaxBytes: 524288 - flushEvery: 1s - queue: - calculationType: fixed - size: 2097152 - blockSize: 10m - -fs: - filePathPrefix: /var/lib/m3db - writeBufferSize: 65536 - dataReadBufferSize: 65536 - infoReadBufferSize: 128 - seekReadBufferSize: 4096 - throughputLimitMbps: 100.0 - throughputCheckEvery: 128 - -repair: - enabled: false - interval: 2h - offset: 30m - jitter: 1h - throttle: 2m - checkInterval: 1m - -pooling: - blockAllocSize: 16 - type: simple - seriesPool: - size: 262144 - lowWatermark: 0.7 - highWatermark: 1.0 - blockPool: - size: 262144 - lowWatermark: 0.7 - highWatermark: 1.0 - encoderPool: - size: 262144 - lowWatermark: 0.7 - highWatermark: 1.0 - closersPool: - size: 104857 - lowWatermark: 0.7 - highWatermark: 1.0 - contextPool: - size: 262144 - lowWatermark: 0.7 - highWatermark: 1.0 - segmentReaderPool: - size: 16384 - lowWatermark: 0.7 - highWatermark: 1.0 - iteratorPool: - size: 2048 - lowWatermark: 0.7 - highWatermark: 1.0 - fetchBlockMetadataResultsPool: - size: 65536 - capacity: 32 - lowWatermark: 0.7 - highWatermark: 1.0 - fetchBlocksMetadataResultsPool: - size: 32 - capacity: 4096 - lowWatermark: 0.7 - highWatermark: 1.0 - hostBlockMetadataSlicePool: - size: 131072 - capacity: 3 - lowWatermark: 0.7 - highWatermark: 1.0 - blockMetadataPool: - size: 65536 - lowWatermark: 0.7 - highWatermark: 1.0 - blockMetadataSlicePool: - size: 65536 - capacity: 32 - lowWatermark: 0.7 - highWatermark: 1.0 - blocksMetadataPool: - size: 65536 - lowWatermark: 0.7 - highWatermark: 1.0 - blocksMetadataSlicePool: - size: 32 - capacity: 4096 - lowWatermark: 0.7 - highWatermark: 1.0 - identifierPool: - size: 262144 - lowWatermark: 0.7 - highWatermark: 1.0 - bytesPool: - buckets: - - capacity: 16 - size: 524288 - lowWatermark: 0.7 - highWatermark: 1.0 - - capacity: 32 - size: 262144 - lowWatermark: 0.7 - highWatermark: 1.0 - - capacity: 64 - size: 131072 - lowWatermark: 0.7 - highWatermark: 1.0 - - capacity: 128 - size: 65536 - lowWatermark: 0.7 - highWatermark: 1.0 - - capacity: 256 - size: 65536 - lowWatermark: 0.7 - highWatermark: 1.0 - - capacity: 1440 - size: 16384 - lowWatermark: 0.7 - highWatermark: 1.0 - - capacity: 4096 - size: 8192 - lowWatermark: 0.7 - highWatermark: 1.0 - -config: - static: - topology: - shards: 32 - replicas: 1 - hosts: - - hostID: m3db_server - listenAddress: "127.0.0.1:9000" - namespaces: - - id: metrics - retention: - retentionPeriod: 48h - blockSize: 2h - bufferPast: 1h - index: - enabled: true - blockSize: 4h diff --git a/src/query/api/v1/handler/placement/add.go b/src/query/api/v1/handler/placement/add.go index b856e0391a..16cb968742 100644 --- a/src/query/api/v1/handler/placement/add.go +++ b/src/query/api/v1/handler/placement/add.go @@ -21,7 +21,6 @@ package placement import ( - "fmt" "net/http" "path" "time" @@ -62,14 +61,6 @@ var ( // AddHandler is the handler for placement adds. type AddHandler Handler -type unsafeAddError struct { - hosts string -} - -func (e unsafeAddError) Error() string { - return fmt.Sprintf("instances [%s] do not have all shards available", e.hosts) -} - // NewAddHandler returns a new instance of AddHandler. func NewAddHandler(opts HandlerOptions) *AddHandler { return &AddHandler{HandlerOptions: opts, nowFn: time.Now} @@ -168,5 +159,6 @@ func (h *AddHandler) Add( return nil, err } + // TODO(schallert): change after https://github.com/m3db/m3/issues/1165 return newPlacement.SetVersion(version + 1), nil } diff --git a/src/query/api/v1/handler/placement/add_test.go b/src/query/api/v1/handler/placement/add_test.go index 4973685c45..92690ab9a5 100644 --- a/src/query/api/v1/handler/placement/add_test.go +++ b/src/query/api/v1/handler/placement/add_test.go @@ -154,7 +154,7 @@ func TestPlacementAddHandler_SafeOK(t *testing.T) { ) switch serviceName { case M3AggregatorServiceName: - req = httptest.NewRequest(AddHTTPMethod, M3DBAddURL, strings.NewReader(`{"instances":[{"id": "host1","isolation_group": "rack1","zone": "test","weight": 1,"endpoint": "http://host1:1234","hostname": "host1","port": 1234}]}`)) + req = httptest.NewRequest(AddHTTPMethod, M3AggAddURL, strings.NewReader(`{"instances":[{"id": "host1","isolation_group": "rack1","zone": "test","weight": 1,"endpoint": "http://host1:1234","hostname": "host1","port": 1234}]}`)) default: req = httptest.NewRequest(AddHTTPMethod, M3DBAddURL, strings.NewReader(`{"instances":[{"id": "host1","isolation_group": "rack1","zone": "test","weight": 1,"endpoint": "http://host1:1234","hostname": "host1","port": 1234}]}`)) } diff --git a/src/query/api/v1/handler/placement/common.go b/src/query/api/v1/handler/placement/common.go index 22210864f2..b4b4d3adc3 100644 --- a/src/query/api/v1/handler/placement/common.go +++ b/src/query/api/v1/handler/placement/common.go @@ -381,6 +381,15 @@ func RegisterRoutes(r *mux.Router, opts HandlerOptions) { r.HandleFunc(M3DBDeleteURL, deleteFn).Methods(DeleteHTTPMethod) r.HandleFunc(M3AggDeleteURL, deleteFn).Methods(DeleteHTTPMethod) r.HandleFunc(M3CoordinatorDeleteURL, getFn).Methods(GetHTTPMethod) + + // Replace + var ( + replaceHandler = NewReplaceHandler(opts) + replaceFn = applyMiddleware(replaceHandler.ServeHTTP) + ) + r.HandleFunc(M3DBReplaceURL, replaceFn).Methods(ReplaceHTTPMethod) + r.HandleFunc(M3AggReplaceURL, replaceFn).Methods(ReplaceHTTPMethod) + r.HandleFunc(M3CoordinatorReplaceURL, replaceFn).Methods(ReplaceHTTPMethod) } func newPlacementCutoverNanosFn( @@ -460,6 +469,14 @@ type m3aggregatorPlacementOpts struct { propagationDelay time.Duration } +type unsafeAddError struct { + hosts string +} + +func (e unsafeAddError) Error() string { + return fmt.Sprintf("instances [%s] do not have all shards available", e.hosts) +} + func validateAllAvailable(p placement.Placement) error { badInsts := []string{} for _, inst := range p.Instances() { @@ -517,3 +534,11 @@ func parseServiceFromRequest(r *http.Request) (string, error) { return "", errUnableToParseService } + +func isStateless(serviceName string) bool { + switch serviceName { + case M3CoordinatorServiceName: + return true + } + return false +} diff --git a/src/query/api/v1/handler/placement/common_test.go b/src/query/api/v1/handler/placement/common_test.go index f9f4b0d68e..de1ea2a96a 100644 --- a/src/query/api/v1/handler/placement/common_test.go +++ b/src/query/api/v1/handler/placement/common_test.go @@ -262,3 +262,18 @@ func runForAllAllowedServices(f func(service string)) { f(service) } } + +func TestIsStateless(t *testing.T) { + for _, s := range []string{ + M3CoordinatorServiceName, + } { + assert.True(t, isStateless(s)) + } + + for _, s := range []string{ + M3AggregatorServiceName, + M3DBServiceName, + } { + assert.False(t, isStateless(s)) + } +} diff --git a/src/query/api/v1/handler/placement/delete.go b/src/query/api/v1/handler/placement/delete.go index c7d3704202..cac06aae65 100644 --- a/src/query/api/v1/handler/placement/delete.go +++ b/src/query/api/v1/handler/placement/delete.go @@ -99,11 +99,11 @@ func (h *DeleteHandler) ServeHTTP(serviceName string, w http.ResponseWriter, r * toRemove := []string{id} - switch serviceName { - case M3CoordinatorServiceName: - // There are no unsafe placement changes because M3Coordinator is stateless + // There are no unsafe placement changes because M3Coordinator is stateless + if isStateless(serviceName) { force = true } + var newPlacement placement.Placement if force { newPlacement, err = service.RemoveInstances(toRemove) @@ -147,6 +147,7 @@ func (h *DeleteHandler) ServeHTTP(serviceName string, w http.ResponseWriter, r * return } + // TODO(schallert): change after https://github.com/m3db/m3/issues/1165 newPlacement = newPlacement.SetVersion(version + 1) } diff --git a/src/query/api/v1/handler/placement/replace.go b/src/query/api/v1/handler/placement/replace.go new file mode 100644 index 0000000000..0d4f2efb9d --- /dev/null +++ b/src/query/api/v1/handler/placement/replace.go @@ -0,0 +1,162 @@ +// Copyright (c) 2018 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package placement + +import ( + "net/http" + "path" + "time" + + "github.com/m3db/m3/src/cluster/placement" + "github.com/m3db/m3/src/query/api/v1/handler" + "github.com/m3db/m3/src/query/generated/proto/admin" + "github.com/m3db/m3/src/query/util/logging" + xhttp "github.com/m3db/m3/src/x/net/http" + + "github.com/gogo/protobuf/jsonpb" + "go.uber.org/zap" +) + +const ( + // ReplaceHTTPMethod is the HTTP method for the the replace endpoint. + ReplaceHTTPMethod = http.MethodPost + + replacePathName = "replace" +) + +var ( + // M3DBReplaceURL is the url for the m3db replace handler (method POST). + M3DBReplaceURL = path.Join(handler.RoutePrefixV1, M3DBServicePlacementPathName, replacePathName) + + // M3AggReplaceURL is the url for the m3aggregator replace handler (method + // POST). + M3AggReplaceURL = path.Join(handler.RoutePrefixV1, M3AggregatorServiceName, replacePathName) + + // M3CoordinatorReplaceURL is the url for the m3coordinator replace handler + // (method POST). + M3CoordinatorReplaceURL = path.Join(handler.RoutePrefixV1, M3CoordinatorServiceName, replacePathName) +) + +// ReplaceHandler is the type for placement replaces. +type ReplaceHandler Handler + +// NewReplaceHandler returns a new ReplaceHandler. +func NewReplaceHandler(opts HandlerOptions) *ReplaceHandler { + return &ReplaceHandler{HandlerOptions: opts, nowFn: time.Now} +} + +func (h *ReplaceHandler) ServeHTTP(serviceName string, w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + logger := logging.WithContext(ctx) + + req, pErr := h.parseRequest(r) + if pErr != nil { + xhttp.Error(w, pErr.Inner(), pErr.Code()) + return + } + + placement, err := h.Replace(serviceName, r, req) + if err != nil { + status := http.StatusInternalServerError + if _, ok := err.(unsafeAddError); ok { + status = http.StatusBadRequest + } + logger.Error("unable to replace instance", zap.Error(err)) + xhttp.Error(w, err, status) + return + } + + placementProto, err := placement.Proto() + if err != nil { + logger.Error("unable to get placement protobuf", zap.Error(err)) + xhttp.Error(w, err, http.StatusInternalServerError) + return + } + + resp := &admin.PlacementGetResponse{ + Placement: placementProto, + Version: int32(placement.GetVersion()), + } + + xhttp.WriteProtoMsgJSONResponse(w, resp, logger) +} + +func (h *ReplaceHandler) parseRequest(r *http.Request) (*admin.PlacementReplaceRequest, *xhttp.ParseError) { + defer r.Body.Close() + + req := &admin.PlacementReplaceRequest{} + if err := jsonpb.Unmarshal(r.Body, req); err != nil { + return nil, xhttp.NewParseError(err, http.StatusBadRequest) + } + + return req, nil +} + +// Replace replaces instances. +func (h *ReplaceHandler) Replace( + serviceName string, + httpReq *http.Request, + req *admin.PlacementReplaceRequest, +) (placement.Placement, error) { + candidates, err := ConvertInstancesProto(req.Candidates) + if err != nil { + return nil, err + } + + serviceOpts := NewServiceOptions(serviceName, httpReq.Header, h.M3AggServiceOptions) + service, algo, err := ServiceWithAlgo(h.ClusterClient, serviceOpts, h.nowFn()) + if err != nil { + return nil, err + } + + if req.Force { + newPlacement, _, err := service.ReplaceInstances(req.LeavingInstanceIDs, candidates) + return newPlacement, err + } + + curPlacement, version, err := service.Placement() + if err != nil { + return nil, err + } + + // M3Coordinator isn't sharded, can't check if its shards are available. + if !isStateless(serviceName) { + if err := validateAllAvailable(curPlacement); err != nil { + return nil, err + } + } + + // We use the algorithm directly so that we can CheckAndSet on the placement + // to make "atomic" forward progress. + newPlacement, err := algo.ReplaceInstances(curPlacement, req.LeavingInstanceIDs, candidates) + if err != nil { + return nil, err + } + + // Ensure the placement we're updating is still the one on which we validated + // all shards are available. + if err := service.CheckAndSet(newPlacement, version); err != nil { + return nil, err + } + + // TODO(schallert): change once https://github.com/m3db/m3/issues/1165 fixed. + return newPlacement.SetVersion(version + 1), nil +} diff --git a/src/query/api/v1/handler/placement/replace_test.go b/src/query/api/v1/handler/placement/replace_test.go new file mode 100644 index 0000000000..cc861f4e51 --- /dev/null +++ b/src/query/api/v1/handler/placement/replace_test.go @@ -0,0 +1,216 @@ +// Copyright (c) 2018 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package placement + +import ( + "errors" + "io/ioutil" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/m3db/m3/src/cluster/placement" + "github.com/m3db/m3/src/cluster/shard" + "github.com/m3db/m3/src/cmd/services/m3query/config" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" +) + +func newReplaceRequest(body string) *http.Request { + rb := strings.NewReader(body) + return httptest.NewRequest(ReplaceHTTPMethod, M3DBReplaceURL, rb) +} + +func TestPlacementReplaceHandler_Force(t *testing.T) { + runForAllAllowedServices(func(s string) { + testPlacementReplaceHandlerForce(t, s) + }) +} + +func TestPlacementReplaceHandler_Safe_Err(t *testing.T) { + runForAllAllowedServices(func(s string) { + testPlacementReplaceHandlerSafeErr(t, s) + }) +} + +func TestPlacementReplaceHandler_Safe_Ok(t *testing.T) { + runForAllAllowedServices(func(s string) { + testPlacementReplaceHandlerSafeOk(t, s) + }) +} + +func testPlacementReplaceHandlerForce(t *testing.T, serviceName string) { + var ( + mockClient, mockPlacementService = SetupPlacementTest(t) + handlerOpts = NewHandlerOptions(mockClient, config.Configuration{}, nil) + handler = NewReplaceHandler(handlerOpts) + ) + handler.nowFn = func() time.Time { return time.Unix(0, 0) } + + w := httptest.NewRecorder() + req := newReplaceRequest(`{"force": true, "leavingInstanceIDs": []}`) + + mockPlacementService.EXPECT().ReplaceInstances([]string{}, gomock.Any()).Return(placement.NewPlacement(), nil, errors.New("test")) + handler.ServeHTTP(serviceName, w, req) + + resp := w.Result() + body, _ := ioutil.ReadAll(resp.Body) + assert.Equal(t, `{"error":"test"}`+"\n", string(body)) + assert.Equal(t, http.StatusInternalServerError, resp.StatusCode) + + w = httptest.NewRecorder() + req = newReplaceRequest(`{"force": true, "leavingInstanceIDs": ["a"]}`) + mockPlacementService.EXPECT().ReplaceInstances([]string{"a"}, gomock.Not(nil)).Return(placement.NewPlacement(), nil, nil) + handler.ServeHTTP(serviceName, w, req) + resp = w.Result() + body, _ = ioutil.ReadAll(resp.Body) + assert.Equal(t, `{"placement":{"instances":{},"replicaFactor":0,"numShards":0,"isSharded":false,"cutoverTime":"0","isMirrored":false,"maxShardSetId":0},"version":0}`, string(body)) + assert.Equal(t, http.StatusOK, resp.StatusCode) +} + +func testPlacementReplaceHandlerSafeErr(t *testing.T, serviceName string) { + var ( + mockClient, mockPlacementService = SetupPlacementTest(t) + handlerOpts = NewHandlerOptions(mockClient, config.Configuration{}, nil) + handler = NewReplaceHandler(handlerOpts) + ) + handler.nowFn = func() time.Time { return time.Unix(0, 0) } + + w := httptest.NewRecorder() + req := newReplaceRequest("{}") + + mockPlacementService.EXPECT().Placement().Return(newInitPlacement(), 0, nil) + if serviceName == M3CoordinatorServiceName { + mockPlacementService.EXPECT().CheckAndSet(gomock.Any(), 0) + } + handler.ServeHTTP(serviceName, w, req) + + resp := w.Result() + body, _ := ioutil.ReadAll(resp.Body) + + switch serviceName { + case M3CoordinatorServiceName: + assert.Equal(t, http.StatusOK, resp.StatusCode) + default: + assert.Equal(t, http.StatusBadRequest, resp.StatusCode) + assert.Equal(t, `{"error":"instances [A,B] do not have all shards available"}`+"\n", string(body)) + } +} + +type placementReplaceMatcher struct{} + +func (placementReplaceMatcher) Matches(x interface{}) bool { + pl := x.(placement.Placement) + + instA, ok := pl.Instance("A") + if !ok { + return false + } + + instC, ok := pl.Instance("C") + if !ok { + return false + } + + return instA.Shards().NumShardsForState(shard.Leaving) == 1 && + instC.Shards().NumShardsForState(shard.Initializing) == 1 +} + +func (placementReplaceMatcher) String() string { + return "matches if the placement has instance A leaving and C initializing" +} + +func newPlacementReplaceMatcher() gomock.Matcher { + return placementReplaceMatcher{} +} + +func testPlacementReplaceHandlerSafeOk(t *testing.T, serviceName string) { + var ( + mockClient, mockPlacementService = SetupPlacementTest(t) + handlerOpts = NewHandlerOptions(mockClient, config.Configuration{}, nil) + handler = NewReplaceHandler(handlerOpts) + ) + handler.nowFn = func() time.Time { return time.Unix(0, 0) } + + pl := newAvailPlacement() + + matcher := gomock.Any() + switch serviceName { + case M3DBServiceName: + pl = pl.SetIsSharded(true) + matcher = newPlacementReplaceMatcher() + case M3AggregatorServiceName: + pl = pl.SetIsSharded(true).SetIsMirrored(true) + matcher = newPlacementReplaceMatcher() + default: + } + + instances := pl.Instances() + for i, inst := range instances { + newInst := inst.SetIsolationGroup("r1").SetZone("z1").SetWeight(1) + if serviceName == M3CoordinatorServiceName { + newInst = newInst.SetShards(shard.NewShards([]shard.Shard{})) + } + instances[i] = newInst + } + pl = pl.SetInstances(instances) + + w := httptest.NewRecorder() + req := newReplaceRequest(` + { + "leavingInstanceIDs": ["A"], + "candidates": [ + { + "id": "C", + "zone": "z1", + "isolation_group": "r1", + "weight": 1 + } + ] + } + `) + + mockPlacementService.EXPECT().Placement().Return(pl, 1, nil) + mockPlacementService.EXPECT().CheckAndSet(matcher, 1) + handler.ServeHTTP(serviceName, w, req) + + resp := w.Result() + body, _ := ioutil.ReadAll(resp.Body) + + switch serviceName { + case M3CoordinatorServiceName: + exp := `{"placement":{"instances":{"B":{"id":"B","isolationGroup":"r1","zone":"z1","weight":1,"endpoint":"","shards":[],"shardSetId":0,"hostname":"","port":0},"C":{"id":"C","isolationGroup":"r1","zone":"z1","weight":1,"endpoint":"","shards":[],"shardSetId":0,"hostname":"","port":0}},"replicaFactor":0,"numShards":0,"isSharded":false,"cutoverTime":"0","isMirrored":false,"maxShardSetId":0},"version":2}` + assert.Equal(t, exp, string(body)) + case M3DBServiceName: + exp := `{"placement":{"instances":{"A":{"id":"A","isolationGroup":"r1","zone":"z1","weight":1,"endpoint":"","shards":[{"id":1,"state":"LEAVING","sourceId":"","cutoverNanos":"0","cutoffNanos":"0"}],"shardSetId":0,"hostname":"","port":0},"B":{"id":"B","isolationGroup":"r1","zone":"z1","weight":1,"endpoint":"","shards":[{"id":1,"state":"AVAILABLE","sourceId":"","cutoverNanos":"0","cutoffNanos":"0"}],"shardSetId":0,"hostname":"","port":0},"C":{"id":"C","isolationGroup":"r1","zone":"z1","weight":1,"endpoint":"","shards":[{"id":1,"state":"INITIALIZING","sourceId":"A","cutoverNanos":"0","cutoffNanos":"0"}],"shardSetId":0,"hostname":"","port":0}},"replicaFactor":0,"numShards":0,"isSharded":true,"cutoverTime":"0","isMirrored":false,"maxShardSetId":0},"version":2}` + assert.Equal(t, exp, string(body)) + case M3AggregatorServiceName: + exp := `{"placement":{"instances":{"A":{"id":"A","isolationGroup":"r1","zone":"z1","weight":1,"endpoint":"","shards":[{"id":1,"state":"LEAVING","sourceId":"","cutoverNanos":"0","cutoffNanos":"0"}],"shardSetId":0,"hostname":"","port":0},"B":{"id":"B","isolationGroup":"r1","zone":"z1","weight":1,"endpoint":"","shards":[{"id":1,"state":"AVAILABLE","sourceId":"","cutoverNanos":"0","cutoffNanos":"0"}],"shardSetId":0,"hostname":"","port":0},"C":{"id":"C","isolationGroup":"r1","zone":"z1","weight":1,"endpoint":"","shards":[{"id":1,"state":"INITIALIZING","sourceId":"A","cutoverNanos":"0","cutoffNanos":"0"}],"shardSetId":0,"hostname":"","port":0}},"replicaFactor":0,"numShards":0,"isSharded":true,"cutoverTime":"0","isMirrored":true,"maxShardSetId":0},"version":2}` + assert.Equal(t, exp, string(body)) + default: + t.Errorf("unknown service name %s", serviceName) + } + + assert.Equal(t, http.StatusOK, resp.StatusCode) +} diff --git a/src/query/generated/assets/openapi/assets.go b/src/query/generated/assets/openapi/assets.go index 19839952ec..4e0a23db61 100644 --- a/src/query/generated/assets/openapi/assets.go +++ b/src/query/generated/assets/openapi/assets.go @@ -239,42 +239,43 @@ d8dUBmQZxiF/+uI1I7E8TMF6pCyWxDpY7WPA8pmKZsl6ouawOaOS+Sj+BQAA//8by2IcfAIAAA== "/spec.yml": { local: "openapi/spec.yml", - size: 19038, + size: 21630, modtime: 12345, compressed: ` -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 -//8tjOO2XkoAAA== +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 `, }, diff --git a/src/query/generated/assets/openapi/spec.yml b/src/query/generated/assets/openapi/spec.yml index 96e11d7306..93fc0606cb 100644 --- a/src/query/generated/assets/openapi/spec.yml +++ b/src/query/generated/assets/openapi/spec.yml @@ -339,6 +339,35 @@ paths: description: "" schema: $ref: "#/definitions/GenericError" + /services/m3db/placement/replace: + post: + tags: + - "M3DB Placement" + - "M3DB" + summary: "Replace an M3DB instance in the placement" + operationId: "placementReplace" + consumes: + - "application/json" + produces: + - "application/json" + parameters: + - name: "body" + in: "body" + schema: + $ref: "#/definitions/PlacementReplaceRequest" + responses: + 200: + description: "" + schema: + $ref: "#/definitions/PlacementGetResponse" + 400: + description: "" + schema: + $ref: "#/definitions/GenericError" + 500: + description: "" + schema: + $ref: "#/definitions/GenericError" /services/m3coordinator/placement/init: post: tags: @@ -368,6 +397,35 @@ paths: description: "" schema: $ref: "#/definitions/GenericError" + /services/m3coordinator/placement/replace: + post: + tags: + - "M3Coordinator Placement" + - "M3Coordinator" + summary: "Replace an M3Coordinator" + operationId: "placementReplace" + consumes: + - "application/json" + produces: + - "application/json" + parameters: + - name: "body" + in: "body" + schema: + $ref: "#/definitions/PlacementReplaceRequest" + responses: + 200: + description: "" + schema: + $ref: "#/definitions/PlacementGetResponse" + 400: + description: "" + schema: + $ref: "#/definitions/GenericError" + 500: + description: "" + schema: + $ref: "#/definitions/GenericError" /services/m3aggregator/placement/init: post: tags: @@ -397,6 +455,35 @@ paths: description: "" schema: $ref: "#/definitions/GenericError" + /services/m3aggregator/placement/replace: + post: + tags: + - "M3Aggregator Placement" + - "M3Aggregator" + summary: "Replace an M3Aggregator" + operationId: "m3AggPlacementReplace" + consumes: + - "application/json" + produces: + - "application/json" + parameters: + - name: "body" + in: "body" + schema: + $ref: "#/definitions/PlacementReplaceRequest" + responses: + 200: + description: "" + schema: + $ref: "#/definitions/PlacementGetResponse" + 400: + description: "" + schema: + $ref: "#/definitions/GenericError" + 500: + description: "" + schema: + $ref: "#/definitions/GenericError" /services/m3db/placement/{instanceID}: delete: tags: @@ -694,6 +781,8 @@ definitions: type: "array" items: $ref: "#/definitions/InstanceRequest" + force: + type: "boolean" PlacementInitRequest: type: "object" properties: @@ -707,6 +796,19 @@ definitions: replicationFactor: type: "integer" format: "int32" + PlacementReplaceRequest: + type: "object" + properties: + leavingInstanceIDs: + type: "array" + items: + type: "string" + candidates: + type: "array" + items: + $ref: "#/definitions/InstanceRequest" + force: + type: "boolean" PlacementInitRequestM3Coordinator: type: "object" properties: diff --git a/src/query/generated/proto/admin/database.pb.go b/src/query/generated/proto/admin/database.pb.go index c7f7b58e9e..3806051883 100644 --- a/src/query/generated/proto/admin/database.pb.go +++ b/src/query/generated/proto/admin/database.pb.go @@ -40,6 +40,7 @@ PlacementInitRequest PlacementGetResponse PlacementAddRequest + PlacementReplaceRequest TopicGetResponse TopicInitRequest TopicAddRequest diff --git a/src/query/generated/proto/admin/placement.pb.go b/src/query/generated/proto/admin/placement.pb.go index 70b67dada6..e3d459327f 100644 --- a/src/query/generated/proto/admin/placement.pb.go +++ b/src/query/generated/proto/admin/placement.pb.go @@ -117,10 +117,43 @@ func (m *PlacementAddRequest) GetForce() bool { return false } +type PlacementReplaceRequest struct { + LeavingInstanceIDs []string `protobuf:"bytes,1,rep,name=leavingInstanceIDs" json:"leavingInstanceIDs,omitempty"` + Candidates []*placementpb.Instance `protobuf:"bytes,2,rep,name=candidates" json:"candidates,omitempty"` + Force bool `protobuf:"varint,3,opt,name=force,proto3" json:"force,omitempty"` +} + +func (m *PlacementReplaceRequest) Reset() { *m = PlacementReplaceRequest{} } +func (m *PlacementReplaceRequest) String() string { return proto.CompactTextString(m) } +func (*PlacementReplaceRequest) ProtoMessage() {} +func (*PlacementReplaceRequest) Descriptor() ([]byte, []int) { return fileDescriptorPlacement, []int{3} } + +func (m *PlacementReplaceRequest) GetLeavingInstanceIDs() []string { + if m != nil { + return m.LeavingInstanceIDs + } + return nil +} + +func (m *PlacementReplaceRequest) GetCandidates() []*placementpb.Instance { + if m != nil { + return m.Candidates + } + return nil +} + +func (m *PlacementReplaceRequest) GetForce() bool { + if m != nil { + return m.Force + } + return false +} + func init() { proto.RegisterType((*PlacementInitRequest)(nil), "admin.PlacementInitRequest") proto.RegisterType((*PlacementGetResponse)(nil), "admin.PlacementGetResponse") proto.RegisterType((*PlacementAddRequest)(nil), "admin.PlacementAddRequest") + proto.RegisterType((*PlacementReplaceRequest)(nil), "admin.PlacementReplaceRequest") } func (m *PlacementInitRequest) Marshal() (dAtA []byte, err error) { size := m.Size() @@ -235,6 +268,61 @@ func (m *PlacementAddRequest) MarshalTo(dAtA []byte) (int, error) { return i, nil } +func (m *PlacementReplaceRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PlacementReplaceRequest) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.LeavingInstanceIDs) > 0 { + for _, s := range m.LeavingInstanceIDs { + dAtA[i] = 0xa + i++ + l = len(s) + for l >= 1<<7 { + dAtA[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + dAtA[i] = uint8(l) + i++ + i += copy(dAtA[i:], s) + } + } + if len(m.Candidates) > 0 { + for _, msg := range m.Candidates { + dAtA[i] = 0x12 + i++ + i = encodeVarintPlacement(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + if m.Force { + dAtA[i] = 0x18 + i++ + if m.Force { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + } + return i, nil +} + func encodeVarintPlacement(dAtA []byte, offset int, v uint64) int { for v >= 1<<7 { dAtA[offset] = uint8(v&0x7f | 0x80) @@ -290,6 +378,27 @@ func (m *PlacementAddRequest) Size() (n int) { return n } +func (m *PlacementReplaceRequest) Size() (n int) { + var l int + _ = l + if len(m.LeavingInstanceIDs) > 0 { + for _, s := range m.LeavingInstanceIDs { + l = len(s) + n += 1 + l + sovPlacement(uint64(l)) + } + } + if len(m.Candidates) > 0 { + for _, e := range m.Candidates { + l = e.Size() + n += 1 + l + sovPlacement(uint64(l)) + } + } + if m.Force { + n += 2 + } + return n +} + func sovPlacement(x uint64) (n int) { for { n++ @@ -625,6 +734,136 @@ func (m *PlacementAddRequest) Unmarshal(dAtA []byte) error { } return nil } +func (m *PlacementReplaceRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPlacement + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PlacementReplaceRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PlacementReplaceRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LeavingInstanceIDs", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPlacement + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthPlacement + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.LeavingInstanceIDs = append(m.LeavingInstanceIDs, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Candidates", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPlacement + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthPlacement + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Candidates = append(m.Candidates, &placementpb.Instance{}) + if err := m.Candidates[len(m.Candidates)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Force", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPlacement + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.Force = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipPlacement(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthPlacement + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipPlacement(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 @@ -735,25 +974,28 @@ func init() { } var fileDescriptorPlacement = []byte{ - // 311 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x90, 0x4f, 0x4e, 0x02, 0x31, - 0x14, 0xc6, 0xad, 0x04, 0x95, 0xb2, 0xd1, 0x8a, 0x66, 0x62, 0xe2, 0x84, 0xb0, 0x62, 0xe3, 0x34, - 0x61, 0xbc, 0x80, 0x2c, 0x34, 0xb8, 0x32, 0xe3, 0x01, 0xb0, 0xd3, 0x3e, 0xa0, 0x09, 0x6d, 0x87, - 0xfe, 0x31, 0xf1, 0x16, 0x6e, 0xbd, 0x91, 0x4b, 0x8f, 0x60, 0xf0, 0x22, 0x26, 0x15, 0x06, 0xd4, - 0xb8, 0x72, 0xf9, 0xbe, 0xef, 0x7b, 0xbf, 0xf7, 0xe5, 0xe1, 0xe1, 0x54, 0xfa, 0x59, 0x28, 0x33, - 0x6e, 0x14, 0x55, 0xb9, 0x28, 0xa9, 0xca, 0xa9, 0xb3, 0x9c, 0x2e, 0x02, 0xd8, 0x27, 0x3a, 0x05, - 0x0d, 0x96, 0x79, 0x10, 0xb4, 0xb2, 0xc6, 0x1b, 0xca, 0x84, 0x92, 0x9a, 0x56, 0x73, 0xc6, 0x41, - 0x81, 0xf6, 0x59, 0x54, 0x49, 0x33, 0xca, 0x67, 0xb7, 0x7f, 0xa0, 0xf8, 0x3c, 0x38, 0x0f, 0xf6, - 0x17, 0xac, 0xc6, 0x54, 0xe5, 0x4f, 0x64, 0xef, 0x05, 0xe1, 0xce, 0xdd, 0x5a, 0x1b, 0x69, 0xe9, - 0x0b, 0x58, 0x04, 0x70, 0x9e, 0xe4, 0xb8, 0x25, 0xb5, 0xf3, 0x4c, 0x73, 0x70, 0x09, 0xea, 0x36, - 0xfa, 0xed, 0xc1, 0x49, 0xb6, 0x45, 0xca, 0x46, 0x2b, 0xb7, 0xd8, 0xe4, 0xc8, 0x39, 0xc6, 0x3a, - 0xa8, 0xb1, 0x9b, 0x31, 0x2b, 0x5c, 0xb2, 0xdb, 0x45, 0xfd, 0x66, 0xd1, 0xd2, 0x41, 0xdd, 0x47, - 0x81, 0x5c, 0x60, 0x62, 0xa1, 0x9a, 0x4b, 0xce, 0xbc, 0x34, 0x7a, 0x3c, 0x61, 0xdc, 0x1b, 0x9b, - 0x34, 0x62, 0xec, 0x68, 0xcb, 0xb9, 0x8e, 0x46, 0x6f, 0xb2, 0x55, 0xed, 0x06, 0x7c, 0x01, 0xae, - 0x32, 0xda, 0x01, 0xb9, 0xc4, 0xad, 0xba, 0x48, 0x82, 0xba, 0xa8, 0xdf, 0x1e, 0x9c, 0x7e, 0xab, - 0x56, 0x6f, 0x15, 0x9b, 0x20, 0x49, 0xf0, 0xfe, 0x23, 0x58, 0x27, 0x8d, 0x5e, 0x15, 0x5b, 0x8f, - 0xbd, 0x07, 0x7c, 0x5c, 0x6f, 0x5c, 0x09, 0xf1, 0xaf, 0x0f, 0x74, 0x70, 0x73, 0x62, 0x2c, 0x87, - 0x78, 0xe3, 0xa0, 0xf8, 0x1a, 0x86, 0x87, 0xaf, 0xcb, 0x14, 0xbd, 0x2d, 0x53, 0xf4, 0xbe, 0x4c, - 0xd1, 0xf3, 0x47, 0xba, 0x53, 0xee, 0xc5, 0xf7, 0xe7, 0x9f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x2b, - 0xce, 0x02, 0xc7, 0x17, 0x02, 0x00, 0x00, + // 365 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x92, 0xcd, 0x8e, 0xd3, 0x30, + 0x14, 0x85, 0x31, 0x55, 0x81, 0xb8, 0x1b, 0x30, 0x05, 0x22, 0x24, 0xa2, 0x2a, 0xab, 0x6e, 0x88, + 0xa5, 0x06, 0x1e, 0x80, 0x0a, 0x81, 0xca, 0x0a, 0x85, 0x07, 0x28, 0x8e, 0x7d, 0xdb, 0x5a, 0x4a, + 0xec, 0xd4, 0x76, 0x2a, 0xf1, 0x16, 0xac, 0x46, 0x9a, 0x37, 0x9a, 0xe5, 0x3c, 0xc2, 0xa8, 0xf3, + 0x22, 0xa3, 0x71, 0x9b, 0x9f, 0xf9, 0xdd, 0xcc, 0xd2, 0xe7, 0x9c, 0x7b, 0xee, 0xa7, 0x2b, 0xe3, + 0xf9, 0x5a, 0xba, 0x4d, 0x9d, 0x27, 0x5c, 0x97, 0xb4, 0x4c, 0x45, 0x4e, 0xcb, 0x94, 0x5a, 0xc3, + 0xe9, 0xb6, 0x06, 0xf3, 0x8f, 0xae, 0x41, 0x81, 0x61, 0x0e, 0x04, 0xad, 0x8c, 0x76, 0x9a, 0x32, + 0x51, 0x4a, 0x45, 0xab, 0x82, 0x71, 0x28, 0x41, 0xb9, 0xc4, 0xab, 0x64, 0xe8, 0xe5, 0x8f, 0xbf, + 0x1e, 0xa8, 0xe2, 0x45, 0x6d, 0x1d, 0x98, 0x3b, 0x65, 0x6d, 0x4d, 0x95, 0xdf, 0xae, 0x8c, 0x4f, + 0x11, 0x1e, 0xff, 0x6e, 0xb4, 0x85, 0x92, 0x2e, 0x83, 0x6d, 0x0d, 0xd6, 0x91, 0x14, 0x07, 0x52, + 0x59, 0xc7, 0x14, 0x07, 0x1b, 0xa2, 0xc9, 0x60, 0x3a, 0x9a, 0xbd, 0x4b, 0x7a, 0x4d, 0xc9, 0xe2, + 0xe8, 0x66, 0x5d, 0x8e, 0x7c, 0xc2, 0x58, 0xd5, 0xe5, 0xd2, 0x6e, 0x98, 0x11, 0x36, 0x7c, 0x3e, + 0x41, 0xd3, 0x61, 0x16, 0xa8, 0xba, 0xfc, 0xe3, 0x05, 0xf2, 0x19, 0x13, 0x03, 0x55, 0x21, 0x39, + 0x73, 0x52, 0xab, 0xe5, 0x8a, 0x71, 0xa7, 0x4d, 0x38, 0xf0, 0xb1, 0x37, 0x3d, 0xe7, 0x87, 0x37, + 0xe2, 0x55, 0x0f, 0xed, 0x27, 0xb8, 0x0c, 0x6c, 0xa5, 0x95, 0x05, 0xf2, 0x05, 0x07, 0x2d, 0x48, + 0x88, 0x26, 0x68, 0x3a, 0x9a, 0xbd, 0xbf, 0x81, 0xd6, 0x4e, 0x65, 0x5d, 0x90, 0x84, 0xf8, 0xe5, + 0x0e, 0x8c, 0x95, 0x5a, 0x1d, 0xc1, 0x9a, 0x67, 0xfc, 0x17, 0xbf, 0x6d, 0x27, 0xbe, 0x09, 0xf1, + 0xa4, 0x0b, 0x8c, 0xf1, 0x70, 0xa5, 0x0d, 0x07, 0xbf, 0xe3, 0x55, 0x76, 0x78, 0xc4, 0x27, 0x08, + 0x7f, 0xe8, 0xa0, 0xc0, 0x97, 0x34, 0x6b, 0x12, 0x4c, 0x0a, 0x60, 0x3b, 0xa9, 0xd6, 0x4d, 0xdf, + 0xe2, 0xfb, 0x61, 0x5f, 0x90, 0xdd, 0xe3, 0x90, 0xaf, 0x18, 0x73, 0xa6, 0x84, 0x14, 0xcc, 0xc1, + 0xf5, 0x8d, 0x1f, 0xe1, 0xea, 0x05, 0x3b, 0xb0, 0x41, 0x0f, 0x6c, 0xfe, 0xfa, 0x6c, 0x1f, 0xa1, + 0xf3, 0x7d, 0x84, 0x2e, 0xf6, 0x11, 0xfa, 0x7f, 0x19, 0x3d, 0xcb, 0x5f, 0xf8, 0x7f, 0x91, 0x5e, + 0x05, 0x00, 0x00, 0xff, 0xff, 0xae, 0x31, 0x55, 0xba, 0xb0, 0x02, 0x00, 0x00, } diff --git a/src/query/generated/proto/admin/placement.proto b/src/query/generated/proto/admin/placement.proto index 5d114da076..77a290e36e 100644 --- a/src/query/generated/proto/admin/placement.proto +++ b/src/query/generated/proto/admin/placement.proto @@ -21,3 +21,9 @@ message PlacementAddRequest { // are AVAILABLE for all their shards. force overrides that. bool force = 2; } + +message PlacementReplaceRequest { + repeated string leavingInstanceIDs = 1; + repeated placementpb.Instance candidates = 2; + bool force = 3; +}