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