github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/gce/environ.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package gce 5 6 import ( 7 "strings" 8 "sync" 9 10 "github.com/juju/errors" 11 "google.golang.org/api/compute/v1" 12 13 jujucloud "github.com/juju/juju/cloud" 14 "github.com/juju/juju/core/instance" 15 "github.com/juju/juju/environs" 16 "github.com/juju/juju/environs/config" 17 "github.com/juju/juju/environs/context" 18 "github.com/juju/juju/environs/simplestreams" 19 "github.com/juju/juju/network" 20 "github.com/juju/juju/provider/common" 21 "github.com/juju/juju/provider/gce/google" 22 ) 23 24 type gceConnection interface { 25 VerifyCredentials() error 26 27 // Instance gets the up-to-date info about the given instance 28 // and returns it. 29 Instance(id, zone string) (google.Instance, error) 30 Instances(prefix string, statuses ...string) ([]google.Instance, error) 31 AddInstance(spec google.InstanceSpec) (*google.Instance, error) 32 RemoveInstances(prefix string, ids ...string) error 33 UpdateMetadata(key, value string, ids ...string) error 34 35 IngressRules(fwname string) ([]network.IngressRule, error) 36 OpenPorts(fwname string, rules ...network.IngressRule) error 37 ClosePorts(fwname string, rules ...network.IngressRule) error 38 39 AvailabilityZones(region string) ([]google.AvailabilityZone, error) 40 // Subnetworks returns the subnetworks that machines can be 41 // assigned to in the given region. 42 Subnetworks(region string) ([]*compute.Subnetwork, error) 43 // Networks returns the available networks that exist across 44 // regions. 45 Networks() ([]*compute.Network, error) 46 47 // Storage related methods. 48 49 // CreateDisks will attempt to create the disks described by <disks> spec and 50 // return a slice of Disk representing the created disks or error if one of them failed. 51 CreateDisks(zone string, disks []google.DiskSpec) ([]*google.Disk, error) 52 // Disks will return a list of all Disks found in the project. 53 Disks() ([]*google.Disk, error) 54 // Disk will return a Disk representing the disk identified by the 55 // passed <name> or error. 56 Disk(zone, id string) (*google.Disk, error) 57 // RemoveDisk will destroy the disk identified by <name> in <zone>. 58 RemoveDisk(zone, id string) error 59 // SetDiskLabels sets the labels on a disk, ensuring that the disk's 60 // label fingerprint matches the one supplied. 61 SetDiskLabels(zone, id, labelFingerprint string, labels map[string]string) error 62 // AttachDisk will attach the volume identified by <volumeName> into the instance 63 // <instanceId> and return an AttachedDisk representing it or error. 64 AttachDisk(zone, volumeName, instanceId string, mode google.DiskMode) (*google.AttachedDisk, error) 65 // DetachDisk will detach <volumeName> disk from <instanceId> if possible 66 // and return error. 67 DetachDisk(zone, instanceId, volumeName string) error 68 // InstanceDisks returns a list of the disks attached to the passed instance. 69 InstanceDisks(zone, instanceId string) ([]*google.AttachedDisk, error) 70 // ListMachineTypes returns a list of machines available in the project and zone provided. 71 ListMachineTypes(zone string) ([]google.MachineType, error) 72 } 73 74 type environ struct { 75 name string 76 uuid string 77 cloud environs.CloudSpec 78 gce gceConnection 79 80 lock sync.Mutex // lock protects access to ecfg 81 ecfg *environConfig 82 83 // namespace is used to create the machine and device hostnames. 84 namespace instance.Namespace 85 } 86 87 var _ environs.Environ = (*environ)(nil) 88 var _ environs.NetworkingEnviron = (*environ)(nil) 89 90 // Function entry points defined as variables so they can be overridden 91 // for testing purposes. 92 var ( 93 newConnection = func(conn google.ConnectionConfig, creds *google.Credentials) (gceConnection, error) { 94 return google.Connect(conn, creds) 95 } 96 destroyEnv = common.Destroy 97 bootstrap = common.Bootstrap 98 ) 99 100 func newEnviron(cloud environs.CloudSpec, cfg *config.Config) (*environ, error) { 101 ecfg, err := newConfig(cfg, nil) 102 if err != nil { 103 return nil, errors.Annotate(err, "invalid config") 104 } 105 106 credAttrs := cloud.Credential.Attributes() 107 if cloud.Credential.AuthType() == jujucloud.JSONFileAuthType { 108 contents := credAttrs[credAttrFile] 109 credential, err := parseJSONAuthFile(strings.NewReader(contents)) 110 if err != nil { 111 return nil, errors.Trace(err) 112 } 113 credAttrs = credential.Attributes() 114 } 115 116 credential := &google.Credentials{ 117 ClientID: credAttrs[credAttrClientID], 118 ProjectID: credAttrs[credAttrProjectID], 119 ClientEmail: credAttrs[credAttrClientEmail], 120 PrivateKey: []byte(credAttrs[credAttrPrivateKey]), 121 } 122 connectionConfig := google.ConnectionConfig{ 123 Region: cloud.Region, 124 ProjectID: credential.ProjectID, 125 } 126 127 // Connect and authenticate. 128 conn, err := newConnection(connectionConfig, credential) 129 if err != nil { 130 return nil, errors.Trace(err) 131 } 132 namespace, err := instance.NewNamespace(cfg.UUID()) 133 if err != nil { 134 return nil, errors.Trace(err) 135 } 136 137 return &environ{ 138 name: ecfg.config.Name(), 139 uuid: ecfg.config.UUID(), 140 cloud: cloud, 141 ecfg: ecfg, 142 gce: conn, 143 namespace: namespace, 144 }, nil 145 } 146 147 // Name returns the name of the environment. 148 func (env *environ) Name() string { 149 return env.name 150 } 151 152 // Provider returns the environment provider that created this env. 153 func (*environ) Provider() environs.EnvironProvider { 154 return providerInstance 155 } 156 157 // Region returns the CloudSpec to use for the provider, as configured. 158 func (env *environ) Region() (simplestreams.CloudSpec, error) { 159 return simplestreams.CloudSpec{ 160 Region: env.cloud.Region, 161 Endpoint: env.cloud.Endpoint, 162 }, nil 163 } 164 165 // SetConfig updates the env's configuration. 166 func (env *environ) SetConfig(cfg *config.Config) error { 167 env.lock.Lock() 168 defer env.lock.Unlock() 169 170 ecfg, err := newConfig(cfg, env.ecfg.config) 171 if err != nil { 172 return errors.Annotate(err, "invalid config change") 173 } 174 env.ecfg = ecfg 175 return nil 176 } 177 178 // Config returns the configuration data with which the env was created. 179 func (env *environ) Config() *config.Config { 180 env.lock.Lock() 181 defer env.lock.Unlock() 182 return env.ecfg.config 183 } 184 185 // PrepareForBootstrap implements environs.Environ. 186 func (env *environ) PrepareForBootstrap(ctx environs.BootstrapContext) error { 187 if ctx.ShouldVerifyCredentials() { 188 if err := env.gce.VerifyCredentials(); err != nil { 189 return errors.Trace(err) 190 } 191 } 192 return nil 193 } 194 195 // Create implements environs.Environ. 196 func (env *environ) Create(ctx context.ProviderCallContext, p environs.CreateParams) error { 197 if err := env.gce.VerifyCredentials(); err != nil { 198 return google.HandleCredentialError(errors.Trace(err), ctx) 199 } 200 return nil 201 } 202 203 // Bootstrap creates a new instance, chosing the series and arch out of 204 // available tools. The series and arch are returned along with a func 205 // that must be called to finalize the bootstrap process by transferring 206 // the tools and installing the initial juju controller. 207 func (env *environ) Bootstrap(ctx environs.BootstrapContext, callCtx context.ProviderCallContext, params environs.BootstrapParams) (*environs.BootstrapResult, error) { 208 // Ensure the API server port is open (globally for all instances 209 // on the network, not just for the specific node of the state 210 // server). See LP bug #1436191 for details. 211 rule := network.NewOpenIngressRule( 212 "tcp", 213 params.ControllerConfig.APIPort(), 214 params.ControllerConfig.APIPort(), 215 ) 216 if err := env.gce.OpenPorts(env.globalFirewallName(), rule); err != nil { 217 return nil, google.HandleCredentialError(errors.Trace(err), callCtx) 218 } 219 if params.ControllerConfig.AutocertDNSName() != "" { 220 // Open port 80 as well as it handles Let's Encrypt HTTP challenge. 221 rule = network.NewOpenIngressRule("tcp", 80, 80) 222 if err := env.gce.OpenPorts(env.globalFirewallName(), rule); err != nil { 223 return nil, google.HandleCredentialError(errors.Trace(err), callCtx) 224 } 225 } 226 return bootstrap(ctx, env, callCtx, params) 227 } 228 229 // Destroy shuts down all known machines and destroys the rest of the 230 // known environment. 231 func (env *environ) Destroy(ctx context.ProviderCallContext) error { 232 ports, err := env.IngressRules(ctx) 233 if err != nil { 234 return errors.Trace(err) 235 } 236 237 if len(ports) > 0 { 238 if err := env.ClosePorts(ctx, ports); err != nil { 239 return errors.Trace(err) 240 } 241 } 242 243 return destroyEnv(env, ctx) 244 } 245 246 // DestroyController implements the Environ interface. 247 func (env *environ) DestroyController(ctx context.ProviderCallContext, controllerUUID string) error { 248 // TODO(wallyworld): destroy hosted model resources 249 return env.Destroy(ctx) 250 }