vitess.io/vitess@v0.16.2/go/vt/topo/keyspace.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 "path" 21 "strings" 22 23 "google.golang.org/protobuf/proto" 24 25 "context" 26 27 "vitess.io/vitess/go/vt/vterrors" 28 29 "vitess.io/vitess/go/event" 30 "vitess.io/vitess/go/vt/log" 31 "vitess.io/vitess/go/vt/topo/events" 32 33 topodatapb "vitess.io/vitess/go/vt/proto/topodata" 34 vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" 35 ) 36 37 // This file contains keyspace utility functions 38 39 // KeyspaceInfo is a meta struct that contains metadata to give the 40 // data more context and convenience. This is the main way we interact 41 // with a keyspace. 42 type KeyspaceInfo struct { 43 keyspace string 44 version Version 45 *topodatapb.Keyspace 46 } 47 48 // KeyspaceName returns the keyspace name 49 func (ki *KeyspaceInfo) KeyspaceName() string { 50 return ki.keyspace 51 } 52 53 // SetKeyspaceName sets the keyspace name 54 func (ki *KeyspaceInfo) SetKeyspaceName(name string) { 55 ki.keyspace = name 56 } 57 58 var invalidKeyspaceNameChars = "/" 59 60 // ValidateKeyspaceName checks if the provided name is a valid name for a 61 // keyspace. 62 // 63 // As of v16.0.1, "all invalid characters" is just the forward slash ("/"). 64 func ValidateKeyspaceName(name string) error { 65 if strings.ContainsAny(name, invalidKeyspaceNameChars) { 66 return vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "keyspace name %s contains invalid characters; may not contain any of the following: %+v", name, strings.Split(invalidKeyspaceNameChars, "")) 67 } 68 69 return nil 70 } 71 72 // GetServedFrom returns a Keyspace_ServedFrom record if it exists. 73 func (ki *KeyspaceInfo) GetServedFrom(tabletType topodatapb.TabletType) *topodatapb.Keyspace_ServedFrom { 74 for _, ksf := range ki.ServedFroms { 75 if ksf.TabletType == tabletType { 76 return ksf 77 } 78 } 79 return nil 80 } 81 82 // CheckServedFromMigration makes sure a requested migration is safe 83 func (ki *KeyspaceInfo) CheckServedFromMigration(tabletType topodatapb.TabletType, cells []string, keyspace string, remove bool) error { 84 // primary is a special case with a few extra checks 85 if tabletType == topodatapb.TabletType_PRIMARY { 86 // TODO(deepthi): these master references will go away when we delete legacy resharding 87 if !remove { 88 return vterrors.Errorf(vtrpcpb.Code_FAILED_PRECONDITION, "cannot add master back to %v", ki.keyspace) 89 } 90 if len(cells) > 0 { 91 return vterrors.Errorf(vtrpcpb.Code_FAILED_PRECONDITION, "cannot migrate only some cells for master removal in keyspace %v", ki.keyspace) 92 } 93 if len(ki.ServedFroms) > 1 { 94 return vterrors.Errorf(vtrpcpb.Code_FAILED_PRECONDITION, "cannot migrate master into %v until everything else is migrated", ki.keyspace) 95 } 96 } 97 98 // we can't remove a type we don't have 99 if ki.GetServedFrom(tabletType) == nil && remove { 100 return vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "supplied type cannot be migrated") 101 } 102 103 // check the keyspace is consistent in any case 104 for _, ksf := range ki.ServedFroms { 105 if ksf.Keyspace != keyspace { 106 return vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "inconsistent keyspace specified in migration: %v != %v for type %v", keyspace, ksf.Keyspace, ksf.TabletType) 107 } 108 } 109 110 return nil 111 } 112 113 // UpdateServedFromMap handles ServedFromMap. It can add or remove 114 // records, cells, ... 115 func (ki *KeyspaceInfo) UpdateServedFromMap(tabletType topodatapb.TabletType, cells []string, keyspace string, remove bool, allCells []string) error { 116 // check parameters to be sure 117 if err := ki.CheckServedFromMigration(tabletType, cells, keyspace, remove); err != nil { 118 return err 119 } 120 121 ksf := ki.GetServedFrom(tabletType) 122 if ksf == nil { 123 // the record doesn't exist 124 if remove { 125 if len(ki.ServedFroms) == 0 { 126 ki.ServedFroms = nil 127 } 128 log.Warningf("Trying to remove KeyspaceServedFrom for missing type %v in keyspace %v", tabletType, ki.keyspace) 129 } else { 130 ki.ServedFroms = append(ki.ServedFroms, &topodatapb.Keyspace_ServedFrom{ 131 TabletType: tabletType, 132 Cells: cells, 133 Keyspace: keyspace, 134 }) 135 } 136 return nil 137 } 138 139 if remove { 140 result, emptyList := removeCells(ksf.Cells, cells, allCells) 141 if emptyList { 142 // we don't have any cell left, we need to clear this record 143 var newServedFroms []*topodatapb.Keyspace_ServedFrom 144 for _, k := range ki.ServedFroms { 145 if k != ksf { 146 newServedFroms = append(newServedFroms, k) 147 } 148 } 149 ki.ServedFroms = newServedFroms 150 } else { 151 ksf.Cells = result 152 } 153 } else { 154 if ksf.Keyspace != keyspace { 155 return vterrors.Errorf(vtrpcpb.Code_FAILED_PRECONDITION, "cannot UpdateServedFromMap on existing record for keyspace %v, different keyspace: %v != %v", ki.keyspace, ksf.Keyspace, keyspace) 156 } 157 ksf.Cells = addCells(ksf.Cells, cells) 158 } 159 return nil 160 } 161 162 // ComputeCellServedFrom returns the ServedFrom list for a cell 163 func (ki *KeyspaceInfo) ComputeCellServedFrom(cell string) []*topodatapb.SrvKeyspace_ServedFrom { 164 var result []*topodatapb.SrvKeyspace_ServedFrom 165 for _, ksf := range ki.ServedFroms { 166 if InCellList(cell, ksf.Cells) { 167 result = append(result, &topodatapb.SrvKeyspace_ServedFrom{ 168 TabletType: ksf.TabletType, 169 Keyspace: ksf.Keyspace, 170 }) 171 } 172 } 173 return result 174 } 175 176 // CreateKeyspace wraps the underlying Conn.Create 177 // and dispatches the event. 178 func (ts *Server) CreateKeyspace(ctx context.Context, keyspace string, value *topodatapb.Keyspace) error { 179 if err := ValidateKeyspaceName(keyspace); err != nil { 180 return vterrors.Wrapf(err, "CreateKeyspace: %s", err) 181 } 182 183 data, err := proto.Marshal(value) 184 if err != nil { 185 return err 186 } 187 188 keyspacePath := path.Join(KeyspacesPath, keyspace, KeyspaceFile) 189 if _, err := ts.globalCell.Create(ctx, keyspacePath, data); err != nil { 190 return err 191 } 192 193 event.Dispatch(&events.KeyspaceChange{ 194 KeyspaceName: keyspace, 195 Keyspace: value, 196 Status: "created", 197 }) 198 return nil 199 } 200 201 // GetKeyspace reads the given keyspace and returns it 202 func (ts *Server) GetKeyspace(ctx context.Context, keyspace string) (*KeyspaceInfo, error) { 203 if err := ValidateKeyspaceName(keyspace); err != nil { 204 return nil, vterrors.Wrapf(err, "GetKeyspace: %s", err) 205 } 206 207 keyspacePath := path.Join(KeyspacesPath, keyspace, KeyspaceFile) 208 data, version, err := ts.globalCell.Get(ctx, keyspacePath) 209 if err != nil { 210 return nil, err 211 } 212 213 k := &topodatapb.Keyspace{} 214 if err = proto.Unmarshal(data, k); err != nil { 215 return nil, vterrors.Wrap(err, "bad keyspace data") 216 } 217 218 return &KeyspaceInfo{ 219 keyspace: keyspace, 220 version: version, 221 Keyspace: k, 222 }, nil 223 } 224 225 // GetKeyspaceDurability reads the given keyspace and returns its durabilty policy 226 func (ts *Server) GetKeyspaceDurability(ctx context.Context, keyspace string) (string, error) { 227 keyspaceInfo, err := ts.GetKeyspace(ctx, keyspace) 228 if err != nil { 229 return "", err 230 } 231 // Get the durability policy from the keyspace information 232 // If it is unspecified, use the default durability which is "none" for backward compatibility 233 if keyspaceInfo.GetDurabilityPolicy() != "" { 234 return keyspaceInfo.GetDurabilityPolicy(), nil 235 } 236 return "none", nil 237 } 238 239 func (ts *Server) GetThrottlerConfig(ctx context.Context, keyspace string) (*topodatapb.ThrottlerConfig, error) { 240 keyspaceInfo, err := ts.GetKeyspace(ctx, keyspace) 241 if err != nil { 242 return nil, err 243 } 244 return keyspaceInfo.ThrottlerConfig, nil 245 } 246 247 // UpdateKeyspace updates the keyspace data. It checks the keyspace is locked. 248 func (ts *Server) UpdateKeyspace(ctx context.Context, ki *KeyspaceInfo) error { 249 // make sure it is locked first 250 if err := CheckKeyspaceLocked(ctx, ki.keyspace); err != nil { 251 return err 252 } 253 254 data, err := proto.Marshal(ki.Keyspace) 255 if err != nil { 256 return err 257 } 258 keyspacePath := path.Join(KeyspacesPath, ki.keyspace, KeyspaceFile) 259 version, err := ts.globalCell.Update(ctx, keyspacePath, data, ki.version) 260 if err != nil { 261 return err 262 } 263 ki.version = version 264 265 event.Dispatch(&events.KeyspaceChange{ 266 KeyspaceName: ki.keyspace, 267 Keyspace: ki.Keyspace, 268 Status: "updated", 269 }) 270 return nil 271 } 272 273 // FindAllShardsInKeyspace reads and returns all the existing shards in 274 // a keyspace. It doesn't take any lock. 275 func (ts *Server) FindAllShardsInKeyspace(ctx context.Context, keyspace string) (map[string]*ShardInfo, error) { 276 shards, err := ts.GetShardNames(ctx, keyspace) 277 if err != nil { 278 return nil, vterrors.Wrapf(err, "failed to get list of shards for keyspace '%v'", keyspace) 279 } 280 281 result := make(map[string]*ShardInfo, len(shards)) 282 for _, shard := range shards { 283 si, err := ts.GetShard(ctx, keyspace, shard) 284 if err != nil { 285 if IsErrType(err, NoNode) { 286 log.Warningf("GetShard(%v, %v) returned ErrNoNode, consider checking the topology.", keyspace, shard) 287 } else { 288 return nil, vterrors.Wrapf(err, "GetShard(%v, %v) failed", keyspace, shard) 289 } 290 } 291 result[shard] = si 292 } 293 return result, nil 294 } 295 296 // GetServingShards returns all shards where the primary is serving. 297 func (ts *Server) GetServingShards(ctx context.Context, keyspace string) ([]*ShardInfo, error) { 298 shards, err := ts.GetShardNames(ctx, keyspace) 299 if err != nil { 300 return nil, vterrors.Wrapf(err, "failed to get list of shards for keyspace '%v'", keyspace) 301 } 302 303 result := make([]*ShardInfo, 0, len(shards)) 304 for _, shard := range shards { 305 si, err := ts.GetShard(ctx, keyspace, shard) 306 if err != nil { 307 return nil, vterrors.Wrapf(err, "GetShard(%v, %v) failed", keyspace, shard) 308 } 309 if !si.IsPrimaryServing { 310 continue 311 } 312 result = append(result, si) 313 } 314 if len(result) == 0 { 315 return nil, vterrors.Errorf(vtrpcpb.Code_FAILED_PRECONDITION, "%v has no serving shards", keyspace) 316 } 317 return result, nil 318 } 319 320 // GetOnlyShard returns the single ShardInfo of an unsharded keyspace. 321 func (ts *Server) GetOnlyShard(ctx context.Context, keyspace string) (*ShardInfo, error) { 322 allShards, err := ts.FindAllShardsInKeyspace(ctx, keyspace) 323 if err != nil { 324 return nil, err 325 } 326 if len(allShards) == 1 { 327 for _, s := range allShards { 328 return s, nil 329 } 330 } 331 return nil, vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "keyspace %s must have one and only one shard: %v", keyspace, allShards) 332 } 333 334 // DeleteKeyspace wraps the underlying Conn.Delete 335 // and dispatches the event. 336 func (ts *Server) DeleteKeyspace(ctx context.Context, keyspace string) error { 337 keyspacePath := path.Join(KeyspacesPath, keyspace, KeyspaceFile) 338 if err := ts.globalCell.Delete(ctx, keyspacePath, nil); err != nil { 339 return err 340 } 341 342 // Delete the cell-global VSchema path 343 // If not remove this, vtctld web page Dashboard will Display Error 344 if err := ts.DeleteVSchema(ctx, keyspace); err != nil && !IsErrType(err, NoNode) { 345 return err 346 } 347 348 event.Dispatch(&events.KeyspaceChange{ 349 KeyspaceName: keyspace, 350 Keyspace: nil, 351 Status: "deleted", 352 }) 353 return nil 354 } 355 356 // GetKeyspaces returns the list of keyspaces in the topology. 357 func (ts *Server) GetKeyspaces(ctx context.Context) ([]string, error) { 358 children, err := ts.globalCell.ListDir(ctx, KeyspacesPath, false /*full*/) 359 switch { 360 case err == nil: 361 return DirEntriesToStringArray(children), nil 362 case IsErrType(err, NoNode): 363 return nil, nil 364 default: 365 return nil, err 366 } 367 } 368 369 // GetShardNames returns the list of shards in a keyspace. 370 func (ts *Server) GetShardNames(ctx context.Context, keyspace string) ([]string, error) { 371 shardsPath := path.Join(KeyspacesPath, keyspace, ShardsPath) 372 children, err := ts.globalCell.ListDir(ctx, shardsPath, false /*full*/) 373 if IsErrType(err, NoNode) { 374 // The directory doesn't exist, let's see if the keyspace 375 // is here or not. 376 _, kerr := ts.GetKeyspace(ctx, keyspace) 377 if kerr == nil { 378 // Keyspace is here, means no shards. 379 return nil, nil 380 } 381 return nil, err 382 } 383 return DirEntriesToStringArray(children), err 384 }