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  }