github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/tools/lxdclient/client_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  	"bytes"
    10  	"fmt"
    11  	"io"
    12  	"os"
    13  	"strings"
    14  
    15  	"github.com/juju/errors"
    16  	"github.com/lxc/lxd"
    17  	"github.com/lxc/lxd/shared"
    18  
    19  	"github.com/juju/juju/container"
    20  	"github.com/juju/juju/network"
    21  )
    22  
    23  type Device map[string]string
    24  type Devices map[string]Device
    25  
    26  type File struct {
    27  	Content []byte
    28  	Path    string
    29  	GID     int
    30  	UID     int
    31  	Mode    os.FileMode
    32  }
    33  type Files []File
    34  
    35  // TODO(ericsnow) We probably need to address some of the things that
    36  // get handled in container/lxc/clonetemplate.go.
    37  
    38  type rawInstanceClient interface {
    39  	ListContainers() ([]shared.ContainerInfo, error)
    40  	ContainerInfo(name string) (*shared.ContainerInfo, error)
    41  	Init(name string, imgremote string, image string, profiles *[]string, config map[string]string, devices shared.Devices, ephem bool) (*lxd.Response, error)
    42  	Action(name string, action shared.ContainerAction, timeout int, force bool, stateful bool) (*lxd.Response, error)
    43  	Delete(name string) (*lxd.Response, error)
    44  
    45  	WaitForSuccess(waitURL string) error
    46  	ContainerState(name string) (*shared.ContainerState, error)
    47  	ContainerDeviceAdd(container, devname, devtype string, props []string) (*lxd.Response, error)
    48  	PushFile(container, path string, gid int, uid int, mode string, buf io.ReadSeeker) error
    49  }
    50  
    51  type instanceClient struct {
    52  	raw    rawInstanceClient
    53  	remote string
    54  }
    55  
    56  func deviceProperties(device Device) []string {
    57  	var props []string
    58  
    59  	for k, v := range device {
    60  		props = append(props, fmt.Sprintf("%s=%s", k, v))
    61  	}
    62  
    63  	return props
    64  }
    65  
    66  func (client *instanceClient) addInstance(spec InstanceSpec) error {
    67  	imageRemote := spec.ImageRemote
    68  	if imageRemote == "" {
    69  		imageRemote = client.remote
    70  	}
    71  
    72  	imageAlias := spec.Image
    73  
    74  	var profiles *[]string
    75  	if len(spec.Profiles) > 0 {
    76  		profiles = &spec.Profiles
    77  	}
    78  
    79  	// TODO(ericsnow) Copy the image first?
    80  
    81  	lxdDevices := make(shared.Devices, len(spec.Devices))
    82  	for name, device := range spec.Devices {
    83  		lxdDevice := make(shared.Device, len(device))
    84  		for key, value := range device {
    85  			lxdDevice[key] = value
    86  		}
    87  		lxdDevices[name] = lxdDevice
    88  	}
    89  
    90  	config := spec.config()
    91  	resp, err := client.raw.Init(spec.Name, imageRemote, imageAlias, profiles, config, lxdDevices, spec.Ephemeral)
    92  	if err != nil {
    93  		return errors.Trace(err)
    94  	}
    95  
    96  	// Init is an async operation, since the tar -xvf (or whatever) might
    97  	// take a while; the result is an LXD operation id, which we can just
    98  	// wait on until it is finished.
    99  	if err := client.raw.WaitForSuccess(resp.Operation); err != nil {
   100  		// TODO(ericsnow) Handle different failures (from the async
   101  		// operation) differently?
   102  		return errors.Trace(err)
   103  	}
   104  
   105  	for _, file := range spec.Files {
   106  		logger.Infof("pushing file %q to container %q", file.Path, spec.Name)
   107  		err := client.raw.PushFile(spec.Name, file.Path, file.GID, file.UID, file.Mode.String(), bytes.NewReader(file.Content))
   108  		if err != nil {
   109  			return errors.Trace(err)
   110  		}
   111  	}
   112  
   113  	return nil
   114  }
   115  
   116  func (client *instanceClient) startInstance(spec InstanceSpec) error {
   117  	timeout := -1
   118  	force := false
   119  	stateful := false
   120  	resp, err := client.raw.Action(spec.Name, shared.Start, timeout, force, stateful)
   121  	if err != nil {
   122  		return errors.Trace(err)
   123  	}
   124  
   125  	if err := client.raw.WaitForSuccess(resp.Operation); err != nil {
   126  		// TODO(ericsnow) Handle different failures (from the async
   127  		// operation) differently?
   128  		return errors.Trace(err)
   129  	}
   130  
   131  	return nil
   132  }
   133  
   134  // AddInstance creates a new instance based on the spec's data and
   135  // returns it. The instance will be created using the client.
   136  func (client *instanceClient) AddInstance(spec InstanceSpec) (*Instance, error) {
   137  	if err := client.addInstance(spec); err != nil {
   138  		return nil, errors.Trace(err)
   139  	}
   140  
   141  	if err := client.startInstance(spec); err != nil {
   142  		if err := client.removeInstance(spec.Name); err != nil {
   143  			logger.Errorf("could not remove container %q after starting it failed", spec.Name)
   144  		}
   145  		return nil, errors.Trace(err)
   146  	}
   147  
   148  	inst, err := client.Instance(spec.Name)
   149  	if err != nil {
   150  		return nil, errors.Trace(err)
   151  	}
   152  	inst.spec = &spec
   153  
   154  	return inst, nil
   155  }
   156  
   157  // Instance gets the up-to-date info about the given instance
   158  // and returns it.
   159  func (client *instanceClient) Instance(name string) (*Instance, error) {
   160  	info, err := client.raw.ContainerInfo(name)
   161  	if err != nil {
   162  		return nil, errors.Trace(err)
   163  	}
   164  
   165  	inst := newInstance(info, nil)
   166  	return inst, nil
   167  }
   168  
   169  func (client *instanceClient) Status(name string) (string, error) {
   170  	info, err := client.raw.ContainerInfo(name)
   171  	if err != nil {
   172  		return "", errors.Trace(err)
   173  	}
   174  
   175  	return info.Status, nil
   176  }
   177  
   178  // Instances sends a request to the API for a list of all instances
   179  // (in the Client's namespace) for which the name starts with the
   180  // provided prefix. The result is also limited to those instances with
   181  // one of the specified statuses (if any).
   182  func (client *instanceClient) Instances(prefix string, statuses ...string) ([]Instance, error) {
   183  	infos, err := client.raw.ListContainers()
   184  	if err != nil {
   185  		return nil, errors.Trace(err)
   186  	}
   187  
   188  	var insts []Instance
   189  	for _, info := range infos {
   190  		name := info.Name
   191  		if prefix != "" && !strings.HasPrefix(name, prefix) {
   192  			continue
   193  		}
   194  		if len(statuses) > 0 && !checkStatus(info, statuses) {
   195  			continue
   196  		}
   197  
   198  		inst := newInstance(&info, nil)
   199  		insts = append(insts, *inst)
   200  	}
   201  	return insts, nil
   202  }
   203  
   204  func checkStatus(info shared.ContainerInfo, statuses []string) bool {
   205  	for _, status := range statuses {
   206  		statusCode := allStatuses[status]
   207  		if info.StatusCode == statusCode {
   208  			return true
   209  		}
   210  	}
   211  	return false
   212  }
   213  
   214  // removeInstance sends a request to the API to remove the instance
   215  // with the provided ID. The call blocks until the instance is removed
   216  // (or the request fails).
   217  func (client *instanceClient) removeInstance(name string) error {
   218  	info, err := client.raw.ContainerInfo(name)
   219  	if err != nil {
   220  		return errors.Trace(err)
   221  	}
   222  
   223  	//if info.Status.StatusCode != 0 && info.Status.StatusCode != shared.Stopped {
   224  	if info.StatusCode != shared.Stopped {
   225  		timeout := -1
   226  		force := true
   227  		stateful := false
   228  		resp, err := client.raw.Action(name, shared.Stop, timeout, force, stateful)
   229  		if err != nil {
   230  			return errors.Trace(err)
   231  		}
   232  
   233  		if err := client.raw.WaitForSuccess(resp.Operation); err != nil {
   234  			// TODO(ericsnow) Handle different failures (from the async
   235  			// operation) differently?
   236  			return errors.Trace(err)
   237  		}
   238  	}
   239  
   240  	resp, err := client.raw.Delete(name)
   241  	if err != nil {
   242  		return errors.Trace(err)
   243  	}
   244  
   245  	if err := client.raw.WaitForSuccess(resp.Operation); err != nil {
   246  		// TODO(ericsnow) Handle different failures (from the async
   247  		// operation) differently?
   248  		return errors.Trace(err)
   249  	}
   250  
   251  	return nil
   252  }
   253  
   254  // RemoveInstances sends a request to the API to terminate all
   255  // instances (in the Client's namespace) that match one of the
   256  // provided IDs. If a prefix is provided, only IDs that start with the
   257  // prefix will be considered. The call blocks until all the instances
   258  // are removed or the request fails.
   259  func (client *instanceClient) RemoveInstances(prefix string, names ...string) error {
   260  	if len(names) == 0 {
   261  		return nil
   262  	}
   263  
   264  	instances, err := client.Instances(prefix)
   265  	if err != nil {
   266  		return errors.Annotatef(err, "while removing instances %v", names)
   267  	}
   268  
   269  	var failed []string
   270  	for _, name := range names {
   271  		if !checkInstanceName(name, instances) {
   272  			// We ignore unknown instance names.
   273  			continue
   274  		}
   275  
   276  		if err := client.removeInstance(name); err != nil {
   277  			failed = append(failed, name)
   278  			logger.Errorf("while removing instance %q: %v", name, err)
   279  		}
   280  	}
   281  	if len(failed) != 0 {
   282  		return errors.Errorf("some instance removals failed: %v", failed)
   283  	}
   284  	return nil
   285  }
   286  
   287  func checkInstanceName(name string, instances []Instance) bool {
   288  	for _, inst := range instances {
   289  		if inst.Name == name {
   290  			return true
   291  		}
   292  	}
   293  	return false
   294  }
   295  
   296  // Addresses returns the list of network.Addresses for this instance. It
   297  // converts the information that LXD tracks into the Juju network model.
   298  func (client *instanceClient) Addresses(name string) ([]network.Address, error) {
   299  	state, err := client.raw.ContainerState(name)
   300  	if err != nil {
   301  		return nil, err
   302  	}
   303  
   304  	networks := state.Network
   305  	if networks == nil {
   306  		return []network.Address{}, nil
   307  	}
   308  
   309  	addrs := []network.Address{}
   310  
   311  	for name, net := range networks {
   312  		if name == container.DefaultLxcBridge || name == container.DefaultLxdBridge {
   313  			continue
   314  		}
   315  		for _, addr := range net.Addresses {
   316  			if err != nil {
   317  				return nil, err
   318  			}
   319  
   320  			addr := network.NewAddress(addr.Address)
   321  			if addr.Scope == network.ScopeLinkLocal || addr.Scope == network.ScopeMachineLocal {
   322  				logger.Tracef("for container %q ignoring address %q", name, addr)
   323  				continue
   324  			}
   325  			addrs = append(addrs, addr)
   326  		}
   327  	}
   328  	return addrs, nil
   329  }