github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/tools/lxdclient/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 "fmt" 10 "math" 11 "strings" 12 "time" 13 14 "github.com/juju/errors" 15 "github.com/juju/utils/arch" 16 "github.com/lxc/lxd/shared" 17 ) 18 19 // Constants related to user metadata. 20 const ( 21 MetadataNamespace = "user" 22 23 // This is defined by the cloud-init code: 24 // http://bazaar.launchpad.net/~cloud-init-dev/cloud-init/trunk/view/head:/cloudinit/sources/ 25 // http://cloudinit.readthedocs.org/en/latest/ 26 // Also see https://github.com/lxc/lxd/blob/master/specs/configuration.md. 27 UserdataKey = "user-data" 28 29 megabyte = 1024 * 1024 30 ) 31 32 func resolveConfigKey(name string, namespace ...string) string { 33 parts := append(namespace, name) 34 return strings.Join(parts, ".") 35 } 36 37 func splitConfigKey(key string) (string, string) { 38 parts := strings.SplitN(key, ".", 2) 39 if len(parts) == 1 { 40 return "", parts[0] 41 } 42 return parts[0], parts[1] 43 } 44 45 // AliveStatuses are the LXD statuses that indicate a container is "alive". 46 var AliveStatuses = []string{ 47 // TODO(ericsnow) Also support StatusOK, StatusPending, and StatusThawed? 48 StatusStarting, 49 StatusStarted, 50 StatusRunning, 51 StatusStopping, 52 StatusStopped, 53 } 54 55 // InstanceSpec holds all the information needed to create a new LXD 56 // container. 57 type InstanceSpec struct { 58 // Name is the "name" of the instance. 59 Name string 60 61 // Image is the name of the image to use. 62 Image string 63 64 // ImageRemote identifies the remote to use for images. By default 65 // the client's remote is used. 66 ImageRemote string 67 68 // Profiles are the names of the container profiles to apply to the 69 // new container, in order. 70 Profiles []string 71 72 // Ephemeral indicates whether or not the container should be 73 // destroyed when the LXD host is restarted. 74 Ephemeral bool 75 76 // Metadata is the instance metadata. 77 Metadata map[string]string 78 79 // Devices to be added at container initialisation time. 80 Devices 81 82 // Files to be pushed after initialisation has completed but 83 // before the container is started. 84 Files 85 86 // TODO(ericsnow) Other possible fields: 87 // Disks 88 // Networks 89 // Metadata 90 // Tags 91 } 92 93 func (spec InstanceSpec) config() map[string]string { 94 return resolveMetadata(spec.Metadata) 95 } 96 97 func (spec InstanceSpec) info(namespace string) *shared.ContainerInfo { 98 name := spec.Name 99 if namespace != "" { 100 name = namespace + "-" + name 101 } 102 103 return &shared.ContainerInfo{ 104 Architecture: "", 105 Config: spec.config(), 106 CreationDate: time.Time{}, 107 Devices: shared.Devices{}, 108 Ephemeral: spec.Ephemeral, 109 ExpandedConfig: map[string]string{}, 110 ExpandedDevices: shared.Devices{}, 111 Name: name, 112 Profiles: spec.Profiles, 113 Status: "", 114 StatusCode: 0, 115 } 116 } 117 118 // Summary builds an InstanceSummary based on the spec and returns it. 119 func (spec InstanceSpec) Summary(namespace string) InstanceSummary { 120 info := spec.info(namespace) 121 return newInstanceSummary(info) 122 } 123 124 // InstanceHardware describes the hardware characteristics of a LXC container. 125 type InstanceHardware struct { 126 // Architecture is the CPU architecture. 127 Architecture string 128 129 // NumCores is the number of CPU cores. 130 NumCores uint 131 132 // MemoryMB is the memory allocation for the container. 133 MemoryMB uint 134 135 // RootDiskMB is the size of the root disk, in MB. 136 RootDiskMB uint64 137 } 138 139 // InstanceSummary captures all the data needed by Instance. 140 type InstanceSummary struct { 141 // Name is the "name" of the instance. 142 Name string 143 144 // Status holds the status of the instance at a certain point in time. 145 Status string 146 147 // Hardware describes the instance's hardware characterstics. 148 Hardware InstanceHardware 149 150 // Metadata is the instance metadata. 151 Metadata map[string]string 152 } 153 154 func newInstanceSummary(info *shared.ContainerInfo) InstanceSummary { 155 archStr := arch.NormaliseArch(info.Architecture) 156 157 var numCores uint = 0 // default to all 158 if raw := info.Config["limits.cpu"]; raw != "" { 159 fmt.Sscanf(raw, "%d", &numCores) 160 } 161 162 var mem uint = 0 // default to all 163 if raw := info.Config["limits.memory"]; raw != "" { 164 result, err := shared.ParseByteSizeString(raw) 165 if err != nil { 166 logger.Errorf("failed to parse %s into bytes, ignoring err: %s", raw, err) 167 mem = 0 168 } else { 169 // We're going to put it into MemoryMB, so adjust by a megabyte 170 result = result / megabyte 171 if result > math.MaxUint32 { 172 logger.Errorf("byte string %s overflowed uint32", raw) 173 mem = math.MaxUint32 174 } else { 175 mem = uint(result) 176 } 177 } 178 } 179 180 // TODO(ericsnow) Factor this out into a function. 181 statusStr := info.Status 182 for status, code := range allStatuses { 183 if info.StatusCode == code { 184 statusStr = status 185 break 186 } 187 } 188 189 metadata := extractMetadata(info.Config) 190 191 return InstanceSummary{ 192 Name: info.Name, 193 Status: statusStr, 194 Metadata: metadata, 195 Hardware: InstanceHardware{ 196 Architecture: archStr, 197 NumCores: numCores, 198 MemoryMB: mem, 199 }, 200 } 201 } 202 203 // Instance represents a single realized LXD container. 204 type Instance struct { 205 InstanceSummary 206 207 // spec is the InstanceSpec used to create this instance. 208 spec *InstanceSpec 209 } 210 211 func newInstance(info *shared.ContainerInfo, spec *InstanceSpec) *Instance { 212 summary := newInstanceSummary(info) 213 return NewInstance(summary, spec) 214 } 215 216 // NewInstance builds an instance from the provided summary and spec 217 // and returns it. 218 func NewInstance(summary InstanceSummary, spec *InstanceSpec) *Instance { 219 if spec != nil { 220 // Make a copy. 221 val := *spec 222 spec = &val 223 } 224 return &Instance{ 225 InstanceSummary: summary, 226 spec: spec, 227 } 228 } 229 230 // Status returns a string identifying the status of the instance. 231 func (gi Instance) Status() string { 232 return gi.InstanceSummary.Status 233 } 234 235 // CurrentStatus returns a string identifying the status of the instance. 236 func (gi Instance) CurrentStatus(client *Client) (string, error) { 237 // TODO(ericsnow) Do this a better way? 238 239 inst, err := client.Instance(gi.Name) 240 if err != nil { 241 return "", errors.Trace(err) 242 } 243 return inst.Status(), nil 244 } 245 246 // Metadata returns the user-specified metadata for the instance. 247 func (gi Instance) Metadata() map[string]string { 248 // TODO*ericsnow) return a copy? 249 return gi.InstanceSummary.Metadata 250 } 251 252 func resolveMetadata(metadata map[string]string) map[string]string { 253 config := make(map[string]string) 254 255 for name, val := range metadata { 256 key := resolveConfigKey(name, MetadataNamespace) 257 config[key] = val 258 } 259 260 return config 261 } 262 263 func extractMetadata(config map[string]string) map[string]string { 264 metadata := make(map[string]string) 265 266 for key, val := range config { 267 namespace, name := splitConfigKey(key) 268 if namespace != MetadataNamespace { 269 continue 270 } 271 metadata[name] = val 272 } 273 274 return metadata 275 }