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