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