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