vitess.io/vitess@v0.16.2/go/vt/topo/wildcards.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 "sync" 23 24 "context" 25 26 "vitess.io/vitess/go/vt/proto/vtrpc" 27 "vitess.io/vitess/go/vt/vterrors" 28 29 "vitess.io/vitess/go/fileutil" 30 "vitess.io/vitess/go/vt/log" 31 ) 32 33 // ResolveKeyspaceWildcard will resolve keyspace wildcards. 34 // - If the param is not a wildcard, it will just be returned (if the keyspace 35 // doesn't exist, it is still returned). 36 // - If the param is a wildcard, it will get all keyspaces and returns 37 // the ones which match the wildcard (which may be an empty list). 38 func (ts *Server) ResolveKeyspaceWildcard(ctx context.Context, param string) ([]string, error) { 39 if !fileutil.HasWildcard(param) { 40 return []string{param}, nil 41 } 42 43 var result []string 44 45 keyspaces, err := ts.GetKeyspaces(ctx) 46 if err != nil { 47 return nil, vterrors.Wrapf(err, "failed to read keyspaces from topo") 48 } 49 for _, k := range keyspaces { 50 matched, err := path.Match(param, k) 51 if err != nil { 52 return nil, vterrors.Wrapf(err, "invalid pattern %v", param) 53 } 54 if matched { 55 result = append(result, k) 56 } 57 } 58 return result, nil 59 } 60 61 // KeyspaceShard is a type used by ResolveShardWildcard 62 type KeyspaceShard struct { 63 Keyspace string 64 Shard string 65 } 66 67 // ResolveShardWildcard will resolve shard wildcards. Both keyspace and shard 68 // names can use wildcard. Errors talking to the topology server are returned. 69 // ErrNoNode is ignored if it's the result of resolving a wildcard. Examples: 70 // - */* returns all keyspace/shard pairs, or empty list if none. 71 // - user/* returns all shards in user keyspace (or error if user keyspace 72 // doesn't exist) 73 // - us*/* returns all shards in all keyspaces that start with 'us'. If no such 74 // keyspace exists, list is empty (it is not an error). 75 func (ts *Server) ResolveShardWildcard(ctx context.Context, param string) ([]KeyspaceShard, error) { 76 parts := strings.Split(param, "/") 77 if len(parts) != 2 { 78 return nil, vterrors.Errorf(vtrpc.Code_INVALID_ARGUMENT, "invalid shard path: %v", param) 79 } 80 result := make([]KeyspaceShard, 0, 1) 81 82 // get all the matched keyspaces first, remember if it was a wildcard 83 keyspaceHasWildcards := fileutil.HasWildcard(parts[0]) 84 matchedKeyspaces, err := ts.ResolveKeyspaceWildcard(ctx, parts[0]) 85 if err != nil { 86 return nil, err 87 } 88 89 // for each matched keyspace, get the shards 90 for _, matchedKeyspace := range matchedKeyspaces { 91 shard := parts[1] 92 if fileutil.HasWildcard(shard) { 93 // get all the shards for the keyspace 94 shardNames, err := ts.GetShardNames(ctx, matchedKeyspace) 95 switch { 96 case err == nil: 97 // got all the shards, we can keep going 98 case IsErrType(err, NoNode): 99 // keyspace doesn't exist 100 if keyspaceHasWildcards { 101 // that's the */* case when a keyspace has no shards 102 continue 103 } 104 return nil, vterrors.Errorf(vtrpc.Code_INVALID_ARGUMENT, "keyspace %v doesn't exist", matchedKeyspace) 105 default: 106 return nil, vterrors.Wrapf(err, "cannot read keyspace shards for %v", matchedKeyspace) 107 } 108 for _, s := range shardNames { 109 matched, err := path.Match(shard, s) 110 if err != nil { 111 return nil, vterrors.Wrapf(err, "invalid pattern %v", shard) 112 } 113 if matched { 114 result = append(result, KeyspaceShard{matchedKeyspace, s}) 115 } 116 } 117 } else { 118 // if the shard name contains a '-', we assume it's the 119 // name for a ranged based shard, so we lower case it. 120 if strings.Contains(shard, "-") { 121 shard = strings.ToLower(shard) 122 } 123 if keyspaceHasWildcards { 124 // keyspace was a wildcard, shard is not, just try it 125 _, err := ts.GetShard(ctx, matchedKeyspace, shard) 126 switch { 127 case err == nil: 128 // shard exists, add it 129 result = append(result, KeyspaceShard{matchedKeyspace, shard}) 130 case IsErrType(err, NoNode): 131 // no shard, ignore 132 default: 133 // other error 134 return nil, vterrors.Wrapf(err, "cannot read shard %v/%v", matchedKeyspace, shard) 135 } 136 } else { 137 // keyspace and shards are not wildcards, just add the value 138 result = append(result, KeyspaceShard{matchedKeyspace, shard}) 139 } 140 } 141 } 142 return result, nil 143 } 144 145 // ResolveWildcards resolves paths like: 146 // /keyspaces/*/Keyspace 147 // into real existing paths 148 // 149 // If you send paths that don't contain any wildcard and 150 // don't exist, this function will return an empty array. 151 func (ts *Server) ResolveWildcards(ctx context.Context, cell string, paths []string) ([]string, error) { 152 results := make([][]string, len(paths)) 153 wg := &sync.WaitGroup{} 154 mu := &sync.Mutex{} 155 var firstError error 156 157 for i, p := range paths { 158 wg.Add(1) 159 parts := strings.Split(p, "/") 160 go func(i int) { 161 defer wg.Done() 162 subResult, err := ts.resolveRecursive(ctx, cell, parts, true) 163 if err != nil { 164 mu.Lock() 165 if firstError != nil { 166 log.Infof("Multiple error: %v", err) 167 } else { 168 firstError = err 169 } 170 mu.Unlock() 171 } else { 172 results[i] = subResult 173 } 174 }(i) 175 } 176 177 wg.Wait() 178 if firstError != nil { 179 return nil, firstError 180 } 181 182 result := make([]string, 0, 32) 183 for i := 0; i < len(paths); i++ { 184 subResult := results[i] 185 if subResult != nil { 186 result = append(result, subResult...) 187 } 188 } 189 190 return result, nil 191 } 192 193 func (ts *Server) resolveRecursive(ctx context.Context, cell string, parts []string, toplevel bool) ([]string, error) { 194 conn, err := ts.ConnForCell(ctx, cell) 195 if err != nil { 196 return nil, err 197 } 198 199 for i, part := range parts { 200 if fileutil.HasWildcard(part) { 201 var children []DirEntry 202 var err error 203 parentPath := strings.Join(parts[:i], "/") 204 children, err = conn.ListDir(ctx, parentPath, false /*full*/) 205 if err != nil { 206 // we asked for something like 207 // /keyspaces/aaa/* and 208 // /keyspaces/aaa doesn't exist 209 // -> return empty list, no error 210 if IsErrType(err, NoNode) { 211 return nil, nil 212 } 213 // otherwise we return the error 214 return nil, err 215 } 216 217 results := make([][]string, len(children)) 218 wg := &sync.WaitGroup{} 219 mu := &sync.Mutex{} 220 var firstError error 221 222 for j, child := range children { 223 matched, err := path.Match(part, child.Name) 224 if err != nil { 225 return nil, err 226 } 227 if matched { 228 // we have a match! 229 wg.Add(1) 230 newParts := make([]string, len(parts)) 231 copy(newParts, parts) 232 newParts[i] = child.Name 233 go func(j int) { 234 defer wg.Done() 235 subResult, err := ts.resolveRecursive(ctx, cell, newParts, false) 236 if err != nil { 237 mu.Lock() 238 if firstError != nil { 239 log.Infof("Multiple error: %v", err) 240 } else { 241 firstError = err 242 } 243 mu.Unlock() 244 } else { 245 results[j] = subResult 246 } 247 }(j) 248 } 249 } 250 251 wg.Wait() 252 if firstError != nil { 253 return nil, firstError 254 } 255 256 result := make([]string, 0, 32) 257 for j := 0; j < len(children); j++ { 258 subResult := results[j] 259 if subResult != nil { 260 result = append(result, subResult...) 261 } 262 } 263 264 // we found a part that is a wildcard, we 265 // added the children already, we're done 266 return result, nil 267 } 268 } 269 270 // no part contains a wildcard, add the path if it exists, and done 271 p := strings.Join(parts, "/") 272 if toplevel { 273 // for whatever the user typed at the toplevel, we don't 274 // check it exists or not, we just return it 275 return []string{p}, nil 276 } 277 278 // This is an expanded path, we need to check if it exists. 279 if _, err = conn.ListDir(ctx, p, false /*full*/); err == nil { 280 // The path exists as a directory, return it. 281 return []string{p}, nil 282 } 283 _, _, err = conn.Get(ctx, p) 284 if err == nil { 285 // The path exists as a file, return it. 286 return []string{p}, nil 287 } else if IsErrType(err, NoNode) { 288 // The path doesn't exist, don't return anything. 289 return nil, nil 290 } 291 return nil, err 292 }