github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/apiserver/common/networkingcommon/subnets.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package networkingcommon 5 6 import ( 7 "fmt" 8 "net" 9 "strings" 10 11 "github.com/juju/errors" 12 "github.com/juju/loggo" 13 "github.com/juju/utils/set" 14 "gopkg.in/juju/names.v2" 15 16 "github.com/juju/juju/apiserver/common" 17 "github.com/juju/juju/apiserver/params" 18 "github.com/juju/juju/environs" 19 "github.com/juju/juju/instance" 20 "github.com/juju/juju/network" 21 providercommon "github.com/juju/juju/provider/common" 22 ) 23 24 var logger = loggo.GetLogger("juju.apiserver.common.networkingcommon") 25 26 // addSubnetsCache holds cached lists of spaces, zones, and subnets, used for 27 // fast lookups while adding subnets. 28 type addSubnetsCache struct { 29 api NetworkBacking 30 allSpaces set.Strings // all defined backing spaces 31 allZones set.Strings // all known provider zones 32 availableZones set.Strings // all the available zones 33 allSubnets []network.SubnetInfo // all (valid) provider subnets 34 // providerIdsByCIDR maps possibly duplicated CIDRs to one or more ids. 35 providerIdsByCIDR map[string]set.Strings 36 // subnetsByProviderId maps unique subnet ProviderIds to pointers 37 // to entries in allSubnets. 38 subnetsByProviderId map[string]*network.SubnetInfo 39 } 40 41 func NewAddSubnetsCache(api NetworkBacking) *addSubnetsCache { 42 // Empty cache initially. 43 return &addSubnetsCache{ 44 api: api, 45 allSpaces: nil, 46 allZones: nil, 47 availableZones: nil, 48 allSubnets: nil, 49 providerIdsByCIDR: nil, 50 subnetsByProviderId: nil, 51 } 52 } 53 54 // validateSpace parses the given spaceTag and verifies it exists by looking it 55 // up in the cache (or populates the cache if empty). 56 func (cache *addSubnetsCache) validateSpace(spaceTag string) (*names.SpaceTag, error) { 57 if spaceTag == "" { 58 return nil, errors.Errorf("SpaceTag is required") 59 } 60 tag, err := names.ParseSpaceTag(spaceTag) 61 if err != nil { 62 return nil, errors.Annotate(err, "given SpaceTag is invalid") 63 } 64 65 // Otherwise we need the cache to validate. 66 if cache.allSpaces == nil { 67 // Not yet cached. 68 logger.Tracef("caching known spaces") 69 70 allSpaces, err := cache.api.AllSpaces() 71 if err != nil { 72 return nil, errors.Annotate(err, "cannot validate given SpaceTag") 73 } 74 cache.allSpaces = set.NewStrings() 75 for _, space := range allSpaces { 76 if cache.allSpaces.Contains(space.Name()) { 77 logger.Warningf("ignoring duplicated space %q", space.Name()) 78 continue 79 } 80 cache.allSpaces.Add(space.Name()) 81 } 82 } 83 if cache.allSpaces.IsEmpty() { 84 return nil, errors.Errorf("no spaces defined") 85 } 86 logger.Tracef("using cached spaces: %v", cache.allSpaces.SortedValues()) 87 88 if !cache.allSpaces.Contains(tag.Id()) { 89 return nil, errors.NotFoundf("space %q", tag.Id()) // " not found" 90 } 91 return &tag, nil 92 } 93 94 // cacheZones populates the allZones and availableZones cache, if it's 95 // empty. 96 func (cache *addSubnetsCache) cacheZones() error { 97 if cache.allZones != nil { 98 // Already cached. 99 logger.Tracef("using cached zones: %v", cache.allZones.SortedValues()) 100 return nil 101 } 102 103 allZones, err := AllZones(cache.api) 104 if err != nil { 105 return errors.Annotate(err, "given Zones cannot be validated") 106 } 107 cache.allZones = set.NewStrings() 108 cache.availableZones = set.NewStrings() 109 for _, zone := range allZones.Results { 110 // AllZones() does not use the Error result field, so no 111 // need to check it here. 112 if cache.allZones.Contains(zone.Name) { 113 logger.Warningf("ignoring duplicated zone %q", zone.Name) 114 continue 115 } 116 117 if zone.Available { 118 cache.availableZones.Add(zone.Name) 119 } 120 cache.allZones.Add(zone.Name) 121 } 122 logger.Tracef( 123 "%d known and %d available zones cached: %v", 124 cache.allZones.Size(), cache.availableZones.Size(), cache.allZones.SortedValues(), 125 ) 126 if cache.allZones.IsEmpty() { 127 cache.allZones = nil 128 // Cached an empty list. 129 return errors.Errorf("no zones defined") 130 } 131 return nil 132 } 133 134 // validateZones ensures givenZones are valid. When providerZones are also set, 135 // givenZones must be a subset of them or match exactly. With non-empty 136 // providerZones and empty givenZones, it returns the providerZones (i.e. trusts 137 // the provider to know better). When no providerZones and only givenZones are 138 // set, only then the cache is used to validate givenZones. 139 func (cache *addSubnetsCache) validateZones(providerZones, givenZones []string) ([]string, error) { 140 givenSet := set.NewStrings(givenZones...) 141 providerSet := set.NewStrings(providerZones...) 142 143 // First check if we can validate without using the cache. 144 switch { 145 case providerSet.IsEmpty() && givenSet.IsEmpty(): 146 return nil, errors.Errorf("Zones cannot be discovered from the provider and must be set") 147 case !providerSet.IsEmpty() && givenSet.IsEmpty(): 148 // Use provider zones when none given. 149 return providerSet.SortedValues(), nil 150 case !providerSet.IsEmpty() && !givenSet.IsEmpty(): 151 // Ensure givenZones either match providerZones or are a 152 // subset of them. 153 extraGiven := givenSet.Difference(providerSet) 154 if !extraGiven.IsEmpty() { 155 extra := `"` + strings.Join(extraGiven.SortedValues(), `", "`) + `"` 156 msg := fmt.Sprintf("Zones contain zones not allowed by the provider: %s", extra) 157 return nil, errors.Errorf(msg) 158 } 159 } 160 161 // Otherwise we need the cache to validate. 162 if err := cache.cacheZones(); err != nil { 163 return nil, errors.Trace(err) 164 } 165 166 diffAvailable := givenSet.Difference(cache.availableZones) 167 diffAll := givenSet.Difference(cache.allZones) 168 169 if !diffAll.IsEmpty() { 170 extra := `"` + strings.Join(diffAll.SortedValues(), `", "`) + `"` 171 return nil, errors.Errorf("Zones contain unknown zones: %s", extra) 172 } 173 if !diffAvailable.IsEmpty() { 174 extra := `"` + strings.Join(diffAvailable.SortedValues(), `", "`) + `"` 175 return nil, errors.Errorf("Zones contain unavailable zones: %s", extra) 176 } 177 // All good - given zones are a subset and none are 178 // unavailable. 179 return givenSet.SortedValues(), nil 180 } 181 182 // cacheSubnets tries to get and cache once all known provider subnets. It 183 // handles the case when subnets have duplicated CIDRs but distinct ProviderIds. 184 // It also handles weird edge cases, like no CIDR and/or ProviderId set for a 185 // subnet. 186 func (cache *addSubnetsCache) cacheSubnets() error { 187 if cache.allSubnets != nil { 188 // Already cached. 189 logger.Tracef("using %d cached subnets", len(cache.allSubnets)) 190 return nil 191 } 192 193 netEnv, err := networkingEnviron(cache.api) 194 if err != nil { 195 return errors.Trace(err) 196 } 197 subnetInfo, err := netEnv.Subnets(instance.UnknownId, nil) 198 if err != nil { 199 return errors.Annotate(err, "cannot get provider subnets") 200 } 201 logger.Tracef("got %d subnets to cache from the provider", len(subnetInfo)) 202 203 if len(subnetInfo) > 0 { 204 // Trying to avoid reallocations. 205 cache.allSubnets = make([]network.SubnetInfo, 0, len(subnetInfo)) 206 } 207 cache.providerIdsByCIDR = make(map[string]set.Strings) 208 cache.subnetsByProviderId = make(map[string]*network.SubnetInfo) 209 210 for i, _ := range subnetInfo { 211 subnet := subnetInfo[i] 212 cidr := subnet.CIDR 213 providerId := string(subnet.ProviderId) 214 logger.Tracef( 215 "caching subnet with CIDR %q, ProviderId %q, Zones: %q", 216 cidr, providerId, subnet.AvailabilityZones, 217 ) 218 219 if providerId == "" && cidr == "" { 220 logger.Warningf("found subnet with empty CIDR and ProviderId") 221 // But we still save it for lookups, which will probably fail anyway. 222 } else if providerId == "" { 223 logger.Warningf("found subnet with CIDR %q and empty ProviderId", cidr) 224 // But we still save it for lookups. 225 } else { 226 _, ok := cache.subnetsByProviderId[providerId] 227 if ok { 228 logger.Warningf( 229 "found subnet with CIDR %q and duplicated ProviderId %q", 230 cidr, providerId, 231 ) 232 // We just overwrite what's there for the same id. 233 // It's a weird case and it shouldn't happen with 234 // properly written providers, but anyway.. 235 } 236 } 237 cache.subnetsByProviderId[providerId] = &subnet 238 239 if ids, ok := cache.providerIdsByCIDR[cidr]; !ok { 240 cache.providerIdsByCIDR[cidr] = set.NewStrings(providerId) 241 } else { 242 ids.Add(providerId) 243 logger.Debugf( 244 "duplicated subnet CIDR %q; collected ProviderIds so far: %v", 245 cidr, ids.SortedValues(), 246 ) 247 cache.providerIdsByCIDR[cidr] = ids 248 } 249 250 cache.allSubnets = append(cache.allSubnets, subnet) 251 } 252 logger.Tracef("%d provider subnets cached", len(cache.allSubnets)) 253 if len(cache.allSubnets) == 0 { 254 // Cached an empty list. 255 return errors.Errorf("no subnets defined") 256 } 257 return nil 258 } 259 260 // validateSubnet ensures either subnetTag or providerId is valid (not both), 261 // then uses the cache to validate and lookup the provider SubnetInfo for the 262 // subnet, if found. 263 func (cache *addSubnetsCache) validateSubnet(subnetTag, providerId string) (*network.SubnetInfo, error) { 264 haveTag := subnetTag != "" 265 haveProviderId := providerId != "" 266 267 if !haveTag && !haveProviderId { 268 return nil, errors.Errorf("either SubnetTag or SubnetProviderId is required") 269 } else if haveTag && haveProviderId { 270 return nil, errors.Errorf("SubnetTag and SubnetProviderId cannot be both set") 271 } 272 var tag names.SubnetTag 273 if haveTag { 274 var err error 275 tag, err = names.ParseSubnetTag(subnetTag) 276 if err != nil { 277 return nil, errors.Annotate(err, "given SubnetTag is invalid") 278 } 279 } 280 281 // Otherwise we need the cache to validate. 282 if err := cache.cacheSubnets(); err != nil { 283 return nil, errors.Trace(err) 284 } 285 286 if haveTag { 287 providerIds, ok := cache.providerIdsByCIDR[tag.Id()] 288 if !ok || providerIds.IsEmpty() { 289 return nil, errors.NotFoundf("subnet with CIDR %q", tag.Id()) 290 } 291 if providerIds.Size() > 1 { 292 ids := `"` + strings.Join(providerIds.SortedValues(), `", "`) + `"` 293 return nil, errors.Errorf( 294 "multiple subnets with CIDR %q: retry using ProviderId from: %s", 295 tag.Id(), ids, 296 ) 297 } 298 // A single CIDR matched. 299 providerId = providerIds.Values()[0] 300 } 301 302 info, ok := cache.subnetsByProviderId[providerId] 303 if !ok || info == nil { 304 return nil, errors.NotFoundf( 305 "subnet with CIDR %q and ProviderId %q", 306 tag.Id(), providerId, 307 ) 308 } 309 // Do last-call validation. 310 if !names.IsValidSubnet(info.CIDR) { 311 _, ipnet, err := net.ParseCIDR(info.CIDR) 312 if err != nil && info.CIDR != "" { 313 // The underlying error is not important here, just that 314 // the CIDR is invalid. 315 return nil, errors.Errorf( 316 "subnet with CIDR %q and ProviderId %q: invalid CIDR", 317 info.CIDR, providerId, 318 ) 319 } 320 if info.CIDR == "" { 321 return nil, errors.Errorf( 322 "subnet with ProviderId %q: empty CIDR", providerId, 323 ) 324 } 325 return nil, errors.Errorf( 326 "subnet with ProviderId %q: incorrect CIDR format %q, expected %q", 327 providerId, info.CIDR, ipnet.String(), 328 ) 329 } 330 return info, nil 331 } 332 333 // addOneSubnet validates the given arguments, using cache for lookups 334 // (initialized on first use), then adds it to the backing store, if successful. 335 func addOneSubnet(api NetworkBacking, args params.AddSubnetParams, cache *addSubnetsCache) error { 336 subnetInfo, err := cache.validateSubnet(args.SubnetTag, args.SubnetProviderId) 337 if err != nil { 338 return errors.Trace(err) 339 } 340 spaceTag, err := cache.validateSpace(args.SpaceTag) 341 if err != nil { 342 return errors.Trace(err) 343 } 344 zones, err := cache.validateZones(subnetInfo.AvailabilityZones, args.Zones) 345 if err != nil { 346 return errors.Trace(err) 347 } 348 349 // Try adding the subnet. 350 backingInfo := BackingSubnetInfo{ 351 ProviderId: subnetInfo.ProviderId, 352 CIDR: subnetInfo.CIDR, 353 VLANTag: subnetInfo.VLANTag, 354 AvailabilityZones: zones, 355 SpaceName: spaceTag.Id(), 356 } 357 if _, err := api.AddSubnet(backingInfo); err != nil { 358 return errors.Trace(err) 359 } 360 return nil 361 } 362 363 // AddSubnets adds. 364 func AddSubnets(api NetworkBacking, args params.AddSubnetsParams) (params.ErrorResults, error) { 365 results := params.ErrorResults{ 366 Results: make([]params.ErrorResult, len(args.Subnets)), 367 } 368 369 if len(args.Subnets) == 0 { 370 return results, nil 371 } 372 373 cache := NewAddSubnetsCache(api) 374 for i, arg := range args.Subnets { 375 err := addOneSubnet(api, arg, cache) 376 if err != nil { 377 results.Results[i].Error = common.ServerError(err) 378 } 379 } 380 return results, nil 381 } 382 383 // ListSubnets lists all the available subnets or only those matching 384 // all given optional filters. 385 func ListSubnets(api NetworkBacking, args params.SubnetsFilters) (results params.ListSubnetsResults, err error) { 386 subnets, err := api.AllSubnets() 387 if err != nil { 388 return results, errors.Trace(err) 389 } 390 391 var spaceFilter string 392 if args.SpaceTag != "" { 393 tag, err := names.ParseSpaceTag(args.SpaceTag) 394 if err != nil { 395 return results, errors.Trace(err) 396 } 397 spaceFilter = tag.Id() 398 } 399 zoneFilter := args.Zone 400 401 for _, subnet := range subnets { 402 if spaceFilter != "" && subnet.SpaceName() != spaceFilter { 403 logger.Tracef( 404 "filtering subnet %q from space %q not matching filter %q", 405 subnet.CIDR(), subnet.SpaceName(), spaceFilter, 406 ) 407 continue 408 } 409 zoneSet := set.NewStrings(subnet.AvailabilityZones()...) 410 if zoneFilter != "" && !zoneSet.IsEmpty() && !zoneSet.Contains(zoneFilter) { 411 logger.Tracef( 412 "filtering subnet %q with zones %v not matching filter %q", 413 subnet.CIDR(), subnet.AvailabilityZones(), zoneFilter, 414 ) 415 continue 416 } 417 result := params.Subnet{ 418 CIDR: subnet.CIDR(), 419 ProviderId: string(subnet.ProviderId()), 420 VLANTag: subnet.VLANTag(), 421 Life: subnet.Life(), 422 SpaceTag: names.NewSpaceTag(subnet.SpaceName()).String(), 423 Zones: subnet.AvailabilityZones(), 424 Status: subnet.Status(), 425 } 426 results.Results = append(results.Results, result) 427 } 428 return results, nil 429 } 430 431 // networkingEnviron returns a environs.NetworkingEnviron instance from the 432 // current model config, if supported. If the model does not support 433 // environs.Networking, an error satisfying errors.IsNotSupported() will be 434 // returned. 435 func networkingEnviron(getter environs.EnvironConfigGetter) (environs.NetworkingEnviron, error) { 436 env, err := environs.GetEnviron(getter, environs.New) 437 if err != nil { 438 return nil, errors.Annotate(err, "opening environment") 439 } 440 if netEnv, ok := environs.SupportsNetworking(env); ok { 441 return netEnv, nil 442 } 443 return nil, errors.NotSupportedf("model networking features") // " not supported" 444 } 445 446 // AllZones is defined on the API interface. 447 func AllZones(api NetworkBacking) (params.ZoneResults, error) { 448 var results params.ZoneResults 449 450 zonesAsString := func(zones []providercommon.AvailabilityZone) string { 451 results := make([]string, len(zones)) 452 for i, zone := range zones { 453 results[i] = zone.Name() 454 } 455 return `"` + strings.Join(results, `", "`) + `"` 456 } 457 458 // Try fetching cached zones first. 459 zones, err := api.AvailabilityZones() 460 if err != nil { 461 return results, errors.Trace(err) 462 } 463 464 if len(zones) == 0 { 465 // This is likely the first time we're called. 466 // Fetch all zones from the provider and update. 467 zones, err = updateZones(api) 468 if err != nil { 469 return results, errors.Annotate(err, "cannot update known zones") 470 } 471 logger.Tracef( 472 "updated the list of known zones from the model: %s", zonesAsString(zones), 473 ) 474 } else { 475 logger.Tracef("using cached list of known zones: %s", zonesAsString(zones)) 476 } 477 478 results.Results = make([]params.ZoneResult, len(zones)) 479 for i, zone := range zones { 480 results.Results[i].Name = zone.Name() 481 results.Results[i].Available = zone.Available() 482 } 483 return results, nil 484 } 485 486 // updateZones attempts to retrieve all availability zones from the environment 487 // provider (if supported) and then updates the persisted list of zones in 488 // state, returning them as well on success. 489 func updateZones(api NetworkBacking) ([]providercommon.AvailabilityZone, error) { 490 zoned, err := zonedEnviron(api) 491 if err != nil { 492 return nil, errors.Trace(err) 493 } 494 zones, err := zoned.AvailabilityZones() 495 if err != nil { 496 return nil, errors.Trace(err) 497 } 498 499 if err := api.SetAvailabilityZones(zones); err != nil { 500 return nil, errors.Trace(err) 501 } 502 return zones, nil 503 } 504 505 // zonedEnviron returns a providercommon.ZonedEnviron instance from the current 506 // model config. If the model does not support zones, an error satisfying 507 // errors.IsNotSupported() will be returned. 508 func zonedEnviron(api NetworkBacking) (providercommon.ZonedEnviron, error) { 509 env, err := environs.GetEnviron(api, environs.New) 510 if err != nil { 511 return nil, errors.Annotate(err, "opening environment") 512 } 513 if zonedEnv, ok := env.(providercommon.ZonedEnviron); ok { 514 return zonedEnv, nil 515 } 516 return nil, errors.NotSupportedf("availability zones") 517 }