github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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/names" 14 "github.com/juju/utils/set" 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 subnetInfo.AllocatableIPLow != nil { 358 backingInfo.AllocatableIPLow = subnetInfo.AllocatableIPLow.String() 359 } 360 if subnetInfo.AllocatableIPHigh != nil { 361 backingInfo.AllocatableIPHigh = subnetInfo.AllocatableIPHigh.String() 362 } 363 if _, err := api.AddSubnet(backingInfo); err != nil { 364 return errors.Trace(err) 365 } 366 return nil 367 } 368 369 // AddSubnets adds. 370 func AddSubnets(api NetworkBacking, args params.AddSubnetsParams) (params.ErrorResults, error) { 371 results := params.ErrorResults{ 372 Results: make([]params.ErrorResult, len(args.Subnets)), 373 } 374 375 if len(args.Subnets) == 0 { 376 return results, nil 377 } 378 379 cache := NewAddSubnetsCache(api) 380 for i, arg := range args.Subnets { 381 err := addOneSubnet(api, arg, cache) 382 if err != nil { 383 results.Results[i].Error = common.ServerError(err) 384 } 385 } 386 return results, nil 387 } 388 389 // ListSubnets lists all the available subnets or only those matching 390 // all given optional filters. 391 func ListSubnets(api NetworkBacking, args params.SubnetsFilters) (results params.ListSubnetsResults, err error) { 392 subnets, err := api.AllSubnets() 393 if err != nil { 394 return results, errors.Trace(err) 395 } 396 397 var spaceFilter string 398 if args.SpaceTag != "" { 399 tag, err := names.ParseSpaceTag(args.SpaceTag) 400 if err != nil { 401 return results, errors.Trace(err) 402 } 403 spaceFilter = tag.Id() 404 } 405 zoneFilter := args.Zone 406 407 for _, subnet := range subnets { 408 if spaceFilter != "" && subnet.SpaceName() != spaceFilter { 409 logger.Tracef( 410 "filtering subnet %q from space %q not matching filter %q", 411 subnet.CIDR(), subnet.SpaceName(), spaceFilter, 412 ) 413 continue 414 } 415 zoneSet := set.NewStrings(subnet.AvailabilityZones()...) 416 if zoneFilter != "" && !zoneSet.IsEmpty() && !zoneSet.Contains(zoneFilter) { 417 logger.Tracef( 418 "filtering subnet %q with zones %v not matching filter %q", 419 subnet.CIDR(), subnet.AvailabilityZones(), zoneFilter, 420 ) 421 continue 422 } 423 result := params.Subnet{ 424 CIDR: subnet.CIDR(), 425 ProviderId: string(subnet.ProviderId()), 426 VLANTag: subnet.VLANTag(), 427 Life: subnet.Life(), 428 SpaceTag: names.NewSpaceTag(subnet.SpaceName()).String(), 429 Zones: subnet.AvailabilityZones(), 430 Status: subnet.Status(), 431 } 432 results.Results = append(results.Results, result) 433 } 434 return results, nil 435 } 436 437 // networkingEnviron returns a environs.NetworkingEnviron instance from the 438 // current model config, if supported. If the model does not support 439 // environs.Networking, an error satisfying errors.IsNotSupported() will be 440 // returned. 441 func networkingEnviron(api NetworkBacking) (environs.NetworkingEnviron, error) { 442 envConfig, err := api.ModelConfig() 443 if err != nil { 444 return nil, errors.Annotate(err, "getting model config") 445 } 446 447 env, err := environs.New(envConfig) 448 if err != nil { 449 return nil, errors.Annotate(err, "opening model") 450 } 451 if netEnv, ok := environs.SupportsNetworking(env); ok { 452 return netEnv, nil 453 } 454 return nil, errors.NotSupportedf("model networking features") // " not supported" 455 } 456 457 // AllZones is defined on the API interface. 458 func AllZones(api NetworkBacking) (params.ZoneResults, error) { 459 var results params.ZoneResults 460 461 zonesAsString := func(zones []providercommon.AvailabilityZone) string { 462 results := make([]string, len(zones)) 463 for i, zone := range zones { 464 results[i] = zone.Name() 465 } 466 return `"` + strings.Join(results, `", "`) + `"` 467 } 468 469 // Try fetching cached zones first. 470 zones, err := api.AvailabilityZones() 471 if err != nil { 472 return results, errors.Trace(err) 473 } 474 475 if len(zones) == 0 { 476 // This is likely the first time we're called. 477 // Fetch all zones from the provider and update. 478 zones, err = updateZones(api) 479 if err != nil { 480 return results, errors.Annotate(err, "cannot update known zones") 481 } 482 logger.Tracef( 483 "updated the list of known zones from the model: %s", zonesAsString(zones), 484 ) 485 } else { 486 logger.Tracef("using cached list of known zones: %s", zonesAsString(zones)) 487 } 488 489 results.Results = make([]params.ZoneResult, len(zones)) 490 for i, zone := range zones { 491 results.Results[i].Name = zone.Name() 492 results.Results[i].Available = zone.Available() 493 } 494 return results, nil 495 } 496 497 // updateZones attempts to retrieve all availability zones from the environment 498 // provider (if supported) and then updates the persisted list of zones in 499 // state, returning them as well on success. 500 func updateZones(api NetworkBacking) ([]providercommon.AvailabilityZone, error) { 501 zoned, err := zonedEnviron(api) 502 if err != nil { 503 return nil, errors.Trace(err) 504 } 505 zones, err := zoned.AvailabilityZones() 506 if err != nil { 507 return nil, errors.Trace(err) 508 } 509 510 if err := api.SetAvailabilityZones(zones); err != nil { 511 return nil, errors.Trace(err) 512 } 513 return zones, nil 514 } 515 516 // zonedEnviron returns a providercommon.ZonedEnviron instance from the current 517 // model config. If the model does not support zones, an error satisfying 518 // errors.IsNotSupported() will be returned. 519 func zonedEnviron(api NetworkBacking) (providercommon.ZonedEnviron, error) { 520 envConfig, err := api.ModelConfig() 521 if err != nil { 522 return nil, errors.Annotate(err, "getting model config") 523 } 524 525 env, err := environs.New(envConfig) 526 if err != nil { 527 return nil, errors.Annotate(err, "opening model") 528 } 529 if zonedEnv, ok := env.(providercommon.ZonedEnviron); ok { 530 return zonedEnv, nil 531 } 532 return nil, errors.NotSupportedf("availability zones") 533 }