github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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  	// TODO(ericsnow) Other possible fields:
    80  	// Disks
    81  	// Networks
    82  	// Metadata
    83  	// Tags
    84  }
    85  
    86  func (spec InstanceSpec) config() map[string]string {
    87  	return resolveMetadata(spec.Metadata)
    88  }
    89  
    90  func (spec InstanceSpec) info(namespace string) *shared.ContainerInfo {
    91  	name := spec.Name
    92  	if namespace != "" {
    93  		name = namespace + "-" + name
    94  	}
    95  
    96  	return &shared.ContainerInfo{
    97  		Architecture:    "",
    98  		Config:          spec.config(),
    99  		CreationDate:    time.Time{},
   100  		Devices:         shared.Devices{},
   101  		Ephemeral:       spec.Ephemeral,
   102  		ExpandedConfig:  map[string]string{},
   103  		ExpandedDevices: shared.Devices{},
   104  		Name:            name,
   105  		Profiles:        spec.Profiles,
   106  		Status:          "",
   107  		StatusCode:      0,
   108  	}
   109  }
   110  
   111  // Summary builds an InstanceSummary based on the spec and returns it.
   112  func (spec InstanceSpec) Summary(namespace string) InstanceSummary {
   113  	info := spec.info(namespace)
   114  	return newInstanceSummary(info)
   115  }
   116  
   117  // InstanceHardware describes the hardware characteristics of a LXC container.
   118  type InstanceHardware struct {
   119  	// Architecture is the CPU architecture.
   120  	Architecture string
   121  
   122  	// NumCores is the number of CPU cores.
   123  	NumCores uint
   124  
   125  	// MemoryMB is the memory allocation for the container.
   126  	MemoryMB uint
   127  
   128  	// RootDiskMB is the size of the root disk, in MB.
   129  	RootDiskMB uint64
   130  }
   131  
   132  // InstanceSummary captures all the data needed by Instance.
   133  type InstanceSummary struct {
   134  	// Name is the "name" of the instance.
   135  	Name string
   136  
   137  	// Status holds the status of the instance at a certain point in time.
   138  	Status string
   139  
   140  	// Hardware describes the instance's hardware characterstics.
   141  	Hardware InstanceHardware
   142  
   143  	// Metadata is the instance metadata.
   144  	Metadata map[string]string
   145  }
   146  
   147  func newInstanceSummary(info *shared.ContainerInfo) InstanceSummary {
   148  	archStr := arch.NormaliseArch(info.Architecture)
   149  
   150  	var numCores uint = 0 // default to all
   151  	if raw := info.Config["limits.cpu"]; raw != "" {
   152  		fmt.Sscanf(raw, "%d", &numCores)
   153  	}
   154  
   155  	var mem uint = 0 // default to all
   156  	if raw := info.Config["limits.memory"]; raw != "" {
   157  		result, err := shared.ParseByteSizeString(raw)
   158  		if err != nil {
   159  			logger.Errorf("failed to parse %s into bytes, ignoring err: %s", raw, err)
   160  			mem = 0
   161  		} else {
   162  			// We're going to put it into MemoryMB, so adjust by a megabyte
   163  			result = result / megabyte
   164  			if result > math.MaxUint32 {
   165  				logger.Errorf("byte string %s overflowed uint32", raw)
   166  				mem = math.MaxUint32
   167  			} else {
   168  				mem = uint(result)
   169  			}
   170  		}
   171  	}
   172  
   173  	// TODO(ericsnow) Factor this out into a function.
   174  	statusStr := info.Status
   175  	for status, code := range allStatuses {
   176  		if info.StatusCode == code {
   177  			statusStr = status
   178  			break
   179  		}
   180  	}
   181  
   182  	metadata := extractMetadata(info.Config)
   183  
   184  	return InstanceSummary{
   185  		Name:     info.Name,
   186  		Status:   statusStr,
   187  		Metadata: metadata,
   188  		Hardware: InstanceHardware{
   189  			Architecture: archStr,
   190  			NumCores:     numCores,
   191  			MemoryMB:     mem,
   192  		},
   193  	}
   194  }
   195  
   196  // Instance represents a single realized LXD container.
   197  type Instance struct {
   198  	InstanceSummary
   199  
   200  	// spec is the InstanceSpec used to create this instance.
   201  	spec *InstanceSpec
   202  }
   203  
   204  func newInstance(info *shared.ContainerInfo, spec *InstanceSpec) *Instance {
   205  	summary := newInstanceSummary(info)
   206  	return NewInstance(summary, spec)
   207  }
   208  
   209  // NewInstance builds an instance from the provided summary and spec
   210  // and returns it.
   211  func NewInstance(summary InstanceSummary, spec *InstanceSpec) *Instance {
   212  	if spec != nil {
   213  		// Make a copy.
   214  		val := *spec
   215  		spec = &val
   216  	}
   217  	return &Instance{
   218  		InstanceSummary: summary,
   219  		spec:            spec,
   220  	}
   221  }
   222  
   223  // Status returns a string identifying the status of the instance.
   224  func (gi Instance) Status() string {
   225  	return gi.InstanceSummary.Status
   226  }
   227  
   228  // CurrentStatus returns a string identifying the status of the instance.
   229  func (gi Instance) CurrentStatus(client *Client) (string, error) {
   230  	// TODO(ericsnow) Do this a better way?
   231  
   232  	inst, err := client.Instance(gi.Name)
   233  	if err != nil {
   234  		return "", errors.Trace(err)
   235  	}
   236  	return inst.Status(), nil
   237  }
   238  
   239  // Metadata returns the user-specified metadata for the instance.
   240  func (gi Instance) Metadata() map[string]string {
   241  	// TODO*ericsnow) return a copy?
   242  	return gi.InstanceSummary.Metadata
   243  }
   244  
   245  func resolveMetadata(metadata map[string]string) map[string]string {
   246  	config := make(map[string]string)
   247  
   248  	for name, val := range metadata {
   249  		key := resolveConfigKey(name, MetadataNamespace)
   250  		config[key] = val
   251  	}
   252  
   253  	return config
   254  }
   255  
   256  func extractMetadata(config map[string]string) map[string]string {
   257  	metadata := make(map[string]string)
   258  
   259  	for key, val := range config {
   260  		namespace, name := splitConfigKey(key)
   261  		if namespace != MetadataNamespace {
   262  			continue
   263  		}
   264  		metadata[name] = val
   265  	}
   266  
   267  	return metadata
   268  }