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  }