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