github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/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  	statusInfo, err := u.AgentStatus()
   142  	if err != nil {
   143  		return false, false, err
   144  	}
   145  	return matchAgentStatus(patterns, statusInfo.Status)
   146  }
   147  
   148  func unitMatchWorkloadStatus(u *state.Unit, patterns []string) (bool, bool, error) {
   149  	workloadStatusInfo, err := u.Status()
   150  	if err != nil {
   151  		return false, false, err
   152  	}
   153  	agentStatusInfo, err := u.AgentStatus()
   154  	if err != nil {
   155  		return false, false, err
   156  	}
   157  	return matchWorkloadStatus(patterns, workloadStatusInfo.Status, agentStatusInfo.Status)
   158  }
   159  
   160  func unitMatchExposure(u *state.Unit, patterns []string) (bool, bool, error) {
   161  	s, err := u.Service()
   162  	if err != nil {
   163  		return false, false, err
   164  	}
   165  	return matchExposure(patterns, s)
   166  }
   167  
   168  func unitMatchSubnet(u *state.Unit, patterns []string) (bool, bool, error) {
   169  	pub, pubOK := u.PublicAddress()
   170  	priv, privOK := u.PrivateAddress()
   171  	if !pubOK && !privOK {
   172  		return true, false, nil
   173  	}
   174  	return matchSubnet(patterns, pub, priv)
   175  }
   176  
   177  func unitMatchPort(u *state.Unit, patterns []string) (bool, bool, error) {
   178  	portRanges, err := u.OpenedPorts()
   179  	if err != nil {
   180  		return false, false, err
   181  	}
   182  	return matchPortRanges(patterns, portRanges...)
   183  }
   184  
   185  func buildServiceMatcherShims(s *state.Service, patterns ...string) (shims []closurePredicate, _ error) {
   186  	// Match on name.
   187  	shims = append(shims, func() (bool, bool, error) {
   188  		for _, p := range patterns {
   189  			if strings.ToLower(s.Name()) == strings.ToLower(p) {
   190  				return true, true, nil
   191  			}
   192  		}
   193  		return false, false, nil
   194  	})
   195  
   196  	// Match on exposure.
   197  	shims = append(shims, func() (bool, bool, error) { return matchExposure(patterns, s) })
   198  
   199  	// Match on network addresses.
   200  	networks, err := s.Networks()
   201  	if err != nil {
   202  		return nil, err
   203  	}
   204  	shims = append(shims, func() (bool, bool, error) { return matchSubnet(patterns, networks...) })
   205  
   206  	// If the service has an unit instance that matches any of the
   207  	// given criteria, consider the service a match as well.
   208  	unitShims, err := buildShimsForUnit(s.AllUnits, patterns...)
   209  	if err != nil {
   210  		return nil, err
   211  	}
   212  	shims = append(shims, unitShims...)
   213  
   214  	// Units may be able to match the pattern. Ultimately defer to
   215  	// that logic, and guard against breaking the predicate-chain.
   216  	if len(unitShims) <= 0 {
   217  		shims = append(shims, func() (bool, bool, error) { return false, true, nil })
   218  	}
   219  
   220  	return shims, nil
   221  }
   222  
   223  func buildShimsForUnit(unitsFn func() ([]*state.Unit, error), patterns ...string) (shims []closurePredicate, _ error) {
   224  	units, err := unitsFn()
   225  	if err != nil {
   226  		return nil, err
   227  	}
   228  	for _, u := range units {
   229  		shims = append(shims, buildUnitMatcherShims(u, patterns)...)
   230  	}
   231  	return shims, nil
   232  }
   233  
   234  func buildMachineMatcherShims(m *state.Machine, patterns []string) (shims []closurePredicate, _ error) {
   235  	// Look at machine status.
   236  	statusInfo, err := m.Status()
   237  	if err != nil {
   238  		return nil, err
   239  	}
   240  	shims = append(shims, func() (bool, bool, error) { return matchAgentStatus(patterns, statusInfo.Status) })
   241  
   242  	// Look at machine addresses. WARNING: Avoid the temptation to
   243  	// bring the append into the loop. The value we would close over
   244  	// will continue to change after the closure is created, and we'd
   245  	// only examine the last element of the loop for all closures.
   246  	var addrs []string
   247  	for _, a := range m.Addresses() {
   248  		addrs = append(addrs, a.Value)
   249  	}
   250  	shims = append(shims, func() (bool, bool, error) { return matchSubnet(patterns, addrs...) })
   251  
   252  	// If the machine hosts a unit that matches any of the given
   253  	// criteria, consider the machine a match as well.
   254  	unitShims, err := buildShimsForUnit(m.Units, patterns...)
   255  	if err != nil {
   256  		return nil, err
   257  	}
   258  	shims = append(shims, unitShims...)
   259  
   260  	// Units may be able to match the pattern. Ultimately defer to
   261  	// that logic, and guard against breaking the predicate-chain.
   262  	if len(unitShims) <= 0 {
   263  		shims = append(shims, func() (bool, bool, error) { return false, true, nil })
   264  	}
   265  
   266  	return
   267  }
   268  
   269  func buildUnitMatcherShims(u *state.Unit, patterns []string) []closurePredicate {
   270  	closeOver := func(f func(*state.Unit, []string) (bool, bool, error)) closurePredicate {
   271  		return func() (bool, bool, error) { return f(u, patterns) }
   272  	}
   273  	return []closurePredicate{
   274  		closeOver(unitMatchUnitName),
   275  		closeOver(unitMatchAgentStatus),
   276  		closeOver(unitMatchWorkloadStatus),
   277  		closeOver(unitMatchExposure),
   278  		closeOver(unitMatchSubnet),
   279  		closeOver(unitMatchPort),
   280  	}
   281  }
   282  
   283  func matchPortRanges(patterns []string, portRanges ...network.PortRange) (bool, bool, error) {
   284  	for _, p := range portRanges {
   285  		for _, patt := range patterns {
   286  			if strings.HasPrefix(p.String(), patt) {
   287  				return true, true, nil
   288  			}
   289  		}
   290  	}
   291  	return false, true, nil
   292  }
   293  
   294  func matchSubnet(patterns []string, addresses ...string) (bool, bool, error) {
   295  	oneValidPattern := false
   296  	for _, p := range patterns {
   297  		for _, a := range addresses {
   298  			ip, err := net.ResolveIPAddr("ip", a)
   299  			if err != nil {
   300  				errors.Trace(errors.Annotate(err, "could not parse machine's address"))
   301  				continue
   302  			} else if pip, err := net.ResolveIPAddr("ip", p); err == nil {
   303  				oneValidPattern = true
   304  				if ip.IP.Equal(pip.IP) {
   305  					return true, true, nil
   306  				}
   307  			} else if pip := net.ParseIP(p); pip != nil {
   308  				oneValidPattern = true
   309  				if ip.IP.Equal(pip) {
   310  					return true, true, nil
   311  				}
   312  			} else if _, ipNet, err := net.ParseCIDR(p); err == nil {
   313  				oneValidPattern = true
   314  				if ipNet.Contains(ip.IP) {
   315  					return true, true, nil
   316  				}
   317  			}
   318  		}
   319  	}
   320  	return false, oneValidPattern, nil
   321  }
   322  
   323  func matchExposure(patterns []string, s *state.Service) (bool, bool, error) {
   324  	if len(patterns) >= 1 && patterns[0] == "exposed" {
   325  		return s.IsExposed(), true, nil
   326  	} else if len(patterns) >= 2 && patterns[0] == "not" && patterns[1] == "exposed" {
   327  		return !s.IsExposed(), true, nil
   328  	}
   329  	return false, false, nil
   330  }
   331  
   332  func matchWorkloadStatus(patterns []string, workloadStatus state.Status, agentStatus state.Status) (bool, bool, error) {
   333  	oneValidStatus := false
   334  	for _, p := range patterns {
   335  		// If the pattern isn't a known status, ignore it.
   336  		ps := state.Status(p)
   337  		if !ps.KnownWorkloadStatus() {
   338  			continue
   339  		}
   340  
   341  		oneValidStatus = true
   342  		// To preserve current expected behaviour, we only report on workload status
   343  		// if the agent itself is not in error.
   344  		if agentStatus != state.StatusError && workloadStatus.WorkloadMatches(ps) {
   345  			return true, true, nil
   346  		}
   347  	}
   348  	return false, oneValidStatus, nil
   349  }
   350  
   351  func matchAgentStatus(patterns []string, status state.Status) (bool, bool, error) {
   352  	oneValidStatus := false
   353  	for _, p := range patterns {
   354  		// If the pattern isn't a known status, ignore it.
   355  		ps := state.Status(p)
   356  		if !ps.KnownAgentStatus() {
   357  			continue
   358  		}
   359  
   360  		oneValidStatus = true
   361  		if status.Matches(ps) {
   362  			return true, true, nil
   363  		}
   364  	}
   365  	return false, oneValidStatus, nil
   366  }
   367  
   368  type unitMatcher struct {
   369  	patterns []string
   370  }
   371  
   372  // matchesAny returns true if the unitMatcher will
   373  // match any unit, regardless of its attributes.
   374  func (m unitMatcher) matchesAny() bool {
   375  	return len(m.patterns) == 0
   376  }
   377  
   378  // matchUnit attempts to match a state.Unit to one of
   379  // a set of patterns, taking into account subordinate
   380  // relationships.
   381  func (m unitMatcher) matchUnit(u *state.Unit) bool {
   382  	if m.matchesAny() {
   383  		return true
   384  	}
   385  
   386  	// Keep the unit if:
   387  	//  (a) its name matches a pattern, or
   388  	//  (b) it's a principal and one of its subordinates matches, or
   389  	//  (c) it's a subordinate and its principal matches.
   390  	//
   391  	// Note: do *not* include a second subordinate if the principal is
   392  	// only matched on account of a first subordinate matching.
   393  	if m.matchString(u.Name()) {
   394  		return true
   395  	}
   396  	if u.IsPrincipal() {
   397  		for _, s := range u.SubordinateNames() {
   398  			if m.matchString(s) {
   399  				return true
   400  			}
   401  		}
   402  		return false
   403  	}
   404  	principal, valid := u.PrincipalName()
   405  	if !valid {
   406  		panic("PrincipalName failed for subordinate unit")
   407  	}
   408  	return m.matchString(principal)
   409  }
   410  
   411  // matchString matches a string to one of the patterns in
   412  // the unit matcher, returning an error if a pattern with
   413  // invalid syntax is encountered.
   414  func (m unitMatcher) matchString(s string) bool {
   415  	for _, pattern := range m.patterns {
   416  		ok, err := path.Match(pattern, s)
   417  		if err != nil {
   418  			// We validate patterns, so should never get here.
   419  			panic(fmt.Errorf("pattern syntax error in %q", pattern))
   420  		} else if ok {
   421  			return true
   422  		}
   423  	}
   424  	return false
   425  }
   426  
   427  // validPattern must match the parts of a unit or service name
   428  // pattern either side of the '/' for it to be valid.
   429  var validPattern = regexp.MustCompile("^[a-z0-9-*]+$")
   430  
   431  // NewUnitMatcher returns a unitMatcher that matches units
   432  // with one of the specified patterns, or all units if no
   433  // patterns are specified.
   434  //
   435  // An error will be returned if any of the specified patterns
   436  // is invalid. Patterns are valid if they contain only
   437  // alpha-numeric characters, hyphens, or asterisks (and one
   438  // optional '/' to separate service/unit).
   439  func NewUnitMatcher(patterns []string) (unitMatcher, error) {
   440  	pattCopy := make([]string, len(patterns))
   441  	for i, pattern := range patterns {
   442  		pattCopy[i] = patterns[i]
   443  		fields := strings.Split(pattern, "/")
   444  		if len(fields) > 2 {
   445  			return unitMatcher{}, fmt.Errorf("pattern %q contains too many '/' characters", pattern)
   446  		}
   447  		for _, f := range fields {
   448  			if !validPattern.MatchString(f) {
   449  				return unitMatcher{}, fmt.Errorf("pattern %q contains invalid characters", pattern)
   450  			}
   451  		}
   452  		if len(fields) == 1 {
   453  			pattCopy[i] += "/*"
   454  		}
   455  	}
   456  	return unitMatcher{pattCopy}, nil
   457  }