github.com/m3db/m3@v1.5.0/src/query/api/v1/handler/database/create.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 database 22 23 import ( 24 "bytes" 25 "errors" 26 "fmt" 27 "math" 28 "net/http" 29 "strconv" 30 "strings" 31 "time" 32 33 clusterclient "github.com/m3db/m3/src/cluster/client" 34 "github.com/m3db/m3/src/cluster/generated/proto/placementpb" 35 clusterplacement "github.com/m3db/m3/src/cluster/placement" 36 "github.com/m3db/m3/src/cluster/placementhandler" 37 "github.com/m3db/m3/src/cluster/placementhandler/handleroptions" 38 dbconfig "github.com/m3db/m3/src/cmd/services/m3dbnode/config" 39 "github.com/m3db/m3/src/cmd/services/m3query/config" 40 dbnamespace "github.com/m3db/m3/src/dbnode/namespace" 41 "github.com/m3db/m3/src/query/api/v1/handler/namespace" 42 "github.com/m3db/m3/src/query/api/v1/options" 43 "github.com/m3db/m3/src/query/api/v1/route" 44 "github.com/m3db/m3/src/query/generated/proto/admin" 45 "github.com/m3db/m3/src/query/util/logging" 46 xerrors "github.com/m3db/m3/src/x/errors" 47 "github.com/m3db/m3/src/x/instrument" 48 xhttp "github.com/m3db/m3/src/x/net/http" 49 50 "github.com/gogo/protobuf/jsonpb" 51 "go.uber.org/zap" 52 ) 53 54 const ( 55 // CreateURL is the URL for the database create handler. 56 CreateURL = route.Prefix + "/database/create" 57 58 // CreateNamespaceURL is the URL for the database namespace create handler. 59 CreateNamespaceURL = route.Prefix + "/database/namespace/create" 60 61 // CreateHTTPMethod is the HTTP method used with the create database resource. 62 CreateHTTPMethod = http.MethodPost 63 64 // CreateNamespaceHTTPMethod is the HTTP method used with the create database namespace resource. 65 CreateNamespaceHTTPMethod = http.MethodPost 66 67 // DefaultLocalHostID is the default local host ID when creating a database. 68 DefaultLocalHostID = "m3db_local" 69 70 // DefaultLocalIsolationGroup is the default isolation group when creating a 71 // local database. 72 DefaultLocalIsolationGroup = "local" 73 74 // DefaultLocalZone is the default zone when creating a local database. 75 DefaultLocalZone = "embedded" 76 77 idealDatapointsPerBlock = 720 78 blockSizeFromExpectedSeriesScalar = idealDatapointsPerBlock * int64(time.Hour) 79 shardMultiplier = 64 80 81 dbTypeLocal dbType = "local" 82 dbTypeCluster dbType = "cluster" 83 84 defaultIsolationGroup = "local" 85 defaultZone = "local" 86 87 defaultLocalRetentionPeriod = 24 * time.Hour 88 89 minRecommendCalculateBlockSize = 30 * time.Minute 90 maxRecommendCalculateBlockSize = 24 * time.Hour 91 ) 92 93 type recommendedBlockSize struct { 94 forRetentionLessThanOrEqual time.Duration 95 blockSize time.Duration 96 } 97 98 var recommendedBlockSizesByRetentionAsc = []recommendedBlockSize{ 99 { 100 forRetentionLessThanOrEqual: 12 * time.Hour, 101 blockSize: 30 * time.Minute, 102 }, 103 { 104 forRetentionLessThanOrEqual: 24 * time.Hour, 105 blockSize: time.Hour, 106 }, 107 { 108 forRetentionLessThanOrEqual: 7 * 24 * time.Hour, 109 blockSize: 2 * time.Hour, 110 }, 111 { 112 forRetentionLessThanOrEqual: 30 * 24 * time.Hour, 113 blockSize: 12 * time.Hour, 114 }, 115 { 116 forRetentionLessThanOrEqual: 365 * 24 * time.Hour, 117 blockSize: 24 * time.Hour, 118 }, 119 } 120 121 var ( 122 errMissingRequiredField = xerrors.NewInvalidParamsError(errors.New("missing required field")) 123 errInvalidDBType = xerrors.NewInvalidParamsError(errors.New("invalid database type")) 124 errMissingEmbeddedDBPort = xerrors.NewInvalidParamsError(errors.New("unable to get port from embedded database listen address")) 125 errMissingEmbeddedDBConfig = xerrors.NewInvalidParamsError(errors.New("unable to find local embedded database config")) 126 errMissingHostID = xerrors.NewInvalidParamsError(errors.New("missing host ID")) 127 128 errClusteredPlacementAlreadyExists = xerrors.NewInvalidParamsError(errors.New("cannot use database create API to modify clustered placements after they are instantiated. Use the placement APIs directly to make placement changes, or remove the list of hosts from the request to add a namespace without modifying the placement")) 129 errCantReplaceLocalPlacementWithClustered = xerrors.NewInvalidParamsError(errors.New("cannot replace existing local placement with a clustered placement. Use the placement APIs directly to make placement changes, or remove the `type` field from the request to add a namespace without modifying the existing local placement")) 130 ) 131 132 type dbType string 133 134 type createHandler struct { 135 placementInitHandler *placementhandler.InitHandler 136 placementGetHandler *placementhandler.GetHandler 137 namespaceAddHandler *namespace.AddHandler 138 namespaceGetHandler *namespace.GetHandler 139 namespaceDeleteHandler *namespace.DeleteHandler 140 embeddedDBCfg *dbconfig.DBConfiguration 141 defaults []handleroptions.ServiceOptionsDefault 142 instrumentOpts instrument.Options 143 } 144 145 // NewCreateHandler returns a new instance of a database create handler. 146 func NewCreateHandler( 147 client clusterclient.Client, 148 cfg config.Configuration, 149 embeddedDBCfg *dbconfig.DBConfiguration, 150 defaults []handleroptions.ServiceOptionsDefault, 151 instrumentOpts instrument.Options, 152 namespaceValidator options.NamespaceValidator, 153 ) (http.Handler, error) { 154 placementHandlerOptions, err := placementhandler.NewHandlerOptions(client, 155 cfg.ClusterManagement.Placement, nil, instrumentOpts) 156 if err != nil { 157 return nil, err 158 } 159 return &createHandler{ 160 placementInitHandler: placementhandler.NewInitHandler(placementHandlerOptions), 161 placementGetHandler: placementhandler.NewGetHandler(placementHandlerOptions), 162 namespaceAddHandler: namespace.NewAddHandler(client, instrumentOpts, namespaceValidator), 163 namespaceGetHandler: namespace.NewGetHandler(client, instrumentOpts), 164 namespaceDeleteHandler: namespace.NewDeleteHandler(client, instrumentOpts), 165 embeddedDBCfg: embeddedDBCfg, 166 defaults: defaults, 167 instrumentOpts: instrumentOpts, 168 }, nil 169 } 170 171 func (h *createHandler) serviceNameAndDefaults() handleroptions.ServiceNameAndDefaults { 172 return handleroptions.ServiceNameAndDefaults{ 173 ServiceName: handleroptions.M3DBServiceName, 174 Defaults: h.defaults, 175 } 176 } 177 178 func (h *createHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 179 var ( 180 ctx = r.Context() 181 logger = logging.WithContext(ctx, h.instrumentOpts) 182 ) 183 currPlacement, err := h.placementGetHandler.Get( 184 h.serviceNameAndDefaults(), nil) 185 if err != nil { 186 logger.Error("unable to get placement", zap.Error(err)) 187 xhttp.WriteError(w, err) 188 return 189 } 190 191 parsedReq, namespaceRequests, placementRequest, rErr := h.parseAndValidateRequest(r, currPlacement) 192 if rErr != nil { 193 logger.Error("unable to parse request", zap.Error(rErr)) 194 xhttp.WriteError(w, rErr) 195 return 196 } 197 198 currPlacement, err = h.maybeInitPlacement(currPlacement, parsedReq, placementRequest, r) 199 if err != nil { 200 logger.Error("unable to initialize placement", zap.Error(err)) 201 xhttp.WriteError(w, err) 202 return 203 } 204 205 opts := handleroptions.NewServiceOptions(h.serviceNameAndDefaults(), 206 r.Header, nil) 207 nsRegistry, err := h.namespaceGetHandler.Get(opts) 208 if err != nil { 209 logger.Error("unable to retrieve existing namespaces", zap.Error(err)) 210 xhttp.WriteError(w, err) 211 return 212 } 213 214 // TODO(rartoul): Add test for NS exists. 215 for _, namespaceRequest := range namespaceRequests { 216 if _, nsExists := nsRegistry.Namespaces[namespaceRequest.Name]; nsExists { 217 err := xerrors.NewInvalidParamsError(fmt.Errorf( 218 "unable to create namespace: %s because it already exists", 219 namespaceRequest.Name)) 220 logger.Error("unable to create namespace", zap.Error(err)) 221 xhttp.WriteError(w, err) 222 return 223 } 224 } 225 226 for _, namespaceRequest := range namespaceRequests { 227 nsRegistry, err = h.namespaceAddHandler.Add(namespaceRequest, opts) 228 if err != nil { 229 logger.Error("unable to add namespace", zap.Error(err)) 230 xhttp.WriteError(w, err) 231 return 232 } 233 } 234 235 placementProto, err := currPlacement.Proto() 236 if err != nil { 237 logger.Error("unable to get placement protobuf", zap.Error(err)) 238 xhttp.WriteError(w, err) 239 return 240 } 241 242 resp := &admin.DatabaseCreateResponse{ 243 Namespace: &admin.NamespaceGetResponse{ 244 Registry: &nsRegistry, 245 }, 246 Placement: &admin.PlacementGetResponse{ 247 Placement: placementProto, 248 }, 249 } 250 251 xhttp.WriteProtoMsgJSONResponse(w, resp, logger) 252 } 253 254 func (h *createHandler) maybeInitPlacement( 255 currPlacement clusterplacement.Placement, 256 parsedReq *admin.DatabaseCreateRequest, 257 placementRequest *admin.PlacementInitRequest, 258 r *http.Request, 259 ) (clusterplacement.Placement, error) { 260 if currPlacement == nil { 261 // If we're here then there is no existing placement, so just create it. This is safe because in 262 // the case where a placement did not already exist, the parse function above validated that we 263 // have all the required information to create a placement. 264 newPlacement, err := h.placementInitHandler.Init(h.serviceNameAndDefaults(), 265 r, placementRequest) 266 if err != nil { 267 return nil, err 268 } 269 270 return newPlacement, nil 271 } 272 273 // NB(rartoul): Pardon the branchiness, making sure every permutation is "reasoned" through for 274 // the scenario where the placement already exists. 275 switch dbType(parsedReq.Type) { 276 case dbTypeCluster: 277 if placementRequest != nil { 278 // If the caller has specified a desired clustered placement and a placement already exists, 279 // throw an error because the create database API should not be used for modifying clustered 280 // placements. Instead, they should use the placement APIs. 281 return nil, errClusteredPlacementAlreadyExists 282 } 283 284 if placementIsLocal(currPlacement) { 285 // If the caller has specified that they desire a clustered placement (without specifying hosts) 286 // and a local placement already exists then throw an error because we can't ignore their request 287 // and we also can't convert a local placement to a clustered one. 288 return nil, errCantReplaceLocalPlacementWithClustered 289 } 290 291 // This is fine because we'll just assume they want to keep the same clustered placement 292 // that they already have because they didn't specify any hosts. 293 return currPlacement, nil 294 case dbTypeLocal: 295 if !placementIsLocal(currPlacement) { 296 // If the caller has specified that they desire a local placement and a clustered placement 297 // already exists then throw an error because we can't ignore their request and we also can't 298 // convert a clustered placement to a local one. 299 return nil, errCantReplaceLocalPlacementWithClustered 300 } 301 302 // This is fine because we'll just assume they want to keep the same local placement 303 // that they already have. 304 return currPlacement, nil 305 case "": 306 // This is fine because we'll just assume they want to keep the same placement that they already 307 // have. 308 return currPlacement, nil 309 default: 310 // Invalid dbType. 311 return nil, xerrors.NewInvalidParamsError(fmt.Errorf("unknown database type: %s", parsedReq.Type)) 312 } 313 } 314 315 func (h *createHandler) parseAndValidateRequest( 316 r *http.Request, 317 existingPlacement clusterplacement.Placement, 318 ) (*admin.DatabaseCreateRequest, []*admin.NamespaceAddRequest, *admin.PlacementInitRequest, error) { 319 requirePlacement := existingPlacement == nil 320 321 defer r.Body.Close() //nolint:errcheck 322 rBody, err := xhttp.DurationToNanosBytes(r.Body) 323 if err != nil { 324 wrapped := fmt.Errorf("error converting duration to nano bytes: %s", err.Error()) 325 return nil, nil, nil, xerrors.NewInvalidParamsError(wrapped) 326 } 327 328 dbCreateReq := new(admin.DatabaseCreateRequest) 329 if err := jsonpb.Unmarshal(bytes.NewReader(rBody), dbCreateReq); err != nil { 330 return nil, nil, nil, xerrors.NewInvalidParamsError(err) 331 } 332 333 if dbCreateReq.NamespaceName == "" { 334 err := fmt.Errorf("%s: namespaceName", errMissingRequiredField) 335 return nil, nil, nil, xerrors.NewInvalidParamsError(err) 336 } 337 338 requestedDBType := dbType(dbCreateReq.Type) 339 if requirePlacement && 340 requestedDBType == dbTypeCluster && 341 len(dbCreateReq.Hosts) == 0 { 342 return nil, nil, nil, xerrors.NewInvalidParamsError(errMissingRequiredField) 343 } 344 345 namespaceAddRequests, err := defaultedNamespaceAddRequests(dbCreateReq, existingPlacement) 346 if err != nil { 347 return nil, nil, nil, xerrors.NewInvalidParamsError(err) 348 } 349 350 var placementInitRequest *admin.PlacementInitRequest 351 if (requestedDBType == dbTypeCluster && len(dbCreateReq.Hosts) > 0) || 352 requestedDBType == dbTypeLocal { 353 placementInitRequest, err = defaultedPlacementInitRequest(dbCreateReq, h.embeddedDBCfg) 354 if err != nil { 355 return nil, nil, nil, xerrors.NewInvalidParamsError(err) 356 } 357 } 358 359 return dbCreateReq, namespaceAddRequests, placementInitRequest, nil 360 } 361 362 func defaultedNamespaceAddRequests( 363 r *admin.DatabaseCreateRequest, 364 existingPlacement clusterplacement.Placement, 365 ) ([]*admin.NamespaceAddRequest, error) { 366 var ( 367 dbType = dbType(r.Type) 368 ) 369 if dbType == "" && existingPlacement != nil { 370 // If they didn't provide a database type, infer it from the 371 // existing placement. 372 if placementIsLocal(existingPlacement) { 373 dbType = dbTypeLocal 374 } else { 375 dbType = dbTypeCluster 376 } 377 } 378 379 nsAddRequests := make([]*admin.NamespaceAddRequest, 0, 2) 380 switch dbType { 381 case dbTypeLocal, dbTypeCluster: 382 unaggregatedNs, err := defaultedUnaggregatedNamespaceAddRequest(r) 383 if err != nil { 384 return nil, err 385 } 386 nsAddRequests = append(nsAddRequests, unaggregatedNs) 387 388 aggregatedNs, err := defaultedAggregatedNamespaceAddRequest(r) 389 if err != nil { 390 return nil, err 391 } 392 if aggregatedNs != nil { 393 nsAddRequests = append(nsAddRequests, aggregatedNs) 394 } 395 default: 396 return nil, errInvalidDBType 397 } 398 399 return nsAddRequests, nil 400 } 401 402 func defaultedUnaggregatedNamespaceAddRequest( 403 r *admin.DatabaseCreateRequest, 404 ) (*admin.NamespaceAddRequest, error) { 405 opts := dbnamespace.NewOptions(). 406 SetRepairEnabled(false) 407 retentionOpts := opts.RetentionOptions() 408 409 if r.RetentionTime == "" { 410 retentionOpts = retentionOpts.SetRetentionPeriod(defaultLocalRetentionPeriod) 411 } else { 412 value, err := time.ParseDuration(r.RetentionTime) 413 if err != nil { 414 return nil, fmt.Errorf("invalid retention time: %v", err) 415 } 416 retentionOpts = retentionOpts.SetRetentionPeriod(value) 417 } 418 419 retentionPeriod := retentionOpts.RetentionPeriod() 420 421 var blockSize time.Duration 422 switch { 423 case r.BlockSize != nil && r.BlockSize.Time != "": 424 value, err := time.ParseDuration(r.BlockSize.Time) 425 if err != nil { 426 return nil, fmt.Errorf("invalid block size time: %v", err) 427 } 428 blockSize = value 429 430 case r.BlockSize != nil && r.BlockSize.ExpectedSeriesDatapointsPerHour > 0: 431 value := r.BlockSize.ExpectedSeriesDatapointsPerHour 432 blockSize = time.Duration(blockSizeFromExpectedSeriesScalar / value) 433 // Snap to the nearest 5mins 434 blockSizeCeil := blockSize.Truncate(5*time.Minute) + 5*time.Minute 435 blockSizeFloor := blockSize.Truncate(5 * time.Minute) 436 if blockSizeFloor%time.Hour == 0 || 437 blockSizeFloor%30*time.Minute == 0 || 438 blockSizeFloor%15*time.Minute == 0 || 439 blockSizeFloor%10*time.Minute == 0 { 440 // Try snap to hour or 30min or 15min or 10min boundary if possible 441 blockSize = blockSizeFloor 442 } else { 443 blockSize = blockSizeCeil 444 } 445 446 if blockSize < minRecommendCalculateBlockSize { 447 blockSize = minRecommendCalculateBlockSize 448 } else if blockSize > maxRecommendCalculateBlockSize { 449 blockSize = maxRecommendCalculateBlockSize 450 } 451 452 default: 453 blockSize = getRecommendedBlockSize(retentionPeriod) 454 } 455 456 retentionOpts = retentionOpts.SetBlockSize(blockSize) 457 458 indexOpts := opts.IndexOptions(). 459 SetEnabled(true). 460 SetBlockSize(blockSize) 461 462 opts = opts.SetRetentionOptions(retentionOpts). 463 SetIndexOptions(indexOpts) 464 465 // Resolution does not apply to unaggregated namespaces so set to 0. 466 opts = opts.SetAggregationOptions(dbnamespace.NewAggregationOptions(). 467 SetAggregations([]dbnamespace.Aggregation{ 468 dbnamespace.NewUnaggregatedAggregation(), 469 })) 470 471 optsProto, err := dbnamespace.OptionsToProto(opts) 472 if err != nil { 473 return nil, err 474 } 475 476 return &admin.NamespaceAddRequest{ 477 Name: r.NamespaceName, 478 Options: optsProto, 479 }, nil 480 } 481 482 func defaultedAggregatedNamespaceAddRequest( 483 r *admin.DatabaseCreateRequest, 484 ) (*admin.NamespaceAddRequest, error) { 485 agg := r.AggregatedNamespace 486 if agg == nil { 487 return nil, nil 488 } 489 490 if agg.Name == "" { 491 return nil, errors.New("name required when aggregatedNamespace is set") 492 } 493 494 if agg.Resolution == "" { 495 return nil, errors.New("resolution required when aggregatedNamespace is set") 496 } 497 498 if agg.RetentionTime == "" { 499 return nil, errors.New("retention_time required when aggregatedNamespace is set") 500 } 501 502 opts := dbnamespace.NewOptions(). 503 SetRepairEnabled(false) 504 505 retentionOpts := opts.RetentionOptions() 506 retentionPeriod, err := time.ParseDuration(agg.RetentionTime) 507 if err != nil { 508 return nil, fmt.Errorf("invalid retention time: %v", err) 509 } 510 retentionOpts = retentionOpts.SetRetentionPeriod(retentionPeriod) 511 512 blockSize := getRecommendedBlockSize(retentionPeriod) 513 514 retentionOpts = retentionOpts.SetBlockSize(blockSize) 515 516 indexOpts := opts.IndexOptions(). 517 SetEnabled(true). 518 SetBlockSize(blockSize) 519 520 opts = opts.SetRetentionOptions(retentionOpts). 521 SetIndexOptions(indexOpts) 522 523 resolution, err := time.ParseDuration(agg.Resolution) 524 if err != nil { 525 return nil, fmt.Errorf("invalid resolution: %v", err) 526 } 527 528 attrs, err := dbnamespace.NewAggregatedAttributes(resolution, dbnamespace.NewDownsampleOptions(true)) 529 if err != nil { 530 return nil, err 531 } 532 533 opts = opts.SetAggregationOptions(dbnamespace.NewAggregationOptions(). 534 SetAggregations([]dbnamespace.Aggregation{ 535 dbnamespace.NewAggregatedAggregation(attrs), 536 })) 537 538 optsProto, err := dbnamespace.OptionsToProto(opts) 539 if err != nil { 540 return nil, err 541 } 542 543 return &admin.NamespaceAddRequest{ 544 Name: agg.Name, 545 Options: optsProto, 546 }, nil 547 } 548 549 func getRecommendedBlockSize(retentionPeriod time.Duration) time.Duration { 550 // Use the maximum block size if we don't find a way to 551 // recommended one based on request parameters 552 max := recommendedBlockSizesByRetentionAsc[len(recommendedBlockSizesByRetentionAsc)-1] 553 blockSize := max.blockSize 554 for _, elem := range recommendedBlockSizesByRetentionAsc { 555 if retentionPeriod <= elem.forRetentionLessThanOrEqual { 556 blockSize = elem.blockSize 557 break 558 } 559 } 560 return blockSize 561 } 562 563 func defaultedPlacementInitRequest( 564 r *admin.DatabaseCreateRequest, 565 embeddedDBCfg *dbconfig.DBConfiguration, 566 ) (*admin.PlacementInitRequest, error) { 567 var ( 568 numShards int32 569 replicationFactor int32 570 instances []*placementpb.Instance 571 ) 572 switch dbType(r.Type) { 573 case dbTypeLocal: 574 if embeddedDBCfg == nil { 575 return nil, errMissingEmbeddedDBConfig 576 } 577 578 addr := embeddedDBCfg.ListenAddressOrDefault() 579 port, err := portFromEmbeddedDBConfigListenAddress(addr) 580 if err != nil { 581 return nil, err 582 } 583 584 numShards = shardMultiplier 585 replicationFactor = 1 586 instances = []*placementpb.Instance{ 587 { 588 Id: DefaultLocalHostID, 589 IsolationGroup: DefaultLocalIsolationGroup, 590 Zone: DefaultLocalZone, 591 Weight: 1, 592 Endpoint: fmt.Sprintf("127.0.0.1:%d", port), 593 Hostname: "localhost", 594 Port: uint32(port), 595 }, 596 } 597 case dbTypeCluster: 598 599 numHosts := len(r.Hosts) 600 numShards = int32(math.Min(math.MaxInt32, powerOfTwoAtLeast(float64(numHosts*shardMultiplier)))) 601 replicationFactor = r.ReplicationFactor 602 if replicationFactor == 0 { 603 replicationFactor = 3 604 } 605 606 instances = make([]*placementpb.Instance, 0, numHosts) 607 608 for _, host := range r.Hosts { 609 id := strings.TrimSpace(host.Id) 610 if id == "" { 611 return nil, errMissingHostID 612 } 613 614 isolationGroup := strings.TrimSpace(host.IsolationGroup) 615 if isolationGroup == "" { 616 isolationGroup = defaultIsolationGroup 617 } 618 619 zone := strings.TrimSpace(host.Zone) 620 if zone == "" { 621 zone = defaultZone 622 } 623 624 weight := host.Weight 625 if weight == 0 { 626 weight = 1 627 } 628 629 instances = append(instances, &placementpb.Instance{ 630 Id: id, 631 IsolationGroup: isolationGroup, 632 Zone: zone, 633 Weight: weight, 634 Endpoint: fmt.Sprintf("%s:%d", host.Address, host.Port), 635 Hostname: host.Address, 636 Port: host.Port, 637 }) 638 } 639 default: 640 return nil, errInvalidDBType 641 } 642 643 numShardsInput := r.GetNumShards() 644 if numShardsInput > 0 { 645 numShards = numShardsInput 646 } 647 648 return &admin.PlacementInitRequest{ 649 NumShards: numShards, 650 ReplicationFactor: replicationFactor, 651 Instances: instances, 652 }, nil 653 } 654 655 func placementIsLocal(p clusterplacement.Placement) bool { 656 existingInstances := p.Instances() 657 return len(existingInstances) == 1 && 658 existingInstances[0].ID() == DefaultLocalHostID && 659 existingInstances[0].IsolationGroup() == DefaultLocalIsolationGroup && 660 existingInstances[0].Zone() == DefaultLocalZone 661 } 662 663 func portFromEmbeddedDBConfigListenAddress(address string) (int, error) { 664 colonIdx := strings.LastIndex(address, ":") 665 if colonIdx == -1 || colonIdx == len(address)-1 { 666 return 0, errMissingEmbeddedDBPort 667 } 668 669 return strconv.Atoi(address[colonIdx+1:]) 670 } 671 672 func powerOfTwoAtLeast(num float64) float64 { 673 return math.Pow(2, math.Ceil(math.Log2(num))) 674 }