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