github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/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  	"strconv"
     9  	"sync/atomic"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/utils"
    13  	"github.com/juju/utils/exec"
    14  	"github.com/juju/utils/fslock"
    15  
    16  	"github.com/juju/juju/agent"
    17  	apiprovisioner "github.com/juju/juju/api/provisioner"
    18  	"github.com/juju/juju/api/watcher"
    19  	"github.com/juju/juju/apiserver/params"
    20  	"github.com/juju/juju/container"
    21  	"github.com/juju/juju/container/kvm"
    22  	"github.com/juju/juju/container/lxc"
    23  	"github.com/juju/juju/environs"
    24  	"github.com/juju/juju/instance"
    25  	"github.com/juju/juju/state"
    26  	"github.com/juju/juju/worker"
    27  )
    28  
    29  // ContainerSetup is a StringsWatchHandler that is notified when containers
    30  // are created on the given machine. It will set up the machine to be able
    31  // to create containers and start a suitable provisioner.
    32  type ContainerSetup struct {
    33  	runner                worker.Runner
    34  	supportedContainers   []instance.ContainerType
    35  	imageURLGetter        container.ImageURLGetter
    36  	provisioner           *apiprovisioner.State
    37  	machine               *apiprovisioner.Machine
    38  	config                agent.Config
    39  	initLock              *fslock.Lock
    40  	addressableContainers bool
    41  	enableNAT             bool
    42  	lxcDefaultMTU         int
    43  
    44  	// Save the workerName so the worker thread can be stopped.
    45  	workerName string
    46  	// setupDone[containerType] is non zero if the container setup has been invoked
    47  	// for that container type.
    48  	setupDone map[instance.ContainerType]*int32
    49  	// The number of provisioners started. Once all necessary provisioners have
    50  	// been started, the container watcher can be stopped.
    51  	numberProvisioners int32
    52  }
    53  
    54  // ContainerSetupParams are used to initialise a container setup handler.
    55  type ContainerSetupParams struct {
    56  	Runner              worker.Runner
    57  	WorkerName          string
    58  	SupportedContainers []instance.ContainerType
    59  	ImageURLGetter      container.ImageURLGetter
    60  	Machine             *apiprovisioner.Machine
    61  	Provisioner         *apiprovisioner.State
    62  	Config              agent.Config
    63  	InitLock            *fslock.Lock
    64  }
    65  
    66  // NewContainerSetupHandler returns a StringsWatchHandler which is notified when
    67  // containers are created on the given machine.
    68  func NewContainerSetupHandler(params ContainerSetupParams) worker.StringsWatchHandler {
    69  	return &ContainerSetup{
    70  		runner:              params.Runner,
    71  		imageURLGetter:      params.ImageURLGetter,
    72  		machine:             params.Machine,
    73  		supportedContainers: params.SupportedContainers,
    74  		provisioner:         params.Provisioner,
    75  		config:              params.Config,
    76  		workerName:          params.WorkerName,
    77  		initLock:            params.InitLock,
    78  	}
    79  }
    80  
    81  // SetUp is defined on the StringsWatchHandler interface.
    82  func (cs *ContainerSetup) SetUp() (watcher watcher.StringsWatcher, err error) {
    83  	// Set up the semaphores for each container type.
    84  	cs.setupDone = make(map[instance.ContainerType]*int32, len(instance.ContainerTypes))
    85  	for _, containerType := range instance.ContainerTypes {
    86  		zero := int32(0)
    87  		cs.setupDone[containerType] = &zero
    88  	}
    89  	// Listen to all container lifecycle events on our machine.
    90  	if watcher, err = cs.machine.WatchAllContainers(); err != nil {
    91  		return nil, err
    92  	}
    93  	return watcher, nil
    94  }
    95  
    96  // Handle is called whenever containers change on the machine being watched.
    97  // Machines start out with no containers so the first time Handle is called,
    98  // it will be because a container has been added.
    99  func (cs *ContainerSetup) Handle(containerIds []string) (resultError error) {
   100  	// Consume the initial watcher event.
   101  	if len(containerIds) == 0 {
   102  		return nil
   103  	}
   104  
   105  	logger.Infof("initial container setup with ids: %v", containerIds)
   106  	for _, id := range containerIds {
   107  		containerType := state.ContainerTypeFromId(id)
   108  		// If this container type has been dealt with, do nothing.
   109  		if atomic.LoadInt32(cs.setupDone[containerType]) != 0 {
   110  			continue
   111  		}
   112  		if err := cs.initialiseAndStartProvisioner(containerType); err != nil {
   113  			logger.Errorf("starting container provisioner for %v: %v", containerType, err)
   114  			// Just because dealing with one type of container fails, we won't exit the entire
   115  			// function because we still want to try and start other container types. So we
   116  			// take note of and return the first such error.
   117  			if resultError == nil {
   118  				resultError = err
   119  			}
   120  		}
   121  	}
   122  	return resultError
   123  }
   124  
   125  func (cs *ContainerSetup) initialiseAndStartProvisioner(containerType instance.ContainerType) (resultError error) {
   126  	// Flag that this container type has been handled.
   127  	atomic.StoreInt32(cs.setupDone[containerType], 1)
   128  
   129  	defer func() {
   130  		if resultError != nil {
   131  			logger.Warningf("not stopping machine agent container watcher due to error: %v", resultError)
   132  			return
   133  		}
   134  		if atomic.AddInt32(&cs.numberProvisioners, 1) == int32(len(cs.supportedContainers)) {
   135  			// We only care about the initial container creation.
   136  			// This worker has done its job so stop it.
   137  			// We do not expect there will be an error, and there's not much we can do anyway.
   138  			if err := cs.runner.StopWorker(cs.workerName); err != nil {
   139  				logger.Warningf("stopping machine agent container watcher: %v", err)
   140  			}
   141  		}
   142  	}()
   143  
   144  	logger.Debugf("setup and start provisioner for %s containers", containerType)
   145  	toolsFinder := getToolsFinder(cs.provisioner)
   146  	initialiser, broker, toolsFinder, err := cs.getContainerArtifacts(containerType, toolsFinder)
   147  	if err != nil {
   148  		return errors.Annotate(err, "initialising container infrastructure on host machine")
   149  	}
   150  	if err := cs.runInitialiser(containerType, initialiser); err != nil {
   151  		return errors.Annotate(err, "setting up container dependencies on host machine")
   152  	}
   153  	return StartProvisioner(cs.runner, containerType, cs.provisioner, cs.config, broker, toolsFinder)
   154  }
   155  
   156  const etcDefaultLXCNet = `
   157  # Modified by Juju to enable addressable LXC containers.
   158  USE_LXC_BRIDGE="true"
   159  LXC_BRIDGE="lxcbr0"
   160  LXC_ADDR="10.0.3.1"
   161  LXC_NETMASK="255.255.255.0"
   162  LXC_NETWORK="10.0.3.0/24"
   163  LXC_DHCP_RANGE="10.0.3.2,10.0.3.254,infinite"
   164  LXC_DHCP_MAX="253"
   165  `
   166  
   167  var etcDefaultLXCNetPath = "/etc/default/lxc-net"
   168  
   169  // maybeOverrideDefaultLXCNet writes a modified version of
   170  // /etc/default/lxc-net file on the host before installing the lxc
   171  // package, if we're about to start an addressable LXC container. This
   172  // is needed to guarantee stable statically assigned IP addresses for
   173  // the container. See also runInitialiser.
   174  func maybeOverrideDefaultLXCNet(containerType instance.ContainerType, addressable bool) error {
   175  	if containerType != instance.LXC || !addressable {
   176  		// Nothing to do.
   177  		return nil
   178  	}
   179  
   180  	err := utils.AtomicWriteFile(etcDefaultLXCNetPath, []byte(etcDefaultLXCNet), 0644)
   181  	if err != nil {
   182  		return errors.Annotatef(err, "cannot write %q", etcDefaultLXCNetPath)
   183  	}
   184  	return nil
   185  }
   186  
   187  // runInitialiser runs the container initialiser with the initialisation hook held.
   188  func (cs *ContainerSetup) runInitialiser(containerType instance.ContainerType, initialiser container.Initialiser) error {
   189  	logger.Debugf("running initialiser for %s containers", containerType)
   190  	if err := cs.initLock.Lock(fmt.Sprintf("initialise-%s", containerType)); err != nil {
   191  		return errors.Annotate(err, "failed to acquire initialization lock")
   192  	}
   193  	defer cs.initLock.Unlock()
   194  
   195  	// Only tweak default LXC network config when address allocation
   196  	// feature flag is enabled.
   197  	if environs.AddressAllocationEnabled() {
   198  		// In order to guarantee stable statically assigned IP addresses
   199  		// for LXC containers, we need to install a custom version of
   200  		// /etc/default/lxc-net before we install the lxc package. The
   201  		// custom version of lxc-net is almost the same as the original,
   202  		// but the defined LXC_DHCP_RANGE (used by dnsmasq to give away
   203  		// 10.0.3.x addresses to containers bound to lxcbr0) has infinite
   204  		// lease time. This is necessary, because with the default lease
   205  		// time of 1h, dhclient running inside each container will request
   206  		// a renewal from dnsmasq and replace our statically configured IP
   207  		// address within an hour after starting the container.
   208  		err := maybeOverrideDefaultLXCNet(containerType, cs.addressableContainers)
   209  		if err != nil {
   210  			return errors.Trace(err)
   211  		}
   212  	}
   213  
   214  	if err := initialiser.Initialise(); err != nil {
   215  		return errors.Trace(err)
   216  	}
   217  
   218  	return nil
   219  }
   220  
   221  // TearDown is defined on the StringsWatchHandler interface.
   222  func (cs *ContainerSetup) TearDown() error {
   223  	// Nothing to do here.
   224  	return nil
   225  }
   226  
   227  // getContainerArtifacts returns type-specific interfaces for
   228  // managing containers.
   229  //
   230  // The ToolsFinder passed in may be replaced or wrapped to
   231  // enforce container-specific constraints.
   232  func (cs *ContainerSetup) getContainerArtifacts(
   233  	containerType instance.ContainerType, toolsFinder ToolsFinder,
   234  ) (
   235  	container.Initialiser,
   236  	environs.InstanceBroker,
   237  	ToolsFinder,
   238  	error,
   239  ) {
   240  	var initialiser container.Initialiser
   241  	var broker environs.InstanceBroker
   242  
   243  	managerConfig, err := containerManagerConfig(containerType, cs.provisioner, cs.config)
   244  	if err != nil {
   245  		return nil, nil, nil, err
   246  	}
   247  
   248  	// Override default MTU for LXC NICs, if needed.
   249  	if mtu := managerConfig.PopValue(container.ConfigLXCDefaultMTU); mtu != "" {
   250  		value, err := strconv.Atoi(mtu)
   251  		if err != nil {
   252  			return nil, nil, nil, errors.Trace(err)
   253  		}
   254  		logger.Infof("setting MTU to %v for all LXC containers' interfaces", value)
   255  		cs.lxcDefaultMTU = value
   256  	}
   257  
   258  	// Enable IP forwarding and ARP proxying if needed.
   259  	if ipfwd := managerConfig.PopValue(container.ConfigIPForwarding); ipfwd != "" {
   260  		if err := setIPAndARPForwarding(true); err != nil {
   261  			return nil, nil, nil, errors.Trace(err)
   262  		}
   263  		cs.addressableContainers = true
   264  		logger.Infof("enabled IP forwarding and ARP proxying for containers")
   265  	}
   266  
   267  	// Enable NAT if needed.
   268  	if nat := managerConfig.PopValue(container.ConfigEnableNAT); nat != "" {
   269  		cs.enableNAT = true
   270  		logger.Infof("enabling NAT for containers")
   271  	}
   272  
   273  	switch containerType {
   274  	case instance.LXC:
   275  		series, err := cs.machine.Series()
   276  		if err != nil {
   277  			return nil, nil, nil, err
   278  		}
   279  
   280  		initialiser = lxc.NewContainerInitialiser(series)
   281  		broker, err = NewLxcBroker(
   282  			cs.provisioner,
   283  			cs.config,
   284  			managerConfig,
   285  			cs.imageURLGetter,
   286  			cs.enableNAT,
   287  			cs.lxcDefaultMTU,
   288  		)
   289  		if err != nil {
   290  			return nil, nil, nil, err
   291  		}
   292  
   293  		// LXC containers must have the same architecture as the host.
   294  		// We should call through to the finder since the version of
   295  		// tools running on the host may not match, but we want to
   296  		// override the arch constraint with the arch of the host.
   297  		toolsFinder = hostArchToolsFinder{toolsFinder}
   298  
   299  	case instance.KVM:
   300  		initialiser = kvm.NewContainerInitialiser()
   301  		broker, err = NewKvmBroker(
   302  			cs.provisioner,
   303  			cs.config,
   304  			managerConfig,
   305  			cs.enableNAT,
   306  		)
   307  		if err != nil {
   308  			logger.Errorf("failed to create new kvm broker")
   309  			return nil, nil, nil, err
   310  		}
   311  	default:
   312  		return nil, nil, nil, fmt.Errorf("unknown container type: %v", containerType)
   313  	}
   314  
   315  	return initialiser, broker, toolsFinder, nil
   316  }
   317  
   318  func containerManagerConfig(
   319  	containerType instance.ContainerType,
   320  	provisioner *apiprovisioner.State,
   321  	agentConfig agent.Config,
   322  ) (container.ManagerConfig, error) {
   323  	// Ask the provisioner for the container manager configuration.
   324  	managerConfigResult, err := provisioner.ContainerManagerConfig(
   325  		params.ContainerManagerConfigParams{Type: containerType},
   326  	)
   327  	if params.IsCodeNotImplemented(err) {
   328  		// We currently don't support upgrading;
   329  		// revert to the old configuration.
   330  		managerConfigResult.ManagerConfig = container.ManagerConfig{container.ConfigName: container.DefaultNamespace}
   331  	}
   332  	if err != nil {
   333  		return nil, err
   334  	}
   335  	// If a namespace is specified, that should instead be used as the config name.
   336  	if namespace := agentConfig.Value(agent.Namespace); namespace != "" {
   337  		managerConfigResult.ManagerConfig[container.ConfigName] = namespace
   338  	}
   339  	managerConfig := container.ManagerConfig(managerConfigResult.ManagerConfig)
   340  
   341  	return managerConfig, nil
   342  }
   343  
   344  // Override for testing.
   345  var (
   346  	StartProvisioner = startProvisionerWorker
   347  
   348  	sysctlConfig = "/etc/sysctl.conf"
   349  )
   350  
   351  const (
   352  	ipForwardSysctlKey = "net.ipv4.ip_forward"
   353  	arpProxySysctlKey  = "net.ipv4.conf.all.proxy_arp"
   354  )
   355  
   356  // startProvisionerWorker kicks off a provisioner task responsible for creating containers
   357  // of the specified type on the machine.
   358  func startProvisionerWorker(
   359  	runner worker.Runner,
   360  	containerType instance.ContainerType,
   361  	provisioner *apiprovisioner.State,
   362  	config agent.Config,
   363  	broker environs.InstanceBroker,
   364  	toolsFinder ToolsFinder,
   365  ) error {
   366  
   367  	workerName := fmt.Sprintf("%s-provisioner", containerType)
   368  	// The provisioner task is created after a container record has
   369  	// already been added to the machine. It will see that the
   370  	// container does not have an instance yet and create one.
   371  	return runner.StartWorker(workerName, func() (worker.Worker, error) {
   372  		return NewContainerProvisioner(containerType, provisioner, config, broker, toolsFinder), nil
   373  	})
   374  }
   375  
   376  // setIPAndARPForwarding enables or disables IP and ARP forwarding on
   377  // the machine. This is needed when the machine needs to host
   378  // addressable containers.
   379  var setIPAndARPForwarding = func(enabled bool) error {
   380  	val := "0"
   381  	if enabled {
   382  		val = "1"
   383  	}
   384  
   385  	runCmds := func(keyAndVal string) (err error) {
   386  
   387  		defer errors.DeferredAnnotatef(&err, "cannot set %s", keyAndVal)
   388  
   389  		commands := []string{
   390  			// Change it immediately:
   391  			fmt.Sprintf("sysctl -w %s", keyAndVal),
   392  
   393  			// Change it also on next boot:
   394  			fmt.Sprintf("echo '%s' | tee -a %s", keyAndVal, sysctlConfig),
   395  		}
   396  		for _, cmd := range commands {
   397  			result, err := exec.RunCommands(exec.RunParams{Commands: cmd})
   398  			if err != nil {
   399  				return errors.Trace(err)
   400  			}
   401  			logger.Debugf(
   402  				"command %q returned: code: %d, stdout: %q, stderr: %q",
   403  				cmd, result.Code, string(result.Stdout), string(result.Stderr),
   404  			)
   405  			if result.Code != 0 {
   406  				return errors.Errorf("unexpected exit code %d", result.Code)
   407  			}
   408  		}
   409  		return nil
   410  	}
   411  
   412  	err := runCmds(fmt.Sprintf("%s=%s", ipForwardSysctlKey, val))
   413  	if err != nil {
   414  		return err
   415  	}
   416  	return runCmds(fmt.Sprintf("%s=%s", arpProxySysctlKey, val))
   417  }