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