github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/oracle/environ.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package oracle 5 6 import ( 7 "fmt" 8 "math/rand" 9 "strconv" 10 "strings" 11 "sync" 12 "time" 13 14 "github.com/juju/clock" 15 "github.com/juju/collections/set" 16 "github.com/juju/errors" 17 oci "github.com/juju/go-oracle-cloud/api" 18 ociCommon "github.com/juju/go-oracle-cloud/common" 19 ociResponse "github.com/juju/go-oracle-cloud/response" 20 "github.com/juju/os" 21 jujuseries "github.com/juju/os/series" 22 "github.com/juju/utils/arch" 23 "github.com/juju/version" 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/core/constraints" 29 "github.com/juju/juju/core/instance" 30 "github.com/juju/juju/environs" 31 "github.com/juju/juju/environs/config" 32 "github.com/juju/juju/environs/context" 33 envinstance "github.com/juju/juju/environs/instances" 34 "github.com/juju/juju/environs/tags" 35 "github.com/juju/juju/network" 36 "github.com/juju/juju/provider/common" 37 commonProvider "github.com/juju/juju/provider/oracle/common" 38 oraclenet "github.com/juju/juju/provider/oracle/network" 39 "github.com/juju/juju/tools" 40 ) 41 42 // OracleEnviron implements the environs.Environ interface 43 type OracleEnviron struct { 44 environs.Networking 45 oraclenet.Firewaller 46 47 mutex *sync.Mutex 48 p *EnvironProvider 49 spec environs.CloudSpec 50 cfg *config.Config 51 client EnvironAPI 52 namespace instance.Namespace 53 clock clock.Clock 54 rand *rand.Rand 55 } 56 57 // EnvironAPI provides interface to access and make operation 58 // inside a oracle environ 59 type EnvironAPI interface { 60 commonProvider.Instancer 61 commonProvider.InstanceAPI 62 commonProvider.Authenticater 63 commonProvider.Shaper 64 commonProvider.Imager 65 commonProvider.IpReservationAPI 66 commonProvider.IpAssociationAPI 67 commonProvider.IpNetworkExchanger 68 commonProvider.IpNetworker 69 commonProvider.VnicSetAPI 70 71 commonProvider.RulesAPI 72 commonProvider.AclAPI 73 commonProvider.SecIpAPI 74 commonProvider.IpAddressPrefixSetAPI 75 commonProvider.SecListAPI 76 commonProvider.ApplicationsAPI 77 commonProvider.SecRulesAPI 78 commonProvider.AssociationAPI 79 80 StorageAPI 81 } 82 83 // AvailabilityZones is defined in the common.ZonedEnviron interface 84 func (o *OracleEnviron) AvailabilityZones(ctx context.ProviderCallContext) ([]common.AvailabilityZone, error) { 85 return []common.AvailabilityZone{ 86 oraclenet.NewAvailabilityZone("default"), 87 }, nil 88 } 89 90 // InstanceAvailabilityzoneNames is defined in the common.ZonedEnviron interface 91 func (o *OracleEnviron) InstanceAvailabilityZoneNames(ctx context.ProviderCallContext, ids []instance.Id) ([]string, error) { 92 instances, err := o.Instances(ctx, ids) 93 if err != nil && err != environs.ErrPartialInstances { 94 return nil, err 95 } 96 zones := make([]string, len(instances)) 97 for idx := range instances { 98 zones[idx] = "default" 99 } 100 return zones, nil 101 } 102 103 // NewOracleEnviron returns a new OracleEnviron 104 func NewOracleEnviron(p *EnvironProvider, args environs.OpenParams, client EnvironAPI, c clock.Clock) (env *OracleEnviron, err error) { 105 if client == nil { 106 return nil, errors.NotFoundf("oracle client") 107 } 108 if p == nil { 109 return nil, errors.NotFoundf("environ proivder") 110 } 111 env = &OracleEnviron{ 112 p: p, 113 spec: args.Cloud, 114 cfg: args.Config, 115 mutex: &sync.Mutex{}, 116 client: client, 117 clock: c, 118 } 119 env.namespace, err = instance.NewNamespace(env.cfg.UUID()) 120 if err != nil { 121 return nil, errors.Trace(err) 122 } 123 env.Firewaller = oraclenet.NewFirewall(env, client, c) 124 env.Networking = oraclenet.NewEnviron(client, env) 125 126 source := rand.NewSource(env.clock.Now().UTC().UnixNano()) 127 r := rand.New(source) 128 env.rand = r 129 130 return env, nil 131 } 132 133 // PrepareForBootstrap is part of the Environ interface. 134 func (o *OracleEnviron) PrepareForBootstrap(ctx environs.BootstrapContext) error { 135 if ctx.ShouldVerifyCredentials() { 136 logger.Infof("Logging into the oracle cloud infrastructure") 137 if err := o.client.Authenticate(); err != nil { 138 return errors.Trace(err) 139 } 140 } 141 142 return nil 143 } 144 145 // Bootstrap is part of the Environ interface. 146 func (o *OracleEnviron) Bootstrap(ctx environs.BootstrapContext, callCtx context.ProviderCallContext, args environs.BootstrapParams) (*environs.BootstrapResult, error) { 147 return common.Bootstrap(ctx, o, callCtx, args) 148 } 149 150 // Create is part of the Environ interface. 151 func (o *OracleEnviron) Create(ctx context.ProviderCallContext, params environs.CreateParams) error { 152 if err := o.client.Authenticate(); err != nil { 153 return errors.Trace(err) 154 } 155 156 return nil 157 } 158 159 // AdoptResources is part of the Environ interface. 160 func (e *OracleEnviron) AdoptResources(ctx context.ProviderCallContext, controllerUUID string, fromVersion version.Number) error { 161 //TODO (gsamfira): Implement AdoptResources. Controller tag for all resources needs to 162 // be changed 163 return nil 164 } 165 166 // getCloudInitConfig returns a CloudConfig instance. this function only exists because of a 167 // limitation that currently exists in cloud-init see bug report: 168 // https://bugs.launchpad.net/cloud-init/+bug/1675370 169 func (e *OracleEnviron) getCloudInitConfig(series string, networks map[string]oci.Networker) (cloudinit.CloudConfig, error) { 170 // TODO (gsamfira): remove this function when the above mention bug is fixed 171 cloudcfg, err := cloudinit.New(series) 172 if err != nil { 173 return nil, errors.Annotate(err, "cannot create cloudinit template") 174 } 175 operatingSystem, err := jujuseries.GetOSFromSeries(series) 176 if err != nil { 177 return nil, errors.Trace(err) 178 } 179 renderer := cloudcfg.ShellRenderer() 180 // by default windows has all NICs set on DHCP, so no need to configure 181 // anything. 182 // gsamfira: I could not find a CentOS image in the oracle compute 183 // marketplace, so not doing anything CentOS specific here. 184 var scripts []string 185 switch operatingSystem { 186 case os.Ubuntu: 187 for key := range networks { 188 if key == defaultNicName { 189 continue 190 } 191 fileBaseName := fmt.Sprintf("%s.cfg", key) 192 fileContents := fmt.Sprintf(ubuntuInterfaceTemplate, key, key) 193 fileName := renderer.Join( 194 renderer.FromSlash(interfacesConfigDir), fileBaseName) 195 scripts = append(scripts, 196 renderer.WriteFile(fileName, []byte(fileContents))...) 197 scripts = append(scripts, fmt.Sprintf("/sbin/ifup %s", key)) 198 scripts = append(scripts, "") 199 200 } 201 if len(scripts) > 0 { 202 cloudcfg.AddScripts(strings.Join(scripts, "\n")) 203 } 204 } 205 return cloudcfg, nil 206 } 207 208 // buildSpacesMap builds a map with juju converted names from provider space names 209 // 210 // shamelessly copied from the MAAS provider 211 func (e *OracleEnviron) buildSpacesMap(ctx context.ProviderCallContext) (map[string]network.SpaceInfo, map[string]string, error) { 212 empty := set.Strings{} 213 providerIdMap := map[string]string{} 214 // NOTE (gsamfira): This seems brittle to me, and I would much rather get this 215 // from state, as that information should already be there from the discovered spaces 216 // and that is the information that gets presented to the user when running: 217 // juju spaces 218 // However I have not found a clean way to access that info from the provider, 219 // without creating a facade. Someone with more knowledge on this might be able to chip in. 220 spaces, err := e.Spaces(ctx) 221 if err != nil { 222 return nil, providerIdMap, errors.Trace(err) 223 } 224 spaceMap := make(map[string]network.SpaceInfo) 225 for _, space := range spaces { 226 jujuName := network.ConvertSpaceName(space.Name, empty) 227 spaceMap[jujuName] = space 228 empty.Add(jujuName) 229 providerIdMap[string(space.ProviderId)] = space.Name 230 } 231 return spaceMap, providerIdMap, nil 232 233 } 234 235 func (e *OracleEnviron) getInstanceNetworks( 236 ctx context.ProviderCallContext, 237 args environs.StartInstanceParams, 238 secLists, vnicSets []string, 239 ) (map[string]oci.Networker, error) { 240 241 // gsamfira: We add a default NIC attached o the shared network provided 242 // by the oracle cloud. This NIC is used for outbound traffic. 243 // While you can attach just a VNIC to the instance, and assign a 244 // public IP to it, I have not been able to get any outbound traffic 245 // through the thing. 246 // NOTE (gsamfira): The NIC ordering inside the instance is determined 247 // by the order in which the API returns the network interfaces. It seems 248 // the API orders the NICs alphanumerically. When adding new network 249 // interfaces, make sure they are ordered to come after eth0 250 networking := map[string]oci.Networker{ 251 defaultNicName: oci.SharedNetwork{ 252 Seclists: secLists, 253 }, 254 } 255 spaces := set.Strings{} 256 if s := args.Constraints.IncludeSpaces(); len(s) != 0 { 257 for _, val := range s { 258 logger.Debugf("Adding space from constraints %s", val) 259 spaces.Add(val) 260 } 261 } 262 263 // NOTE (gsamfira): The way spaces works seems really iffy to me. We currently 264 // rely on two sources of truth to determine the spaces to which we should attach 265 // an instance. This becomes evident then we try to use --constraints and --bind 266 // to specify the space. 267 // In both cases, the user specifies the space name that comes up in juju spaces 268 // When fetching the space using constraints inside the provider, we get the actual 269 // space name passed in by the user. However, when we go through args.EndpointBindings 270 // we get a mapping between the endpoint binding name (not the space name) and the ProviderID 271 // of the space (unlike constraints where you get the name). All this without being able to access 272 // the source of truth the user used when selecting the space, which is juju state. So we need to: 273 // 1) fetch spaces from the provider 274 // 2) parse the name and mutate it to match what the discover spaces worker does (and hope 275 // that the API returns the spaces in the same order every time) 276 // 3) create a map of those spaces both name-->space and providerID-->name to be able to match 277 // both cases. This all seems really brittle to me. 278 providerSpaces, providerIds, err := e.buildSpacesMap(ctx) 279 if err != nil { 280 return map[string]oci.Networker{}, err 281 } 282 283 if len(args.EndpointBindings) != 0 { 284 for _, providerID := range args.EndpointBindings { 285 if name, ok := providerIds[string(providerID)]; ok { 286 logger.Debugf("Adding space from bindings %s", name) 287 spaces.Add(name) 288 } 289 } 290 } 291 292 //start from 1. eth0 is the default nic that gets attached by default. 293 idx := 1 294 for _, space := range spaces.Values() { 295 providerSpace, ok := providerSpaces[space] 296 if !ok { 297 return map[string]oci.Networker{}, 298 errors.Errorf("Could not find space %q", space) 299 } 300 if len(providerSpace.Subnets) == 0 { 301 return map[string]oci.Networker{}, 302 errors.Errorf("No usable subnets found in space %q", space) 303 } 304 305 ipNet := providerSpace.Subnets[e.rand.Intn(len(providerSpace.Subnets))] 306 vnic := oci.IPNetwork{ 307 Ipnetwork: string(ipNet.ProviderId), 308 Vnicsets: vnicSets, 309 } 310 nicName := fmt.Sprintf("%s%s", nicPrefix, strconv.Itoa(idx)) 311 networking[nicName] = vnic 312 idx++ 313 } 314 logger.Debugf("returning networking interfaces: %v", networking) 315 return networking, nil 316 } 317 318 // StartInstance is part of the InstanceBroker interface. 319 func (o *OracleEnviron) StartInstance(ctx context.ProviderCallContext, args environs.StartInstanceParams) (*environs.StartInstanceResult, error) { 320 if args.ControllerUUID == "" { 321 return nil, errors.NotFoundf("Controller UUID") 322 } 323 324 series := args.Tools.OneSeries() 325 arches := args.Tools.Arches() 326 327 // take all instance types from the oracle cloud provider 328 types, err := instanceTypes(o.client) 329 if err != nil { 330 return nil, errors.Trace(err) 331 } 332 333 // check if we find an image that is compliant with the 334 // constraints provided in the oracle cloud account 335 if args.ImageMetadata, err = checkImageList(o.client); err != nil { 336 return nil, errors.Trace(err) 337 } 338 339 // find the best suitable instance based on 340 // the oracle cloud instance types, 341 // the images that already matched the juju constrains 342 spec, imagelist, err := findInstanceSpec( 343 o.client, 344 args.ImageMetadata, 345 types, 346 &envinstance.InstanceConstraint{ 347 Region: o.spec.Region, 348 Series: series, 349 Arches: arches, 350 Constraints: args.Constraints, 351 }, 352 ) 353 if err != nil { 354 return nil, errors.Trace(err) 355 } 356 357 tools, err := args.Tools.Match(tools.Filter{Arch: spec.Image.Arch}) 358 if err != nil { 359 return nil, errors.Trace(err) 360 } 361 logger.Tracef("agent binaries: %v", tools) 362 if err = args.InstanceConfig.SetTools(tools); err != nil { 363 return nil, errors.Trace(err) 364 } 365 366 if err = instancecfg.FinishInstanceConfig( 367 args.InstanceConfig, 368 o.Config(), 369 ); err != nil { 370 return nil, errors.Trace(err) 371 } 372 373 hostname, err := o.namespace.Hostname(args.InstanceConfig.MachineId) 374 if err != nil { 375 return nil, errors.Trace(err) 376 } 377 378 machineName := o.client.ComposeName(hostname) 379 imageName := o.client.ComposeName(imagelist) 380 381 tags := make([]string, 0, len(args.InstanceConfig.Tags)+1) 382 for k, v := range args.InstanceConfig.Tags { 383 if k == "" || v == "" { 384 continue 385 } 386 t := tagValue{k, v} 387 tags = append(tags, t.String()) 388 } 389 tags = append(tags, machineName) 390 391 var apiPort int 392 var desiredStatus ociCommon.InstanceState 393 if args.InstanceConfig.Controller != nil { 394 apiPort = args.InstanceConfig.Controller.Config.APIPort() 395 desiredStatus = ociCommon.StateRunning 396 } else { 397 // All ports are the same so pick the first. 398 apiPort = args.InstanceConfig.APIInfo.Ports()[0] 399 desiredStatus = ociCommon.StateStarting 400 } 401 402 // create a new seclists 403 secLists, err := o.CreateMachineSecLists( 404 args.InstanceConfig.MachineId, apiPort) 405 if err != nil { 406 return nil, errors.Trace(err) 407 } 408 logger.Debugf("Creating vnic sets") 409 vnicSets, err := o.ensureVnicSet(ctx, args.InstanceConfig.MachineId, tags) 410 if err != nil { 411 return nil, errors.Trace(err) 412 } 413 // fetch instance network card configuration 414 logger.Debugf("Getting instance networks") 415 networking, err := o.getInstanceNetworks(ctx, args, secLists, []string{vnicSets.Name}) 416 if err != nil { 417 return nil, errors.Trace(err) 418 } 419 420 logger.Debugf("Getting cloud config") 421 cloudcfg, err := o.getCloudInitConfig(args.InstanceConfig.Series, networking) 422 if err != nil { 423 return nil, errors.Annotate(err, "cannot create cloudinit template") 424 } 425 426 // compose userdata with the cloud config template 427 logger.Debugf("Composing userdata") 428 userData, err := providerinit.ComposeUserData( 429 args.InstanceConfig, 430 cloudcfg, 431 OracleRenderer{}, 432 ) 433 if err != nil { 434 return nil, errors.Annotate(err, "cannot make user data") 435 } 436 437 logger.Debugf("oracle user data: %d bytes", len(userData)) 438 439 attributes := map[string]interface{}{ 440 "userdata": string(userData), 441 } 442 443 // create the instance based on the instance params 444 instance, err := o.createInstance(oci.InstanceParams{ 445 Instances: []oci.Instances{ 446 { 447 Shape: spec.InstanceType.Name, 448 Imagelist: imageName, 449 Name: machineName, 450 Label: args.InstanceConfig.MachineAgentServiceName, 451 Hostname: hostname, 452 Tags: tags, 453 Attributes: attributes, 454 Reverse_dns: true, 455 Networking: networking, 456 }, 457 }, 458 }) 459 if err != nil { 460 return nil, errors.Trace(err) 461 } 462 463 instance.arch = &spec.Image.Arch 464 instance.instType = &spec.InstanceType 465 466 machineId := instance.machine.Name 467 timeout := 10 * time.Minute 468 if err := instance.waitForMachineStatus(desiredStatus, timeout); err != nil { 469 return nil, errors.Trace(err) 470 } 471 logger.Infof("started instance %q", machineId) 472 473 //TODO: add config option for public IP allocation 474 logger.Debugf("Associating public IP to instance %q", machineId) 475 if err := instance.associatePublicIP(); err != nil { 476 return nil, errors.Trace(err) 477 } 478 result := &environs.StartInstanceResult{ 479 Instance: instance, 480 Hardware: instance.hardwareCharacteristics(), 481 } 482 483 return result, nil 484 } 485 486 // StopInstances is part of the InstanceBroker interface. 487 func (o *OracleEnviron) StopInstances(ctx context.ProviderCallContext, ids ...instance.Id) error { 488 oracleInstances, err := o.getOracleInstances(ids...) 489 if err == environs.ErrNoInstances { 490 return nil 491 } else if err != nil { 492 return err 493 } 494 495 logger.Debugf("terminating instances %v", ids) 496 if err := o.terminateInstances(oracleInstances...); err != nil { 497 return err 498 } 499 500 return nil 501 } 502 503 func (o *OracleEnviron) terminateInstances(instances ...*oracleInstance) error { 504 wg := sync.WaitGroup{} 505 wg.Add(len(instances)) 506 errs := []error{} 507 instIds := []instance.Id{} 508 for _, oInst := range instances { 509 inst := oInst 510 go func() { 511 defer wg.Done() 512 if err := inst.deleteInstanceAndResources(true); err != nil { 513 if !oci.IsNotFound(err) { 514 instIds = append(instIds, instance.Id(inst.name)) 515 errs = append(errs, err) 516 } 517 } 518 }() 519 } 520 wg.Wait() 521 switch len(errs) { 522 case 0: 523 return nil 524 case 1: 525 return errors.Annotatef(errs[0], "failed to stop instance %s", instIds[0]) 526 default: 527 return errors.Errorf( 528 "failed to stop instances %s: %s", 529 instIds, errs, 530 ) 531 } 532 } 533 534 type tagValue struct { 535 tag, value string 536 } 537 538 func (t *tagValue) String() string { 539 return fmt.Sprintf("%s=%s", t.tag, t.value) 540 } 541 542 // allControllerManagedInstances returns all instances managed by this 543 // environment's controller, matching the optionally specified filter. 544 func (o *OracleEnviron) allControllerManagedInstances(controllerUUID string) ([]*oracleInstance, error) { 545 return o.allInstances(tagValue{ 546 tag: tags.JujuController, 547 value: controllerUUID, 548 }) 549 } 550 551 // getOracleInstances attempts to fetch information from the oracle API for the 552 // specified IDs. 553 func (o *OracleEnviron) getOracleInstances(ids ...instance.Id) ([]*oracleInstance, error) { 554 ret := make([]*oracleInstance, 0, len(ids)) 555 resp, err := o.client.AllInstances(nil) 556 if err != nil { 557 return nil, errors.Trace(err) 558 } 559 560 if len(resp.Result) == 0 { 561 return nil, environs.ErrNoInstances 562 } 563 564 for _, val := range resp.Result { 565 for _, id := range ids { 566 oInst, err := newInstance(val, o) 567 if err != nil { 568 return nil, errors.Trace(err) 569 } 570 if oInst.Id() == id { 571 ret = append(ret, oInst) 572 break 573 } 574 } 575 } 576 if len(ret) < len(ids) { 577 return ret, environs.ErrPartialInstances 578 } 579 return ret, nil 580 } 581 582 func (o *OracleEnviron) getOracleInstancesAsMap(ids ...instance.Id) (map[string]*oracleInstance, error) { 583 instances, err := o.getOracleInstances(ids...) 584 if err != nil { 585 return map[string]*oracleInstance{}, errors.Trace(err) 586 } 587 ret := map[string]*oracleInstance{} 588 for _, val := range instances { 589 ret[string(val.Id())] = val 590 } 591 return ret, nil 592 } 593 594 // AllInstances is part of the InstanceBroker interface. 595 func (o *OracleEnviron) AllInstances(ctx context.ProviderCallContext) ([]envinstance.Instance, error) { 596 tagFilter := tagValue{tags.JujuModel, o.Config().UUID()} 597 all, err := o.allInstances(tagFilter) 598 if err != nil { 599 return nil, err 600 } 601 602 ret := make([]envinstance.Instance, len(all)) 603 for i, val := range all { 604 ret[i] = val 605 } 606 return ret, nil 607 } 608 609 func (o *OracleEnviron) allInstances(tagFilter tagValue) ([]*oracleInstance, error) { 610 filter := []oci.Filter{ 611 { 612 Arg: "tags", 613 Value: tagFilter.String(), 614 }, 615 } 616 logger.Infof("Looking for instances with tags: %v", filter) 617 resp, err := o.client.AllInstances(filter) 618 if err != nil { 619 return nil, err 620 } 621 622 n := len(resp.Result) 623 instances := make([]*oracleInstance, 0, n) 624 for _, val := range resp.Result { 625 oracleInstance, err := newInstance(val, o) 626 if err != nil { 627 return nil, errors.Trace(err) 628 } 629 instances = append(instances, oracleInstance) 630 } 631 632 return instances, nil 633 } 634 635 // MaintainInstance is part of the InstanceBroker interface. 636 func (o *OracleEnviron) MaintainInstance(ctx context.ProviderCallContext, args environs.StartInstanceParams) error { 637 return nil 638 } 639 640 // Config is part of the Environ interface. 641 func (o *OracleEnviron) Config() *config.Config { 642 o.mutex.Lock() 643 defer o.mutex.Unlock() 644 return o.cfg 645 } 646 647 // ConstraintsValidator is part of the environs.Environ interface. 648 func (o *OracleEnviron) ConstraintsValidator(ctx context.ProviderCallContext) (constraints.Validator, error) { 649 // list of unsupported oracle provider constraints 650 unsupportedConstraints := []string{ 651 constraints.Container, 652 constraints.CpuPower, 653 constraints.RootDisk, 654 constraints.VirtType, 655 } 656 657 // we choose to use the default validator implementation 658 validator := constraints.NewValidator() 659 // we must feed the validator that the oracle cloud 660 // provider does not support these constraints 661 validator.RegisterUnsupported(unsupportedConstraints) 662 validator.RegisterVocabulary(constraints.Arch, []string{arch.I386, arch.AMD64}) 663 logger.Infof("Returning constraints validator: %v", validator) 664 return validator, nil 665 } 666 667 // SetConfig is part of the environs.Environ interface. 668 func (o *OracleEnviron) SetConfig(cfg *config.Config) error { 669 o.mutex.Lock() 670 defer o.mutex.Unlock() 671 672 var old *config.Config 673 if o.cfg != nil { 674 old = o.cfg 675 } 676 if err := config.Validate(cfg, old); err != nil { 677 return errors.Trace(err) 678 } 679 o.cfg = cfg 680 return nil 681 } 682 683 func (o *OracleEnviron) Details(id instance.Id) (ociResponse.Instance, error) { 684 inst, err := o.getOracleInstances(id) 685 if err != nil { 686 return ociResponse.Instance{}, err 687 } 688 689 return inst[0].machine, nil 690 } 691 692 // Instances is part of the environs.Environ interface. 693 func (o *OracleEnviron) Instances(ctx context.ProviderCallContext, ids []instance.Id) ([]envinstance.Instance, error) { 694 if len(ids) == 0 { 695 return nil, nil 696 } 697 instances, err := o.getOracleInstances(ids...) 698 if err != nil { 699 return nil, err 700 } 701 702 ret := []envinstance.Instance{} 703 for _, val := range instances { 704 ret = append(ret, val) 705 } 706 return ret, nil 707 } 708 709 // ControllerInstances is part of the environs.Environ interface. 710 func (o *OracleEnviron) ControllerInstances(ctx context.ProviderCallContext, controllerUUID string) ([]instance.Id, error) { 711 instances, err := o.allControllerManagedInstances(controllerUUID) 712 if err != nil { 713 return nil, errors.Trace(err) 714 } 715 716 filter := tagValue{tags.JujuIsController, "true"} 717 ids := make([]instance.Id, 0, 1) 718 for _, val := range instances { 719 found := false 720 for _, tag := range val.machine.Tags { 721 if tag == filter.String() { 722 found = true 723 break 724 } 725 } 726 if found == false { 727 continue 728 } 729 ids = append(ids, val.Id()) 730 } 731 732 if len(ids) == 0 { 733 return nil, environs.ErrNoInstances 734 } 735 736 return ids, nil 737 } 738 739 // Destroy is part of the environs.Environ interface. 740 func (o *OracleEnviron) Destroy(ctx context.ProviderCallContext) error { 741 return common.Destroy(o, ctx) 742 } 743 744 // DestroyController is part of the environs.Environ interface. 745 func (o *OracleEnviron) DestroyController(ctx context.ProviderCallContext, controllerUUID string) error { 746 err := o.Destroy(ctx) 747 if err != nil { 748 logger.Errorf("Failed to destroy environment through controller: %s", errors.Trace(err)) 749 } 750 instances, err := o.allControllerManagedInstances(controllerUUID) 751 if err != nil { 752 if err == environs.ErrNoInstances { 753 return nil 754 } 755 return errors.Trace(err) 756 } 757 ids := make([]instance.Id, len(instances)) 758 for i, val := range instances { 759 ids[i] = val.Id() 760 } 761 return o.StopInstances(ctx, ids...) 762 } 763 764 // Provider is part of the environs.Environ interface. 765 func (o *OracleEnviron) Provider() environs.EnvironProvider { 766 return o.p 767 } 768 769 // PrecheckInstance is part of the environs.Environ interface. 770 func (o *OracleEnviron) PrecheckInstance(context.ProviderCallContext, environs.PrecheckInstanceParams) error { 771 return nil 772 } 773 774 // InstanceTypes is part of the environs.InstanceTypesFetcher interface. 775 func (o *OracleEnviron) InstanceTypes(context.ProviderCallContext, constraints.Value) (envinstance.InstanceTypesWithCostMetadata, error) { 776 var i envinstance.InstanceTypesWithCostMetadata 777 return i, nil 778 } 779 780 // createInstance creates a new instance inside the oracle infrastructure 781 func (e *OracleEnviron) createInstance(params oci.InstanceParams) (*oracleInstance, error) { 782 if len(params.Instances) > 1 { 783 return nil, errors.NotSupportedf("launching multiple instances") 784 } 785 786 logger.Debugf("running createInstance") 787 resp, err := e.client.CreateInstance(params) 788 if err != nil { 789 return nil, errors.Trace(err) 790 } 791 792 instance, err := newInstance(resp.Instances[0], e) 793 if err != nil { 794 return nil, errors.Trace(err) 795 } 796 797 return instance, nil 798 }