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 }