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 }