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 }