github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/container/broker/lxd-broker.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package broker
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/loggo"
    11  	"github.com/juju/names/v5"
    12  
    13  	"github.com/juju/juju/agent"
    14  	"github.com/juju/juju/cloudconfig/instancecfg"
    15  	"github.com/juju/juju/container"
    16  	"github.com/juju/juju/core/instance"
    17  	"github.com/juju/juju/core/lxdprofile"
    18  	"github.com/juju/juju/environs"
    19  	"github.com/juju/juju/environs/context"
    20  	"github.com/juju/juju/environs/instances"
    21  )
    22  
    23  var lxdLogger = loggo.GetLogger("juju.container.broker.lxd")
    24  
    25  type PrepareHostFunc func(containerTag names.MachineTag, log loggo.Logger, abort <-chan struct{}) error
    26  
    27  // NewLXDBroker creates a Broker that can be used to start LXD containers in a
    28  // similar fashion to normal StartInstance requests.
    29  // prepareHost is a callback that will be called when a new container is about
    30  // to be started. It provides the intersection point where the host can update
    31  // itself to be ready for whatever changes are necessary to have a functioning
    32  // container. (such as bridging host devices.)
    33  // manager is the infrastructure to actually launch the container.
    34  // agentConfig is currently only used to find out the 'default' bridge to use
    35  // when a specific network device is not specified in StartInstanceParams. This
    36  // should be deprecated. And hopefully removed in the future.
    37  func NewLXDBroker(
    38  	prepareHost PrepareHostFunc,
    39  	api APICalls,
    40  	manager container.Manager,
    41  	agentConfig agent.Config,
    42  ) (environs.InstanceBroker, error) {
    43  	return &lxdBroker{
    44  		prepareHost: prepareHost,
    45  		manager:     manager,
    46  		api:         api,
    47  		agentConfig: agentConfig,
    48  	}, nil
    49  }
    50  
    51  type lxdBroker struct {
    52  	prepareHost PrepareHostFunc
    53  	manager     container.Manager
    54  	api         APICalls
    55  	agentConfig agent.Config
    56  }
    57  
    58  func (broker *lxdBroker) StartInstance(ctx context.ProviderCallContext, args environs.StartInstanceParams) (*environs.StartInstanceResult, error) {
    59  	containerMachineID := args.InstanceConfig.MachineId
    60  
    61  	config, err := broker.api.ContainerConfig()
    62  	if err != nil {
    63  		lxdLogger.Errorf("failed to get container config: %v", err)
    64  		return nil, err
    65  	}
    66  
    67  	if err := broker.prepareHost(names.NewMachineTag(containerMachineID), lxdLogger, args.Abort); err != nil {
    68  		return nil, errors.Trace(err)
    69  	}
    70  
    71  	preparedInfo, err := prepareContainerInterfaceInfo(broker.api, containerMachineID, lxdLogger)
    72  	if err != nil {
    73  		return nil, errors.Trace(err)
    74  	}
    75  
    76  	interfaces, err := finishNetworkConfig(preparedInfo)
    77  	if err != nil {
    78  		return nil, errors.Trace(err)
    79  	}
    80  	net := container.BridgeNetworkConfig(0, interfaces)
    81  
    82  	pNames, err := broker.writeProfiles(containerMachineID)
    83  	if err != nil {
    84  		err = fmt.Errorf("cannot write charm profile: %w", err)
    85  		return nil, errors.WithType(err, environs.ErrAvailabilityZoneIndependent)
    86  	}
    87  
    88  	// The provisioner worker will provide all tools it knows about
    89  	// (after applying explicitly specified constraints), which may
    90  	// include tools for architectures other than the host's. We
    91  	// must constrain to the host's architecture for LXD.
    92  	archTools, err := matchHostArchTools(args.Tools)
    93  	if err != nil {
    94  		return nil, errors.Trace(err)
    95  	}
    96  
    97  	args.InstanceConfig.MachineContainerType = instance.LXD
    98  	if err := args.InstanceConfig.SetTools(archTools); err != nil {
    99  		return nil, errors.Trace(err)
   100  	}
   101  
   102  	cloudInitUserData, err := combinedCloudInitData(
   103  		config.CloudInitUserData,
   104  		config.ContainerInheritProperties,
   105  		args.InstanceConfig.Base, lxdLogger)
   106  	if err != nil {
   107  		return nil, errors.Trace(err)
   108  	}
   109  
   110  	if err := instancecfg.PopulateInstanceConfig(
   111  		args.InstanceConfig,
   112  		config.ProviderType,
   113  		config.AuthorizedKeys,
   114  		config.SSLHostnameVerification,
   115  		proxyConfigurationFromContainerCfg(config),
   116  		config.EnableOSRefreshUpdate,
   117  		config.EnableOSUpgrade,
   118  		cloudInitUserData,
   119  		append([]string{"default"}, pNames...),
   120  	); err != nil {
   121  		lxdLogger.Errorf("failed to populate machine config: %v", err)
   122  		return nil, err
   123  	}
   124  
   125  	storageConfig := &container.StorageConfig{}
   126  	inst, hardware, err := broker.manager.CreateContainer(
   127  		ctx, args.InstanceConfig, args.Constraints, args.InstanceConfig.Base, net, storageConfig, args.StatusCallback,
   128  	)
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  
   133  	return &environs.StartInstanceResult{
   134  		Instance: inst,
   135  		Hardware: hardware,
   136  	}, nil
   137  }
   138  
   139  func (broker *lxdBroker) StopInstances(ctx context.ProviderCallContext, ids ...instance.Id) error {
   140  	// TODO: potentially parallelise.
   141  	for _, id := range ids {
   142  		lxdLogger.Infof("stopping lxd container for instance: %s", id)
   143  		if err := broker.manager.DestroyContainer(id); err != nil {
   144  			lxdLogger.Errorf("container did not stop: %v", err)
   145  			return err
   146  		}
   147  		releaseContainerAddresses(broker.api, id, broker.manager.Namespace(), lxdLogger)
   148  	}
   149  	return nil
   150  }
   151  
   152  // AllInstances returns all containers.
   153  func (broker *lxdBroker) AllInstances(ctx context.ProviderCallContext) (result []instances.Instance, err error) {
   154  	return broker.manager.ListContainers()
   155  }
   156  
   157  // AllRunningInstances only returns running containers.
   158  func (broker *lxdBroker) AllRunningInstances(ctx context.ProviderCallContext) (result []instances.Instance, err error) {
   159  	return broker.manager.ListContainers()
   160  }
   161  
   162  // LXDProfileNames returns all the profiles for a container that the broker
   163  // currently is aware of.
   164  // LXDProfileNames implements environs.LXDProfiler.
   165  func (broker *lxdBroker) LXDProfileNames(containerName string) ([]string, error) {
   166  	nameRetriever, ok := broker.manager.(container.LXDProfileNameRetriever)
   167  	if !ok {
   168  		return make([]string, 0), nil
   169  	}
   170  	return nameRetriever.LXDProfileNames(containerName)
   171  }
   172  
   173  func (broker *lxdBroker) writeProfiles(machineID string) ([]string, error) {
   174  	containerTag := names.NewMachineTag(machineID)
   175  	profileInfo, err := broker.api.GetContainerProfileInfo(containerTag)
   176  	if err != nil {
   177  		return nil, err
   178  	}
   179  	var names []string
   180  	for _, profile := range profileInfo {
   181  		if profile == nil {
   182  			continue
   183  		}
   184  		if profile.Name == "" {
   185  			return nil, errors.Errorf("request to write LXD profile for machine %s with no profile name", machineID)
   186  		}
   187  		err := broker.MaybeWriteLXDProfile(profile.Name, lxdprofile.Profile{
   188  			Config:      profile.Config,
   189  			Description: profile.Description,
   190  			Devices:     profile.Devices,
   191  		})
   192  		if err != nil {
   193  			return nil, err
   194  		}
   195  		names = append(names, profile.Name)
   196  	}
   197  	return names, nil
   198  }
   199  
   200  // MaybeWriteLXDProfile implements environs.LXDProfiler.
   201  func (broker *lxdBroker) MaybeWriteLXDProfile(pName string, put lxdprofile.Profile) error {
   202  	profileMgr, ok := broker.manager.(container.LXDProfileManager)
   203  	if !ok {
   204  		return nil
   205  	}
   206  	return profileMgr.MaybeWriteLXDProfile(pName, put)
   207  }
   208  
   209  // AssignLXDProfiles implements environs.LXDProfiler.
   210  func (broker *lxdBroker) AssignLXDProfiles(instID string, profilesNames []string, profilePosts []lxdprofile.ProfilePost) ([]string, error) {
   211  	profileMgr, ok := broker.manager.(container.LXDProfileManager)
   212  	if !ok {
   213  		return []string{}, nil
   214  	}
   215  	return profileMgr.AssignLXDProfiles(instID, profilesNames, profilePosts)
   216  }