github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/container/broker/instance_broker.go (about)

     1  // Copyright 2019 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package broker
     5  
     6  import (
     7  	"io"
     8  	"os"
     9  	"time"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/loggo"
    13  	"github.com/juju/names/v5"
    14  
    15  	"github.com/juju/juju/agent"
    16  	"github.com/juju/juju/container"
    17  	"github.com/juju/juju/container/factory"
    18  	"github.com/juju/juju/core/instance"
    19  	"github.com/juju/juju/core/machinelock"
    20  	corenetwork "github.com/juju/juju/core/network"
    21  	"github.com/juju/juju/environs"
    22  	"github.com/juju/juju/network"
    23  	"github.com/juju/juju/rpc/params"
    24  )
    25  
    26  // NewBrokerFunc returns a Instance Broker.
    27  type NewBrokerFunc func(Config) (environs.InstanceBroker, error)
    28  
    29  var (
    30  	systemNetworkInterfacesFile = "/etc/network/interfaces"
    31  	systemNetplanDirectory      = "/etc/netplan"
    32  	activateBridgesTimeout      = 5 * time.Minute
    33  )
    34  
    35  // NetConfigFunc returns a slice of NetworkConfig from a source config.
    36  type NetConfigFunc func(corenetwork.ConfigSource) (corenetwork.InterfaceInfos, error)
    37  
    38  // Config describes the resources used by the instance broker.
    39  type Config struct {
    40  	Name          string
    41  	ContainerType instance.ContainerType
    42  	ManagerConfig container.ManagerConfig
    43  	APICaller     APICalls
    44  	AgentConfig   agent.Config
    45  	MachineTag    names.MachineTag
    46  	MachineLock   machinelock.Lock
    47  	GetNetConfig  NetConfigFunc
    48  }
    49  
    50  // Validate validates the instance broker configuration.
    51  func (c Config) Validate() error {
    52  	if c.Name == "" {
    53  		return errors.NotValidf("empty Name")
    54  	}
    55  	if string(c.ContainerType) == "" {
    56  		return errors.NotValidf("empty ContainerType")
    57  	}
    58  	if c.APICaller == nil {
    59  		return errors.NotValidf("nil APICaller")
    60  	}
    61  	if c.AgentConfig == nil {
    62  		return errors.NotValidf("nil AgentConfig")
    63  	}
    64  	if c.MachineTag.Id() == "" {
    65  		return errors.NotValidf("empty MachineTag")
    66  	}
    67  	if c.MachineLock == nil {
    68  		return errors.NotValidf("nil MachineLock")
    69  	}
    70  	if c.GetNetConfig == nil {
    71  		return errors.NotValidf("nil GetNetConfig")
    72  	}
    73  	return nil
    74  }
    75  
    76  // ContainerBrokerFunc is used to align the constructors of the various brokers
    77  // so that we can create them with the same arguments.
    78  type ContainerBrokerFunc func(PrepareHostFunc, APICalls, container.Manager, agent.Config) (environs.InstanceBroker, error)
    79  
    80  // New creates a new InstanceBroker from the Config
    81  func New(config Config) (environs.InstanceBroker, error) {
    82  	if err := config.Validate(); err != nil {
    83  		return nil, errors.Trace(err)
    84  	}
    85  
    86  	manager, err := factory.NewContainerManager(config.ContainerType, config.ManagerConfig)
    87  	if err != nil {
    88  		return nil, errors.Trace(err)
    89  	}
    90  
    91  	var newBroker ContainerBrokerFunc
    92  	switch config.ContainerType {
    93  	case instance.KVM:
    94  		newBroker = NewKVMBroker
    95  	case instance.LXD:
    96  		newBroker = NewLXDBroker
    97  	default:
    98  		return nil, errors.NotValidf("ContainerType %s", config.ContainerType)
    99  	}
   100  
   101  	broker, err := newBroker(prepareHost(config), config.APICaller, manager, config.AgentConfig)
   102  	if err != nil {
   103  		logger.Errorf("failed to create new %s broker", config.ContainerType)
   104  		return nil, errors.Trace(err)
   105  	}
   106  
   107  	return broker, nil
   108  }
   109  
   110  func prepareHost(config Config) PrepareHostFunc {
   111  	return func(containerTag names.MachineTag, log loggo.Logger, abort <-chan struct{}) error {
   112  		preparer := NewHostPreparer(HostPreparerParams{
   113  			API:                config.APICaller,
   114  			ObserveNetworkFunc: observeNetwork(config),
   115  			AcquireLockFunc:    acquireLock(config),
   116  			CreateBridger:      defaultBridger,
   117  			AbortChan:          abort,
   118  			MachineTag:         config.MachineTag,
   119  			Logger:             log,
   120  		})
   121  		return errors.Trace(preparer.Prepare(containerTag))
   122  	}
   123  }
   124  
   125  // Patch for testing.
   126  var (
   127  	openFunc    = os.Open
   128  	readDirFunc = func(f *os.File, n int) (names []string, err error) {
   129  		return f.Readdirnames(n)
   130  	}
   131  )
   132  
   133  func isDirectoryEmpty(directory string) (bool, error) {
   134  	f, err := openFunc(directory)
   135  	if err != nil {
   136  		return false, err
   137  	}
   138  	defer func() { _ = f.Close() }()
   139  
   140  	_, err = readDirFunc(f, 1)
   141  	if err == io.EOF {
   142  		return true, nil
   143  	}
   144  
   145  	return false, err
   146  }
   147  
   148  // defaultBridger will prefer to use netplan if there is an /etc/netplan directory
   149  // and it is not empty, falling back to ENI if the directory doesn't exist or is empty.
   150  func defaultBridger() (network.Bridger, error) {
   151  	if empty, err := isDirectoryEmpty(systemNetplanDirectory); (err == nil) && !empty {
   152  		return network.DefaultNetplanBridger(activateBridgesTimeout, systemNetplanDirectory)
   153  	} else {
   154  		return network.DefaultEtcNetworkInterfacesBridger(activateBridgesTimeout, systemNetworkInterfacesFile)
   155  	}
   156  }
   157  
   158  // acquireLock tries to grab the machine lock (initLockName), and either
   159  // returns it in a locked state, or returns an error.
   160  func acquireLock(config Config) func(string, <-chan struct{}) (func(), error) {
   161  	return func(comment string, abort <-chan struct{}) (func(), error) {
   162  		spec := machinelock.Spec{
   163  			Cancel:  abort,
   164  			Worker:  config.Name,
   165  			Comment: comment,
   166  		}
   167  		return config.MachineLock.Acquire(spec)
   168  	}
   169  }
   170  
   171  func observeNetwork(config Config) func() ([]params.NetworkConfig, error) {
   172  	return func() ([]params.NetworkConfig, error) {
   173  		interfaceInfos, err := config.GetNetConfig(corenetwork.DefaultConfigSource())
   174  		if err != nil {
   175  			return nil, err
   176  		}
   177  		return params.NetworkConfigFromInterfaceInfo(interfaceInfos), nil
   178  	}
   179  }
   180  
   181  type AvailabilityZoner interface {
   182  	AvailabilityZone() (string, error)
   183  }
   184  
   185  // ConfigureAvailabilityZone reads the availability zone from the machine and
   186  // adds the resulting information to the the manager config.
   187  func ConfigureAvailabilityZone(managerConfig container.ManagerConfig, machineZone AvailabilityZoner) (container.ManagerConfig, error) {
   188  	availabilityZone, err := machineZone.AvailabilityZone()
   189  	if err != nil {
   190  		return nil, errors.Trace(err)
   191  	}
   192  	managerConfig[container.ConfigAvailabilityZone] = availabilityZone
   193  
   194  	return managerConfig, nil
   195  }