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  }