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 }