github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/provider/cloudsigma/client.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package cloudsigma
     5  
     6  import (
     7  	"encoding/base64"
     8  
     9  	"github.com/altoros/gosigma"
    10  	"github.com/juju/errors"
    11  	"github.com/juju/loggo"
    12  	"github.com/juju/utils"
    13  
    14  	"github.com/juju/juju/environs"
    15  	"github.com/juju/juju/environs/imagemetadata"
    16  	"github.com/juju/juju/instance"
    17  	"github.com/juju/juju/juju/arch"
    18  	"github.com/juju/juju/state/multiwatcher"
    19  )
    20  
    21  type environClient struct {
    22  	conn   *gosigma.Client
    23  	uuid   string
    24  	config *environConfig
    25  }
    26  
    27  type tracer struct{}
    28  
    29  func (tracer) Logf(format string, args ...interface{}) {
    30  	logger.Tracef(format, args...)
    31  }
    32  
    33  // newClient returns an instance of the CloudSigma client.
    34  var newClient = func(cfg *environConfig) (client *environClient, err error) {
    35  	uuid, ok := cfg.UUID()
    36  	if !ok {
    37  		return nil, errors.New("Environ uuid must not be empty")
    38  	}
    39  
    40  	logger.Debugf("creating CloudSigma client: id=%q", uuid)
    41  
    42  	// create connection to CloudSigma
    43  	conn, err := gosigma.NewClient(cfg.region(), cfg.username(), cfg.password(), nil)
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  
    48  	// configure trace logger
    49  	if logger.LogLevel() <= loggo.TRACE {
    50  		conn.Logger(&tracer{})
    51  	}
    52  
    53  	client = &environClient{
    54  		conn:   conn,
    55  		uuid:   uuid,
    56  		config: cfg,
    57  	}
    58  
    59  	return client, nil
    60  }
    61  
    62  const (
    63  	jujuMetaInstance            = "juju-instance"
    64  	jujuMetaInstanceStateServer = "state-server"
    65  	jujuMetaInstanceServer      = "server"
    66  
    67  	jujuMetaEnvironment = "juju-environment"
    68  	jujuMetaCoudInit    = "cloudinit-user-data"
    69  	jujuMetaBase64      = "base64_fields"
    70  )
    71  
    72  func (c *environClient) isMyEnvironment(s gosigma.Server) bool {
    73  	if v, ok := s.Get(jujuMetaEnvironment); ok && c.uuid == v {
    74  		return true
    75  	}
    76  	return false
    77  }
    78  
    79  func (c *environClient) isMyServer(s gosigma.Server) bool {
    80  	if _, ok := s.Get(jujuMetaInstance); ok {
    81  		return c.isMyEnvironment(s)
    82  	}
    83  	return false
    84  }
    85  
    86  // isMyStateServer is used to filter servers in the CloudSigma account
    87  func (c environClient) isMyStateServer(s gosigma.Server) bool {
    88  	if v, ok := s.Get(jujuMetaInstance); ok && v == jujuMetaInstanceStateServer {
    89  		return c.isMyEnvironment(s)
    90  	}
    91  	return false
    92  }
    93  
    94  // instances returns a list of CloudSigma servers for this environment
    95  func (c *environClient) instances() ([]gosigma.Server, error) {
    96  	return c.conn.ServersFiltered(gosigma.RequestDetail, c.isMyServer)
    97  }
    98  
    99  // instanceMap of server ids to servers at CloudSigma account
   100  func (c *environClient) instanceMap() (map[string]gosigma.Server, error) {
   101  	servers, err := c.conn.ServersFiltered(gosigma.RequestDetail, c.isMyServer)
   102  	if err != nil {
   103  		return nil, errors.Trace(err)
   104  	}
   105  
   106  	m := make(map[string]gosigma.Server, len(servers))
   107  	for _, s := range servers {
   108  		m[s.UUID()] = s
   109  	}
   110  
   111  	return m, nil
   112  }
   113  
   114  //getStateServerIds get list of ids for all state server instances
   115  func (c *environClient) getStateServerIds() (ids []instance.Id, err error) {
   116  	logger.Tracef("query state...")
   117  
   118  	servers, err := c.conn.ServersFiltered(gosigma.RequestDetail, c.isMyStateServer)
   119  	if err != nil {
   120  		return []instance.Id{}, errors.Trace(err)
   121  	}
   122  
   123  	if len(servers) == 0 {
   124  		return []instance.Id{}, environs.ErrNotBootstrapped
   125  	}
   126  
   127  	ids = make([]instance.Id, len(servers))
   128  
   129  	for i, server := range servers {
   130  		logger.Tracef("State server id: %s", server.UUID())
   131  		ids[i] = instance.Id(server.UUID())
   132  	}
   133  
   134  	return ids, nil
   135  }
   136  
   137  //stopInstance stops the CloudSigma server corresponding to the given instance ID.
   138  func (c *environClient) stopInstance(id instance.Id) error {
   139  	uuid := string(id)
   140  	if uuid == "" {
   141  		return errors.New("invalid instance id")
   142  	}
   143  
   144  	s, err := c.conn.Server(uuid)
   145  	if err != nil {
   146  		return errors.Trace(err)
   147  	}
   148  
   149  	err = s.StopWait()
   150  	logger.Tracef("environClient.StopInstance - stop server, %q = %v", uuid, err)
   151  
   152  	err = s.Remove(gosigma.RecurseAllDrives)
   153  	logger.Tracef("environClient.StopInstance - remove server, %q = %v", uuid, err)
   154  
   155  	return nil
   156  }
   157  
   158  //newInstance creates and starts new instance.
   159  func (c *environClient) newInstance(args environs.StartInstanceParams, img *imagemetadata.ImageMetadata, userData []byte) (srv gosigma.Server, drv gosigma.Drive, ar string, err error) {
   160  
   161  	defer func() {
   162  		if err == nil {
   163  			return
   164  		}
   165  		if srv != nil {
   166  			srv.Remove(gosigma.RecurseAllDrives)
   167  		} else if drv != nil {
   168  			drv.Remove()
   169  		}
   170  		srv = nil
   171  		drv = nil
   172  	}()
   173  
   174  	if args.InstanceConfig == nil {
   175  		err = errors.New("invalid configuration for new instance: InstanceConfig is nil")
   176  		return nil, nil, "", err
   177  	}
   178  
   179  	logger.Debugf("Tools: %v", args.Tools.URLs())
   180  	logger.Debugf("Juju Constraints:" + args.Constraints.String())
   181  	logger.Debugf("InstanceConfig: %#v", args.InstanceConfig)
   182  
   183  	constraints := newConstraints(args.InstanceConfig.Bootstrap, args.Constraints, img)
   184  	logger.Debugf("CloudSigma Constraints: %v", constraints)
   185  
   186  	originalDrive, err := c.conn.Drive(constraints.driveTemplate, gosigma.LibraryMedia)
   187  	if err != nil {
   188  		err = errors.Annotatef(err, "Failed to query drive template")
   189  		return nil, nil, "", err
   190  	}
   191  
   192  	baseName := "juju-" + c.uuid + "-" + args.InstanceConfig.MachineId
   193  
   194  	cloneParams := gosigma.CloneParams{Name: baseName}
   195  	if drv, err = originalDrive.CloneWait(cloneParams, nil); err != nil {
   196  		err = errors.Errorf("error cloning drive: %v", err)
   197  		return nil, nil, "", err
   198  	}
   199  
   200  	if drv.Size() < constraints.driveSize {
   201  		if err = drv.ResizeWait(constraints.driveSize); err != nil {
   202  			err = errors.Errorf("error resizing drive: %v", err)
   203  			return nil, nil, "", err
   204  		}
   205  	}
   206  
   207  	cc, err := c.generateSigmaComponents(baseName, constraints, args, drv, userData)
   208  	if err != nil {
   209  		return nil, nil, "", errors.Trace(err)
   210  	}
   211  
   212  	if srv, err = c.conn.CreateServer(cc); err != nil {
   213  		err = errors.Annotatef(err, "error creating new instance")
   214  		return nil, nil, "", err
   215  	}
   216  
   217  	if err = srv.Start(); err != nil {
   218  		err = errors.Annotatef(err, "error booting new instance")
   219  		return nil, nil, "", err
   220  	}
   221  
   222  	// populate root drive hardware characteristics
   223  	switch originalDrive.Arch() {
   224  	case "64":
   225  		ar = arch.AMD64
   226  	case "32":
   227  		ar = arch.I386
   228  	default:
   229  		err = errors.Errorf("unknown arch: %v", ar)
   230  		return nil, nil, "", err
   231  	}
   232  
   233  	return srv, drv, ar, nil
   234  }
   235  
   236  func (c *environClient) generateSigmaComponents(baseName string, constraints *sigmaConstraints, args environs.StartInstanceParams, drv gosigma.Drive, userData []byte) (cc gosigma.Components, err error) {
   237  	cc.SetName(baseName)
   238  	cc.SetDescription(baseName)
   239  	cc.SetSMP(constraints.cores)
   240  	cc.SetCPU(constraints.power)
   241  	cc.SetMem(constraints.mem)
   242  
   243  	vncpass, err := utils.RandomPassword()
   244  	if err != nil {
   245  		err = errors.Errorf("error generating password: %v", err)
   246  		return
   247  	}
   248  	cc.SetVNCPassword(vncpass)
   249  	logger.Debugf("Setting ssh key: %s end", c.config.AuthorizedKeys())
   250  	cc.SetSSHPublicKey(c.config.AuthorizedKeys())
   251  	cc.AttachDrive(1, "0:0", "virtio", drv.UUID())
   252  	cc.NetworkDHCP4(gosigma.ModelVirtio)
   253  
   254  	if multiwatcher.AnyJobNeedsState(args.InstanceConfig.Jobs...) {
   255  		cc.SetMeta(jujuMetaInstance, jujuMetaInstanceStateServer)
   256  	} else {
   257  		cc.SetMeta(jujuMetaInstance, jujuMetaInstanceServer)
   258  	}
   259  
   260  	cc.SetMeta(jujuMetaEnvironment, c.uuid)
   261  	data, err := utils.Gunzip(userData)
   262  	if err != nil {
   263  		return cc, errors.Trace(err)
   264  	}
   265  	cc.SetMeta(jujuMetaCoudInit, base64.StdEncoding.EncodeToString(data))
   266  	cc.SetMeta(jujuMetaBase64, jujuMetaCoudInit)
   267  
   268  	return cc, nil
   269  }