github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/provider/maas/environ.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package maas 5 6 import ( 7 "encoding/json" 8 "fmt" 9 "net" 10 "net/http" 11 "net/url" 12 "regexp" 13 "strconv" 14 "strings" 15 "sync" 16 "time" 17 18 "github.com/juju/errors" 19 "github.com/juju/gomaasapi" 20 "github.com/juju/names" 21 "github.com/juju/utils" 22 "github.com/juju/utils/featureflag" 23 "github.com/juju/utils/os" 24 "github.com/juju/utils/series" 25 "github.com/juju/utils/set" 26 27 "github.com/juju/juju/cloudconfig/cloudinit" 28 "github.com/juju/juju/cloudconfig/instancecfg" 29 "github.com/juju/juju/cloudconfig/providerinit" 30 "github.com/juju/juju/constraints" 31 "github.com/juju/juju/environs" 32 "github.com/juju/juju/environs/config" 33 "github.com/juju/juju/environs/storage" 34 "github.com/juju/juju/feature" 35 "github.com/juju/juju/instance" 36 "github.com/juju/juju/network" 37 "github.com/juju/juju/provider/common" 38 "github.com/juju/juju/state/multiwatcher" 39 "github.com/juju/juju/status" 40 "github.com/juju/juju/tools" 41 ) 42 43 const ( 44 // The string from the api indicating the dynamic range of a subnet. 45 dynamicRange = "dynamic-range" 46 // The version strings indicating the MAAS API version. 47 apiVersion1 = "1.0" 48 apiVersion2 = "2.0" 49 ) 50 51 // A request may fail to due "eventual consistency" semantics, which 52 // should resolve fairly quickly. A request may also fail due to a slow 53 // state transition (for instance an instance taking a while to release 54 // a security group after termination). The former failure mode is 55 // dealt with by shortAttempt, the latter by LongAttempt. 56 var shortAttempt = utils.AttemptStrategy{ 57 Total: 5 * time.Second, 58 Delay: 200 * time.Millisecond, 59 } 60 61 var ( 62 ReleaseNodes = releaseNodes 63 DeploymentStatusCall = deploymentStatusCall 64 GetCapabilities = getCapabilities 65 GetMAAS2Controller = getMAAS2Controller 66 ) 67 68 func getMAAS2Controller(maasServer, apiKey string) (gomaasapi.Controller, error) { 69 return gomaasapi.NewController(gomaasapi.ControllerArgs{ 70 BaseURL: maasServer, 71 APIKey: apiKey, 72 }) 73 } 74 75 func subnetToSpaceIds(spaces gomaasapi.MAASObject) (map[string]network.Id, error) { 76 spacesJson, err := spaces.CallGet("", nil) 77 if err != nil { 78 return nil, errors.Trace(err) 79 } 80 spacesArray, err := spacesJson.GetArray() 81 if err != nil { 82 return nil, errors.Trace(err) 83 } 84 subnetsMap := make(map[string]network.Id) 85 for _, spaceJson := range spacesArray { 86 spaceMap, err := spaceJson.GetMap() 87 if err != nil { 88 return nil, errors.Trace(err) 89 } 90 providerIdRaw, err := spaceMap["id"].GetFloat64() 91 if err != nil { 92 return nil, errors.Trace(err) 93 } 94 providerId := network.Id(fmt.Sprintf("%.0f", providerIdRaw)) 95 subnetsArray, err := spaceMap["subnets"].GetArray() 96 if err != nil { 97 return nil, errors.Trace(err) 98 } 99 for _, subnetJson := range subnetsArray { 100 subnetMap, err := subnetJson.GetMap() 101 if err != nil { 102 return nil, errors.Trace(err) 103 } 104 subnet, err := subnetMap["cidr"].GetString() 105 if err != nil { 106 return nil, errors.Trace(err) 107 } 108 subnetsMap[subnet] = providerId 109 } 110 } 111 return subnetsMap, nil 112 } 113 114 func releaseNodes(nodes gomaasapi.MAASObject, ids url.Values) error { 115 _, err := nodes.CallPost("release", ids) 116 return err 117 } 118 119 type maasEnviron struct { 120 common.SupportsUnitPlacementPolicy 121 122 name string 123 124 // archMutex gates access to supportedArchitectures 125 archMutex sync.Mutex 126 // supportedArchitectures caches the architectures 127 // for which images can be instantiated. 128 supportedArchitectures []string 129 130 // ecfgMutex protects the *Unlocked fields below. 131 ecfgMutex sync.Mutex 132 133 ecfgUnlocked *maasModelConfig 134 maasClientUnlocked *gomaasapi.MAASObject 135 storageUnlocked storage.Storage 136 137 // maasController provides access to the MAAS 2.0 API. 138 maasController gomaasapi.Controller 139 140 availabilityZonesMutex sync.Mutex 141 availabilityZones []common.AvailabilityZone 142 143 // apiVersion tells us if we are using the MAAS 1.0 or 2.0 api. 144 apiVersion string 145 } 146 147 var _ environs.Environ = (*maasEnviron)(nil) 148 149 func NewEnviron(cfg *config.Config) (*maasEnviron, error) { 150 env := new(maasEnviron) 151 err := env.SetConfig(cfg) 152 if err != nil { 153 return nil, err 154 } 155 env.name = cfg.Name() 156 env.storageUnlocked = NewStorage(env) 157 158 return env, nil 159 } 160 161 func (env *maasEnviron) usingMAAS2() bool { 162 if !featureflag.Enabled(feature.MAAS2) { 163 return false 164 } 165 return env.apiVersion == apiVersion2 166 } 167 168 // Bootstrap is specified in the Environ interface. 169 func (env *maasEnviron) Bootstrap(ctx environs.BootstrapContext, args environs.BootstrapParams) (*environs.BootstrapResult, error) { 170 if featureflag.Enabled(feature.AddressAllocation) { 171 logger.Warningf("address-allocation feature flag is no longer supported on MAAS and is ignored!") 172 } 173 174 result, series, finalizer, err := common.BootstrapInstance(ctx, env, args) 175 if err != nil { 176 return nil, err 177 } 178 179 // We want to destroy the started instance if it doesn't transition to Deployed. 180 defer func() { 181 if err != nil { 182 if err := env.StopInstances(result.Instance.Id()); err != nil { 183 logger.Errorf("error releasing bootstrap instance: %v", err) 184 } 185 } 186 }() 187 // Wait for bootstrap instance to change to deployed state. 188 if err := env.waitForNodeDeployment(result.Instance.Id()); err != nil { 189 return nil, errors.Annotate(err, "bootstrap instance started but did not change to Deployed state") 190 } 191 192 bsResult := &environs.BootstrapResult{ 193 Arch: *result.Hardware.Arch, 194 Series: series, 195 Finalize: finalizer, 196 } 197 return bsResult, nil 198 } 199 200 // ControllerInstances is specified in the Environ interface. 201 func (env *maasEnviron) ControllerInstances() ([]instance.Id, error) { 202 return common.ProviderStateInstances(env, env.Storage()) 203 } 204 205 // ecfg returns the environment's maasModelConfig, and protects it with a 206 // mutex. 207 func (env *maasEnviron) ecfg() *maasModelConfig { 208 env.ecfgMutex.Lock() 209 cfg := *env.ecfgUnlocked 210 env.ecfgMutex.Unlock() 211 return &cfg 212 } 213 214 // Config is specified in the Environ interface. 215 func (env *maasEnviron) Config() *config.Config { 216 return env.ecfg().Config 217 } 218 219 // SetConfig is specified in the Environ interface. 220 func (env *maasEnviron) SetConfig(cfg *config.Config) error { 221 env.ecfgMutex.Lock() 222 defer env.ecfgMutex.Unlock() 223 224 // The new config has already been validated by itself, but now we 225 // validate the transition from the old config to the new. 226 var oldCfg *config.Config 227 if env.ecfgUnlocked != nil { 228 oldCfg = env.ecfgUnlocked.Config 229 } 230 cfg, err := env.Provider().Validate(cfg, oldCfg) 231 if err != nil { 232 return errors.Trace(err) 233 } 234 235 ecfg, err := providerInstance.newConfig(cfg) 236 if err != nil { 237 return errors.Trace(err) 238 } 239 240 env.ecfgUnlocked = ecfg 241 242 // We need to know the version of the server we're on. We support 1.9 243 // and 2.0. MAAS 1.9 uses the 1.0 api version and 2.0 uses the 2.0 api 244 // version. 245 apiVersion := apiVersion2 246 maas2Enabled := featureflag.Enabled(feature.MAAS2) 247 controller, err := GetMAAS2Controller(ecfg.maasServer(), ecfg.maasOAuth()) 248 switch { 249 case !maas2Enabled && err == nil: 250 return errors.NewNotSupported(nil, "MAAS 2 is not supported unless the 'maas2' feature flag is set") 251 case !maas2Enabled || gomaasapi.IsUnsupportedVersionError(err): 252 apiVersion = apiVersion1 253 authClient, err := gomaasapi.NewAuthenticatedClient(ecfg.maasServer(), ecfg.maasOAuth(), apiVersion1) 254 if err != nil { 255 return errors.Trace(err) 256 } 257 env.maasClientUnlocked = gomaasapi.NewMAAS(*authClient) 258 caps, err := GetCapabilities(env.maasClientUnlocked) 259 if err != nil { 260 return errors.Trace(err) 261 } 262 if !caps.Contains(capNetworkDeploymentUbuntu) { 263 return errors.NotSupportedf("MAAS 1.9 or more recent is required") 264 } 265 case err != nil: 266 return errors.Trace(err) 267 default: 268 env.maasController = controller 269 } 270 env.apiVersion = apiVersion 271 return nil 272 } 273 274 // SupportedArchitectures is specified on the EnvironCapability interface. 275 func (env *maasEnviron) SupportedArchitectures() ([]string, error) { 276 env.archMutex.Lock() 277 defer env.archMutex.Unlock() 278 if env.supportedArchitectures != nil { 279 return env.supportedArchitectures, nil 280 } 281 282 fetchArchitectures := env.allArchitecturesWithFallback 283 if env.usingMAAS2() { 284 fetchArchitectures = env.allArchitectures2 285 } 286 architectures, err := fetchArchitectures() 287 if err != nil { 288 return nil, errors.Trace(err) 289 } 290 env.supportedArchitectures = architectures 291 return env.supportedArchitectures, nil 292 } 293 294 // SupportsSpaces is specified on environs.Networking. 295 func (env *maasEnviron) SupportsSpaces() (bool, error) { 296 return true, nil 297 } 298 299 // SupportsSpaceDiscovery is specified on environs.Networking. 300 func (env *maasEnviron) SupportsSpaceDiscovery() (bool, error) { 301 return true, nil 302 } 303 304 // SupportsAddressAllocation is specified on environs.Networking. 305 func (env *maasEnviron) SupportsAddressAllocation(_ network.Id) (bool, error) { 306 return false, errors.NotSupportedf("legacy address allocation") 307 } 308 309 // allArchitectures2 uses the MAAS2 controller to get architectures from boot 310 // resources. 311 func (env *maasEnviron) allArchitectures2() ([]string, error) { 312 resources, err := env.maasController.BootResources() 313 if err != nil { 314 return nil, errors.Trace(err) 315 } 316 architectures := set.NewStrings() 317 for _, resource := range resources { 318 architectures.Add(strings.Split(resource.Architecture(), "/")[0]) 319 } 320 return architectures.SortedValues(), nil 321 } 322 323 // allArchitectureWithFallback queries MAAS for all of the boot-images 324 // across all registered nodegroups and collapses them down to unique 325 // architectures. 326 func (env *maasEnviron) allArchitecturesWithFallback() ([]string, error) { 327 architectures, err := env.allArchitectures() 328 if err != nil || len(architectures) == 0 { 329 logger.Debugf("error querying boot-images: %v", err) 330 logger.Debugf("falling back to listing nodes") 331 architectures, err := env.nodeArchitectures() 332 if err != nil { 333 return nil, errors.Trace(err) 334 } 335 return architectures, nil 336 } else { 337 return architectures, nil 338 } 339 } 340 341 func (env *maasEnviron) allArchitectures() ([]string, error) { 342 nodegroups, err := env.getNodegroups() 343 if err != nil { 344 return nil, err 345 } 346 architectures := set.NewStrings() 347 for _, nodegroup := range nodegroups { 348 bootImages, err := env.nodegroupBootImages(nodegroup) 349 if err != nil { 350 return nil, errors.Annotatef(err, "cannot get boot images for nodegroup %v", nodegroup) 351 } 352 for _, image := range bootImages { 353 architectures.Add(image.architecture) 354 } 355 } 356 return architectures.SortedValues(), nil 357 } 358 359 // getNodegroups returns the UUID corresponding to each nodegroup 360 // in the MAAS installation. 361 func (env *maasEnviron) getNodegroups() ([]string, error) { 362 nodegroupsListing := env.getMAASClient().GetSubObject("nodegroups") 363 nodegroupsResult, err := nodegroupsListing.CallGet("list", nil) 364 if err != nil { 365 return nil, err 366 } 367 list, err := nodegroupsResult.GetArray() 368 if err != nil { 369 return nil, err 370 } 371 nodegroups := make([]string, len(list)) 372 for i, obj := range list { 373 nodegroup, err := obj.GetMap() 374 if err != nil { 375 return nil, err 376 } 377 uuid, err := nodegroup["uuid"].GetString() 378 if err != nil { 379 return nil, err 380 } 381 nodegroups[i] = uuid 382 } 383 return nodegroups, nil 384 } 385 386 type bootImage struct { 387 architecture string 388 release string 389 } 390 391 // nodegroupBootImages returns the set of boot-images for the specified nodegroup. 392 func (env *maasEnviron) nodegroupBootImages(nodegroupUUID string) ([]bootImage, error) { 393 nodegroupObject := env.getMAASClient().GetSubObject("nodegroups").GetSubObject(nodegroupUUID) 394 bootImagesObject := nodegroupObject.GetSubObject("boot-images/") 395 result, err := bootImagesObject.CallGet("", nil) 396 if err != nil { 397 return nil, err 398 } 399 list, err := result.GetArray() 400 if err != nil { 401 return nil, err 402 } 403 var bootImages []bootImage 404 for _, obj := range list { 405 bootimage, err := obj.GetMap() 406 if err != nil { 407 return nil, err 408 } 409 arch, err := bootimage["architecture"].GetString() 410 if err != nil { 411 return nil, err 412 } 413 release, err := bootimage["release"].GetString() 414 if err != nil { 415 return nil, err 416 } 417 bootImages = append(bootImages, bootImage{ 418 architecture: arch, 419 release: release, 420 }) 421 } 422 return bootImages, nil 423 } 424 425 // nodeArchitectures returns the architectures of all 426 // available nodes in the system. 427 // 428 // Note: this should only be used if we cannot query 429 // boot-images. 430 func (env *maasEnviron) nodeArchitectures() ([]string, error) { 431 filter := make(url.Values) 432 filter.Add("status", gomaasapi.NodeStatusDeclared) 433 filter.Add("status", gomaasapi.NodeStatusCommissioning) 434 filter.Add("status", gomaasapi.NodeStatusReady) 435 filter.Add("status", gomaasapi.NodeStatusReserved) 436 filter.Add("status", gomaasapi.NodeStatusAllocated) 437 // This is fine - nodeArchitectures is only used in MAAS 1 cases. 438 allInstances, err := env.instances1(filter) 439 if err != nil { 440 return nil, err 441 } 442 architectures := make(set.Strings) 443 for _, inst := range allInstances { 444 inst := inst.(*maas1Instance) 445 arch, _, err := inst.architecture() 446 if err != nil { 447 return nil, err 448 } 449 architectures.Add(arch) 450 } 451 // TODO(dfc) why is this sorted 452 return architectures.SortedValues(), nil 453 } 454 455 type maasAvailabilityZone struct { 456 name string 457 } 458 459 func (z maasAvailabilityZone) Name() string { 460 return z.name 461 } 462 463 func (z maasAvailabilityZone) Available() bool { 464 // MAAS' physical zone attributes only include name and description; 465 // there is no concept of availability. 466 return true 467 } 468 469 // AvailabilityZones returns a slice of availability zones 470 // for the configured region. 471 func (e *maasEnviron) AvailabilityZones() ([]common.AvailabilityZone, error) { 472 e.availabilityZonesMutex.Lock() 473 defer e.availabilityZonesMutex.Unlock() 474 if e.availabilityZones == nil { 475 var availabilityZones []common.AvailabilityZone 476 var err error 477 if e.usingMAAS2() { 478 availabilityZones, err = e.availabilityZones2() 479 if err != nil { 480 return nil, errors.Trace(err) 481 } 482 } else { 483 availabilityZones, err = e.availabilityZones1() 484 if err != nil { 485 return nil, errors.Trace(err) 486 } 487 } 488 e.availabilityZones = availabilityZones 489 } 490 return e.availabilityZones, nil 491 } 492 493 func (e *maasEnviron) availabilityZones1() ([]common.AvailabilityZone, error) { 494 zonesObject := e.getMAASClient().GetSubObject("zones") 495 result, err := zonesObject.CallGet("", nil) 496 if err, ok := errors.Cause(err).(gomaasapi.ServerError); ok && err.StatusCode == http.StatusNotFound { 497 return nil, errors.NewNotImplemented(nil, "the MAAS server does not support zones") 498 } 499 if err != nil { 500 return nil, errors.Annotate(err, "cannot query ") 501 } 502 list, err := result.GetArray() 503 if err != nil { 504 return nil, err 505 } 506 logger.Debugf("availability zones: %+v", list) 507 availabilityZones := make([]common.AvailabilityZone, len(list)) 508 for i, obj := range list { 509 zone, err := obj.GetMap() 510 if err != nil { 511 return nil, err 512 } 513 name, err := zone["name"].GetString() 514 if err != nil { 515 return nil, err 516 } 517 availabilityZones[i] = maasAvailabilityZone{name} 518 } 519 return availabilityZones, nil 520 } 521 522 func (e *maasEnviron) availabilityZones2() ([]common.AvailabilityZone, error) { 523 zones, err := e.maasController.Zones() 524 if err != nil { 525 return nil, errors.Trace(err) 526 } 527 availabilityZones := make([]common.AvailabilityZone, len(zones)) 528 for i, zone := range zones { 529 availabilityZones[i] = maasAvailabilityZone{zone.Name()} 530 } 531 return availabilityZones, nil 532 533 } 534 535 // InstanceAvailabilityZoneNames returns the availability zone names for each 536 // of the specified instances. 537 func (e *maasEnviron) InstanceAvailabilityZoneNames(ids []instance.Id) ([]string, error) { 538 instances, err := e.Instances(ids) 539 if err != nil && err != environs.ErrPartialInstances { 540 return nil, err 541 } 542 zones := make([]string, len(instances)) 543 for i, inst := range instances { 544 if inst == nil { 545 continue 546 } 547 z, err := inst.(maasInstance).zone() 548 if err != nil { 549 logger.Errorf("could not get availability zone %v", err) 550 continue 551 } 552 zones[i] = z 553 } 554 return zones, nil 555 } 556 557 type maasPlacement struct { 558 nodeName string 559 zoneName string 560 } 561 562 func (e *maasEnviron) parsePlacement(placement string) (*maasPlacement, error) { 563 pos := strings.IndexRune(placement, '=') 564 if pos == -1 { 565 // If there's no '=' delimiter, assume it's a node name. 566 return &maasPlacement{nodeName: placement}, nil 567 } 568 switch key, value := placement[:pos], placement[pos+1:]; key { 569 case "zone": 570 availabilityZone := value 571 zones, err := e.AvailabilityZones() 572 if err != nil { 573 return nil, err 574 } 575 for _, z := range zones { 576 if z.Name() == availabilityZone { 577 return &maasPlacement{zoneName: availabilityZone}, nil 578 } 579 } 580 return nil, errors.Errorf("invalid availability zone %q", availabilityZone) 581 } 582 return nil, errors.Errorf("unknown placement directive: %v", placement) 583 } 584 585 func (env *maasEnviron) PrecheckInstance(series string, cons constraints.Value, placement string) error { 586 if placement == "" { 587 return nil 588 } 589 _, err := env.parsePlacement(placement) 590 return err 591 } 592 593 const ( 594 capNetworkDeploymentUbuntu = "network-deployment-ubuntu" 595 ) 596 597 // getCapabilities asks the MAAS server for its capabilities, if 598 // supported by the server. 599 func getCapabilities(client *gomaasapi.MAASObject) (set.Strings, error) { 600 caps := make(set.Strings) 601 var result gomaasapi.JSONObject 602 var err error 603 604 for a := shortAttempt.Start(); a.Next(); { 605 version := client.GetSubObject("version/") 606 result, err = version.CallGet("", nil) 607 if err != nil { 608 if err, ok := errors.Cause(err).(gomaasapi.ServerError); ok && err.StatusCode == 404 { 609 return caps, errors.NotSupportedf("MAAS version 1.9 or more recent is required") 610 } 611 } else { 612 break 613 } 614 } 615 if err != nil { 616 return caps, err 617 } 618 info, err := result.GetMap() 619 if err != nil { 620 return caps, err 621 } 622 capsObj, ok := info["capabilities"] 623 if !ok { 624 return caps, fmt.Errorf("MAAS does not report capabilities") 625 } 626 items, err := capsObj.GetArray() 627 if err != nil { 628 return caps, err 629 } 630 for _, item := range items { 631 val, err := item.GetString() 632 if err != nil { 633 return set.NewStrings(), err 634 } 635 caps.Add(val) 636 } 637 return caps, nil 638 } 639 640 // getMAASClient returns a MAAS client object to use for a request, in a 641 // lock-protected fashion. 642 func (env *maasEnviron) getMAASClient() *gomaasapi.MAASObject { 643 env.ecfgMutex.Lock() 644 defer env.ecfgMutex.Unlock() 645 646 return env.maasClientUnlocked 647 } 648 649 var dashSuffix = regexp.MustCompile("^(.*)-\\d+$") 650 651 func spaceNamesToSpaceInfo(spaces []string, spaceMap map[string]network.SpaceInfo) ([]network.SpaceInfo, error) { 652 spaceInfos := []network.SpaceInfo{} 653 for _, name := range spaces { 654 info, ok := spaceMap[name] 655 if !ok { 656 matches := dashSuffix.FindAllStringSubmatch(name, 1) 657 if matches == nil { 658 return nil, errors.Errorf("unrecognised space in constraint %q", name) 659 } 660 // A -number was added to the space name when we 661 // converted to a juju name, we found 662 info, ok = spaceMap[matches[0][1]] 663 if !ok { 664 return nil, errors.Errorf("unrecognised space in constraint %q", name) 665 } 666 } 667 spaceInfos = append(spaceInfos, info) 668 } 669 return spaceInfos, nil 670 } 671 672 func (environ *maasEnviron) buildSpaceMap() (map[string]network.SpaceInfo, error) { 673 spaces, err := environ.Spaces() 674 if err != nil { 675 return nil, errors.Trace(err) 676 } 677 spaceMap := make(map[string]network.SpaceInfo) 678 empty := set.Strings{} 679 for _, space := range spaces { 680 jujuName := network.ConvertSpaceName(space.Name, empty) 681 spaceMap[jujuName] = space 682 } 683 return spaceMap, nil 684 } 685 686 func (environ *maasEnviron) spaceNamesToSpaceInfo(positiveSpaces, negativeSpaces []string) ([]network.SpaceInfo, []network.SpaceInfo, error) { 687 spaceMap, err := environ.buildSpaceMap() 688 if err != nil { 689 return nil, nil, errors.Trace(err) 690 } 691 692 positiveSpaceIds, err := spaceNamesToSpaceInfo(positiveSpaces, spaceMap) 693 if err != nil { 694 return nil, nil, errors.Trace(err) 695 } 696 negativeSpaceIds, err := spaceNamesToSpaceInfo(negativeSpaces, spaceMap) 697 if err != nil { 698 return nil, nil, errors.Trace(err) 699 } 700 return positiveSpaceIds, negativeSpaceIds, nil 701 } 702 703 // acquireNode2 allocates a machine from MAAS2. 704 func (environ *maasEnviron) acquireNode2( 705 nodeName, zoneName string, 706 cons constraints.Value, 707 interfaces []interfaceBinding, 708 volumes []volumeInfo, 709 ) (maasInstance, error) { 710 acquireParams := convertConstraints2(cons) 711 positiveSpaceNames, negativeSpaceNames := convertSpacesFromConstraints(cons.Spaces) 712 positiveSpaces, negativeSpaces, err := environ.spaceNamesToSpaceInfo(positiveSpaceNames, negativeSpaceNames) 713 // If spaces aren't supported the constraints should be empty anyway. 714 if err != nil && !errors.IsNotSupported(err) { 715 return nil, errors.Trace(err) 716 } 717 err = addInterfaces2(&acquireParams, interfaces, positiveSpaces, negativeSpaces) 718 if err != nil { 719 return nil, errors.Trace(err) 720 } 721 addStorage2(&acquireParams, volumes) 722 acquireParams.AgentName = environ.ecfg().maasAgentName() 723 if zoneName != "" { 724 acquireParams.Zone = zoneName 725 } 726 if nodeName != "" { 727 acquireParams.Hostname = nodeName 728 } else if cons.Arch == nil { 729 logger.Warningf( 730 "no architecture was specified, acquiring an arbitrary node", 731 ) 732 } 733 machine, constraintMatches, err := environ.maasController.AllocateMachine(acquireParams) 734 735 if err != nil { 736 return nil, errors.Trace(err) 737 } 738 return &maas2Instance{machine, constraintMatches}, nil 739 } 740 741 // acquireNode allocates a node from the MAAS. 742 func (environ *maasEnviron) acquireNode( 743 nodeName, zoneName string, 744 cons constraints.Value, 745 interfaces []interfaceBinding, 746 volumes []volumeInfo, 747 ) (gomaasapi.MAASObject, error) { 748 749 acquireParams := convertConstraints(cons) 750 positiveSpaceNames, negativeSpaceNames := convertSpacesFromConstraints(cons.Spaces) 751 positiveSpaces, negativeSpaces, err := environ.spaceNamesToSpaceInfo(positiveSpaceNames, negativeSpaceNames) 752 // If spaces aren't supported the constraints should be empty anyway. 753 if err != nil && !errors.IsNotSupported(err) { 754 return gomaasapi.MAASObject{}, errors.Trace(err) 755 } 756 err = addInterfaces(acquireParams, interfaces, positiveSpaces, negativeSpaces) 757 if err != nil { 758 return gomaasapi.MAASObject{}, errors.Trace(err) 759 } 760 addStorage(acquireParams, volumes) 761 acquireParams.Add("agent_name", environ.ecfg().maasAgentName()) 762 if zoneName != "" { 763 acquireParams.Add("zone", zoneName) 764 } 765 if nodeName != "" { 766 acquireParams.Add("name", nodeName) 767 } else if cons.Arch == nil { 768 // TODO(axw) 2014-08-18 #1358219 769 // We should be requesting preferred 770 // architectures if unspecified, like 771 // in the other providers. 772 // 773 // This is slightly complicated in MAAS 774 // as there are a finite number of each 775 // architecture; preference may also 776 // conflict with other constraints, such 777 // as tags. Thus, a preference becomes a 778 // demand (which may fail) if not handled 779 // properly. 780 logger.Warningf( 781 "no architecture was specified, acquiring an arbitrary node", 782 ) 783 } 784 785 var result gomaasapi.JSONObject 786 for a := shortAttempt.Start(); a.Next(); { 787 client := environ.getMAASClient().GetSubObject("nodes/") 788 logger.Tracef("calling acquire with params: %+v", acquireParams) 789 result, err = client.CallPost("acquire", acquireParams) 790 if err == nil { 791 break 792 } 793 } 794 if err != nil { 795 return gomaasapi.MAASObject{}, err 796 } 797 node, err := result.GetMAASObject() 798 if err != nil { 799 err := errors.Annotate(err, "unexpected result from 'acquire' on MAAS API") 800 return gomaasapi.MAASObject{}, err 801 } 802 return node, nil 803 } 804 805 // startNode installs and boots a node. 806 func (environ *maasEnviron) startNode(node gomaasapi.MAASObject, series string, userdata []byte) (*gomaasapi.MAASObject, error) { 807 params := url.Values{ 808 "distro_series": {series}, 809 "user_data": {string(userdata)}, 810 } 811 // Initialize err to a non-nil value as a sentinel for the following 812 // loop. 813 err := fmt.Errorf("(no error)") 814 var result gomaasapi.JSONObject 815 for a := shortAttempt.Start(); a.Next() && err != nil; { 816 result, err = node.CallPost("start", params) 817 if err == nil { 818 break 819 } 820 } 821 822 if err == nil { 823 var startedNode gomaasapi.MAASObject 824 startedNode, err = result.GetMAASObject() 825 if err != nil { 826 logger.Errorf("cannot process API response after successfully starting node: %v", err) 827 return nil, err 828 } 829 return &startedNode, nil 830 } 831 return nil, err 832 } 833 834 func (environ *maasEnviron) startNode2(node maas2Instance, series string, userdata []byte) (*maas2Instance, error) { 835 err := node.machine.Start(gomaasapi.StartArgs{DistroSeries: series, UserData: string(userdata)}) 836 if err != nil { 837 return nil, errors.Trace(err) 838 } 839 // Machine.Start updates the machine in-place when it succeeds. 840 return &maas2Instance{machine: node.machine}, nil 841 842 } 843 844 // DistributeInstances implements the state.InstanceDistributor policy. 845 func (e *maasEnviron) DistributeInstances(candidates, distributionGroup []instance.Id) ([]instance.Id, error) { 846 return common.DistributeInstances(e, candidates, distributionGroup) 847 } 848 849 var availabilityZoneAllocations = common.AvailabilityZoneAllocations 850 851 // MaintainInstance is specified in the InstanceBroker interface. 852 func (*maasEnviron) MaintainInstance(args environs.StartInstanceParams) error { 853 return nil 854 } 855 856 // StartInstance is specified in the InstanceBroker interface. 857 func (environ *maasEnviron) StartInstance(args environs.StartInstanceParams) ( 858 *environs.StartInstanceResult, error, 859 ) { 860 var availabilityZones []string 861 var nodeName string 862 if args.Placement != "" { 863 placement, err := environ.parsePlacement(args.Placement) 864 if err != nil { 865 return nil, err 866 } 867 switch { 868 case placement.zoneName != "": 869 availabilityZones = append(availabilityZones, placement.zoneName) 870 default: 871 nodeName = placement.nodeName 872 } 873 } 874 875 // If no placement is specified, then automatically spread across 876 // the known zones for optimal spread across the instance distribution 877 // group. 878 if args.Placement == "" { 879 var group []instance.Id 880 var err error 881 if args.DistributionGroup != nil { 882 group, err = args.DistributionGroup() 883 if err != nil { 884 return nil, errors.Annotate(err, "cannot get distribution group") 885 } 886 } 887 zoneInstances, err := availabilityZoneAllocations(environ, group) 888 // TODO (mfoord): this branch is for old versions of MAAS and 889 // can be removed, but this means fixing tests. 890 if errors.IsNotImplemented(err) { 891 // Availability zones are an extension, so we may get a 892 // not implemented error; ignore these. 893 } else if err != nil { 894 return nil, errors.Annotate(err, "cannot get availability zone allocations") 895 } else if len(zoneInstances) > 0 { 896 for _, z := range zoneInstances { 897 availabilityZones = append(availabilityZones, z.ZoneName) 898 } 899 } 900 } 901 if len(availabilityZones) == 0 { 902 availabilityZones = []string{""} 903 } 904 905 // Storage. 906 volumes, err := buildMAASVolumeParameters(args.Volumes, args.Constraints) 907 if err != nil { 908 return nil, errors.Annotate(err, "invalid volume parameters") 909 } 910 911 var interfaceBindings []interfaceBinding 912 if len(args.EndpointBindings) != 0 { 913 for endpoint, spaceProviderID := range args.EndpointBindings { 914 interfaceBindings = append(interfaceBindings, interfaceBinding{ 915 Name: endpoint, 916 SpaceProviderId: string(spaceProviderID), 917 }) 918 } 919 } 920 snArgs := selectNodeArgs{ 921 Constraints: args.Constraints, 922 AvailabilityZones: availabilityZones, 923 NodeName: nodeName, 924 Interfaces: interfaceBindings, 925 Volumes: volumes, 926 } 927 var inst maasInstance 928 if !environ.usingMAAS2() { 929 selectedNode, err := environ.selectNode(snArgs) 930 if err != nil { 931 return nil, errors.Errorf("cannot run instances: %v", err) 932 } 933 934 inst = &maas1Instance{ 935 maasObject: selectedNode, 936 environ: environ, 937 statusGetter: environ.deploymentStatusOne, 938 } 939 } else { 940 inst, err = environ.selectNode2(snArgs) 941 if err != nil { 942 return nil, errors.Annotatef(err, "cannot run instances") 943 } 944 } 945 defer func() { 946 if err != nil { 947 if err := environ.StopInstances(inst.Id()); err != nil { 948 logger.Errorf("error releasing failed instance: %v", err) 949 } 950 } 951 }() 952 953 hc, err := inst.hardwareCharacteristics() 954 if err != nil { 955 return nil, err 956 } 957 958 series := args.Tools.OneSeries() 959 selectedTools, err := args.Tools.Match(tools.Filter{ 960 Arch: *hc.Arch, 961 }) 962 if err != nil { 963 return nil, errors.Trace(err) 964 } 965 if err := args.InstanceConfig.SetTools(selectedTools); err != nil { 966 return nil, errors.Trace(err) 967 } 968 969 hostname, err := inst.hostname() 970 if err != nil { 971 return nil, err 972 } 973 974 if err := instancecfg.FinishInstanceConfig(args.InstanceConfig, environ.Config()); err != nil { 975 return nil, errors.Trace(err) 976 } 977 978 cloudcfg, err := environ.newCloudinitConfig(hostname, series) 979 if err != nil { 980 return nil, errors.Trace(err) 981 } 982 userdata, err := providerinit.ComposeUserData(args.InstanceConfig, cloudcfg, MAASRenderer{}) 983 if err != nil { 984 return nil, errors.Annotatef(err, "could not compose userdata for bootstrap node") 985 } 986 logger.Debugf("maas user data; %d bytes", len(userdata)) 987 988 subnetsMap, err := environ.subnetToSpaceIds() 989 if err != nil { 990 return nil, errors.Trace(err) 991 } 992 var interfaces []network.InterfaceInfo 993 if !environ.usingMAAS2() { 994 inst1 := inst.(*maas1Instance) 995 startedNode, err := environ.startNode(*inst1.maasObject, series, userdata) 996 if err != nil { 997 return nil, errors.Trace(err) 998 } 999 // Once the instance has started the response should contain the 1000 // assigned IP addresses, even when NICs are set to "auto" instead of 1001 // "static". So instead of selectedNode, which only contains the 1002 // acquire-time details (no IP addresses for NICs set to "auto" vs 1003 // "static"), we use the up-to-date startedNode response to get the 1004 // interfaces. 1005 interfaces, err = maasObjectNetworkInterfaces(startedNode, subnetsMap) 1006 if err != nil { 1007 return nil, errors.Trace(err) 1008 } 1009 } else { 1010 startedInst, err := environ.startNode2(*inst.(*maas2Instance), series, userdata) 1011 if err != nil { 1012 return nil, errors.Trace(err) 1013 } 1014 interfaces, err = maas2NetworkInterfaces(startedInst, subnetsMap) 1015 if err != nil { 1016 return nil, errors.Trace(err) 1017 } 1018 } 1019 logger.Debugf("started instance %q", inst.Id()) 1020 1021 if multiwatcher.AnyJobNeedsState(args.InstanceConfig.Jobs...) { 1022 if err := common.AddStateInstance(environ.Storage(), inst.Id()); err != nil { 1023 logger.Errorf("could not record instance in provider-state: %v", err) 1024 } 1025 } 1026 1027 requestedVolumes := make([]names.VolumeTag, len(args.Volumes)) 1028 for i, v := range args.Volumes { 1029 requestedVolumes[i] = v.Tag 1030 } 1031 resultVolumes, resultAttachments, err := inst.volumes( 1032 names.NewMachineTag(args.InstanceConfig.MachineId), 1033 requestedVolumes, 1034 ) 1035 if err != nil { 1036 return nil, err 1037 } 1038 if len(resultVolumes) != len(requestedVolumes) { 1039 err = errors.Errorf("requested %v storage volumes. %v returned.", len(requestedVolumes), len(resultVolumes)) 1040 return nil, err 1041 } 1042 1043 return &environs.StartInstanceResult{ 1044 Instance: inst, 1045 Hardware: hc, 1046 NetworkInfo: interfaces, 1047 Volumes: resultVolumes, 1048 VolumeAttachments: resultAttachments, 1049 }, nil 1050 } 1051 1052 // Override for testing. 1053 var nodeDeploymentTimeout = func(environ *maasEnviron) time.Duration { 1054 sshTimeouts := environ.Config().BootstrapSSHOpts() 1055 return sshTimeouts.Timeout 1056 } 1057 1058 func (environ *maasEnviron) waitForNodeDeployment(id instance.Id) error { 1059 if environ.usingMAAS2() { 1060 return environ.waitForNodeDeployment2(id) 1061 } 1062 systemId := extractSystemId(id) 1063 1064 longAttempt := utils.AttemptStrategy{ 1065 Delay: 10 * time.Second, 1066 Total: nodeDeploymentTimeout(environ), 1067 } 1068 1069 for a := longAttempt.Start(); a.Next(); { 1070 statusValues, err := environ.deploymentStatus(id) 1071 if errors.IsNotImplemented(err) { 1072 return nil 1073 } 1074 if err != nil { 1075 return errors.Trace(err) 1076 } 1077 if statusValues[systemId] == "Deployed" { 1078 return nil 1079 } 1080 if statusValues[systemId] == "Failed deployment" { 1081 return errors.Errorf("instance %q failed to deploy", id) 1082 } 1083 } 1084 return errors.Errorf("instance %q is started but not deployed", id) 1085 } 1086 1087 func (environ *maasEnviron) waitForNodeDeployment2(id instance.Id) error { 1088 longAttempt := utils.AttemptStrategy{ 1089 Delay: 10 * time.Second, 1090 Total: nodeDeploymentTimeout(environ), 1091 } 1092 1093 for a := longAttempt.Start(); a.Next(); { 1094 machine, err := environ.getInstance(id) 1095 if err != nil { 1096 return errors.Trace(err) 1097 } 1098 stat := machine.Status() 1099 if stat.Status == status.StatusRunning { 1100 return nil 1101 } 1102 if stat.Status == status.StatusProvisioningError { 1103 return errors.Errorf("instance %q failed to deploy", id) 1104 1105 } 1106 } 1107 return errors.Errorf("instance %q is started but not deployed", id) 1108 } 1109 1110 func (environ *maasEnviron) deploymentStatusOne(id instance.Id) (string, string) { 1111 results, err := environ.deploymentStatus(id) 1112 if err != nil { 1113 return "", "" 1114 } 1115 systemId := extractSystemId(id) 1116 substatus := environ.getDeploymentSubstatus(systemId) 1117 return results[systemId], substatus 1118 } 1119 1120 func (environ *maasEnviron) getDeploymentSubstatus(systemId string) string { 1121 nodesAPI := environ.getMAASClient().GetSubObject("nodes") 1122 result, err := nodesAPI.CallGet("list", nil) 1123 if err != nil { 1124 return "" 1125 } 1126 slices, err := result.GetArray() 1127 if err != nil { 1128 return "" 1129 } 1130 for _, slice := range slices { 1131 resultMap, err := slice.GetMap() 1132 if err != nil { 1133 continue 1134 } 1135 sysId, err := resultMap["system_id"].GetString() 1136 if err != nil { 1137 continue 1138 } 1139 if sysId == systemId { 1140 message, err := resultMap["substatus_message"].GetString() 1141 if err != nil { 1142 logger.Warningf("could not get string for substatus_message: %v", resultMap["substatus_message"]) 1143 return "" 1144 } 1145 return message 1146 } 1147 } 1148 1149 return "" 1150 } 1151 1152 // deploymentStatus returns the deployment state of MAAS instances with 1153 // the specified Juju instance ids. 1154 // Note: the result is a map of MAAS systemId to state. 1155 func (environ *maasEnviron) deploymentStatus(ids ...instance.Id) (map[string]string, error) { 1156 nodesAPI := environ.getMAASClient().GetSubObject("nodes") 1157 result, err := DeploymentStatusCall(nodesAPI, ids...) 1158 if err != nil { 1159 if err, ok := errors.Cause(err).(gomaasapi.ServerError); ok && err.StatusCode == http.StatusBadRequest { 1160 return nil, errors.NewNotImplemented(err, "deployment status") 1161 } 1162 return nil, errors.Trace(err) 1163 } 1164 resultMap, err := result.GetMap() 1165 if err != nil { 1166 return nil, errors.Trace(err) 1167 } 1168 statusValues := make(map[string]string) 1169 for systemId, jsonValue := range resultMap { 1170 status, err := jsonValue.GetString() 1171 if err != nil { 1172 return nil, errors.Trace(err) 1173 } 1174 statusValues[systemId] = status 1175 } 1176 return statusValues, nil 1177 } 1178 1179 func deploymentStatusCall(nodes gomaasapi.MAASObject, ids ...instance.Id) (gomaasapi.JSONObject, error) { 1180 filter := getSystemIdValues("nodes", ids) 1181 return nodes.CallGet("deployment_status", filter) 1182 } 1183 1184 type selectNodeArgs struct { 1185 AvailabilityZones []string 1186 NodeName string 1187 Constraints constraints.Value 1188 Interfaces []interfaceBinding 1189 Volumes []volumeInfo 1190 } 1191 1192 func (environ *maasEnviron) selectNode(args selectNodeArgs) (*gomaasapi.MAASObject, error) { 1193 var err error 1194 var node gomaasapi.MAASObject 1195 1196 for i, zoneName := range args.AvailabilityZones { 1197 node, err = environ.acquireNode( 1198 args.NodeName, 1199 zoneName, 1200 args.Constraints, 1201 args.Interfaces, 1202 args.Volumes, 1203 ) 1204 1205 if err, ok := errors.Cause(err).(gomaasapi.ServerError); ok && err.StatusCode == http.StatusConflict { 1206 if i+1 < len(args.AvailabilityZones) { 1207 logger.Infof("could not acquire a node in zone %q, trying another zone", zoneName) 1208 continue 1209 } 1210 } 1211 if err != nil { 1212 return nil, errors.Errorf("cannot run instances: %v", err) 1213 } 1214 // Since a return at the end of the function is required 1215 // just break here. 1216 break 1217 } 1218 return &node, nil 1219 } 1220 1221 func (environ *maasEnviron) selectNode2(args selectNodeArgs) (maasInstance, error) { 1222 var err error 1223 var inst maasInstance 1224 1225 for i, zoneName := range args.AvailabilityZones { 1226 inst, err = environ.acquireNode2( 1227 args.NodeName, 1228 zoneName, 1229 args.Constraints, 1230 args.Interfaces, 1231 args.Volumes, 1232 ) 1233 1234 if gomaasapi.IsNoMatchError(err) { 1235 if i+1 < len(args.AvailabilityZones) { 1236 logger.Infof("could not acquire a node in zone %q, trying another zone", zoneName) 1237 continue 1238 } 1239 } 1240 if err != nil { 1241 return nil, errors.Annotatef(err, "cannot run instance") 1242 } 1243 // Since a return at the end of the function is required 1244 // just break here. 1245 break 1246 } 1247 return inst, nil 1248 } 1249 1250 // setupJujuNetworking returns a string representing the script to run 1251 // in order to prepare the Juju-specific networking config on a node. 1252 func setupJujuNetworking() string { 1253 // For ubuntu series < xenial we prefer python2 over python3 1254 // as we don't want to invalidate lots of testing against 1255 // known cloud-image contents. A summary of Ubuntu releases 1256 // and python inclusion in the default install of Ubuntu 1257 // Server is as follows: 1258 // 1259 // 12.04 precise: python 2 (2.7.3) 1260 // 14.04 trusty: python 2 (2.7.5) and python3 (3.4.0) 1261 // 14.10 utopic: python 2 (2.7.8) and python3 (3.4.2) 1262 // 15.04 vivid: python 2 (2.7.9) and python3 (3.4.3) 1263 // 15.10 wily: python 2 (2.7.9) and python3 (3.4.3) 1264 // 16.04 xenial: python 3 only (3.5.1) 1265 // 1266 // going forward: python 3 only 1267 1268 return fmt.Sprintf(` 1269 trap 'rm -f %[1]q' EXIT 1270 1271 if [ -x /usr/bin/python2 ]; then 1272 juju_networking_preferred_python_binary=/usr/bin/python2 1273 elif [ -x /usr/bin/python3 ]; then 1274 juju_networking_preferred_python_binary=/usr/bin/python3 1275 elif [ -x /usr/bin/python ]; then 1276 juju_networking_preferred_python_binary=/usr/bin/python 1277 fi 1278 1279 if [ ! -z "${juju_networking_preferred_python_binary:-}" ]; then 1280 if [ -f %[1]q ]; then 1281 # We are sharing this code between master, maas-spaces2 and 1.25. 1282 # For the moment we want master and 1.25 to not bridge all interfaces. 1283 # This setting allows us to easily switch the behaviour when merging 1284 # the code between those various branches. 1285 juju_bridge_all_interfaces=1 1286 if [ $juju_bridge_all_interfaces -eq 1 ]; then 1287 $juju_networking_preferred_python_binary %[1]q --bridge-prefix=%[2]q --one-time-backup --activate %[4]q 1288 else 1289 juju_ipv4_interface_to_bridge=$(ip -4 route list exact default | head -n1 | cut -d' ' -f5) 1290 $juju_networking_preferred_python_binary %[1]q --bridge-name=%[3]q --interface-to-bridge="${juju_ipv4_interface_to_bridge:-unknown}" --one-time-backup --activate %[4]q 1291 fi 1292 fi 1293 else 1294 echo "error: no Python installation found; cannot run Juju's bridge script" 1295 fi`, 1296 bridgeScriptPath, 1297 instancecfg.DefaultBridgePrefix, 1298 instancecfg.DefaultBridgeName, 1299 "/etc/network/interfaces") 1300 } 1301 1302 func renderEtcNetworkInterfacesScript() string { 1303 return setupJujuNetworking() 1304 } 1305 1306 // newCloudinitConfig creates a cloudinit.Config structure suitable as a base 1307 // for initialising a MAAS node. 1308 func (environ *maasEnviron) newCloudinitConfig(hostname, forSeries string) (cloudinit.CloudConfig, error) { 1309 cloudcfg, err := cloudinit.New(forSeries) 1310 if err != nil { 1311 return nil, err 1312 } 1313 1314 info := machineInfo{hostname} 1315 runCmd, err := info.cloudinitRunCmd(cloudcfg) 1316 if err != nil { 1317 return nil, errors.Trace(err) 1318 } 1319 1320 operatingSystem, err := series.GetOSFromSeries(forSeries) 1321 if err != nil { 1322 return nil, errors.Trace(err) 1323 } 1324 switch operatingSystem { 1325 case os.Windows: 1326 cloudcfg.AddScripts(runCmd) 1327 case os.Ubuntu: 1328 cloudcfg.SetSystemUpdate(true) 1329 cloudcfg.AddScripts("set -xe", runCmd) 1330 // DisableNetworkManagement can still disable the bridge(s) creation. 1331 if on, set := environ.Config().DisableNetworkManagement(); on && set { 1332 logger.Infof( 1333 "network management disabled - not using %q bridge for containers", 1334 instancecfg.DefaultBridgeName, 1335 ) 1336 break 1337 } 1338 cloudcfg.AddPackage("bridge-utils") 1339 cloudcfg.AddBootTextFile(bridgeScriptPath, bridgeScriptPython, 0755) 1340 cloudcfg.AddScripts(setupJujuNetworking()) 1341 } 1342 return cloudcfg, nil 1343 } 1344 1345 func (environ *maasEnviron) releaseNodes1(nodes gomaasapi.MAASObject, ids url.Values, recurse bool) error { 1346 err := ReleaseNodes(nodes, ids) 1347 if err == nil { 1348 return nil 1349 } 1350 maasErr, ok := errors.Cause(err).(gomaasapi.ServerError) 1351 if !ok { 1352 return errors.Annotate(err, "cannot release nodes") 1353 } 1354 1355 // StatusCode 409 means a node couldn't be released due to 1356 // a state conflict. Likely it's already released or disk 1357 // erasing. We're assuming an error of 409 *only* means it's 1358 // safe to assume the instance is already released. 1359 // MaaS also releases (or attempts) all nodes, and raises 1360 // a single error on failure. So even with an error 409, all 1361 // nodes have been released. 1362 if maasErr.StatusCode == 409 { 1363 logger.Infof("ignoring error while releasing nodes (%v); all nodes released OK", err) 1364 return nil 1365 } 1366 1367 // a status code of 400, 403 or 404 means one of the nodes 1368 // couldn't be found and none have been released. We have 1369 // to release all the ones we can individually. 1370 if maasErr.StatusCode != 400 && maasErr.StatusCode != 403 && maasErr.StatusCode != 404 { 1371 return errors.Annotate(err, "cannot release nodes") 1372 } 1373 if !recurse { 1374 // this node has already been released and we're golden 1375 return nil 1376 } 1377 1378 var lastErr error 1379 for _, id := range ids["nodes"] { 1380 idFilter := url.Values{} 1381 idFilter.Add("nodes", id) 1382 err := environ.releaseNodes1(nodes, idFilter, false) 1383 if err != nil { 1384 lastErr = err 1385 logger.Errorf("error while releasing node %v (%v)", id, err) 1386 } 1387 } 1388 return errors.Trace(lastErr) 1389 1390 } 1391 1392 func (environ *maasEnviron) releaseNodes2(ids []instance.Id, recurse bool) error { 1393 args := gomaasapi.ReleaseMachinesArgs{ 1394 SystemIDs: instanceIdsToSystemIDs(ids), 1395 Comment: "Released by Juju MAAS provider", 1396 } 1397 err := environ.maasController.ReleaseMachines(args) 1398 1399 switch { 1400 case err == nil: 1401 return nil 1402 case gomaasapi.IsCannotCompleteError(err): 1403 // CannotCompleteError means a node couldn't be released due to 1404 // a state conflict. Likely it's already released or disk 1405 // erasing. We're assuming this error *only* means it's 1406 // safe to assume the instance is already released. 1407 // MaaS also releases (or attempts) all nodes, and raises 1408 // a single error on failure. So even with an error 409, all 1409 // nodes have been released. 1410 logger.Infof("ignoring error while releasing nodes (%v); all nodes released OK", err) 1411 return nil 1412 case gomaasapi.IsBadRequestError(err), gomaasapi.IsPermissionError(err): 1413 // a status code of 400 or 403 means one of the nodes 1414 // couldn't be found and none have been released. We have to 1415 // release all the ones we can individually. 1416 if !recurse { 1417 // this node has already been released and we're golden 1418 return nil 1419 } 1420 return environ.releaseNodesIndividually(ids) 1421 1422 default: 1423 return errors.Annotatef(err, "cannot release nodes") 1424 } 1425 } 1426 1427 func (environ *maasEnviron) releaseNodesIndividually(ids []instance.Id) error { 1428 var lastErr error 1429 for _, id := range ids { 1430 err := environ.releaseNodes2([]instance.Id{id}, false) 1431 if err != nil { 1432 lastErr = err 1433 logger.Errorf("error while releasing node %v (%v)", id, err) 1434 } 1435 } 1436 return errors.Trace(lastErr) 1437 } 1438 1439 func instanceIdsToSystemIDs(ids []instance.Id) []string { 1440 systemIDs := make([]string, len(ids)) 1441 for index, id := range ids { 1442 systemIDs[index] = string(id) 1443 } 1444 return systemIDs 1445 } 1446 1447 // StopInstances is specified in the InstanceBroker interface. 1448 func (environ *maasEnviron) StopInstances(ids ...instance.Id) error { 1449 // Shortcut to exit quickly if 'instances' is an empty slice or nil. 1450 if len(ids) == 0 { 1451 return nil 1452 } 1453 1454 if environ.usingMAAS2() { 1455 err := environ.releaseNodes2(ids, true) 1456 if err != nil { 1457 return errors.Trace(err) 1458 } 1459 } else { 1460 nodes := environ.getMAASClient().GetSubObject("nodes") 1461 err := environ.releaseNodes1(nodes, getSystemIdValues("nodes", ids), true) 1462 if err != nil { 1463 return errors.Trace(err) 1464 } 1465 } 1466 return common.RemoveStateInstances(environ.Storage(), ids...) 1467 1468 } 1469 1470 // acquireInstances calls the MAAS API to list acquired nodes. 1471 // 1472 // The "ids" slice is a filter for specific instance IDs. 1473 // Due to how this works in the HTTP API, an empty "ids" 1474 // matches all instances (not none as you might expect). 1475 func (environ *maasEnviron) acquiredInstances(ids []instance.Id) ([]instance.Instance, error) { 1476 if !environ.usingMAAS2() { 1477 filter := getSystemIdValues("id", ids) 1478 filter.Add("agent_name", environ.ecfg().maasAgentName()) 1479 return environ.instances1(filter) 1480 } 1481 args := gomaasapi.MachinesArgs{ 1482 AgentName: environ.ecfg().maasAgentName(), 1483 SystemIDs: instanceIdsToSystemIDs(ids), 1484 } 1485 return environ.instances2(args) 1486 } 1487 1488 // instances calls the MAAS API to list nodes matching the given filter. 1489 func (environ *maasEnviron) instances1(filter url.Values) ([]instance.Instance, error) { 1490 nodeListing := environ.getMAASClient().GetSubObject("nodes") 1491 listNodeObjects, err := nodeListing.CallGet("list", filter) 1492 if err != nil { 1493 return nil, err 1494 } 1495 listNodes, err := listNodeObjects.GetArray() 1496 if err != nil { 1497 return nil, err 1498 } 1499 instances := make([]instance.Instance, len(listNodes)) 1500 for index, nodeObj := range listNodes { 1501 node, err := nodeObj.GetMAASObject() 1502 if err != nil { 1503 return nil, err 1504 } 1505 instances[index] = &maas1Instance{ 1506 maasObject: &node, 1507 environ: environ, 1508 statusGetter: environ.deploymentStatusOne, 1509 } 1510 } 1511 return instances, nil 1512 } 1513 1514 func (environ *maasEnviron) instances2(args gomaasapi.MachinesArgs) ([]instance.Instance, error) { 1515 machines, err := environ.maasController.Machines(args) 1516 if err != nil { 1517 return nil, errors.Trace(err) 1518 } 1519 instances := make([]instance.Instance, len(machines)) 1520 for index, machine := range machines { 1521 instances[index] = &maas2Instance{machine: machine} 1522 } 1523 return instances, nil 1524 } 1525 1526 // Instances returns the instance.Instance objects corresponding to the given 1527 // slice of instance.Id. The error is ErrNoInstances if no instances 1528 // were found. 1529 func (environ *maasEnviron) Instances(ids []instance.Id) ([]instance.Instance, error) { 1530 if len(ids) == 0 { 1531 // This would be treated as "return all instances" below, so 1532 // treat it as a special case. 1533 // The interface requires us to return this particular error 1534 // if no instances were found. 1535 return nil, environs.ErrNoInstances 1536 } 1537 instances, err := environ.acquiredInstances(ids) 1538 if err != nil { 1539 return nil, errors.Trace(err) 1540 } 1541 if len(instances) == 0 { 1542 return nil, environs.ErrNoInstances 1543 } 1544 1545 idMap := make(map[instance.Id]instance.Instance) 1546 for _, instance := range instances { 1547 idMap[instance.Id()] = instance 1548 } 1549 1550 missing := false 1551 result := make([]instance.Instance, len(ids)) 1552 for index, id := range ids { 1553 val, ok := idMap[id] 1554 if !ok { 1555 missing = true 1556 continue 1557 } 1558 result[index] = val 1559 } 1560 1561 if missing { 1562 return result, environs.ErrPartialInstances 1563 } 1564 return result, nil 1565 } 1566 1567 // AllocateAddress requests an address to be allocated for the given instance on 1568 // the given network. 1569 func (environ *maasEnviron) AllocateAddress(instId instance.Id, subnetId network.Id, addr *network.Address, macAddress, hostname string) (err error) { 1570 return errors.NotSupportedf("AllocateAddress") 1571 } 1572 1573 // ReleaseAddress releases a specific address previously allocated with 1574 // AllocateAddress. 1575 func (environ *maasEnviron) ReleaseAddress(instId instance.Id, _ network.Id, addr network.Address, macAddress, hostname string) (err error) { 1576 return errors.NotSupportedf("ReleaseAddress") 1577 } 1578 1579 // subnetsFromNode fetches all the subnets for a specific node. 1580 func (environ *maasEnviron) subnetsFromNode(nodeId string) ([]gomaasapi.JSONObject, error) { 1581 client := environ.getMAASClient().GetSubObject("nodes").GetSubObject(nodeId) 1582 json, err := client.CallGet("", nil) 1583 if err != nil { 1584 if maasErr, ok := errors.Cause(err).(gomaasapi.ServerError); ok && maasErr.StatusCode == http.StatusNotFound { 1585 return nil, errors.NotFoundf("intance %q", nodeId) 1586 } 1587 return nil, errors.Trace(err) 1588 } 1589 nodeMap, err := json.GetMap() 1590 if err != nil { 1591 return nil, errors.Trace(err) 1592 } 1593 interfacesArray, err := nodeMap["interface_set"].GetArray() 1594 if err != nil { 1595 return nil, errors.Trace(err) 1596 } 1597 var subnets []gomaasapi.JSONObject 1598 for _, iface := range interfacesArray { 1599 ifaceMap, err := iface.GetMap() 1600 if err != nil { 1601 return nil, errors.Trace(err) 1602 } 1603 linksArray, err := ifaceMap["links"].GetArray() 1604 if err != nil { 1605 return nil, errors.Trace(err) 1606 } 1607 for _, link := range linksArray { 1608 linkMap, err := link.GetMap() 1609 if err != nil { 1610 return nil, errors.Trace(err) 1611 } 1612 subnet, ok := linkMap["subnet"] 1613 if !ok { 1614 return nil, errors.New("subnet not found") 1615 } 1616 subnets = append(subnets, subnet) 1617 } 1618 } 1619 return subnets, nil 1620 } 1621 1622 // Deduce the allocatable portion of the subnet by subtracting the dynamic 1623 // range from the full subnet range. 1624 func (environ *maasEnviron) allocatableRangeForSubnet(cidr string, subnetId string) (net.IP, net.IP, error) { 1625 // Initialize the low and high bounds of the allocatable range to the 1626 // whole CIDR. Reduce the scope of this when we find the dynamic range. 1627 ip, ipnet, err := net.ParseCIDR(cidr) 1628 if err != nil { 1629 return nil, nil, errors.Trace(err) 1630 } 1631 // Skip IPv6 subnets until we can handle them correctly. 1632 if ip.To4() == nil && ip.To16() != nil { 1633 logger.Debugf("ignoring static IP range for IPv6 subnet %q", cidr) 1634 return nil, nil, nil 1635 } 1636 1637 // TODO(mfoord): needs updating to work with IPv6 as well. 1638 lowBound, err := network.IPv4ToDecimal(ip) 1639 if err != nil { 1640 return nil, nil, errors.Trace(err) 1641 } 1642 // Don't include the zero address in the allocatable bounds. 1643 lowBound = lowBound + 1 1644 ones, bits := ipnet.Mask.Size() 1645 zeros := bits - ones 1646 numIPs := uint32(1) << uint32(zeros) 1647 highBound := lowBound + numIPs - 2 1648 1649 client := environ.getMAASClient().GetSubObject("subnets").GetSubObject(subnetId) 1650 1651 json, err := client.CallGet("reserved_ip_ranges", nil) 1652 if err != nil { 1653 return nil, nil, errors.Trace(err) 1654 } 1655 jsonRanges, err := json.GetArray() 1656 if err != nil { 1657 return nil, nil, errors.Trace(err) 1658 } 1659 1660 for _, jsonRange := range jsonRanges { 1661 rangeMap, err := jsonRange.GetMap() 1662 if err != nil { 1663 return nil, nil, errors.Trace(err) 1664 } 1665 purposeArray, err := rangeMap["purpose"].GetArray() 1666 if err != nil { 1667 return nil, nil, errors.Trace(err) 1668 } 1669 found := false 1670 for _, jsonPurpose := range purposeArray { 1671 purpose, err := jsonPurpose.GetString() 1672 if err != nil { 1673 return nil, nil, errors.Trace(err) 1674 } 1675 if purpose == dynamicRange { 1676 found = true 1677 break 1678 } 1679 } 1680 if !found { 1681 // This is not the range we're looking for 1682 continue 1683 } 1684 1685 start, err := rangeMap["start"].GetString() 1686 if err != nil { 1687 return nil, nil, errors.Trace(err) 1688 } 1689 end, err := rangeMap["end"].GetString() 1690 if err != nil { 1691 return nil, nil, errors.Trace(err) 1692 } 1693 dynamicLow, err := network.IPv4ToDecimal(net.ParseIP(start)) 1694 if err != nil { 1695 return nil, nil, errors.Trace(err) 1696 } 1697 dynamicHigh, err := network.IPv4ToDecimal(net.ParseIP(end)) 1698 if err != nil { 1699 return nil, nil, errors.Trace(err) 1700 } 1701 1702 // We pick the larger of the two portions of the subnet around 1703 // the dynamic range. Either ending one below the start of the 1704 // dynamic range or starting one after the end. 1705 above := highBound - dynamicHigh 1706 below := dynamicLow - lowBound 1707 if above > below { 1708 lowBound = dynamicHigh + 1 1709 } else { 1710 highBound = dynamicLow - 1 1711 } 1712 break 1713 } 1714 return network.DecimalToIPv4(lowBound), network.DecimalToIPv4(highBound), nil 1715 } 1716 1717 // subnetFromJson populates a network.SubnetInfo from a gomaasapi.JSONObject 1718 // representing a single subnet. This can come from either the subnets api 1719 // endpoint or the node endpoint. 1720 func (environ *maasEnviron) subnetFromJson(subnet gomaasapi.JSONObject, spaceId network.Id) (network.SubnetInfo, error) { 1721 var subnetInfo network.SubnetInfo 1722 fields, err := subnet.GetMap() 1723 if err != nil { 1724 return subnetInfo, errors.Trace(err) 1725 } 1726 subnetIdFloat, err := fields["id"].GetFloat64() 1727 if err != nil { 1728 return subnetInfo, errors.Annotatef(err, "cannot get subnet Id") 1729 } 1730 subnetId := strconv.Itoa(int(subnetIdFloat)) 1731 cidr, err := fields["cidr"].GetString() 1732 if err != nil { 1733 return subnetInfo, errors.Annotatef(err, "cannot get cidr") 1734 } 1735 vid := 0 1736 vidField, ok := fields["vid"] 1737 if ok && !vidField.IsNil() { 1738 // vid is optional, so assume it's 0 when missing or nil. 1739 vidFloat, err := vidField.GetFloat64() 1740 if err != nil { 1741 return subnetInfo, errors.Errorf("cannot get vlan tag: %v", err) 1742 } 1743 vid = int(vidFloat) 1744 } 1745 allocatableLow, allocatableHigh, err := environ.allocatableRangeForSubnet(cidr, subnetId) 1746 if err != nil { 1747 return subnetInfo, errors.Trace(err) 1748 } 1749 1750 subnetInfo = network.SubnetInfo{ 1751 ProviderId: network.Id(subnetId), 1752 VLANTag: vid, 1753 CIDR: cidr, 1754 SpaceProviderId: spaceId, 1755 AllocatableIPLow: allocatableLow, 1756 AllocatableIPHigh: allocatableHigh, 1757 } 1758 return subnetInfo, nil 1759 } 1760 1761 // filteredSubnets fetches subnets, filtering optionally by nodeId and/or a 1762 // slice of subnetIds. If subnetIds is empty then all subnets for that node are 1763 // fetched. If nodeId is empty, all subnets are returned (filtering by subnetIds 1764 // first, if set). 1765 func (environ *maasEnviron) filteredSubnets(nodeId string, subnetIds []network.Id) ([]network.SubnetInfo, error) { 1766 var jsonNets []gomaasapi.JSONObject 1767 var err error 1768 if nodeId != "" { 1769 jsonNets, err = environ.subnetsFromNode(nodeId) 1770 if err != nil { 1771 return nil, errors.Trace(err) 1772 } 1773 } else { 1774 jsonNets, err = environ.fetchAllSubnets() 1775 if err != nil { 1776 return nil, errors.Trace(err) 1777 } 1778 } 1779 subnetIdSet := make(map[string]bool) 1780 for _, netId := range subnetIds { 1781 subnetIdSet[string(netId)] = false 1782 } 1783 1784 subnetsMap, err := environ.subnetToSpaceIds() 1785 if err != nil { 1786 return nil, errors.Trace(err) 1787 } 1788 1789 subnets := []network.SubnetInfo{} 1790 for _, jsonNet := range jsonNets { 1791 fields, err := jsonNet.GetMap() 1792 if err != nil { 1793 return nil, err 1794 } 1795 subnetIdFloat, err := fields["id"].GetFloat64() 1796 if err != nil { 1797 return nil, errors.Annotatef(err, "cannot get subnet Id: %v") 1798 } 1799 subnetId := strconv.Itoa(int(subnetIdFloat)) 1800 // If we're filtering by subnet id check if this subnet is one 1801 // we're looking for. 1802 if len(subnetIds) != 0 { 1803 _, ok := subnetIdSet[subnetId] 1804 if !ok { 1805 // This id is not what we're looking for. 1806 continue 1807 } 1808 subnetIdSet[subnetId] = true 1809 } 1810 cidr, err := fields["cidr"].GetString() 1811 if err != nil { 1812 return nil, errors.Annotatef(err, "cannot get subnet Id") 1813 } 1814 spaceId, ok := subnetsMap[cidr] 1815 if !ok { 1816 logger.Warningf("unrecognised subnet: %q, setting empty space id", cidr) 1817 spaceId = network.UnknownId 1818 } 1819 1820 subnetInfo, err := environ.subnetFromJson(jsonNet, spaceId) 1821 if err != nil { 1822 return nil, errors.Trace(err) 1823 } 1824 subnets = append(subnets, subnetInfo) 1825 logger.Tracef("found subnet with info %#v", subnetInfo) 1826 } 1827 return subnets, checkNotFound(subnetIdSet) 1828 } 1829 1830 func (environ *maasEnviron) getInstance(instId instance.Id) (instance.Instance, error) { 1831 instances, err := environ.acquiredInstances([]instance.Id{instId}) 1832 if err != nil { 1833 // This path can never trigger on MAAS 2, but MAAS 2 doesn't 1834 // return an error for a machine not found, it just returns 1835 // empty results. The clause below catches that. 1836 if maasErr, ok := errors.Cause(err).(gomaasapi.ServerError); ok && maasErr.StatusCode == http.StatusNotFound { 1837 return nil, errors.NotFoundf("instance %q", instId) 1838 } 1839 return nil, errors.Annotatef(err, "getting instance %q", instId) 1840 } 1841 if len(instances) == 0 { 1842 return nil, errors.NotFoundf("instance %q", instId) 1843 } 1844 inst := instances[0] 1845 return inst, nil 1846 } 1847 1848 // fetchAllSubnets calls the MAAS subnets API to get all subnets and returns the 1849 // JSON response or an error. If capNetworkDeploymentUbuntu is not available, an 1850 // error satisfying errors.IsNotSupported will be returned. 1851 func (environ *maasEnviron) fetchAllSubnets() ([]gomaasapi.JSONObject, error) { 1852 client := environ.getMAASClient().GetSubObject("subnets") 1853 1854 json, err := client.CallGet("", nil) 1855 if err != nil { 1856 return nil, errors.Trace(err) 1857 } 1858 return json.GetArray() 1859 } 1860 1861 // subnetToSpaceIds fetches the spaces from MAAS and builds a map of subnets to 1862 // space ids. 1863 func (environ *maasEnviron) subnetToSpaceIds() (map[string]network.Id, error) { 1864 subnetsMap := make(map[string]network.Id) 1865 spaces, err := environ.Spaces() 1866 if err != nil { 1867 return subnetsMap, errors.Trace(err) 1868 } 1869 for _, space := range spaces { 1870 for _, subnet := range space.Subnets { 1871 subnetsMap[subnet.CIDR] = space.ProviderId 1872 } 1873 } 1874 return subnetsMap, nil 1875 } 1876 1877 // Spaces returns all the spaces, that have subnets, known to the provider. 1878 // Space name is not filled in as the provider doesn't know the juju name for 1879 // the space. 1880 func (environ *maasEnviron) Spaces() ([]network.SpaceInfo, error) { 1881 if environ.usingMAAS2() { 1882 return environ.spaces2() 1883 } else { 1884 return environ.spaces1() 1885 } 1886 } 1887 1888 func (environ *maasEnviron) spaces1() ([]network.SpaceInfo, error) { 1889 spacesClient := environ.getMAASClient().GetSubObject("spaces") 1890 spacesJson, err := spacesClient.CallGet("", nil) 1891 if err != nil { 1892 return nil, errors.Trace(err) 1893 } 1894 spacesArray, err := spacesJson.GetArray() 1895 if err != nil { 1896 return nil, errors.Trace(err) 1897 } 1898 spaces := []network.SpaceInfo{} 1899 for _, spaceJson := range spacesArray { 1900 spaceMap, err := spaceJson.GetMap() 1901 if err != nil { 1902 return nil, errors.Trace(err) 1903 } 1904 providerIdRaw, err := spaceMap["id"].GetFloat64() 1905 if err != nil { 1906 return nil, errors.Trace(err) 1907 } 1908 providerId := network.Id(fmt.Sprintf("%.0f", providerIdRaw)) 1909 name, err := spaceMap["name"].GetString() 1910 if err != nil { 1911 return nil, errors.Trace(err) 1912 } 1913 1914 space := network.SpaceInfo{Name: name, ProviderId: providerId} 1915 subnetsArray, err := spaceMap["subnets"].GetArray() 1916 if err != nil { 1917 return nil, errors.Trace(err) 1918 } 1919 for _, subnetJson := range subnetsArray { 1920 subnet, err := environ.subnetFromJson(subnetJson, providerId) 1921 if err != nil { 1922 return nil, errors.Trace(err) 1923 } 1924 space.Subnets = append(space.Subnets, subnet) 1925 } 1926 // Skip spaces with no subnets. 1927 if len(space.Subnets) > 0 { 1928 spaces = append(spaces, space) 1929 } 1930 } 1931 return spaces, nil 1932 } 1933 1934 func (environ *maasEnviron) spaces2() ([]network.SpaceInfo, error) { 1935 spaces, err := environ.maasController.Spaces() 1936 if err != nil { 1937 return nil, errors.Trace(err) 1938 } 1939 var result []network.SpaceInfo 1940 for _, space := range spaces { 1941 if len(space.Subnets()) == 0 { 1942 continue 1943 } 1944 outSpace := network.SpaceInfo{ 1945 Name: space.Name(), 1946 ProviderId: network.Id(strconv.Itoa(space.ID())), 1947 Subnets: make([]network.SubnetInfo, len(space.Subnets())), 1948 } 1949 for i, subnet := range space.Subnets() { 1950 subnetInfo := network.SubnetInfo{ 1951 ProviderId: network.Id(strconv.Itoa(subnet.ID())), 1952 VLANTag: subnet.VLAN().VID(), 1953 CIDR: subnet.CIDR(), 1954 SpaceProviderId: network.Id(strconv.Itoa(space.ID())), 1955 } 1956 outSpace.Subnets[i] = subnetInfo 1957 } 1958 result = append(result, outSpace) 1959 } 1960 return result, nil 1961 } 1962 1963 // Subnets returns basic information about the specified subnets known 1964 // by the provider for the specified instance. subnetIds must not be 1965 // empty. Implements NetworkingEnviron.Subnets. 1966 func (environ *maasEnviron) Subnets(instId instance.Id, subnetIds []network.Id) ([]network.SubnetInfo, error) { 1967 if environ.usingMAAS2() { 1968 return environ.subnets2(instId, subnetIds) 1969 } 1970 return environ.subnets1(instId, subnetIds) 1971 } 1972 1973 func (environ *maasEnviron) subnets1(instId instance.Id, subnetIds []network.Id) ([]network.SubnetInfo, error) { 1974 var nodeId string 1975 if instId != instance.UnknownId { 1976 inst, err := environ.getInstance(instId) 1977 if err != nil { 1978 return nil, errors.Trace(err) 1979 } 1980 nodeId, err = environ.nodeIdFromInstance(inst) 1981 if err != nil { 1982 return nil, errors.Trace(err) 1983 } 1984 } 1985 subnets, err := environ.filteredSubnets(nodeId, subnetIds) 1986 if err != nil { 1987 return nil, errors.Trace(err) 1988 } 1989 if instId != instance.UnknownId { 1990 logger.Debugf("instance %q has subnets %v", instId, subnets) 1991 } else { 1992 logger.Debugf("found subnets %v", subnets) 1993 } 1994 1995 return subnets, nil 1996 } 1997 1998 func (environ *maasEnviron) subnets2(instId instance.Id, subnetIds []network.Id) ([]network.SubnetInfo, error) { 1999 subnets := []network.SubnetInfo{} 2000 if instId == instance.UnknownId { 2001 spaces, err := environ.Spaces() 2002 if err != nil { 2003 return nil, errors.Trace(err) 2004 } 2005 for _, space := range spaces { 2006 subnets = append(subnets, space.Subnets...) 2007 } 2008 } else { 2009 var err error 2010 subnets, err = environ.filteredSubnets2(instId) 2011 if err != nil { 2012 return nil, errors.Trace(err) 2013 } 2014 } 2015 2016 if len(subnetIds) == 0 { 2017 return subnets, nil 2018 } 2019 result := []network.SubnetInfo{} 2020 subnetMap := make(map[string]bool) 2021 for _, subnetId := range subnetIds { 2022 subnetMap[string(subnetId)] = false 2023 } 2024 for _, subnet := range subnets { 2025 _, ok := subnetMap[string(subnet.ProviderId)] 2026 if !ok { 2027 // This id is not what we're looking for. 2028 continue 2029 } 2030 subnetMap[string(subnet.ProviderId)] = true 2031 result = append(result, subnet) 2032 } 2033 2034 return result, checkNotFound(subnetMap) 2035 } 2036 2037 func (environ *maasEnviron) filteredSubnets2(instId instance.Id) ([]network.SubnetInfo, error) { 2038 args := gomaasapi.MachinesArgs{ 2039 AgentName: environ.ecfg().maasAgentName(), 2040 SystemIDs: []string{string(instId)}, 2041 } 2042 machines, err := environ.maasController.Machines(args) 2043 if err != nil { 2044 return nil, errors.Trace(err) 2045 } 2046 if len(machines) == 0 { 2047 return nil, errors.NotFoundf("machine %v", instId) 2048 } else if len(machines) > 1 { 2049 return nil, errors.Errorf("unexpected response getting machine details %v: %v", instId, machines) 2050 } 2051 2052 machine := machines[0] 2053 spaceMap, err := environ.buildSpaceMap() 2054 if err != nil { 2055 return nil, errors.Trace(err) 2056 } 2057 result := []network.SubnetInfo{} 2058 for _, iface := range machine.InterfaceSet() { 2059 for _, link := range iface.Links() { 2060 subnet := link.Subnet() 2061 space, ok := spaceMap[subnet.Space()] 2062 if !ok { 2063 return nil, errors.Errorf("missing space %v on subnet %v", subnet.Space(), subnet.CIDR()) 2064 } 2065 subnetInfo := network.SubnetInfo{ 2066 ProviderId: network.Id(strconv.Itoa(subnet.ID())), 2067 VLANTag: subnet.VLAN().VID(), 2068 CIDR: subnet.CIDR(), 2069 SpaceProviderId: space.ProviderId, 2070 } 2071 result = append(result, subnetInfo) 2072 } 2073 } 2074 return result, nil 2075 } 2076 2077 func checkNotFound(subnetIdSet map[string]bool) error { 2078 notFound := []string{} 2079 for subnetId, found := range subnetIdSet { 2080 if !found { 2081 notFound = append(notFound, string(subnetId)) 2082 } 2083 } 2084 if len(notFound) != 0 { 2085 return errors.Errorf("failed to find the following subnets: %v", strings.Join(notFound, ", ")) 2086 } 2087 return nil 2088 } 2089 2090 // AllInstances returns all the instance.Instance in this provider. 2091 func (environ *maasEnviron) AllInstances() ([]instance.Instance, error) { 2092 return environ.acquiredInstances(nil) 2093 } 2094 2095 // Storage is defined by the Environ interface. 2096 func (env *maasEnviron) Storage() storage.Storage { 2097 env.ecfgMutex.Lock() 2098 defer env.ecfgMutex.Unlock() 2099 return env.storageUnlocked 2100 } 2101 2102 func (environ *maasEnviron) Destroy() error { 2103 if environ.ecfg().maasAgentName() == "" { 2104 logger.Warningf("No MAAS agent name specified.\n\n" + 2105 "The environment is either not running or from a very early Juju version.\n" + 2106 "It is not safe to release all MAAS instances without an agent name.\n" + 2107 "If the environment is still running, please manually decomission the MAAS machines.") 2108 return errors.New("unsafe destruction") 2109 } 2110 if err := common.Destroy(environ); err != nil { 2111 return errors.Trace(err) 2112 } 2113 return environ.Storage().RemoveAll() 2114 } 2115 2116 // MAAS does not do firewalling so these port methods do nothing. 2117 func (*maasEnviron) OpenPorts([]network.PortRange) error { 2118 logger.Debugf("unimplemented OpenPorts() called") 2119 return nil 2120 } 2121 2122 func (*maasEnviron) ClosePorts([]network.PortRange) error { 2123 logger.Debugf("unimplemented ClosePorts() called") 2124 return nil 2125 } 2126 2127 func (*maasEnviron) Ports() ([]network.PortRange, error) { 2128 logger.Debugf("unimplemented Ports() called") 2129 return nil, nil 2130 } 2131 2132 func (*maasEnviron) Provider() environs.EnvironProvider { 2133 return &providerInstance 2134 } 2135 2136 func (environ *maasEnviron) nodeIdFromInstance(inst instance.Instance) (string, error) { 2137 maasInst := inst.(*maas1Instance) 2138 maasObj := maasInst.maasObject 2139 nodeId, err := maasObj.GetField("system_id") 2140 if err != nil { 2141 return "", err 2142 } 2143 return nodeId, err 2144 } 2145 2146 func (env *maasEnviron) AllocateContainerAddresses(hostInstanceID instance.Id, preparedInfo []network.InterfaceInfo) ([]network.InterfaceInfo, error) { 2147 if len(preparedInfo) == 0 { 2148 return nil, errors.Errorf("no prepared info to allocate") 2149 } 2150 logger.Debugf("using prepared container info: %+v", preparedInfo) 2151 2152 subnetCIDRToVLANID := make(map[string]string) 2153 subnetsAPI := env.getMAASClient().GetSubObject("subnets") 2154 result, err := subnetsAPI.CallGet("", nil) 2155 if err != nil { 2156 return nil, errors.Annotate(err, "cannot get subnets") 2157 } 2158 subnetsJSON, err := getJSONBytes(result) 2159 if err != nil { 2160 return nil, errors.Annotate(err, "cannot get subnets JSON") 2161 } 2162 var subnets []maasSubnet 2163 if err := json.Unmarshal(subnetsJSON, &subnets); err != nil { 2164 return nil, errors.Annotate(err, "cannot parse subnets JSON") 2165 } 2166 for _, subnet := range subnets { 2167 subnetCIDRToVLANID[subnet.CIDR] = strconv.Itoa(subnet.VLAN.ID) 2168 } 2169 2170 var primaryNICInfo network.InterfaceInfo 2171 for _, nic := range preparedInfo { 2172 if nic.InterfaceName == "eth0" { 2173 primaryNICInfo = nic 2174 break 2175 } 2176 } 2177 if primaryNICInfo.InterfaceName == "" { 2178 return nil, errors.Errorf("cannot find primary interface for container") 2179 } 2180 logger.Debugf("primary device NIC prepared info: %+v", primaryNICInfo) 2181 2182 primaryMACAddress := primaryNICInfo.MACAddress 2183 containerDevice, err := env.createDevice(hostInstanceID, primaryMACAddress) 2184 if err != nil { 2185 return nil, errors.Annotate(err, "cannot create device for container") 2186 } 2187 deviceID := instance.Id(containerDevice.ResourceURI) 2188 logger.Debugf("created device %q with primary MAC address %q", deviceID, primaryMACAddress) 2189 2190 interfaces, err := env.deviceInterfaces(deviceID) 2191 if err != nil { 2192 return nil, errors.Annotate(err, "cannot get device interfaces") 2193 } 2194 if len(interfaces) != 1 { 2195 return nil, errors.Errorf("expected 1 device interface, got %d", len(interfaces)) 2196 } 2197 2198 primaryNICName := interfaces[0].Name 2199 primaryNICID := strconv.Itoa(interfaces[0].ID) 2200 primaryNICSubnetCIDR := primaryNICInfo.CIDR 2201 primaryNICVLANID := subnetCIDRToVLANID[primaryNICSubnetCIDR] 2202 updatedPrimaryNIC, err := env.updateDeviceInterface(deviceID, primaryNICID, primaryNICName, primaryMACAddress, primaryNICVLANID) 2203 if err != nil { 2204 return nil, errors.Annotatef(err, "cannot update device interface %q", interfaces[0].Name) 2205 } 2206 logger.Debugf("device %q primary interface %q updated: %+v", containerDevice.SystemID, primaryNICName, updatedPrimaryNIC) 2207 2208 deviceNICIDs := make([]string, len(preparedInfo)) 2209 nameToParentName := make(map[string]string) 2210 for i, nic := range preparedInfo { 2211 maasNICID := "" 2212 nameToParentName[nic.InterfaceName] = nic.ParentInterfaceName 2213 if nic.InterfaceName != primaryNICName { 2214 nicVLANID := subnetCIDRToVLANID[nic.CIDR] 2215 createdNIC, err := env.createDeviceInterface(deviceID, nic.InterfaceName, nic.MACAddress, nicVLANID) 2216 if err != nil { 2217 return nil, errors.Annotate(err, "creating device interface") 2218 } 2219 maasNICID = strconv.Itoa(createdNIC.ID) 2220 logger.Debugf("created device interface: %+v", createdNIC) 2221 } else { 2222 maasNICID = primaryNICID 2223 } 2224 deviceNICIDs[i] = maasNICID 2225 subnetID := string(nic.ProviderSubnetId) 2226 2227 linkedInterface, err := env.linkDeviceInterfaceToSubnet(deviceID, maasNICID, subnetID, modeStatic) 2228 if err != nil { 2229 return nil, errors.Annotate(err, "cannot link device interface to subnet") 2230 } 2231 logger.Debugf("linked device interface to subnet: %+v", linkedInterface) 2232 } 2233 finalInterfaces, err := env.deviceInterfaceInfo(deviceID, nameToParentName) 2234 if err != nil { 2235 return nil, errors.Annotate(err, "cannot get device interfaces") 2236 } 2237 logger.Debugf("allocated device interfaces: %+v", finalInterfaces) 2238 return finalInterfaces, nil 2239 }