github.com/rogpeppe/juju@v0.0.0-20140613142852-6337964b789e/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 "github.com/juju/utils/fslock" 11 12 "github.com/juju/juju/agent" 13 "github.com/juju/juju/container" 14 "github.com/juju/juju/container/kvm" 15 "github.com/juju/juju/container/lxc" 16 "github.com/juju/juju/environs" 17 "github.com/juju/juju/instance" 18 "github.com/juju/juju/state" 19 "github.com/juju/juju/state/api/params" 20 apiprovisioner "github.com/juju/juju/state/api/provisioner" 21 "github.com/juju/juju/state/api/watcher" 22 "github.com/juju/juju/worker" 23 ) 24 25 // ContainerSetup is a StringsWatchHandler that is notified when containers 26 // are created on the given machine. It will set up the machine to be able 27 // to create containers and start a suitable provisioner. 28 type ContainerSetup struct { 29 runner worker.Runner 30 supportedContainers []instance.ContainerType 31 provisioner *apiprovisioner.State 32 machine *apiprovisioner.Machine 33 config agent.Config 34 initLock *fslock.Lock 35 36 // Save the workerName so the worker thread can be stopped. 37 workerName string 38 // setupDone[containerType] is non zero if the container setup has been invoked 39 // for that container type. 40 setupDone map[instance.ContainerType]*int32 41 // The number of provisioners started. Once all necessary provisioners have 42 // been started, the container watcher can be stopped. 43 numberProvisioners int32 44 } 45 46 // NewContainerSetupHandler returns a StringsWatchHandler which is notified when 47 // containers are created on the given machine. 48 func NewContainerSetupHandler(runner worker.Runner, workerName string, supportedContainers []instance.ContainerType, 49 machine *apiprovisioner.Machine, provisioner *apiprovisioner.State, 50 config agent.Config, initLock *fslock.Lock) worker.StringsWatchHandler { 51 52 return &ContainerSetup{ 53 runner: runner, 54 machine: machine, 55 supportedContainers: supportedContainers, 56 provisioner: provisioner, 57 config: config, 58 workerName: workerName, 59 initLock: initLock, 60 } 61 } 62 63 // SetUp is defined on the StringsWatchHandler interface. 64 func (cs *ContainerSetup) SetUp() (watcher watcher.StringsWatcher, err error) { 65 // Set up the semaphores for each container type. 66 cs.setupDone = make(map[instance.ContainerType]*int32, len(instance.ContainerTypes)) 67 for _, containerType := range instance.ContainerTypes { 68 zero := int32(0) 69 cs.setupDone[containerType] = &zero 70 } 71 // Listen to all container lifecycle events on our machine. 72 if watcher, err = cs.machine.WatchAllContainers(); err != nil { 73 return nil, err 74 } 75 return watcher, nil 76 } 77 78 // Handle is called whenever containers change on the machine being watched. 79 // Machines start out with no containers so the first time Handle is called, 80 // it will be because a container has been added. 81 func (cs *ContainerSetup) Handle(containerIds []string) (resultError error) { 82 // Consume the initial watcher event. 83 if len(containerIds) == 0 { 84 return nil 85 } 86 87 logger.Tracef("initial container setup with ids: %v", containerIds) 88 for _, id := range containerIds { 89 containerType := state.ContainerTypeFromId(id) 90 // If this container type has been dealt with, do nothing. 91 if atomic.LoadInt32(cs.setupDone[containerType]) != 0 { 92 continue 93 } 94 if err := cs.initialiseAndStartProvisioner(containerType); err != nil { 95 logger.Errorf("starting container provisioner for %v: %v", containerType, err) 96 // Just because dealing with one type of container fails, we won't exit the entire 97 // function because we still want to try and start other container types. So we 98 // take note of and return the first such error. 99 if resultError == nil { 100 resultError = err 101 } 102 } 103 } 104 return resultError 105 } 106 107 func (cs *ContainerSetup) initialiseAndStartProvisioner(containerType instance.ContainerType) error { 108 // Flag that this container type has been handled. 109 atomic.StoreInt32(cs.setupDone[containerType], 1) 110 111 if atomic.AddInt32(&cs.numberProvisioners, 1) == int32(len(cs.supportedContainers)) { 112 // We only care about the initial container creation. 113 // This worker has done its job so stop it. 114 // We do not expect there will be an error, and there's not much we can do anyway. 115 if err := cs.runner.StopWorker(cs.workerName); err != nil { 116 logger.Warningf("stopping machine agent container watcher: %v", err) 117 } 118 } 119 120 // We only care about the initial container creation. 121 // This worker has done its job so stop it. 122 // We do not expect there will be an error, and there's not much we can do anyway. 123 if err := cs.runner.StopWorker(cs.workerName); err != nil { 124 logger.Warningf("stopping machine agent container watcher: %v", err) 125 } 126 if initialiser, broker, err := cs.getContainerArtifacts(containerType); err != nil { 127 return fmt.Errorf("initialising container infrastructure on host machine: %v", err) 128 } else { 129 if err := cs.runInitialiser(containerType, initialiser); err != nil { 130 return fmt.Errorf("setting up container dependencies on host machine: %v", err) 131 } 132 return StartProvisioner(cs.runner, containerType, cs.provisioner, cs.config, broker) 133 } 134 } 135 136 // runInitialiser runs the container initialiser with the initialisation hook held. 137 func (cs *ContainerSetup) runInitialiser(containerType instance.ContainerType, initialiser container.Initialiser) error { 138 if err := cs.initLock.Lock(fmt.Sprintf("initialise-%s", containerType)); err != nil { 139 return fmt.Errorf("failed to acquire initialization lock: %v", err) 140 } 141 defer cs.initLock.Unlock() 142 return initialiser.Initialise() 143 } 144 145 // TearDown is defined on the StringsWatchHandler interface. 146 func (cs *ContainerSetup) TearDown() error { 147 // Nothing to do here. 148 return nil 149 } 150 151 func (cs *ContainerSetup) getContainerArtifacts(containerType instance.ContainerType) (container.Initialiser, environs.InstanceBroker, error) { 152 tools, err := cs.provisioner.Tools(cs.config.Tag()) 153 if err != nil { 154 logger.Errorf("cannot get tools from machine for %s container", containerType) 155 return nil, nil, err 156 } 157 var initialiser container.Initialiser 158 var broker environs.InstanceBroker 159 160 managerConfig, err := containerManagerConfig(containerType, cs.provisioner, cs.config) 161 if err != nil { 162 return nil, nil, err 163 } 164 165 switch containerType { 166 case instance.LXC: 167 series, err := cs.machine.Series() 168 if err != nil { 169 return nil, nil, err 170 } 171 172 initialiser = lxc.NewContainerInitialiser(series) 173 broker, err = NewLxcBroker(cs.provisioner, tools, cs.config, managerConfig) 174 if err != nil { 175 return nil, nil, err 176 } 177 case instance.KVM: 178 initialiser = kvm.NewContainerInitialiser() 179 broker, err = NewKvmBroker(cs.provisioner, tools, cs.config, managerConfig) 180 if err != nil { 181 logger.Errorf("failed to create new kvm broker") 182 return nil, nil, err 183 } 184 default: 185 return nil, nil, fmt.Errorf("unknown container type: %v", containerType) 186 } 187 return initialiser, broker, nil 188 } 189 190 func containerManagerConfig( 191 containerType instance.ContainerType, 192 provisioner *apiprovisioner.State, 193 agentConfig agent.Config, 194 ) (container.ManagerConfig, error) { 195 // Ask the provisioner for the container manager configuration. 196 managerConfigResult, err := provisioner.ContainerManagerConfig( 197 params.ContainerManagerConfigParams{Type: containerType}, 198 ) 199 if params.IsCodeNotImplemented(err) { 200 // We currently don't support upgrading; 201 // revert to the old configuration. 202 managerConfigResult.ManagerConfig = container.ManagerConfig{container.ConfigName: "juju"} 203 } 204 if err != nil { 205 return nil, err 206 } 207 // If a namespace is specified, that should instead be used as the config name. 208 if namespace := agentConfig.Value(agent.Namespace); namespace != "" { 209 managerConfigResult.ManagerConfig[container.ConfigName] = namespace 210 } 211 managerConfig := container.ManagerConfig(managerConfigResult.ManagerConfig) 212 return managerConfig, nil 213 } 214 215 // Override for testing. 216 var StartProvisioner = startProvisionerWorker 217 218 // startProvisionerWorker kicks off a provisioner task responsible for creating containers 219 // of the specified type on the machine. 220 func startProvisionerWorker(runner worker.Runner, containerType instance.ContainerType, 221 provisioner *apiprovisioner.State, config agent.Config, broker environs.InstanceBroker) error { 222 223 workerName := fmt.Sprintf("%s-provisioner", containerType) 224 // The provisioner task is created after a container record has already been added to the machine. 225 // It will see that the container does not have an instance yet and create one. 226 return runner.StartWorker(workerName, func() (worker.Worker, error) { 227 return NewContainerProvisioner(containerType, provisioner, config, broker), nil 228 }) 229 }