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