github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/containerbroker/broker.go (about) 1 // Copyright 2019 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package containerbroker 5 6 import ( 7 "github.com/juju/errors" 8 "github.com/juju/names/v5" 9 "github.com/juju/worker/v3" 10 "github.com/juju/worker/v3/catacomb" 11 "github.com/juju/worker/v3/dependency" 12 13 "github.com/juju/juju/agent" 14 "github.com/juju/juju/api/agent/provisioner" 15 "github.com/juju/juju/api/base" 16 "github.com/juju/juju/container" 17 "github.com/juju/juju/container/broker" 18 "github.com/juju/juju/core/instance" 19 "github.com/juju/juju/core/life" 20 "github.com/juju/juju/core/machinelock" 21 "github.com/juju/juju/core/network" 22 "github.com/juju/juju/environs" 23 "github.com/juju/juju/rpc/params" 24 ) 25 26 //go:generate go run go.uber.org/mock/mockgen -package mocks -destination mocks/state_mock.go github.com/juju/juju/worker/containerbroker State 27 //go:generate go run go.uber.org/mock/mockgen -package mocks -destination mocks/machine_mock.go github.com/juju/juju/api/agent/provisioner MachineProvisioner 28 29 // Config describes the dependencies of a Tracker. 30 // 31 // It's arguable that it should be called TrackerConfig, because of the heavy 32 // use of model config in this package. 33 type Config struct { 34 APICaller base.APICaller 35 AgentConfig agent.Config 36 MachineLock machinelock.Lock 37 NewBrokerFunc func(broker.Config) (environs.InstanceBroker, error) 38 NewStateFunc func(base.APICaller) State 39 } 40 41 // State represents the interaction for the apiserver 42 type State interface { 43 broker.APICalls 44 Machines(...names.MachineTag) ([]provisioner.MachineResult, error) 45 ContainerManagerConfig(params.ContainerManagerConfigParams) (params.ContainerManagerConfig, error) 46 } 47 48 // Validate returns an error if the config cannot be used to start a Tracker. 49 func (config Config) Validate() error { 50 if config.APICaller == nil { 51 return errors.NotValidf("nil APICaller") 52 } 53 if config.AgentConfig == nil { 54 return errors.NotValidf("nil AgentConfig") 55 } 56 if config.MachineLock == nil { 57 return errors.NotValidf("nil MachineLock") 58 } 59 if config.NewBrokerFunc == nil { 60 return errors.NotValidf("nil NewBrokerFunc") 61 } 62 if config.NewStateFunc == nil { 63 return errors.NotValidf("nil NewStateFunc") 64 } 65 return nil 66 } 67 68 // NewWorkerTracker defines a function that is covariant in the return type 69 // so that the manifold can use the covariance of the function as an alias. 70 func NewWorkerTracker(config Config) (worker.Worker, error) { 71 return NewTracker(config) 72 } 73 74 // Tracker loads a broker, makes it available to clients, and updates 75 // the broker in response to config changes until it is killed. 76 type Tracker struct { 77 config Config 78 catacomb catacomb.Catacomb 79 broker environs.InstanceBroker 80 } 81 82 // NewTracker returns a new Tracker, or an error if anything goes wrong. 83 // If a tracker is returned, its Broker() method is immediately usable. 84 // 85 // The caller is responsible for Kill()ing the returned Tracker and Wait()ing 86 // for any errors it might return. 87 func NewTracker(config Config) (*Tracker, error) { 88 if err := config.Validate(); err != nil { 89 return nil, errors.Trace(err) 90 } 91 92 machineTag := config.AgentConfig.Tag().(names.MachineTag) 93 provisioner := config.NewStateFunc(config.APICaller) 94 result, err := provisioner.Machines(machineTag) 95 if err != nil { 96 return nil, errors.Annotatef(err, "cannot load machine %s from state", machineTag) 97 } 98 if len(result) != 1 { 99 return nil, errors.Errorf("expected 1 result, got %d", len(result)) 100 } 101 if errors.IsNotFound(result[0].Err) || (result[0].Err == nil && result[0].Machine.Life() == life.Dead) { 102 return nil, dependency.ErrUninstall 103 } 104 machine := result[0].Machine 105 instanceContainers, determined, err := machine.SupportedContainers() 106 if err != nil { 107 return nil, errors.Annotate(err, "retrieving supported container types") 108 } 109 if !determined { 110 return nil, errors.Errorf("no container types determined") 111 } 112 if len(instanceContainers) == 0 { 113 return nil, dependency.ErrUninstall 114 } 115 // we only work on LXD, so check for that. 116 var found bool 117 for _, containerType := range instanceContainers { 118 if containerType == instance.LXD { 119 found = true 120 break 121 } 122 } 123 if !found { 124 return nil, dependency.ErrUninstall 125 } 126 127 // We guarded against non-LXD types, so lets talk about specific container 128 // types to prevent confusion. 129 containerType := instance.LXD 130 managerConfigResult, err := provisioner.ContainerManagerConfig( 131 params.ContainerManagerConfigParams{Type: containerType}, 132 ) 133 if err != nil { 134 return nil, errors.Annotate(err, "generating container manager config") 135 } 136 managerConfig := container.ManagerConfig(managerConfigResult.ManagerConfig) 137 managerConfigWithZones, err := broker.ConfigureAvailabilityZone(managerConfig, machine) 138 if err != nil { 139 return nil, errors.Annotate(err, "configuring availability zones") 140 } 141 142 broker, err := config.NewBrokerFunc(broker.Config{ 143 Name: "instance-broker", 144 ContainerType: containerType, 145 ManagerConfig: managerConfigWithZones, 146 APICaller: provisioner, 147 AgentConfig: config.AgentConfig, 148 MachineTag: machineTag, 149 MachineLock: config.MachineLock, 150 GetNetConfig: network.GetObservedNetworkConfig, 151 }) 152 if err != nil { 153 return nil, errors.Annotate(err, "cannot create instance broker") 154 } 155 t := &Tracker{ 156 config: config, 157 broker: broker, 158 } 159 err = catacomb.Invoke(catacomb.Plan{ 160 Site: &t.catacomb, 161 Work: t.loop, 162 }) 163 if err != nil { 164 return nil, errors.Trace(err) 165 } 166 return t, nil 167 } 168 169 // Broker returns the encapsulated Broker. It will continue to be updated in 170 // the background for as long as the Tracker continues to run. 171 func (t *Tracker) Broker() environs.InstanceBroker { 172 return t.broker 173 } 174 175 func (t *Tracker) loop() error { 176 for { 177 select { 178 case <-t.catacomb.Dying(): 179 return t.catacomb.ErrDying() 180 } 181 } 182 } 183 184 // Kill is part of the worker.Worker interface. 185 func (t *Tracker) Kill() { 186 t.catacomb.Kill(nil) 187 } 188 189 // Wait is part of the worker.Worker interface. 190 func (t *Tracker) Wait() error { 191 return t.catacomb.Wait() 192 }