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 }