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