launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/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 "sync/atomic" 9 10 "launchpad.net/errgo/errors" 11 "launchpad.net/juju-core/agent" 12 "launchpad.net/juju-core/container" 13 "launchpad.net/juju-core/container/kvm" 14 "launchpad.net/juju-core/container/lxc" 15 "launchpad.net/juju-core/environs" 16 "launchpad.net/juju-core/instance" 17 "launchpad.net/juju-core/state" 18 apiprovisioner "launchpad.net/juju-core/state/api/provisioner" 19 "launchpad.net/juju-core/state/api/watcher" 20 "launchpad.net/juju-core/worker" 21 ) 22 23 // ContainerSetup is a StringsWatchHandler that is notified when containers 24 // are created on the given machine. It will set up the machine to be able 25 // to create containers and start a suitable provisioner. 26 type ContainerSetup struct { 27 runner worker.Runner 28 supportedContainers []instance.ContainerType 29 provisioner *apiprovisioner.State 30 machine *apiprovisioner.Machine 31 config agent.Config 32 33 // Save the workerName so the worker thread can be stopped. 34 workerName string 35 // setupDone[containerType] is non zero if the container setup has been invoked 36 // for that container type. 37 setupDone map[instance.ContainerType]*int32 38 // The number of provisioners started. Once all necessary provisioners have 39 // been started, the container watcher can be stopped. 40 numberProvisioners int32 41 } 42 43 // NewContainerSetupHandler returns a StringsWatchHandler which is notified when 44 // containers are created on the given machine. 45 func NewContainerSetupHandler(runner worker.Runner, workerName string, supportedContainers []instance.ContainerType, 46 machine *apiprovisioner.Machine, provisioner *apiprovisioner.State, 47 config agent.Config) worker.StringsWatchHandler { 48 49 return &ContainerSetup{ 50 runner: runner, 51 machine: machine, 52 supportedContainers: supportedContainers, 53 provisioner: provisioner, 54 config: config, 55 workerName: workerName, 56 } 57 } 58 59 // SetUp is defined on the StringsWatchHandler interface. 60 func (cs *ContainerSetup) SetUp() (watcher watcher.StringsWatcher, err error) { 61 // Set up the semaphores for each container type. 62 cs.setupDone = make(map[instance.ContainerType]*int32, len(instance.ContainerTypes)) 63 for _, containerType := range instance.ContainerTypes { 64 zero := int32(0) 65 cs.setupDone[containerType] = &zero 66 } 67 // Listen to all container lifecycle events on our machine. 68 if watcher, err = cs.machine.WatchAllContainers(); err != nil { 69 return nil, mask(err) 70 } 71 return watcher, nil 72 } 73 74 // Handle is called whenever containers change on the machine being watched. 75 // All machines start out with so containers so the first time Handle is called, 76 // it will be because a container has been added. 77 func (cs *ContainerSetup) Handle(containerIds []string) (resultError error) { 78 // Consume the initial watcher event. 79 if len(containerIds) == 0 { 80 return nil 81 } 82 83 logger.Tracef("initial container setup with ids: %v", containerIds) 84 for _, id := range containerIds { 85 containerType := state.ContainerTypeFromId(id) 86 // If this container type has been dealt with, do nothing. 87 if atomic.LoadInt32(cs.setupDone[containerType]) != 0 { 88 continue 89 } 90 if err := cs.initialiseAndStartProvisioner(containerType); err != nil { 91 logger.Errorf("starting container provisioner for %v: %v", containerType, err) 92 // Just because dealing with one type of container fails, we won't exit the entire 93 // function because we still want to try and start other container types. So we 94 // take note of and return the first such error. 95 if resultError == nil { 96 resultError = err 97 } 98 } 99 } 100 return resultError 101 } 102 103 func (cs *ContainerSetup) initialiseAndStartProvisioner(containerType instance.ContainerType) error { 104 // Flag that this container type has been handled. 105 atomic.StoreInt32(cs.setupDone[containerType], 1) 106 107 if atomic.AddInt32(&cs.numberProvisioners, 1) == int32(len(cs.supportedContainers)) { 108 // We only care about the initial container creation. 109 // This worker has done its job so stop it. 110 // We do not expect there will be an error, and there's not much we can do anyway. 111 if err := cs.runner.StopWorker(cs.workerName); err != nil { 112 logger.Warningf("stopping machine agent container watcher: %v", err) 113 } 114 } 115 116 // We only care about the initial container creation. 117 // This worker has done its job so stop it. 118 // We do not expect there will be an error, and there's not much we can do anyway. 119 if err := cs.runner.StopWorker(cs.workerName); err != nil { 120 logger.Warningf("stopping machine agent container watcher: %v", err) 121 } 122 if initialiser, broker, err := cs.getContainerArtifacts(containerType); err != nil { 123 return errors.Notef(err, "initialising container infrastructure on host machine") 124 } else { 125 if err := initialiser.Initialise(); err != nil { 126 return errors.Notef(err, "setting up container dependnecies on host machine") 127 } 128 return StartProvisioner(cs.runner, containerType, cs.provisioner, cs.config, broker) 129 } 130 } 131 132 // TearDown is defined on the StringsWatchHandler interface. 133 func (cs *ContainerSetup) TearDown() error { 134 // Nothing to do here. 135 return nil 136 } 137 138 func (cs *ContainerSetup) getContainerArtifacts(containerType instance.ContainerType) (container.Initialiser, environs.InstanceBroker, error) { 139 tools, err := cs.provisioner.Tools(cs.config.Tag()) 140 if err != nil { 141 logger.Errorf("cannot get tools from machine for %s container", containerType) 142 return nil, nil, err 143 } 144 var initialiser container.Initialiser 145 var broker environs.InstanceBroker 146 switch containerType { 147 case instance.LXC: 148 initialiser = lxc.NewContainerInitialiser() 149 broker = NewLxcBroker(cs.provisioner, tools, cs.config) 150 case instance.KVM: 151 initialiser = kvm.NewContainerInitialiser() 152 broker, err = NewKvmBroker(cs.provisioner, tools, cs.config) 153 if err != nil { 154 logger.Errorf("failed to create new kvm broker") 155 return nil, nil, err 156 } 157 default: 158 return nil, nil, errors.Newf("unknown container type: %v", containerType) 159 } 160 return initialiser, broker, nil 161 } 162 163 // Override for testing. 164 var StartProvisioner = startProvisionerWorker 165 166 // startProvisionerWorker kicks off a provisioner task responsible for creating containers 167 // of the specified type on the machine. 168 func startProvisionerWorker(runner worker.Runner, containerType instance.ContainerType, 169 provisioner *apiprovisioner.State, config agent.Config, broker environs.InstanceBroker) error { 170 171 workerName := fmt.Sprintf("%s-provisioner", containerType) 172 // The provisioner task is created after a container record has already been added to the machine. 173 // It will see that the container does not have an instance yet and create one. 174 return runner.StartWorker(workerName, func() (worker.Worker, error) { 175 return NewContainerProvisioner(containerType, provisioner, config, broker), nil 176 }) 177 }