github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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 } 23 24 type tracer struct{} 25 26 func (tracer) Logf(format string, args ...interface{}) { 27 logger.Tracef(format, args...) 28 } 29 30 // newClient returns an instance of the CloudSigma client. 31 var newClient = func(cloud environs.CloudSpec, uuid string) (client *environClient, err error) { 32 logger.Debugf("creating CloudSigma client: id=%q", uuid) 33 34 credAttrs := cloud.Credential.Attributes() 35 username := credAttrs[credAttrUsername] 36 password := credAttrs[credAttrPassword] 37 38 // create connection to CloudSigma 39 conn, err := gosigma.NewClient(cloud.Endpoint, username, password, nil) 40 if err != nil { 41 return nil, err 42 } 43 44 // configure trace logger 45 if logger.LogLevel() <= loggo.TRACE { 46 conn.Logger(&tracer{}) 47 } 48 49 client = &environClient{ 50 conn: conn, 51 uuid: uuid, 52 } 53 54 return client, nil 55 } 56 57 const ( 58 jujuMetaInstance = "juju-instance" 59 jujuMetaInstanceController = "controller" 60 jujuMetaInstanceServer = "server" 61 62 jujuMetaEnvironment = "juju-model" 63 jujuMetaCoudInit = "cloudinit-user-data" 64 jujuMetaBase64 = "base64_fields" 65 ) 66 67 func (c *environClient) isMyEnvironment(s gosigma.Server) bool { 68 if v, ok := s.Get(jujuMetaEnvironment); ok && c.uuid == v { 69 return true 70 } 71 return false 72 } 73 74 func (c *environClient) isMyServer(s gosigma.Server) bool { 75 if _, ok := s.Get(jujuMetaInstance); ok { 76 return c.isMyEnvironment(s) 77 } 78 return false 79 } 80 81 // isMyController is used to filter servers in the CloudSigma account 82 func (c environClient) isMyController(s gosigma.Server) bool { 83 if v, ok := s.Get(jujuMetaInstance); ok && v == jujuMetaInstanceController { 84 return c.isMyEnvironment(s) 85 } 86 return false 87 } 88 89 // instances returns a list of CloudSigma servers for this environment 90 func (c *environClient) instances() ([]gosigma.Server, error) { 91 return c.conn.ServersFiltered(gosigma.RequestDetail, c.isMyServer) 92 } 93 94 // instanceMap of server ids to servers at CloudSigma account 95 func (c *environClient) instanceMap() (map[string]gosigma.Server, error) { 96 servers, err := c.conn.ServersFiltered(gosigma.RequestDetail, c.isMyServer) 97 if err != nil { 98 return nil, errors.Trace(err) 99 } 100 101 m := make(map[string]gosigma.Server, len(servers)) 102 for _, s := range servers { 103 m[s.UUID()] = s 104 } 105 106 return m, nil 107 } 108 109 //getControllerIds get list of ids for all controller instances 110 func (c *environClient) getControllerIds() (ids []instance.Id, err error) { 111 logger.Tracef("query state...") 112 113 servers, err := c.conn.ServersFiltered(gosigma.RequestDetail, c.isMyController) 114 if err != nil { 115 return []instance.Id{}, errors.Trace(err) 116 } 117 118 if len(servers) == 0 { 119 return []instance.Id{}, environs.ErrNotBootstrapped 120 } 121 122 ids = make([]instance.Id, len(servers)) 123 124 for i, server := range servers { 125 logger.Tracef("controller id: %s", server.UUID()) 126 ids[i] = instance.Id(server.UUID()) 127 } 128 129 return ids, nil 130 } 131 132 //stopInstance stops the CloudSigma server corresponding to the given instance ID. 133 func (c *environClient) stopInstance(id instance.Id) error { 134 uuid := string(id) 135 if uuid == "" { 136 return errors.New("invalid instance id") 137 } 138 139 s, err := c.conn.Server(uuid) 140 if err != nil { 141 return errors.Trace(err) 142 } 143 144 err = s.StopWait() 145 logger.Tracef("environClient.StopInstance - stop server, %q = %v", uuid, err) 146 147 err = s.Remove(gosigma.RecurseAllDrives) 148 logger.Tracef("environClient.StopInstance - remove server, %q = %v", uuid, err) 149 150 return nil 151 } 152 153 //newInstance creates and starts new instance. 154 func (c *environClient) newInstance( 155 args environs.StartInstanceParams, 156 img *imagemetadata.ImageMetadata, 157 userData []byte, 158 authorizedKeys string, 159 ) (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 != nil, 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( 208 baseName, constraints, args, drv, userData, authorizedKeys, 209 ) 210 if err != nil { 211 return nil, nil, "", errors.Trace(err) 212 } 213 214 if srv, err = c.conn.CreateServer(cc); err != nil { 215 err = errors.Annotatef(err, "error creating new instance") 216 return nil, nil, "", err 217 } 218 219 if err = srv.Start(); err != nil { 220 err = errors.Annotatef(err, "error booting new instance") 221 return nil, nil, "", err 222 } 223 224 // populate root drive hardware characteristics 225 switch originalDrive.Arch() { 226 case "64": 227 ar = arch.AMD64 228 case "32": 229 ar = arch.I386 230 default: 231 err = errors.Errorf("unknown arch: %v", ar) 232 return nil, nil, "", err 233 } 234 235 return srv, drv, ar, nil 236 } 237 238 func (c *environClient) generateSigmaComponents( 239 baseName string, 240 constraints *sigmaConstraints, 241 args environs.StartInstanceParams, 242 drv gosigma.Drive, 243 userData []byte, 244 authorizedKeys string, 245 ) (cc gosigma.Components, err error) { 246 cc.SetName(baseName) 247 cc.SetDescription(baseName) 248 cc.SetSMP(constraints.cores) 249 cc.SetCPU(constraints.power) 250 cc.SetMem(constraints.mem) 251 252 vncpass, err := utils.RandomPassword() 253 if err != nil { 254 err = errors.Errorf("error generating password: %v", err) 255 return 256 } 257 cc.SetVNCPassword(vncpass) 258 logger.Debugf("Setting ssh key: %s end", authorizedKeys) 259 cc.SetSSHPublicKey(authorizedKeys) 260 cc.AttachDrive(1, "0:0", "virtio", drv.UUID()) 261 cc.NetworkDHCP4(gosigma.ModelVirtio) 262 263 if multiwatcher.AnyJobNeedsState(args.InstanceConfig.Jobs...) { 264 cc.SetMeta(jujuMetaInstance, jujuMetaInstanceController) 265 } else { 266 cc.SetMeta(jujuMetaInstance, jujuMetaInstanceServer) 267 } 268 269 cc.SetMeta(jujuMetaEnvironment, c.uuid) 270 cc.SetMeta(jujuMetaCoudInit, string(userData)) 271 cc.SetMeta(jujuMetaBase64, jujuMetaCoudInit) 272 273 return cc, nil 274 }