github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/apiserver/client/filtering.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package client
     5  
     6  import (
     7  	"fmt"
     8  	"net"
     9  	"path"
    10  	"regexp"
    11  	"strings"
    12  
    13  	"github.com/juju/errors"
    14  
    15  	"github.com/juju/juju/network"
    16  	"github.com/juju/juju/state"
    17  )
    18  
    19  var InvalidFormatErr = errors.Errorf("the given filter did not match any known patterns.")
    20  
    21  // UnitChainPredicateFn builds a function which runs the given
    22  // predicate over a unit and all of its subordinates. If one unit in
    23  // the chain matches, the entire chain matches.
    24  func UnitChainPredicateFn(
    25  	predicate Predicate,
    26  	getUnit func(string) *state.Unit,
    27  ) func(*state.Unit) (bool, error) {
    28  	considered := make(map[string]bool)
    29  	var f func(unit *state.Unit) (bool, error)
    30  	f = func(unit *state.Unit) (bool, error) {
    31  		// Don't try and filter the same unit 2x.
    32  		if matches, ok := considered[unit.Name()]; ok {
    33  			logger.Debugf("%s has already been examined and found to be: %t", unit.Name(), matches)
    34  			return matches, nil
    35  		}
    36  
    37  		// Check the current unit.
    38  		matches, err := predicate(unit)
    39  		if err != nil {
    40  			return false, errors.Annotate(err, "could not filter units")
    41  		}
    42  		considered[unit.Name()] = matches
    43  
    44  		// Now check all of this unit's subordinates.
    45  		for _, subName := range unit.SubordinateNames() {
    46  			// A master match supercedes any subordinate match.
    47  			if matches {
    48  				logger.Infof("%s is a subordinate to a match.", subName)
    49  				considered[subName] = true
    50  				continue
    51  			}
    52  
    53  			subUnit := getUnit(subName)
    54  			if subUnit == nil {
    55  				// We have already deleted this unit
    56  				matches = false
    57  				continue
    58  			}
    59  			matches, err = f(subUnit)
    60  			if err != nil {
    61  				return false, err
    62  			}
    63  			considered[subName] = matches
    64  		}
    65  
    66  		return matches, nil
    67  	}
    68  	return f
    69  }
    70  
    71  // BuildPredicate returns a Predicate which will evaluate a machine,
    72  // service, or unit against the given patterns.
    73  func BuildPredicateFor(patterns []string) Predicate {
    74  
    75  	or := func(predicates ...closurePredicate) (bool, error) {
    76  		// Differentiate between a valid format that elimintated all
    77  		// elements, and an invalid query.
    78  		oneValidFmt := false
    79  		for _, p := range predicates {
    80  			if matches, ok, err := p(); err != nil {
    81  				return false, err
    82  			} else if ok {
    83  				oneValidFmt = true
    84  				if matches {
    85  					return true, nil
    86  				}
    87  			}
    88  		}
    89  
    90  		if !oneValidFmt && len(predicates) > 0 {
    91  			return false, InvalidFormatErr
    92  		}
    93  
    94  		return false, nil
    95  	}
    96  
    97  	return func(i interface{}) (bool, error) {
    98  		switch i.(type) {
    99  		default:
   100  			panic(errors.Errorf("Programming error. We should only ever pass in machines, services, or units. Received %T.", i))
   101  		case *state.Machine:
   102  			shims, err := buildMachineMatcherShims(i.(*state.Machine), patterns)
   103  			if err != nil {
   104  				return false, err
   105  			}
   106  			return or(shims...)
   107  		case *state.Unit:
   108  			return or(buildUnitMatcherShims(i.(*state.Unit), patterns)...)
   109  		case *state.Service:
   110  			shims, err := buildServiceMatcherShims(i.(*state.Service), patterns...)
   111  			if err != nil {
   112  				return false, err
   113  			}
   114  			return or(shims...)
   115  		}
   116  	}
   117  }
   118  
   119  // Predicate is a function that when given a unit, machine, or
   120  // service, will determine whether the unit meets some criteria.
   121  type Predicate func(interface{}) (matches bool, _ error)
   122  
   123  // closurePredicate is a function which has at some point been closed
   124  // around an element so that it can examine whether this element
   125  // matches some criteria.
   126  type closurePredicate func() (matches bool, formatOK bool, _ error)
   127  
   128  func unitMatchUnitName(u *state.Unit, patterns []string) (bool, bool, error) {
   129  	um, err := NewUnitMatcher(patterns)
   130  	if err != nil {
   131  		// Currently, the only error possible here is a matching
   132  		// error. We don't want this error to hold up further
   133  		// matching.
   134  		logger.Debugf("ignoring matching error: %v", err)
   135  		return false, false, nil
   136  	}
   137  	return um.matchUnit(u), true, nil
   138  }
   139  
   140  func unitMatchAgentStatus(u *state.Unit, patterns []string) (bool, bool, error) {
   141  	status, _, _, err := u.Status()
   142  	if err != nil {
   143  		return false, false, err
   144  	}
   145  	return matchAgentStatus(patterns, status)
   146  }
   147  
   148  func unitMatchExposure(u *state.Unit, patterns []string) (bool, bool, error) {
   149  	s, err := u.Service()
   150  	if err != nil {
   151  		return false, false, err
   152  	}
   153  	return matchExposure(patterns, s)
   154  }
   155  
   156  func unitMatchSubnet(u *state.Unit, patterns []string) (bool, bool, error) {
   157  	pub, pubOK := u.PublicAddress()
   158  	priv, privOK := u.PrivateAddress()
   159  	if !pubOK && !privOK {
   160  		return true, false, nil
   161  	}
   162  	return matchSubnet(patterns, pub, priv)
   163  }
   164  
   165  func unitMatchPort(u *state.Unit, patterns []string) (bool, bool, error) {
   166  	portRanges, err := u.OpenedPorts()
   167  	if err != nil {
   168  		return false, false, err
   169  	}
   170  	return matchPortRanges(patterns, portRanges...)
   171  }
   172  
   173  func buildServiceMatcherShims(s *state.Service, patterns ...string) (shims []closurePredicate, _ error) {
   174  	// Match on name.
   175  	shims = append(shims, func() (bool, bool, error) {
   176  		for _, p := range patterns {
   177  			if strings.ToLower(s.Name()) == strings.ToLower(p) {
   178  				return true, true, nil
   179  			}
   180  		}
   181  		return false, false, nil
   182  	})
   183  
   184  	// Match on exposure.
   185  	shims = append(shims, func() (bool, bool, error) { return matchExposure(patterns, s) })
   186  
   187  	// Match on network addresses.
   188  	networks, err := s.Networks()
   189  	if err != nil {
   190  		return nil, err
   191  	}
   192  	shims = append(shims, func() (bool, bool, error) { return matchSubnet(patterns, networks...) })
   193  
   194  	// If the service has an unit instance that matches any of the
   195  	// given criteria, consider the service a match as well.
   196  	unitShims, err := buildShimsForUnit(s.AllUnits, patterns...)
   197  	if err != nil {
   198  		return nil, err
   199  	}
   200  	shims = append(shims, unitShims...)
   201  
   202  	// Units may be able to match the pattern. Ultimately defer to
   203  	// that logic, and guard against breaking the predicate-chain.
   204  	if len(unitShims) <= 0 {
   205  		shims = append(shims, func() (bool, bool, error) { return false, true, nil })
   206  	}
   207  
   208  	return shims, nil
   209  }
   210  
   211  func buildShimsForUnit(unitsFn func() ([]*state.Unit, error), patterns ...string) (shims []closurePredicate, _ error) {
   212  	units, err := unitsFn()
   213  	if err != nil {
   214  		return nil, err
   215  	}
   216  	for _, u := range units {
   217  		shims = append(shims, buildUnitMatcherShims(u, patterns)...)
   218  	}
   219  	return shims, nil
   220  }
   221  
   222  func buildMachineMatcherShims(m *state.Machine, patterns []string) (shims []closurePredicate, _ error) {
   223  	// Look at machine status.
   224  	status, _, _, err := m.Status()
   225  	if err != nil {
   226  		return nil, err
   227  	}
   228  	shims = append(shims, func() (bool, bool, error) { return matchAgentStatus(patterns, status) })
   229  
   230  	// Look at machine addresses. WARNING: Avoid the temptation to
   231  	// bring the append into the loop. The value we would close over
   232  	// will continue to change after the closure is created, and we'd
   233  	// only examine the last element of the loop for all closures.
   234  	var addrs []string
   235  	for _, a := range m.Addresses() {
   236  		addrs = append(addrs, a.Value)
   237  	}
   238  	shims = append(shims, func() (bool, bool, error) { return matchSubnet(patterns, addrs...) })
   239  
   240  	// If the machine hosts a unit that matches any of the given
   241  	// criteria, consider the machine a match as well.
   242  	unitShims, err := buildShimsForUnit(m.Units, patterns...)
   243  	if err != nil {
   244  		return nil, err
   245  	}
   246  	shims = append(shims, unitShims...)
   247  
   248  	// Units may be able to match the pattern. Ultimately defer to
   249  	// that logic, and guard against breaking the predicate-chain.
   250  	if len(unitShims) <= 0 {
   251  		shims = append(shims, func() (bool, bool, error) { return false, true, nil })
   252  	}
   253  
   254  	return
   255  }
   256  
   257  func buildUnitMatcherShims(u *state.Unit, patterns []string) []closurePredicate {
   258  	closeOver := func(f func(*state.Unit, []string) (bool, bool, error)) closurePredicate {
   259  		return func() (bool, bool, error) { return f(u, patterns) }
   260  	}
   261  	return []closurePredicate{
   262  		closeOver(unitMatchUnitName),
   263  		closeOver(unitMatchAgentStatus),
   264  		closeOver(unitMatchExposure),
   265  		closeOver(unitMatchSubnet),
   266  		closeOver(unitMatchPort),
   267  	}
   268  }
   269  
   270  func matchPortRanges(patterns []string, portRanges ...network.PortRange) (bool, bool, error) {
   271  	for _, p := range portRanges {
   272  		for _, patt := range patterns {
   273  			if strings.HasPrefix(p.String(), patt) {
   274  				return true, true, nil
   275  			}
   276  		}
   277  	}
   278  	return false, true, nil
   279  }
   280  
   281  func matchSubnet(patterns []string, addresses ...string) (bool, bool, error) {
   282  	oneValidPattern := false
   283  	for _, p := range patterns {
   284  		for _, a := range addresses {
   285  			ip, err := net.ResolveIPAddr("ip", a)
   286  			if err != nil {
   287  				errors.Trace(errors.Annotate(err, "could not parse machine's address"))
   288  				continue
   289  			} else if pip, err := net.ResolveIPAddr("ip", p); err == nil {
   290  				oneValidPattern = true
   291  				if ip.IP.Equal(pip.IP) {
   292  					return true, true, nil
   293  				}
   294  			} else if pip := net.ParseIP(p); pip != nil {
   295  				oneValidPattern = true
   296  				if ip.IP.Equal(pip) {
   297  					return true, true, nil
   298  				}
   299  			} else if _, ipNet, err := net.ParseCIDR(p); err == nil {
   300  				oneValidPattern = true
   301  				if ipNet.Contains(ip.IP) {
   302  					return true, true, nil
   303  				}
   304  			}
   305  		}
   306  	}
   307  	return false, oneValidPattern, nil
   308  }
   309  
   310  func matchExposure(patterns []string, s *state.Service) (bool, bool, error) {
   311  	if len(patterns) >= 1 && patterns[0] == "exposed" {
   312  		return s.IsExposed(), true, nil
   313  	} else if len(patterns) >= 2 && patterns[0] == "not" && patterns[1] == "exposed" {
   314  		return !s.IsExposed(), true, nil
   315  	}
   316  	return false, false, nil
   317  }
   318  
   319  func matchAgentStatus(patterns []string, status state.Status) (bool, bool, error) {
   320  	oneValidStatus := false
   321  	for _, p := range patterns {
   322  		// If the pattern isn't a valid status, ignore it.
   323  		ps := state.Status(p)
   324  		if !ps.ValidAgentStatus() {
   325  			continue
   326  		}
   327  
   328  		oneValidStatus = true
   329  		if status.Matches(ps) {
   330  			return true, true, nil
   331  		}
   332  	}
   333  	return false, oneValidStatus, nil
   334  }
   335  
   336  type unitMatcher struct {
   337  	patterns []string
   338  }
   339  
   340  // matchesAny returns true if the unitMatcher will
   341  // match any unit, regardless of its attributes.
   342  func (m unitMatcher) matchesAny() bool {
   343  	return len(m.patterns) == 0
   344  }
   345  
   346  // matchUnit attempts to match a state.Unit to one of
   347  // a set of patterns, taking into account subordinate
   348  // relationships.
   349  func (m unitMatcher) matchUnit(u *state.Unit) bool {
   350  	if m.matchesAny() {
   351  		return true
   352  	}
   353  
   354  	// Keep the unit if:
   355  	//  (a) its name matches a pattern, or
   356  	//  (b) it's a principal and one of its subordinates matches, or
   357  	//  (c) it's a subordinate and its principal matches.
   358  	//
   359  	// Note: do *not* include a second subordinate if the principal is
   360  	// only matched on account of a first subordinate matching.
   361  	if m.matchString(u.Name()) {
   362  		return true
   363  	}
   364  	if u.IsPrincipal() {
   365  		for _, s := range u.SubordinateNames() {
   366  			if m.matchString(s) {
   367  				return true
   368  			}
   369  		}
   370  		return false
   371  	}
   372  	principal, valid := u.PrincipalName()
   373  	if !valid {
   374  		panic("PrincipalName failed for subordinate unit")
   375  	}
   376  	return m.matchString(principal)
   377  }
   378  
   379  // matchString matches a string to one of the patterns in
   380  // the unit matcher, returning an error if a pattern with
   381  // invalid syntax is encountered.
   382  func (m unitMatcher) matchString(s string) bool {
   383  	for _, pattern := range m.patterns {
   384  		ok, err := path.Match(pattern, s)
   385  		if err != nil {
   386  			// We validate patterns, so should never get here.
   387  			panic(fmt.Errorf("pattern syntax error in %q", pattern))
   388  		} else if ok {
   389  			return true
   390  		}
   391  	}
   392  	return false
   393  }
   394  
   395  // validPattern must match the parts of a unit or service name
   396  // pattern either side of the '/' for it to be valid.
   397  var validPattern = regexp.MustCompile("^[a-z0-9-*]+$")
   398  
   399  // NewUnitMatcher returns a unitMatcher that matches units
   400  // with one of the specified patterns, or all units if no
   401  // patterns are specified.
   402  //
   403  // An error will be returned if any of the specified patterns
   404  // is invalid. Patterns are valid if they contain only
   405  // alpha-numeric characters, hyphens, or asterisks (and one
   406  // optional '/' to separate service/unit).
   407  func NewUnitMatcher(patterns []string) (unitMatcher, error) {
   408  	pattCopy := make([]string, len(patterns))
   409  	for i, pattern := range patterns {
   410  		pattCopy[i] = patterns[i]
   411  		fields := strings.Split(pattern, "/")
   412  		if len(fields) > 2 {
   413  			return unitMatcher{}, fmt.Errorf("pattern %q contains too many '/' characters", pattern)
   414  		}
   415  		for _, f := range fields {
   416  			if !validPattern.MatchString(f) {
   417  				return unitMatcher{}, fmt.Errorf("pattern %q contains invalid characters", pattern)
   418  			}
   419  		}
   420  		if len(fields) == 1 {
   421  			pattCopy[i] += "/*"
   422  		}
   423  	}
   424  	return unitMatcher{pattCopy}, nil
   425  }