github.com/rogpeppe/juju@v0.0.0-20140613142852-6337964b789e/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  	"github.com/juju/utils/fslock"
    11  
    12  	"github.com/juju/juju/agent"
    13  	"github.com/juju/juju/container"
    14  	"github.com/juju/juju/container/kvm"
    15  	"github.com/juju/juju/container/lxc"
    16  	"github.com/juju/juju/environs"
    17  	"github.com/juju/juju/instance"
    18  	"github.com/juju/juju/state"
    19  	"github.com/juju/juju/state/api/params"
    20  	apiprovisioner "github.com/juju/juju/state/api/provisioner"
    21  	"github.com/juju/juju/state/api/watcher"
    22  	"github.com/juju/juju/worker"
    23  )
    24  
    25  // ContainerSetup is a StringsWatchHandler that is notified when containers
    26  // are created on the given machine. It will set up the machine to be able
    27  // to create containers and start a suitable provisioner.
    28  type ContainerSetup struct {
    29  	runner              worker.Runner
    30  	supportedContainers []instance.ContainerType
    31  	provisioner         *apiprovisioner.State
    32  	machine             *apiprovisioner.Machine
    33  	config              agent.Config
    34  	initLock            *fslock.Lock
    35  
    36  	// Save the workerName so the worker thread can be stopped.
    37  	workerName string
    38  	// setupDone[containerType] is non zero if the container setup has been invoked
    39  	// for that container type.
    40  	setupDone map[instance.ContainerType]*int32
    41  	// The number of provisioners started. Once all necessary provisioners have
    42  	// been started, the container watcher can be stopped.
    43  	numberProvisioners int32
    44  }
    45  
    46  // NewContainerSetupHandler returns a StringsWatchHandler which is notified when
    47  // containers are created on the given machine.
    48  func NewContainerSetupHandler(runner worker.Runner, workerName string, supportedContainers []instance.ContainerType,
    49  	machine *apiprovisioner.Machine, provisioner *apiprovisioner.State,
    50  	config agent.Config, initLock *fslock.Lock) worker.StringsWatchHandler {
    51  
    52  	return &ContainerSetup{
    53  		runner:              runner,
    54  		machine:             machine,
    55  		supportedContainers: supportedContainers,
    56  		provisioner:         provisioner,
    57  		config:              config,
    58  		workerName:          workerName,
    59  		initLock:            initLock,
    60  	}
    61  }
    62  
    63  // SetUp is defined on the StringsWatchHandler interface.
    64  func (cs *ContainerSetup) SetUp() (watcher watcher.StringsWatcher, err error) {
    65  	// Set up the semaphores for each container type.
    66  	cs.setupDone = make(map[instance.ContainerType]*int32, len(instance.ContainerTypes))
    67  	for _, containerType := range instance.ContainerTypes {
    68  		zero := int32(0)
    69  		cs.setupDone[containerType] = &zero
    70  	}
    71  	// Listen to all container lifecycle events on our machine.
    72  	if watcher, err = cs.machine.WatchAllContainers(); err != nil {
    73  		return nil, err
    74  	}
    75  	return watcher, nil
    76  }
    77  
    78  // Handle is called whenever containers change on the machine being watched.
    79  // Machines start out with no containers so the first time Handle is called,
    80  // it will be because a container has been added.
    81  func (cs *ContainerSetup) Handle(containerIds []string) (resultError error) {
    82  	// Consume the initial watcher event.
    83  	if len(containerIds) == 0 {
    84  		return nil
    85  	}
    86  
    87  	logger.Tracef("initial container setup with ids: %v", containerIds)
    88  	for _, id := range containerIds {
    89  		containerType := state.ContainerTypeFromId(id)
    90  		// If this container type has been dealt with, do nothing.
    91  		if atomic.LoadInt32(cs.setupDone[containerType]) != 0 {
    92  			continue
    93  		}
    94  		if err := cs.initialiseAndStartProvisioner(containerType); err != nil {
    95  			logger.Errorf("starting container provisioner for %v: %v", containerType, err)
    96  			// Just because dealing with one type of container fails, we won't exit the entire
    97  			// function because we still want to try and start other container types. So we
    98  			// take note of and return the first such error.
    99  			if resultError == nil {
   100  				resultError = err
   101  			}
   102  		}
   103  	}
   104  	return resultError
   105  }
   106  
   107  func (cs *ContainerSetup) initialiseAndStartProvisioner(containerType instance.ContainerType) error {
   108  	// Flag that this container type has been handled.
   109  	atomic.StoreInt32(cs.setupDone[containerType], 1)
   110  
   111  	if atomic.AddInt32(&cs.numberProvisioners, 1) == int32(len(cs.supportedContainers)) {
   112  		// We only care about the initial container creation.
   113  		// This worker has done its job so stop it.
   114  		// We do not expect there will be an error, and there's not much we can do anyway.
   115  		if err := cs.runner.StopWorker(cs.workerName); err != nil {
   116  			logger.Warningf("stopping machine agent container watcher: %v", err)
   117  		}
   118  	}
   119  
   120  	// We only care about the initial container creation.
   121  	// This worker has done its job so stop it.
   122  	// We do not expect there will be an error, and there's not much we can do anyway.
   123  	if err := cs.runner.StopWorker(cs.workerName); err != nil {
   124  		logger.Warningf("stopping machine agent container watcher: %v", err)
   125  	}
   126  	if initialiser, broker, err := cs.getContainerArtifacts(containerType); err != nil {
   127  		return fmt.Errorf("initialising container infrastructure on host machine: %v", err)
   128  	} else {
   129  		if err := cs.runInitialiser(containerType, initialiser); err != nil {
   130  			return fmt.Errorf("setting up container dependencies on host machine: %v", err)
   131  		}
   132  		return StartProvisioner(cs.runner, containerType, cs.provisioner, cs.config, broker)
   133  	}
   134  }
   135  
   136  // runInitialiser runs the container initialiser with the initialisation hook held.
   137  func (cs *ContainerSetup) runInitialiser(containerType instance.ContainerType, initialiser container.Initialiser) error {
   138  	if err := cs.initLock.Lock(fmt.Sprintf("initialise-%s", containerType)); err != nil {
   139  		return fmt.Errorf("failed to acquire initialization lock: %v", err)
   140  	}
   141  	defer cs.initLock.Unlock()
   142  	return initialiser.Initialise()
   143  }
   144  
   145  // TearDown is defined on the StringsWatchHandler interface.
   146  func (cs *ContainerSetup) TearDown() error {
   147  	// Nothing to do here.
   148  	return nil
   149  }
   150  
   151  func (cs *ContainerSetup) getContainerArtifacts(containerType instance.ContainerType) (container.Initialiser, environs.InstanceBroker, error) {
   152  	tools, err := cs.provisioner.Tools(cs.config.Tag())
   153  	if err != nil {
   154  		logger.Errorf("cannot get tools from machine for %s container", containerType)
   155  		return nil, nil, err
   156  	}
   157  	var initialiser container.Initialiser
   158  	var broker environs.InstanceBroker
   159  
   160  	managerConfig, err := containerManagerConfig(containerType, cs.provisioner, cs.config)
   161  	if err != nil {
   162  		return nil, nil, err
   163  	}
   164  
   165  	switch containerType {
   166  	case instance.LXC:
   167  		series, err := cs.machine.Series()
   168  		if err != nil {
   169  			return nil, nil, err
   170  		}
   171  
   172  		initialiser = lxc.NewContainerInitialiser(series)
   173  		broker, err = NewLxcBroker(cs.provisioner, tools, cs.config, managerConfig)
   174  		if err != nil {
   175  			return nil, nil, err
   176  		}
   177  	case instance.KVM:
   178  		initialiser = kvm.NewContainerInitialiser()
   179  		broker, err = NewKvmBroker(cs.provisioner, tools, cs.config, managerConfig)
   180  		if err != nil {
   181  			logger.Errorf("failed to create new kvm broker")
   182  			return nil, nil, err
   183  		}
   184  	default:
   185  		return nil, nil, fmt.Errorf("unknown container type: %v", containerType)
   186  	}
   187  	return initialiser, broker, nil
   188  }
   189  
   190  func containerManagerConfig(
   191  	containerType instance.ContainerType,
   192  	provisioner *apiprovisioner.State,
   193  	agentConfig agent.Config,
   194  ) (container.ManagerConfig, error) {
   195  	// Ask the provisioner for the container manager configuration.
   196  	managerConfigResult, err := provisioner.ContainerManagerConfig(
   197  		params.ContainerManagerConfigParams{Type: containerType},
   198  	)
   199  	if params.IsCodeNotImplemented(err) {
   200  		// We currently don't support upgrading;
   201  		// revert to the old configuration.
   202  		managerConfigResult.ManagerConfig = container.ManagerConfig{container.ConfigName: "juju"}
   203  	}
   204  	if err != nil {
   205  		return nil, err
   206  	}
   207  	// If a namespace is specified, that should instead be used as the config name.
   208  	if namespace := agentConfig.Value(agent.Namespace); namespace != "" {
   209  		managerConfigResult.ManagerConfig[container.ConfigName] = namespace
   210  	}
   211  	managerConfig := container.ManagerConfig(managerConfigResult.ManagerConfig)
   212  	return managerConfig, nil
   213  }
   214  
   215  // Override for testing.
   216  var StartProvisioner = startProvisionerWorker
   217  
   218  // startProvisionerWorker kicks off a provisioner task responsible for creating containers
   219  // of the specified type on the machine.
   220  func startProvisionerWorker(runner worker.Runner, containerType instance.ContainerType,
   221  	provisioner *apiprovisioner.State, config agent.Config, broker environs.InstanceBroker) error {
   222  
   223  	workerName := fmt.Sprintf("%s-provisioner", containerType)
   224  	// The provisioner task is created after a container record has already been added to the machine.
   225  	// It will see that the container does not have an instance yet and create one.
   226  	return runner.StartWorker(workerName, func() (worker.Worker, error) {
   227  		return NewContainerProvisioner(containerType, provisioner, config, broker), nil
   228  	})
   229  }