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 }