github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/tools/lxdclient/client_instance.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 // +build go1.3 5 6 package lxdclient 7 8 import ( 9 "bytes" 10 "fmt" 11 "io" 12 "os" 13 "strings" 14 15 "github.com/juju/errors" 16 "github.com/lxc/lxd" 17 "github.com/lxc/lxd/shared" 18 19 "github.com/juju/juju/container" 20 "github.com/juju/juju/network" 21 ) 22 23 type Device map[string]string 24 type Devices map[string]Device 25 26 type File struct { 27 Content []byte 28 Path string 29 GID int 30 UID int 31 Mode os.FileMode 32 } 33 type Files []File 34 35 // TODO(ericsnow) We probably need to address some of the things that 36 // get handled in container/lxc/clonetemplate.go. 37 38 type rawInstanceClient interface { 39 ListContainers() ([]shared.ContainerInfo, error) 40 ContainerInfo(name string) (*shared.ContainerInfo, error) 41 Init(name string, imgremote string, image string, profiles *[]string, config map[string]string, devices shared.Devices, ephem bool) (*lxd.Response, error) 42 Action(name string, action shared.ContainerAction, timeout int, force bool, stateful bool) (*lxd.Response, error) 43 Delete(name string) (*lxd.Response, error) 44 45 WaitForSuccess(waitURL string) error 46 ContainerState(name string) (*shared.ContainerState, error) 47 ContainerDeviceAdd(container, devname, devtype string, props []string) (*lxd.Response, error) 48 PushFile(container, path string, gid int, uid int, mode string, buf io.ReadSeeker) error 49 } 50 51 type instanceClient struct { 52 raw rawInstanceClient 53 remote string 54 } 55 56 func deviceProperties(device Device) []string { 57 var props []string 58 59 for k, v := range device { 60 props = append(props, fmt.Sprintf("%s=%s", k, v)) 61 } 62 63 return props 64 } 65 66 func (client *instanceClient) addInstance(spec InstanceSpec) error { 67 imageRemote := spec.ImageRemote 68 if imageRemote == "" { 69 imageRemote = client.remote 70 } 71 72 imageAlias := spec.Image 73 74 var profiles *[]string 75 if len(spec.Profiles) > 0 { 76 profiles = &spec.Profiles 77 } 78 79 // TODO(ericsnow) Copy the image first? 80 81 lxdDevices := make(shared.Devices, len(spec.Devices)) 82 for name, device := range spec.Devices { 83 lxdDevice := make(shared.Device, len(device)) 84 for key, value := range device { 85 lxdDevice[key] = value 86 } 87 lxdDevices[name] = lxdDevice 88 } 89 90 config := spec.config() 91 resp, err := client.raw.Init(spec.Name, imageRemote, imageAlias, profiles, config, lxdDevices, spec.Ephemeral) 92 if err != nil { 93 return errors.Trace(err) 94 } 95 96 // Init is an async operation, since the tar -xvf (or whatever) might 97 // take a while; the result is an LXD operation id, which we can just 98 // wait on until it is finished. 99 if err := client.raw.WaitForSuccess(resp.Operation); err != nil { 100 // TODO(ericsnow) Handle different failures (from the async 101 // operation) differently? 102 return errors.Trace(err) 103 } 104 105 for _, file := range spec.Files { 106 logger.Infof("pushing file %q to container %q", file.Path, spec.Name) 107 err := client.raw.PushFile(spec.Name, file.Path, file.GID, file.UID, file.Mode.String(), bytes.NewReader(file.Content)) 108 if err != nil { 109 return errors.Trace(err) 110 } 111 } 112 113 return nil 114 } 115 116 func (client *instanceClient) startInstance(spec InstanceSpec) error { 117 timeout := -1 118 force := false 119 stateful := false 120 resp, err := client.raw.Action(spec.Name, shared.Start, timeout, force, stateful) 121 if err != nil { 122 return errors.Trace(err) 123 } 124 125 if err := client.raw.WaitForSuccess(resp.Operation); err != nil { 126 // TODO(ericsnow) Handle different failures (from the async 127 // operation) differently? 128 return errors.Trace(err) 129 } 130 131 return nil 132 } 133 134 // AddInstance creates a new instance based on the spec's data and 135 // returns it. The instance will be created using the client. 136 func (client *instanceClient) AddInstance(spec InstanceSpec) (*Instance, error) { 137 if err := client.addInstance(spec); err != nil { 138 return nil, errors.Trace(err) 139 } 140 141 if err := client.startInstance(spec); err != nil { 142 if err := client.removeInstance(spec.Name); err != nil { 143 logger.Errorf("could not remove container %q after starting it failed", spec.Name) 144 } 145 return nil, errors.Trace(err) 146 } 147 148 inst, err := client.Instance(spec.Name) 149 if err != nil { 150 return nil, errors.Trace(err) 151 } 152 inst.spec = &spec 153 154 return inst, nil 155 } 156 157 // Instance gets the up-to-date info about the given instance 158 // and returns it. 159 func (client *instanceClient) Instance(name string) (*Instance, error) { 160 info, err := client.raw.ContainerInfo(name) 161 if err != nil { 162 return nil, errors.Trace(err) 163 } 164 165 inst := newInstance(info, nil) 166 return inst, nil 167 } 168 169 func (client *instanceClient) Status(name string) (string, error) { 170 info, err := client.raw.ContainerInfo(name) 171 if err != nil { 172 return "", errors.Trace(err) 173 } 174 175 return info.Status, nil 176 } 177 178 // Instances sends a request to the API for a list of all instances 179 // (in the Client's namespace) for which the name starts with the 180 // provided prefix. The result is also limited to those instances with 181 // one of the specified statuses (if any). 182 func (client *instanceClient) Instances(prefix string, statuses ...string) ([]Instance, error) { 183 infos, err := client.raw.ListContainers() 184 if err != nil { 185 return nil, errors.Trace(err) 186 } 187 188 var insts []Instance 189 for _, info := range infos { 190 name := info.Name 191 if prefix != "" && !strings.HasPrefix(name, prefix) { 192 continue 193 } 194 if len(statuses) > 0 && !checkStatus(info, statuses) { 195 continue 196 } 197 198 inst := newInstance(&info, nil) 199 insts = append(insts, *inst) 200 } 201 return insts, nil 202 } 203 204 func checkStatus(info shared.ContainerInfo, statuses []string) bool { 205 for _, status := range statuses { 206 statusCode := allStatuses[status] 207 if info.StatusCode == statusCode { 208 return true 209 } 210 } 211 return false 212 } 213 214 // removeInstance sends a request to the API to remove the instance 215 // with the provided ID. The call blocks until the instance is removed 216 // (or the request fails). 217 func (client *instanceClient) removeInstance(name string) error { 218 info, err := client.raw.ContainerInfo(name) 219 if err != nil { 220 return errors.Trace(err) 221 } 222 223 //if info.Status.StatusCode != 0 && info.Status.StatusCode != shared.Stopped { 224 if info.StatusCode != shared.Stopped { 225 timeout := -1 226 force := true 227 stateful := false 228 resp, err := client.raw.Action(name, shared.Stop, timeout, force, stateful) 229 if err != nil { 230 return errors.Trace(err) 231 } 232 233 if err := client.raw.WaitForSuccess(resp.Operation); err != nil { 234 // TODO(ericsnow) Handle different failures (from the async 235 // operation) differently? 236 return errors.Trace(err) 237 } 238 } 239 240 resp, err := client.raw.Delete(name) 241 if err != nil { 242 return errors.Trace(err) 243 } 244 245 if err := client.raw.WaitForSuccess(resp.Operation); err != nil { 246 // TODO(ericsnow) Handle different failures (from the async 247 // operation) differently? 248 return errors.Trace(err) 249 } 250 251 return nil 252 } 253 254 // RemoveInstances sends a request to the API to terminate all 255 // instances (in the Client's namespace) that match one of the 256 // provided IDs. If a prefix is provided, only IDs that start with the 257 // prefix will be considered. The call blocks until all the instances 258 // are removed or the request fails. 259 func (client *instanceClient) RemoveInstances(prefix string, names ...string) error { 260 if len(names) == 0 { 261 return nil 262 } 263 264 instances, err := client.Instances(prefix) 265 if err != nil { 266 return errors.Annotatef(err, "while removing instances %v", names) 267 } 268 269 var failed []string 270 for _, name := range names { 271 if !checkInstanceName(name, instances) { 272 // We ignore unknown instance names. 273 continue 274 } 275 276 if err := client.removeInstance(name); err != nil { 277 failed = append(failed, name) 278 logger.Errorf("while removing instance %q: %v", name, err) 279 } 280 } 281 if len(failed) != 0 { 282 return errors.Errorf("some instance removals failed: %v", failed) 283 } 284 return nil 285 } 286 287 func checkInstanceName(name string, instances []Instance) bool { 288 for _, inst := range instances { 289 if inst.Name == name { 290 return true 291 } 292 } 293 return false 294 } 295 296 // Addresses returns the list of network.Addresses for this instance. It 297 // converts the information that LXD tracks into the Juju network model. 298 func (client *instanceClient) Addresses(name string) ([]network.Address, error) { 299 state, err := client.raw.ContainerState(name) 300 if err != nil { 301 return nil, err 302 } 303 304 networks := state.Network 305 if networks == nil { 306 return []network.Address{}, nil 307 } 308 309 addrs := []network.Address{} 310 311 for name, net := range networks { 312 if name == container.DefaultLxcBridge || name == container.DefaultLxdBridge { 313 continue 314 } 315 for _, addr := range net.Addresses { 316 if err != nil { 317 return nil, err 318 } 319 320 addr := network.NewAddress(addr.Address) 321 if addr.Scope == network.ScopeLinkLocal || addr.Scope == network.ScopeMachineLocal { 322 logger.Tracef("for container %q ignoring address %q", name, addr) 323 continue 324 } 325 addrs = append(addrs, addr) 326 } 327 } 328 return addrs, nil 329 }