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