launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/worker/provisioner/container_initialisation.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package provisioner
     5  
     6  import (
     7  	"fmt"
     8  	"sync/atomic"
     9  
    10  	"launchpad.net/errgo/errors"
    11  	"launchpad.net/juju-core/agent"
    12  	"launchpad.net/juju-core/container"
    13  	"launchpad.net/juju-core/container/kvm"
    14  	"launchpad.net/juju-core/container/lxc"
    15  	"launchpad.net/juju-core/environs"
    16  	"launchpad.net/juju-core/instance"
    17  	"launchpad.net/juju-core/state"
    18  	apiprovisioner "launchpad.net/juju-core/state/api/provisioner"
    19  	"launchpad.net/juju-core/state/api/watcher"
    20  	"launchpad.net/juju-core/worker"
    21  )
    22  
    23  // ContainerSetup is a StringsWatchHandler that is notified when containers
    24  // are created on the given machine. It will set up the machine to be able
    25  // to create containers and start a suitable provisioner.
    26  type ContainerSetup struct {
    27  	runner              worker.Runner
    28  	supportedContainers []instance.ContainerType
    29  	provisioner         *apiprovisioner.State
    30  	machine             *apiprovisioner.Machine
    31  	config              agent.Config
    32  
    33  	// Save the workerName so the worker thread can be stopped.
    34  	workerName string
    35  	// setupDone[containerType] is non zero if the container setup has been invoked
    36  	// for that container type.
    37  	setupDone map[instance.ContainerType]*int32
    38  	// The number of provisioners started. Once all necessary provisioners have
    39  	// been started, the container watcher can be stopped.
    40  	numberProvisioners int32
    41  }
    42  
    43  // NewContainerSetupHandler returns a StringsWatchHandler which is notified when
    44  // containers are created on the given machine.
    45  func NewContainerSetupHandler(runner worker.Runner, workerName string, supportedContainers []instance.ContainerType,
    46  	machine *apiprovisioner.Machine, provisioner *apiprovisioner.State,
    47  	config agent.Config) worker.StringsWatchHandler {
    48  
    49  	return &ContainerSetup{
    50  		runner:              runner,
    51  		machine:             machine,
    52  		supportedContainers: supportedContainers,
    53  		provisioner:         provisioner,
    54  		config:              config,
    55  		workerName:          workerName,
    56  	}
    57  }
    58  
    59  // SetUp is defined on the StringsWatchHandler interface.
    60  func (cs *ContainerSetup) SetUp() (watcher watcher.StringsWatcher, err error) {
    61  	// Set up the semaphores for each container type.
    62  	cs.setupDone = make(map[instance.ContainerType]*int32, len(instance.ContainerTypes))
    63  	for _, containerType := range instance.ContainerTypes {
    64  		zero := int32(0)
    65  		cs.setupDone[containerType] = &zero
    66  	}
    67  	// Listen to all container lifecycle events on our machine.
    68  	if watcher, err = cs.machine.WatchAllContainers(); err != nil {
    69  		return nil, mask(err)
    70  	}
    71  	return watcher, nil
    72  }
    73  
    74  // Handle is called whenever containers change on the machine being watched.
    75  // All machines start out with so containers so the first time Handle is called,
    76  // it will be because a container has been added.
    77  func (cs *ContainerSetup) Handle(containerIds []string) (resultError error) {
    78  	// Consume the initial watcher event.
    79  	if len(containerIds) == 0 {
    80  		return nil
    81  	}
    82  
    83  	logger.Tracef("initial container setup with ids: %v", containerIds)
    84  	for _, id := range containerIds {
    85  		containerType := state.ContainerTypeFromId(id)
    86  		// If this container type has been dealt with, do nothing.
    87  		if atomic.LoadInt32(cs.setupDone[containerType]) != 0 {
    88  			continue
    89  		}
    90  		if err := cs.initialiseAndStartProvisioner(containerType); err != nil {
    91  			logger.Errorf("starting container provisioner for %v: %v", containerType, err)
    92  			// Just because dealing with one type of container fails, we won't exit the entire
    93  			// function because we still want to try and start other container types. So we
    94  			// take note of and return the first such error.
    95  			if resultError == nil {
    96  				resultError = err
    97  			}
    98  		}
    99  	}
   100  	return resultError
   101  }
   102  
   103  func (cs *ContainerSetup) initialiseAndStartProvisioner(containerType instance.ContainerType) error {
   104  	// Flag that this container type has been handled.
   105  	atomic.StoreInt32(cs.setupDone[containerType], 1)
   106  
   107  	if atomic.AddInt32(&cs.numberProvisioners, 1) == int32(len(cs.supportedContainers)) {
   108  		// We only care about the initial container creation.
   109  		// This worker has done its job so stop it.
   110  		// We do not expect there will be an error, and there's not much we can do anyway.
   111  		if err := cs.runner.StopWorker(cs.workerName); err != nil {
   112  			logger.Warningf("stopping machine agent container watcher: %v", err)
   113  		}
   114  	}
   115  
   116  	// We only care about the initial container creation.
   117  	// This worker has done its job so stop it.
   118  	// We do not expect there will be an error, and there's not much we can do anyway.
   119  	if err := cs.runner.StopWorker(cs.workerName); err != nil {
   120  		logger.Warningf("stopping machine agent container watcher: %v", err)
   121  	}
   122  	if initialiser, broker, err := cs.getContainerArtifacts(containerType); err != nil {
   123  		return errors.Notef(err, "initialising container infrastructure on host machine")
   124  	} else {
   125  		if err := initialiser.Initialise(); err != nil {
   126  			return errors.Notef(err, "setting up container dependnecies on host machine")
   127  		}
   128  		return StartProvisioner(cs.runner, containerType, cs.provisioner, cs.config, broker)
   129  	}
   130  }
   131  
   132  // TearDown is defined on the StringsWatchHandler interface.
   133  func (cs *ContainerSetup) TearDown() error {
   134  	// Nothing to do here.
   135  	return nil
   136  }
   137  
   138  func (cs *ContainerSetup) getContainerArtifacts(containerType instance.ContainerType) (container.Initialiser, environs.InstanceBroker, error) {
   139  	tools, err := cs.provisioner.Tools(cs.config.Tag())
   140  	if err != nil {
   141  		logger.Errorf("cannot get tools from machine for %s container", containerType)
   142  		return nil, nil, err
   143  	}
   144  	var initialiser container.Initialiser
   145  	var broker environs.InstanceBroker
   146  	switch containerType {
   147  	case instance.LXC:
   148  		initialiser = lxc.NewContainerInitialiser()
   149  		broker = NewLxcBroker(cs.provisioner, tools, cs.config)
   150  	case instance.KVM:
   151  		initialiser = kvm.NewContainerInitialiser()
   152  		broker, err = NewKvmBroker(cs.provisioner, tools, cs.config)
   153  		if err != nil {
   154  			logger.Errorf("failed to create new kvm broker")
   155  			return nil, nil, err
   156  		}
   157  	default:
   158  		return nil, nil, errors.Newf("unknown container type: %v", containerType)
   159  	}
   160  	return initialiser, broker, nil
   161  }
   162  
   163  // Override for testing.
   164  var StartProvisioner = startProvisionerWorker
   165  
   166  // startProvisionerWorker kicks off a provisioner task responsible for creating containers
   167  // of the specified type on the machine.
   168  func startProvisionerWorker(runner worker.Runner, containerType instance.ContainerType,
   169  	provisioner *apiprovisioner.State, config agent.Config, broker environs.InstanceBroker) error {
   170  
   171  	workerName := fmt.Sprintf("%s-provisioner", containerType)
   172  	// The provisioner task is created after a container record has already been added to the machine.
   173  	// It will see that the container does not have an instance yet and create one.
   174  	return runner.StartWorker(workerName, func() (worker.Worker, error) {
   175  		return NewContainerProvisioner(containerType, provisioner, config, broker), nil
   176  	})
   177  }