vitess.io/vitess@v0.16.2/go/vt/topo/tablet.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package topo 18 19 import ( 20 "context" 21 "fmt" 22 "path" 23 "sort" 24 "sync" 25 "time" 26 27 "vitess.io/vitess/go/vt/key" 28 29 "vitess.io/vitess/go/vt/proto/vtrpc" 30 "vitess.io/vitess/go/vt/vterrors" 31 32 "google.golang.org/protobuf/proto" 33 34 "vitess.io/vitess/go/event" 35 "vitess.io/vitess/go/netutil" 36 "vitess.io/vitess/go/trace" 37 "vitess.io/vitess/go/vt/log" 38 "vitess.io/vitess/go/vt/logutil" 39 40 topodatapb "vitess.io/vitess/go/vt/proto/topodata" 41 "vitess.io/vitess/go/vt/topo/events" 42 "vitess.io/vitess/go/vt/topo/topoproto" 43 ) 44 45 // IsTrivialTypeChange returns if this db type be trivially reassigned 46 // without changes to the replication graph 47 func IsTrivialTypeChange(oldTabletType, newTabletType topodatapb.TabletType) bool { 48 switch oldTabletType { 49 case topodatapb.TabletType_REPLICA, topodatapb.TabletType_RDONLY, topodatapb.TabletType_SPARE, topodatapb.TabletType_BACKUP, topodatapb.TabletType_EXPERIMENTAL, topodatapb.TabletType_DRAINED: 50 switch newTabletType { 51 case topodatapb.TabletType_REPLICA, topodatapb.TabletType_RDONLY, topodatapb.TabletType_SPARE, topodatapb.TabletType_BACKUP, topodatapb.TabletType_EXPERIMENTAL, topodatapb.TabletType_DRAINED: 52 return true 53 } 54 case topodatapb.TabletType_RESTORE: 55 switch newTabletType { 56 case topodatapb.TabletType_SPARE: 57 return true 58 } 59 } 60 return false 61 } 62 63 // IsInServingGraph returns if a tablet appears in the serving graph 64 func IsInServingGraph(tt topodatapb.TabletType) bool { 65 switch tt { 66 case topodatapb.TabletType_PRIMARY, topodatapb.TabletType_REPLICA, topodatapb.TabletType_RDONLY: 67 return true 68 } 69 return false 70 } 71 72 // IsRunningQueryService returns if a tablet is running the query service 73 func IsRunningQueryService(tt topodatapb.TabletType) bool { 74 switch tt { 75 case topodatapb.TabletType_PRIMARY, topodatapb.TabletType_REPLICA, topodatapb.TabletType_RDONLY, topodatapb.TabletType_EXPERIMENTAL, topodatapb.TabletType_DRAINED: 76 return true 77 } 78 return false 79 } 80 81 // IsSubjectToLameduck returns if a tablet is subject to being 82 // lameduck. Lameduck is a transition period where we are still 83 // allowed to serve, but we tell the clients we are going away 84 // soon. Typically, a vttablet will still serve, but broadcast a 85 // non-serving state through its health check. then vtgate will catch 86 // that non-serving state, and stop sending queries. 87 // 88 // Primaries are not subject to lameduck, as we usually want to transition 89 // them as fast as possible. 90 // 91 // Replica and rdonly will use lameduck when going from healthy to 92 // unhealthy (either because health check fails, or they're shutting down). 93 // 94 // Other types are probably not serving user visible traffic, so they 95 // need to transition as fast as possible too. 96 func IsSubjectToLameduck(tt topodatapb.TabletType) bool { 97 switch tt { 98 case topodatapb.TabletType_REPLICA, topodatapb.TabletType_RDONLY: 99 return true 100 } 101 return false 102 } 103 104 // IsRunningUpdateStream returns if a tablet is running the update stream 105 // RPC service. 106 func IsRunningUpdateStream(tt topodatapb.TabletType) bool { 107 switch tt { 108 case topodatapb.TabletType_PRIMARY, topodatapb.TabletType_REPLICA, topodatapb.TabletType_RDONLY: 109 return true 110 } 111 return false 112 } 113 114 // IsReplicaType returns if this type should be connected to a primary db 115 // and actively replicating? 116 // PRIMARY is not obviously (only support one level replication graph) 117 // BACKUP, RESTORE, DRAINED may or may not be, but we don't know for sure 118 func IsReplicaType(tt topodatapb.TabletType) bool { 119 switch tt { 120 case topodatapb.TabletType_PRIMARY, topodatapb.TabletType_BACKUP, topodatapb.TabletType_RESTORE, topodatapb.TabletType_DRAINED: 121 return false 122 } 123 return true 124 } 125 126 // NewTablet create a new Tablet record with the given id, cell, and hostname. 127 func NewTablet(uid uint32, cell, host string) *topodatapb.Tablet { 128 return &topodatapb.Tablet{ 129 Alias: &topodatapb.TabletAlias{ 130 Cell: cell, 131 Uid: uid, 132 }, 133 Hostname: host, 134 PortMap: make(map[string]int32), 135 } 136 } 137 138 // TabletEquality returns true iff two Tablet are representing the same tablet 139 // process: same uid/cell, running on the same host / ports. 140 func TabletEquality(left, right *topodatapb.Tablet) bool { 141 if !topoproto.TabletAliasEqual(left.Alias, right.Alias) { 142 return false 143 } 144 if left.Hostname != right.Hostname { 145 return false 146 } 147 if left.MysqlHostname != right.MysqlHostname { 148 return false 149 } 150 if left.MysqlPort != right.MysqlPort { 151 return false 152 } 153 if len(left.PortMap) != len(right.PortMap) { 154 return false 155 } 156 for key, lvalue := range left.PortMap { 157 rvalue, ok := right.PortMap[key] 158 if !ok { 159 return false 160 } 161 if lvalue != rvalue { 162 return false 163 } 164 } 165 return true 166 } 167 168 // TabletInfo is the container for a Tablet, read from the topology server. 169 type TabletInfo struct { 170 version Version // node version - used to prevent stomping concurrent writes 171 *topodatapb.Tablet 172 } 173 174 // String returns a string describing the tablet. 175 func (ti *TabletInfo) String() string { 176 return fmt.Sprintf("Tablet{%v}", topoproto.TabletAliasString(ti.Alias)) 177 } 178 179 // AliasString returns the string representation of the tablet alias 180 func (ti *TabletInfo) AliasString() string { 181 return topoproto.TabletAliasString(ti.Alias) 182 } 183 184 // Addr returns hostname:vt port. 185 func (ti *TabletInfo) Addr() string { 186 return netutil.JoinHostPort(ti.Hostname, int32(ti.PortMap["vt"])) 187 } 188 189 // MysqlAddr returns hostname:mysql port. 190 func (ti *TabletInfo) MysqlAddr() string { 191 return netutil.JoinHostPort(ti.Tablet.MysqlHostname, ti.Tablet.MysqlPort) 192 } 193 194 // DbName is usually implied by keyspace. Having the shard information in the 195 // database name complicates mysql replication. 196 func (ti *TabletInfo) DbName() string { 197 return topoproto.TabletDbName(ti.Tablet) 198 } 199 200 // Version returns the version of this tablet from last time it was read or 201 // updated. 202 func (ti *TabletInfo) Version() Version { 203 return ti.version 204 } 205 206 // IsInServingGraph returns if this tablet is in the serving graph 207 func (ti *TabletInfo) IsInServingGraph() bool { 208 return IsInServingGraph(ti.Type) 209 } 210 211 // IsReplicaType returns if this tablet's type is a replica 212 func (ti *TabletInfo) IsReplicaType() bool { 213 return IsReplicaType(ti.Type) 214 } 215 216 // GetPrimaryTermStartTime returns the tablet's primary term start time as a Time value. 217 func (ti *TabletInfo) GetPrimaryTermStartTime() time.Time { 218 return logutil.ProtoToTime(ti.Tablet.PrimaryTermStartTime) 219 } 220 221 // NewTabletInfo returns a TabletInfo basing on tablet with the 222 // version set. This function should be only used by Server 223 // implementations. 224 func NewTabletInfo(tablet *topodatapb.Tablet, version Version) *TabletInfo { 225 return &TabletInfo{version: version, Tablet: tablet} 226 } 227 228 // GetTablet is a high level function to read tablet data. 229 // It generates trace spans. 230 func (ts *Server) GetTablet(ctx context.Context, alias *topodatapb.TabletAlias) (*TabletInfo, error) { 231 conn, err := ts.ConnForCell(ctx, alias.Cell) 232 if err != nil { 233 log.Errorf("Unable to get connection for cell %s", alias.Cell) 234 return nil, err 235 } 236 237 span, ctx := trace.NewSpan(ctx, "TopoServer.GetTablet") 238 span.Annotate("tablet", topoproto.TabletAliasString(alias)) 239 defer span.Finish() 240 241 tabletPath := path.Join(TabletsPath, topoproto.TabletAliasString(alias), TabletFile) 242 data, version, err := conn.Get(ctx, tabletPath) 243 if err != nil { 244 log.Errorf("unable to connect to tablet %s: %s", alias, err) 245 return nil, err 246 } 247 tablet := &topodatapb.Tablet{} 248 if err := proto.Unmarshal(data, tablet); err != nil { 249 return nil, err 250 } 251 252 return &TabletInfo{ 253 version: version, 254 Tablet: tablet, 255 }, nil 256 } 257 258 // GetTabletAliasesByCell returns all the tablet aliases in a cell. 259 // It returns ErrNoNode if the cell doesn't exist. 260 // It returns (nil, nil) if the cell exists, but there are no tablets in it. 261 func (ts *Server) GetTabletAliasesByCell(ctx context.Context, cell string) ([]*topodatapb.TabletAlias, error) { 262 // If the cell doesn't exist, this will return ErrNoNode. 263 conn, err := ts.ConnForCell(ctx, cell) 264 if err != nil { 265 return nil, err 266 } 267 268 // List the directory, and parse the aliases 269 children, err := conn.ListDir(ctx, TabletsPath, false /*full*/) 270 if err != nil { 271 if IsErrType(err, NoNode) { 272 // directory doesn't exist, empty list, no error. 273 return nil, nil 274 } 275 return nil, err 276 } 277 278 result := make([]*topodatapb.TabletAlias, len(children)) 279 for i, child := range children { 280 result[i], err = topoproto.ParseTabletAlias(child.Name) 281 if err != nil { 282 return nil, err 283 } 284 } 285 return result, nil 286 } 287 288 // GetTabletsByCell returns all the tablets in the cell. 289 // It returns ErrNoNode if the cell doesn't exist. 290 // It returns (nil, nil) if the cell exists, but there are no tablets in it. 291 func (ts *Server) GetTabletsByCell(ctx context.Context, cellAlias string) ([]*TabletInfo, error) { 292 // If the cell doesn't exist, this will return ErrNoNode. 293 cellConn, err := ts.ConnForCell(ctx, cellAlias) 294 if err != nil { 295 return nil, err 296 } 297 listResults, err := cellConn.List(ctx, TabletsPath) 298 if err != nil || len(listResults) == 0 { 299 // Currently the ZooKeeper and Memory topo implementations do not support scans 300 // so we fall back to the more costly method of fetching the tablets one by one. 301 if IsErrType(err, NoImplementation) { 302 return ts.GetTabletsIndividuallyByCell(ctx, cellAlias) 303 } 304 if IsErrType(err, NoNode) { 305 return nil, nil 306 } 307 return nil, err 308 } 309 310 tablets := make([]*TabletInfo, len(listResults)) 311 for n := range listResults { 312 tablet := &topodatapb.Tablet{} 313 if err := proto.Unmarshal(listResults[n].Value, tablet); err != nil { 314 return nil, err 315 } 316 tablets[n] = &TabletInfo{Tablet: tablet, version: listResults[n].Version} 317 } 318 319 return tablets, nil 320 } 321 322 // GetTabletsIndividuallyByCell returns a sorted list of tablets for topo servers that do not 323 // directly support the topoConn.List() functionality. 324 // It returns ErrNoNode if the cell doesn't exist. 325 // It returns (nil, nil) if the cell exists, but there are no tablets in it. 326 func (ts *Server) GetTabletsIndividuallyByCell(ctx context.Context, cell string) ([]*TabletInfo, error) { 327 // If the cell doesn't exist, this will return ErrNoNode. 328 aliases, err := ts.GetTabletAliasesByCell(ctx, cell) 329 if err != nil { 330 return nil, err 331 } 332 sort.Sort(topoproto.TabletAliasList(aliases)) 333 334 tabletMap, err := ts.GetTabletMap(ctx, aliases) 335 if err != nil { 336 // we got another error than topo.ErrNoNode 337 return nil, err 338 } 339 tablets := make([]*TabletInfo, 0, len(aliases)) 340 for _, tabletAlias := range aliases { 341 tabletInfo, ok := tabletMap[topoproto.TabletAliasString(tabletAlias)] 342 if !ok { 343 // tablet disappeared on us (GetTabletMap ignores 344 // topo.ErrNoNode), just echo a warning 345 log.Warningf("failed to load tablet %v", tabletAlias) 346 } else { 347 tablets = append(tablets, tabletInfo) 348 } 349 } 350 351 return tablets, nil 352 } 353 354 // UpdateTablet updates the tablet data only - not associated replication paths. 355 // It also uses a span, and sends the event. 356 func (ts *Server) UpdateTablet(ctx context.Context, ti *TabletInfo) error { 357 conn, err := ts.ConnForCell(ctx, ti.Tablet.Alias.Cell) 358 if err != nil { 359 return err 360 } 361 362 span, ctx := trace.NewSpan(ctx, "TopoServer.UpdateTablet") 363 span.Annotate("tablet", topoproto.TabletAliasString(ti.Alias)) 364 defer span.Finish() 365 366 data, err := proto.Marshal(ti.Tablet) 367 if err != nil { 368 return err 369 } 370 tabletPath := path.Join(TabletsPath, topoproto.TabletAliasString(ti.Tablet.Alias), TabletFile) 371 newVersion, err := conn.Update(ctx, tabletPath, data, ti.version) 372 if err != nil { 373 return err 374 } 375 ti.version = newVersion 376 377 event.Dispatch(&events.TabletChange{ 378 Tablet: ti.Tablet, 379 Status: "updated", 380 }) 381 return nil 382 } 383 384 // UpdateTabletFields is a high level helper to read a tablet record, call an 385 // update function on it, and then write it back. If the write fails due to 386 // a version mismatch, it will re-read the record and retry the update. 387 // If the update succeeds, it returns the updated tablet. 388 // If the update method returns ErrNoUpdateNeeded, nothing is written, 389 // and nil,nil is returned. 390 func (ts *Server) UpdateTabletFields(ctx context.Context, alias *topodatapb.TabletAlias, update func(*topodatapb.Tablet) error) (*topodatapb.Tablet, error) { 391 span, ctx := trace.NewSpan(ctx, "TopoServer.UpdateTabletFields") 392 span.Annotate("tablet", topoproto.TabletAliasString(alias)) 393 defer span.Finish() 394 395 for { 396 ti, err := ts.GetTablet(ctx, alias) 397 if err != nil { 398 return nil, err 399 } 400 if err = update(ti.Tablet); err != nil { 401 if IsErrType(err, NoUpdateNeeded) { 402 return nil, nil 403 } 404 return nil, err 405 } 406 if err = ts.UpdateTablet(ctx, ti); !IsErrType(err, BadVersion) { 407 return ti.Tablet, err 408 } 409 } 410 } 411 412 // Validate makes sure a tablet is represented correctly in the topology server. 413 func Validate(ctx context.Context, ts *Server, tabletAlias *topodatapb.TabletAlias) error { 414 // read the tablet record, make sure it parses 415 tablet, err := ts.GetTablet(ctx, tabletAlias) 416 if err != nil { 417 return err 418 } 419 if !topoproto.TabletAliasEqual(tablet.Alias, tabletAlias) { 420 return vterrors.Errorf(vtrpc.Code_INVALID_ARGUMENT, "bad tablet alias data for tablet %v: %#v", topoproto.TabletAliasString(tabletAlias), tablet.Alias) 421 } 422 423 // Validate the entry in the shard replication nodes 424 si, err := ts.GetShardReplication(ctx, tablet.Alias.Cell, tablet.Keyspace, tablet.Shard) 425 if err != nil { 426 return err 427 } 428 429 if _, err = si.GetShardReplicationNode(tabletAlias); err != nil { 430 return vterrors.Wrapf(err, "tablet %v not found in cell %v shard replication", tabletAlias, tablet.Alias.Cell) 431 } 432 433 return nil 434 } 435 436 // CreateTablet creates a new tablet and all associated paths for the 437 // replication graph. 438 func (ts *Server) CreateTablet(ctx context.Context, tablet *topodatapb.Tablet) error { 439 conn, err := ts.ConnForCell(ctx, tablet.Alias.Cell) 440 if err != nil { 441 return err 442 } 443 444 data, err := proto.Marshal(tablet) 445 if err != nil { 446 return err 447 } 448 tabletPath := path.Join(TabletsPath, topoproto.TabletAliasString(tablet.Alias), TabletFile) 449 if _, err = conn.Create(ctx, tabletPath, data); err != nil { 450 return err 451 } 452 453 if updateErr := UpdateTabletReplicationData(ctx, ts, tablet); updateErr != nil { 454 return updateErr 455 } 456 457 if err == nil { 458 event.Dispatch(&events.TabletChange{ 459 Tablet: tablet, 460 Status: "created", 461 }) 462 } 463 return err 464 } 465 466 // DeleteTablet wraps the underlying conn.Delete 467 // and dispatches the event. 468 func (ts *Server) DeleteTablet(ctx context.Context, tabletAlias *topodatapb.TabletAlias) error { 469 conn, err := ts.ConnForCell(ctx, tabletAlias.Cell) 470 if err != nil { 471 return err 472 } 473 474 // get the current tablet record, if any, to log the deletion 475 ti, tErr := ts.GetTablet(ctx, tabletAlias) 476 477 tabletPath := path.Join(TabletsPath, topoproto.TabletAliasString(tabletAlias), TabletFile) 478 if err := conn.Delete(ctx, tabletPath, nil); err != nil { 479 return err 480 } 481 482 // Only try to log if we have the required info. 483 if tErr == nil { 484 // Only copy the identity info for the tablet. The rest has been deleted. 485 event.Dispatch(&events.TabletChange{ 486 Tablet: &topodatapb.Tablet{ 487 Alias: tabletAlias, 488 Keyspace: ti.Tablet.Keyspace, 489 Shard: ti.Tablet.Shard, 490 }, 491 Status: "deleted", 492 }) 493 } 494 return nil 495 } 496 497 // UpdateTabletReplicationData creates or updates the replication 498 // graph data for a tablet 499 func UpdateTabletReplicationData(ctx context.Context, ts *Server, tablet *topodatapb.Tablet) error { 500 return UpdateShardReplicationRecord(ctx, ts, tablet.Keyspace, tablet.Shard, tablet.Alias) 501 } 502 503 // DeleteTabletReplicationData deletes replication data. 504 func DeleteTabletReplicationData(ctx context.Context, ts *Server, tablet *topodatapb.Tablet) error { 505 return RemoveShardReplicationRecord(ctx, ts, tablet.Alias.Cell, tablet.Keyspace, tablet.Shard, tablet.Alias) 506 } 507 508 // GetTabletMap tries to read all the tablets in the provided list, 509 // and returns them all in a map. 510 // If error is ErrPartialResult, the results in the dictionary are 511 // incomplete, meaning some tablets couldn't be read. 512 // The map is indexed by topoproto.TabletAliasString(tablet alias). 513 func (ts *Server) GetTabletMap(ctx context.Context, tabletAliases []*topodatapb.TabletAlias) (map[string]*TabletInfo, error) { 514 span, ctx := trace.NewSpan(ctx, "topo.GetTabletMap") 515 span.Annotate("num_tablets", len(tabletAliases)) 516 defer span.Finish() 517 518 wg := sync.WaitGroup{} 519 mutex := sync.Mutex{} 520 521 tabletMap := make(map[string]*TabletInfo) 522 var someError error 523 524 for _, tabletAlias := range tabletAliases { 525 wg.Add(1) 526 go func(tabletAlias *topodatapb.TabletAlias) { 527 defer wg.Done() 528 tabletInfo, err := ts.GetTablet(ctx, tabletAlias) 529 mutex.Lock() 530 if err != nil { 531 log.Warningf("%v: %v", tabletAlias, err) 532 // There can be data races removing nodes - ignore them for now. 533 if !IsErrType(err, NoNode) { 534 someError = NewError(PartialResult, "") 535 } 536 } else { 537 tabletMap[topoproto.TabletAliasString(tabletAlias)] = tabletInfo 538 } 539 mutex.Unlock() 540 }(tabletAlias) 541 } 542 wg.Wait() 543 return tabletMap, someError 544 } 545 546 // InitTablet creates or updates a tablet. If no parent is specified 547 // in the tablet, and the tablet has a replica type, we will find the 548 // appropriate parent. If createShardAndKeyspace is true and the 549 // parent keyspace or shard don't exist, they will be created. If 550 // allowUpdate is true, and a tablet with the same ID exists, just update it. 551 // If a tablet is created as primary, and there is already a different 552 // primary in the shard, allowPrimaryOverride must be set. 553 func (ts *Server) InitTablet(ctx context.Context, tablet *topodatapb.Tablet, allowPrimaryOverride, createShardAndKeyspace, allowUpdate bool) error { 554 shard, kr, err := ValidateShardName(tablet.Shard) 555 if err != nil { 556 return err 557 } 558 tablet.Shard = shard 559 tablet.KeyRange = kr 560 561 // get the shard, possibly creating it 562 var si *ShardInfo 563 564 if createShardAndKeyspace { 565 // create the parent keyspace and shard if needed 566 si, err = ts.GetOrCreateShard(ctx, tablet.Keyspace, tablet.Shard) 567 } else { 568 si, err = ts.GetShard(ctx, tablet.Keyspace, tablet.Shard) 569 if IsErrType(err, NoNode) { 570 return fmt.Errorf("missing parent shard, use -parent option to create it, or CreateKeyspace / CreateShard") 571 } 572 } 573 574 // get the shard, checks a couple things 575 if err != nil { 576 return fmt.Errorf("cannot get (or create) shard %v/%v: %v", tablet.Keyspace, tablet.Shard, err) 577 } 578 if !key.KeyRangeEqual(si.KeyRange, tablet.KeyRange) { 579 return fmt.Errorf("shard %v/%v has a different KeyRange: %v != %v", tablet.Keyspace, tablet.Shard, si.KeyRange, tablet.KeyRange) 580 } 581 if tablet.Type == topodatapb.TabletType_PRIMARY && si.HasPrimary() && !topoproto.TabletAliasEqual(si.PrimaryAlias, tablet.Alias) && !allowPrimaryOverride { 582 // InitTablet is deprecated, so the flag has not been renamed 583 return fmt.Errorf("creating this tablet would override old primary %v in shard %v/%v, use allow_master_override flag", topoproto.TabletAliasString(si.PrimaryAlias), tablet.Keyspace, tablet.Shard) 584 } 585 586 if tablet.Type == topodatapb.TabletType_PRIMARY { 587 // we update primary_term_start_time even if the primary hasn't changed 588 // because that means a new primary term with the same primary 589 tablet.PrimaryTermStartTime = logutil.TimeToProto(time.Now()) 590 } 591 592 err = ts.CreateTablet(ctx, tablet) 593 if IsErrType(err, NodeExists) && allowUpdate { 594 // Try to update then 595 oldTablet, err := ts.GetTablet(ctx, tablet.Alias) 596 if err != nil { 597 return fmt.Errorf("failed reading existing tablet %v: %v", topoproto.TabletAliasString(tablet.Alias), err) 598 } 599 600 // Check we have the same keyspace / shard, and if not, 601 // require the allowDifferentShard flag. 602 if oldTablet.Keyspace != tablet.Keyspace || oldTablet.Shard != tablet.Shard { 603 return fmt.Errorf("old tablet has shard %v/%v. Cannot override with shard %v/%v. Delete and re-add tablet if you want to change the tablet's keyspace/shard", oldTablet.Keyspace, oldTablet.Shard, tablet.Keyspace, tablet.Shard) 604 } 605 oldTablet.Tablet = proto.Clone(tablet).(*topodatapb.Tablet) 606 if err := ts.UpdateTablet(ctx, oldTablet); err != nil { 607 return fmt.Errorf("failed updating tablet %v: %v", topoproto.TabletAliasString(tablet.Alias), err) 608 } 609 return nil 610 } 611 return err 612 } 613 614 // ParseServingTabletType parses the tablet type into the enum, and makes sure 615 // that the enum is of serving type (PRIMARY, REPLICA, RDONLY/BATCH). 616 // 617 // Note: This function more closely belongs in topoproto, but that would create 618 // a circular import between packages topo and topoproto. 619 func ParseServingTabletType(param string) (topodatapb.TabletType, error) { 620 servedType, err := topoproto.ParseTabletType(param) 621 if err != nil { 622 return topodatapb.TabletType_UNKNOWN, err 623 } 624 625 if !IsInServingGraph(servedType) { 626 return topodatapb.TabletType_UNKNOWN, fmt.Errorf("served_type has to be in the serving graph, not %v", param) 627 } 628 629 return servedType, nil 630 }