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  }