github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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  	"time"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/mutex"
    13  	"github.com/juju/utils/clock"
    14  
    15  	"github.com/juju/juju/agent"
    16  	apiprovisioner "github.com/juju/juju/api/provisioner"
    17  	"github.com/juju/juju/apiserver/params"
    18  	"github.com/juju/juju/container"
    19  	"github.com/juju/juju/container/kvm"
    20  	"github.com/juju/juju/container/lxd"
    21  	"github.com/juju/juju/environs"
    22  	"github.com/juju/juju/instance"
    23  	"github.com/juju/juju/state"
    24  	"github.com/juju/juju/watcher"
    25  	"github.com/juju/juju/worker"
    26  )
    27  
    28  // ContainerSetup is a StringsWatchHandler that is notified when containers
    29  // are created on the given machine. It will set up the machine to be able
    30  // to create containers and start a suitable provisioner.
    31  type ContainerSetup struct {
    32  	runner              worker.Runner
    33  	supportedContainers []instance.ContainerType
    34  	provisioner         *apiprovisioner.State
    35  	machine             *apiprovisioner.Machine
    36  	config              agent.Config
    37  	initLockName        string
    38  
    39  	// Save the workerName so the worker thread can be stopped.
    40  	workerName string
    41  	// setupDone[containerType] is non zero if the container setup has been invoked
    42  	// for that container type.
    43  	setupDone map[instance.ContainerType]*int32
    44  	// The number of provisioners started. Once all necessary provisioners have
    45  	// been started, the container watcher can be stopped.
    46  	numberProvisioners int32
    47  }
    48  
    49  // ContainerSetupParams are used to initialise a container setup handler.
    50  type ContainerSetupParams struct {
    51  	Runner              worker.Runner
    52  	WorkerName          string
    53  	SupportedContainers []instance.ContainerType
    54  	Machine             *apiprovisioner.Machine
    55  	Provisioner         *apiprovisioner.State
    56  	Config              agent.Config
    57  	InitLockName        string
    58  }
    59  
    60  // NewContainerSetupHandler returns a StringsWatchHandler which is notified when
    61  // containers are created on the given machine.
    62  func NewContainerSetupHandler(params ContainerSetupParams) watcher.StringsHandler {
    63  	return &ContainerSetup{
    64  		runner:              params.Runner,
    65  		machine:             params.Machine,
    66  		supportedContainers: params.SupportedContainers,
    67  		provisioner:         params.Provisioner,
    68  		config:              params.Config,
    69  		workerName:          params.WorkerName,
    70  		initLockName:        params.InitLockName,
    71  	}
    72  }
    73  
    74  // SetUp is defined on the StringsWatchHandler interface.
    75  func (cs *ContainerSetup) SetUp() (watcher watcher.StringsWatcher, err error) {
    76  	// Set up the semaphores for each container type.
    77  	cs.setupDone = make(map[instance.ContainerType]*int32, len(instance.ContainerTypes))
    78  	for _, containerType := range instance.ContainerTypes {
    79  		zero := int32(0)
    80  		cs.setupDone[containerType] = &zero
    81  	}
    82  	// Listen to all container lifecycle events on our machine.
    83  	if watcher, err = cs.machine.WatchAllContainers(); err != nil {
    84  		return nil, err
    85  	}
    86  	return watcher, nil
    87  }
    88  
    89  // Handle is called whenever containers change on the machine being watched.
    90  // Machines start out with no containers so the first time Handle is called,
    91  // it will be because a container has been added.
    92  func (cs *ContainerSetup) Handle(abort <-chan struct{}, containerIds []string) (resultError error) {
    93  	// Consume the initial watcher event.
    94  	if len(containerIds) == 0 {
    95  		return nil
    96  	}
    97  
    98  	logger.Infof("initial container setup with ids: %v", containerIds)
    99  	for _, id := range containerIds {
   100  		containerType := state.ContainerTypeFromId(id)
   101  		// If this container type has been dealt with, do nothing.
   102  		if atomic.LoadInt32(cs.setupDone[containerType]) != 0 {
   103  			continue
   104  		}
   105  		if err := cs.initialiseAndStartProvisioner(abort, containerType); err != nil {
   106  			logger.Errorf("starting container provisioner for %v: %v", containerType, err)
   107  			// Just because dealing with one type of container fails, we won't exit the entire
   108  			// function because we still want to try and start other container types. So we
   109  			// take note of and return the first such error.
   110  			if resultError == nil {
   111  				resultError = err
   112  			}
   113  		}
   114  	}
   115  	return resultError
   116  }
   117  
   118  func (cs *ContainerSetup) initialiseAndStartProvisioner(abort <-chan struct{}, containerType instance.ContainerType) (resultError error) {
   119  	// Flag that this container type has been handled.
   120  	atomic.StoreInt32(cs.setupDone[containerType], 1)
   121  
   122  	defer func() {
   123  		if resultError != nil {
   124  			logger.Warningf("not stopping machine agent container watcher due to error: %v", resultError)
   125  			return
   126  		}
   127  		if atomic.AddInt32(&cs.numberProvisioners, 1) == int32(len(cs.supportedContainers)) {
   128  			// We only care about the initial container creation.
   129  			// This worker has done its job so stop it.
   130  			// We do not expect there will be an error, and there's not much we can do anyway.
   131  			if err := cs.runner.StopWorker(cs.workerName); err != nil {
   132  				logger.Warningf("stopping machine agent container watcher: %v", err)
   133  			}
   134  		}
   135  	}()
   136  
   137  	logger.Debugf("setup and start provisioner for %s containers", containerType)
   138  	toolsFinder := getToolsFinder(cs.provisioner)
   139  	initialiser, broker, toolsFinder, err := cs.getContainerArtifacts(containerType, toolsFinder)
   140  	if err != nil {
   141  		return errors.Annotate(err, "initialising container infrastructure on host machine")
   142  	}
   143  	if err := cs.runInitialiser(abort, containerType, initialiser); err != nil {
   144  		return errors.Annotate(err, "setting up container dependencies on host machine")
   145  	}
   146  	return StartProvisioner(cs.runner, containerType, cs.provisioner, cs.config, broker, toolsFinder)
   147  }
   148  
   149  // runInitialiser runs the container initialiser with the initialisation hook held.
   150  func (cs *ContainerSetup) runInitialiser(abort <-chan struct{}, containerType instance.ContainerType, initialiser container.Initialiser) error {
   151  	logger.Debugf("running initialiser for %s containers", containerType)
   152  	spec := mutex.Spec{
   153  		Name:  cs.initLockName,
   154  		Clock: clock.WallClock,
   155  		// If we don't get the lock straigh away, there is no point trying multiple
   156  		// times per second for an operation that is likelty to take multiple seconds.
   157  		Delay:  time.Second,
   158  		Cancel: abort,
   159  	}
   160  	logger.Debugf("acquire lock %q for container initialisation", cs.initLockName)
   161  	releaser, err := mutex.Acquire(spec)
   162  	if err != nil {
   163  		return errors.Annotate(err, "failed to acquire initialization lock")
   164  	}
   165  	logger.Debugf("lock %q acquired", cs.initLockName)
   166  	defer logger.Debugf("release lock %q for container initialisation", cs.initLockName)
   167  	defer releaser.Release()
   168  
   169  	if err := initialiser.Initialise(); err != nil {
   170  		return errors.Trace(err)
   171  	}
   172  
   173  	return nil
   174  }
   175  
   176  // TearDown is defined on the StringsWatchHandler interface.
   177  func (cs *ContainerSetup) TearDown() error {
   178  	// Nothing to do here.
   179  	return nil
   180  }
   181  
   182  // getContainerArtifacts returns type-specific interfaces for
   183  // managing containers.
   184  //
   185  // The ToolsFinder passed in may be replaced or wrapped to
   186  // enforce container-specific constraints.
   187  func (cs *ContainerSetup) getContainerArtifacts(
   188  	containerType instance.ContainerType, toolsFinder ToolsFinder,
   189  ) (
   190  	container.Initialiser,
   191  	environs.InstanceBroker,
   192  	ToolsFinder,
   193  	error,
   194  ) {
   195  	var initialiser container.Initialiser
   196  	var broker environs.InstanceBroker
   197  
   198  	managerConfig, err := containerManagerConfig(containerType, cs.provisioner, cs.config)
   199  	if err != nil {
   200  		return nil, nil, nil, err
   201  	}
   202  
   203  	switch containerType {
   204  	case instance.KVM:
   205  		initialiser = kvm.NewContainerInitialiser()
   206  		broker, err = NewKvmBroker(
   207  			cs.provisioner,
   208  			cs.config,
   209  			managerConfig,
   210  		)
   211  		if err != nil {
   212  			logger.Errorf("failed to create new kvm broker")
   213  			return nil, nil, nil, err
   214  		}
   215  	case instance.LXD:
   216  		series, err := cs.machine.Series()
   217  		if err != nil {
   218  			return nil, nil, nil, err
   219  		}
   220  
   221  		initialiser = lxd.NewContainerInitialiser(series)
   222  		manager, err := lxd.NewContainerManager(managerConfig)
   223  		if err != nil {
   224  			return nil, nil, nil, err
   225  		}
   226  		broker, err = NewLxdBroker(
   227  			cs.provisioner,
   228  			manager,
   229  			cs.config,
   230  		)
   231  		if err != nil {
   232  			logger.Errorf("failed to create new lxd broker")
   233  			return nil, nil, nil, err
   234  		}
   235  	default:
   236  		return nil, nil, nil, fmt.Errorf("unknown container type: %v", containerType)
   237  	}
   238  
   239  	return initialiser, broker, toolsFinder, nil
   240  }
   241  
   242  func containerManagerConfig(
   243  	containerType instance.ContainerType,
   244  	provisioner *apiprovisioner.State,
   245  	agentConfig agent.Config,
   246  ) (container.ManagerConfig, error) {
   247  	// Ask the provisioner for the container manager configuration.
   248  	managerConfigResult, err := provisioner.ContainerManagerConfig(
   249  		params.ContainerManagerConfigParams{Type: containerType},
   250  	)
   251  	if err != nil {
   252  		return nil, errors.Trace(err)
   253  	}
   254  	managerConfig := container.ManagerConfig(managerConfigResult.ManagerConfig)
   255  	return managerConfig, nil
   256  }
   257  
   258  // Override for testing.
   259  var StartProvisioner = startProvisionerWorker
   260  
   261  // startProvisionerWorker kicks off a provisioner task responsible for creating containers
   262  // of the specified type on the machine.
   263  func startProvisionerWorker(
   264  	runner worker.Runner,
   265  	containerType instance.ContainerType,
   266  	provisioner *apiprovisioner.State,
   267  	config agent.Config,
   268  	broker environs.InstanceBroker,
   269  	toolsFinder ToolsFinder,
   270  ) error {
   271  
   272  	workerName := fmt.Sprintf("%s-provisioner", containerType)
   273  	// The provisioner task is created after a container record has
   274  	// already been added to the machine. It will see that the
   275  	// container does not have an instance yet and create one.
   276  	return runner.StartWorker(workerName, func() (worker.Worker, error) {
   277  		w, err := NewContainerProvisioner(containerType, provisioner, config, broker, toolsFinder)
   278  		if err != nil {
   279  			return nil, errors.Trace(err)
   280  		}
   281  		return w, nil
   282  	})
   283  }