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