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 }