github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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  	"os"
     9  	"sync/atomic"
    10  	"time"
    11  
    12  	"github.com/juju/errors"
    13  	"github.com/juju/loggo"
    14  	"gopkg.in/juju/names.v2"
    15  	"gopkg.in/juju/worker.v1"
    16  
    17  	"github.com/juju/juju/agent"
    18  	"github.com/juju/juju/api/common"
    19  	apiprovisioner "github.com/juju/juju/api/provisioner"
    20  	"github.com/juju/juju/apiserver/params"
    21  	"github.com/juju/juju/container"
    22  	"github.com/juju/juju/container/factory"
    23  	"github.com/juju/juju/container/kvm"
    24  	"github.com/juju/juju/container/lxd"
    25  	"github.com/juju/juju/core/instance"
    26  	"github.com/juju/juju/core/machinelock"
    27  	"github.com/juju/juju/core/watcher"
    28  	"github.com/juju/juju/environs"
    29  	"github.com/juju/juju/network"
    30  	"github.com/juju/juju/state"
    31  	workercommon "github.com/juju/juju/worker/common"
    32  )
    33  
    34  var (
    35  	systemNetworkInterfacesFile = "/etc/network/interfaces"
    36  	systemSbinIfup              = "/sbin/ifup"
    37  	systemNetplanDirectory      = "/etc/netplan"
    38  	activateBridgesTimeout      = 5 * time.Minute
    39  )
    40  
    41  // ContainerSetup is a StringsWatchHandler that is notified when containers
    42  // are created on the given machine. It will set up the machine to be able
    43  // to create containers and start a suitable provisioner.
    44  type ContainerSetup struct {
    45  	runner              *worker.Runner
    46  	supportedContainers []instance.ContainerType
    47  	provisioner         *apiprovisioner.State
    48  	machine             apiprovisioner.MachineProvisioner
    49  	config              agent.Config
    50  	machineLock         machinelock.Lock
    51  
    52  	// Save the workerName so the worker thread can be stopped.
    53  	workerName string
    54  	// setupDone[containerType] is non zero if the container setup has been
    55  	// invoked for that container type.
    56  	setupDone map[instance.ContainerType]*int32
    57  	// The number of provisioners started. Once all necessary provisioners have
    58  	// been started, the container watcher can be stopped.
    59  	numberProvisioners int32
    60  	credentialAPI      workercommon.CredentialAPI
    61  	getNetConfig       func(common.NetworkConfigSource) ([]params.NetworkConfig, error)
    62  }
    63  
    64  // ContainerSetupParams are used to initialise a container setup handler.
    65  type ContainerSetupParams struct {
    66  	Runner              *worker.Runner
    67  	WorkerName          string
    68  	SupportedContainers []instance.ContainerType
    69  	Machine             apiprovisioner.MachineProvisioner
    70  	Provisioner         *apiprovisioner.State
    71  	Config              agent.Config
    72  	MachineLock         machinelock.Lock
    73  	CredentialAPI       workercommon.CredentialAPI
    74  }
    75  
    76  // NewContainerSetupHandler returns a StringsWatchHandler which is notified
    77  // when containers are created on the given machine.
    78  func NewContainerSetupHandler(params ContainerSetupParams) watcher.StringsHandler {
    79  	return &ContainerSetup{
    80  		runner:              params.Runner,
    81  		machine:             params.Machine,
    82  		supportedContainers: params.SupportedContainers,
    83  		provisioner:         params.Provisioner,
    84  		config:              params.Config,
    85  		workerName:          params.WorkerName,
    86  		machineLock:         params.MachineLock,
    87  		credentialAPI:       params.CredentialAPI,
    88  		getNetConfig:        common.GetObservedNetworkConfig,
    89  	}
    90  }
    91  
    92  // SetUp is defined on the StringsWatchHandler interface.
    93  func (cs *ContainerSetup) SetUp() (watcher watcher.StringsWatcher, err error) {
    94  	// Set up the semaphores for each container type.
    95  	cs.setupDone = make(map[instance.ContainerType]*int32, len(instance.ContainerTypes))
    96  	for _, containerType := range instance.ContainerTypes {
    97  		zero := int32(0)
    98  		cs.setupDone[containerType] = &zero
    99  	}
   100  	// Listen to all container lifecycle events on our machine.
   101  	if watcher, err = cs.machine.WatchAllContainers(); err != nil {
   102  		return nil, err
   103  	}
   104  	return watcher, nil
   105  }
   106  
   107  // Handle is called whenever containers change on the machine being watched.
   108  // Machines start out with no containers so the first time Handle is called,
   109  // it will be because a container has been added.
   110  func (cs *ContainerSetup) Handle(abort <-chan struct{}, containerIds []string) (resultError error) {
   111  	// Consume the initial watcher event.
   112  	if len(containerIds) == 0 {
   113  		return nil
   114  	}
   115  
   116  	logger.Infof("initial container setup with ids: %v", containerIds)
   117  	for _, id := range containerIds {
   118  		containerType := state.ContainerTypeFromId(id)
   119  		// If this container type has been dealt with, do nothing.
   120  		if atomic.LoadInt32(cs.setupDone[containerType]) != 0 {
   121  			continue
   122  		}
   123  		if err := cs.initialiseAndStartProvisioner(abort, containerType); err != nil {
   124  			logger.Errorf("starting container provisioner for %v: %v", containerType, err)
   125  			// Just because dealing with one type of container fails, we won't
   126  			// exit the entire function because we still want to try and start
   127  			// other container types. So we take note of and return the first
   128  			// such error.
   129  			if resultError == nil {
   130  				resultError = err
   131  			}
   132  		}
   133  	}
   134  	return errors.Trace(resultError)
   135  }
   136  
   137  func (cs *ContainerSetup) initialiseAndStartProvisioner(
   138  	abort <-chan struct{}, containerType instance.ContainerType,
   139  ) (resultError error) {
   140  	// Flag that this container type has been handled.
   141  	atomic.StoreInt32(cs.setupDone[containerType], 1)
   142  
   143  	defer func() {
   144  		if resultError != nil {
   145  			logger.Warningf("not stopping machine agent container watcher due to error: %v", resultError)
   146  			return
   147  		}
   148  		if atomic.AddInt32(&cs.numberProvisioners, 1) == int32(len(cs.supportedContainers)) {
   149  			// We only care about the initial container creation.
   150  			// This worker has done its job so stop it.
   151  			// We do not expect there will be an error, and there's not much we can do anyway.
   152  			if err := cs.runner.StopWorker(cs.workerName); err != nil {
   153  				logger.Warningf("stopping machine agent container watcher: %v", err)
   154  			}
   155  		}
   156  	}()
   157  
   158  	logger.Debugf("setup and start provisioner for %s containers", containerType)
   159  
   160  	// Do an early check.
   161  	if containerType != instance.LXD && containerType != instance.KVM {
   162  		return fmt.Errorf("unknown container type: %v", containerType)
   163  	}
   164  
   165  	// Get the container manager config before other initialisation,
   166  	// so we know if there are issues with host machine config.
   167  	managerConfig, err := cs.getManagerConfig(containerType)
   168  	if err != nil {
   169  		return errors.Annotate(err, "generating container manager config")
   170  	}
   171  
   172  	if err := cs.initContainerDependencies(abort, containerType); err != nil {
   173  		return errors.Annotate(err, "setting up container dependencies on host machine")
   174  	}
   175  
   176  	toolsFinder := getToolsFinder(cs.provisioner)
   177  	broker, err := cs.getContainerBroker(containerType, toolsFinder, managerConfig)
   178  	if err != nil {
   179  		return errors.Annotate(err, "initialising container infrastructure on host machine")
   180  	}
   181  
   182  	return StartProvisioner(
   183  		cs.runner,
   184  		containerType,
   185  		cs.provisioner,
   186  		cs.config,
   187  		broker,
   188  		toolsFinder,
   189  		getDistributionGroupFinder(cs.provisioner),
   190  		cs.credentialAPI,
   191  	)
   192  }
   193  
   194  // getManagerConfig gets gets container manager config from the provisioner,
   195  // then decorates it with the host machine availability zone before returning.
   196  func (cs *ContainerSetup) getManagerConfig(containerType instance.ContainerType) (container.ManagerConfig, error) {
   197  	managerConfig, err := containerManagerConfig(containerType, cs.provisioner)
   198  	if err != nil {
   199  		return nil, errors.Trace(err)
   200  	}
   201  
   202  	availabilityZone, err := cs.machine.AvailabilityZone()
   203  	if err != nil {
   204  		return nil, errors.Trace(err)
   205  	}
   206  	managerConfig[container.ConfigAvailabilityZone] = availabilityZone
   207  
   208  	return managerConfig, nil
   209  }
   210  
   211  // initContainerDependencies ensures that the host machine is set-up to manage
   212  // containers of the input type.
   213  func (cs *ContainerSetup) initContainerDependencies(abort <-chan struct{}, containerType instance.ContainerType) error {
   214  	initialiser := getContainerInitialiser(containerType)
   215  
   216  	releaser, err := cs.acquireLock(fmt.Sprintf("%s container initialisation", containerType), abort)
   217  	if err != nil {
   218  		return errors.Annotate(err, "failed to acquire initialization lock")
   219  	}
   220  	defer releaser()
   221  
   222  	if err := initialiser.Initialise(); err != nil {
   223  		return errors.Trace(err)
   224  	}
   225  
   226  	// At this point, Initialiser likely has changed host network information,
   227  	// so re-probe to have an accurate view.
   228  	observedConfig, err := cs.observeNetwork()
   229  	if err != nil {
   230  		return errors.Annotate(err, "cannot discover observed network config")
   231  	}
   232  	if len(observedConfig) > 0 {
   233  		machineTag := cs.machine.MachineTag()
   234  		logger.Tracef("updating observed network config for %q %s containers to %#v",
   235  			machineTag, containerType, observedConfig)
   236  		if err := cs.provisioner.SetHostMachineNetworkConfig(machineTag, observedConfig); err != nil {
   237  			return errors.Trace(err)
   238  		}
   239  	}
   240  
   241  	return nil
   242  }
   243  
   244  // acquireLock tries to grab the machine lock (initLockName), and either
   245  // returns it in a locked state, or returns an error.
   246  func (cs *ContainerSetup) acquireLock(comment string, abort <-chan struct{}) (func(), error) {
   247  	spec := machinelock.Spec{
   248  		Cancel:  abort,
   249  		Worker:  "provisioner",
   250  		Comment: comment,
   251  	}
   252  	return cs.machineLock.Acquire(spec)
   253  }
   254  
   255  func (cs *ContainerSetup) observeNetwork() ([]params.NetworkConfig, error) {
   256  	return cs.getNetConfig(common.DefaultNetworkConfigSource())
   257  }
   258  
   259  func defaultBridger() (network.Bridger, error) {
   260  	if _, err := os.Stat(systemSbinIfup); err == nil {
   261  		return network.DefaultEtcNetworkInterfacesBridger(activateBridgesTimeout, systemNetworkInterfacesFile)
   262  	} else {
   263  		return network.DefaultNetplanBridger(activateBridgesTimeout, systemNetplanDirectory)
   264  	}
   265  }
   266  
   267  func (cs *ContainerSetup) prepareHost(containerTag names.MachineTag, log loggo.Logger, abort <-chan struct{}) error {
   268  	preparer := NewHostPreparer(HostPreparerParams{
   269  		API:                cs.provisioner,
   270  		ObserveNetworkFunc: cs.observeNetwork,
   271  		AcquireLockFunc:    cs.acquireLock,
   272  		CreateBridger:      defaultBridger,
   273  		AbortChan:          abort,
   274  		MachineTag:         cs.machine.MachineTag(),
   275  		Logger:             log,
   276  	})
   277  	return preparer.Prepare(containerTag)
   278  }
   279  
   280  // getContainerArtifacts returns type-specific interfaces for
   281  // managing containers.
   282  func (cs *ContainerSetup) getContainerBroker(
   283  	containerType instance.ContainerType, toolsFinder ToolsFinder, managerConfig container.ManagerConfig,
   284  ) (environs.InstanceBroker, error) {
   285  	manager, err := factory.NewContainerManager(containerType, managerConfig)
   286  	if err != nil {
   287  		return nil, errors.Trace(err)
   288  	}
   289  
   290  	newBroker := NewKVMBroker
   291  	if containerType == instance.LXD {
   292  		newBroker = NewLXDBroker
   293  	}
   294  	broker, err := newBroker(cs.prepareHost, cs.provisioner, manager, cs.config)
   295  	if err != nil {
   296  		logger.Errorf("failed to create new %s broker", containerType)
   297  		return nil, errors.Trace(err)
   298  	}
   299  
   300  	return broker, nil
   301  }
   302  
   303  // TearDown is defined on the StringsWatchHandler interface. NoOp here.
   304  func (cs *ContainerSetup) TearDown() error {
   305  	return nil
   306  }
   307  
   308  // getContainerInitialiser exists to patch out in tests.
   309  var getContainerInitialiser = func(ct instance.ContainerType) container.Initialiser {
   310  	if ct == instance.LXD {
   311  		return lxd.NewContainerInitialiser()
   312  	}
   313  	return kvm.NewContainerInitialiser()
   314  }
   315  
   316  func containerManagerConfig(
   317  	containerType instance.ContainerType, provisioner *apiprovisioner.State,
   318  ) (container.ManagerConfig, error) {
   319  	// Ask the provisioner for the container manager configuration.
   320  	managerConfigResult, err := provisioner.ContainerManagerConfig(
   321  		params.ContainerManagerConfigParams{Type: containerType},
   322  	)
   323  	if err != nil {
   324  		return nil, errors.Trace(err)
   325  	}
   326  	managerConfig := container.ManagerConfig(managerConfigResult.ManagerConfig)
   327  	return managerConfig, nil
   328  }
   329  
   330  // Override for testing.
   331  var StartProvisioner = startProvisionerWorker
   332  
   333  // startProvisionerWorker kicks off a provisioner task responsible for creating
   334  // containers of the specified type on the machine.
   335  func startProvisionerWorker(
   336  	runner *worker.Runner,
   337  	containerType instance.ContainerType,
   338  	provisioner *apiprovisioner.State,
   339  	config agent.Config,
   340  	broker environs.InstanceBroker,
   341  	toolsFinder ToolsFinder,
   342  	distributionGroupFinder DistributionGroupFinder,
   343  	credentialAPI workercommon.CredentialAPI,
   344  ) error {
   345  
   346  	workerName := fmt.Sprintf("%s-provisioner", containerType)
   347  	// The provisioner task is created after a container record has
   348  	// already been added to the machine. It will see that the
   349  	// container does not have an instance yet and create one.
   350  	return runner.StartWorker(workerName, func() (worker.Worker, error) {
   351  		w, err := NewContainerProvisioner(containerType,
   352  			provisioner,
   353  			config,
   354  			broker,
   355  			toolsFinder,
   356  			distributionGroupFinder,
   357  			credentialAPI,
   358  		)
   359  		if err != nil {
   360  			return nil, errors.Trace(err)
   361  		}
   362  		return w, nil
   363  	})
   364  }