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