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