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