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  }