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