github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/container/lxd/manager.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package lxd 5 6 import ( 7 "fmt" 8 "strings" 9 "sync" 10 11 "github.com/juju/errors" 12 "github.com/juju/loggo" 13 jujuarch "github.com/juju/utils/arch" 14 "github.com/lxc/lxd/shared/api" 15 "gopkg.in/juju/charm.v6" 16 17 "github.com/juju/juju/cloudconfig/cloudinit" 18 "github.com/juju/juju/cloudconfig/containerinit" 19 "github.com/juju/juju/cloudconfig/instancecfg" 20 "github.com/juju/juju/container" 21 "github.com/juju/juju/core/constraints" 22 "github.com/juju/juju/core/instance" 23 "github.com/juju/juju/core/status" 24 "github.com/juju/juju/environs" 25 "github.com/juju/juju/environs/config" 26 "github.com/juju/juju/environs/imagemetadata" 27 "github.com/juju/juju/environs/instances" 28 "github.com/juju/juju/network" 29 ) 30 31 var ( 32 logger = loggo.GetLogger("juju.container.lxd") 33 ) 34 35 const lxdDefaultProfileName = "default" 36 37 type containerManager struct { 38 server *Server 39 40 modelUUID string 41 namespace instance.Namespace 42 availabilityZone string 43 44 imageMetadataURL string 45 imageStream string 46 imageMutex sync.Mutex 47 } 48 49 // containerManager implements container.Manager. 50 var _ container.Manager = (*containerManager)(nil) 51 52 // NewContainerManager creates the entity that knows how to create and manage 53 // LXD containers. 54 // TODO(jam): This needs to grow support for things like LXC's ImageURLGetter 55 // functionality. 56 func NewContainerManager(cfg container.ManagerConfig, svr *Server) (container.Manager, error) { 57 modelUUID := cfg.PopValue(container.ConfigModelUUID) 58 if modelUUID == "" { 59 return nil, errors.Errorf("model UUID is required") 60 } 61 namespace, err := instance.NewNamespace(modelUUID) 62 if err != nil { 63 return nil, errors.Trace(err) 64 } 65 66 availabilityZone := cfg.PopValue(container.ConfigAvailabilityZone) 67 if availabilityZone == "" { 68 logger.Infof("Availability zone will be empty for this container manager") 69 } 70 71 imageMetaDataURL := cfg.PopValue(config.ContainerImageMetadataURLKey) 72 imageStream := cfg.PopValue(config.ContainerImageStreamKey) 73 74 cfg.WarnAboutUnused() 75 return &containerManager{ 76 server: svr, 77 modelUUID: modelUUID, 78 namespace: namespace, 79 availabilityZone: availabilityZone, 80 imageMetadataURL: imageMetaDataURL, 81 imageStream: imageStream, 82 }, nil 83 } 84 85 // Namespace implements container.Manager. 86 func (m *containerManager) Namespace() instance.Namespace { 87 return m.namespace 88 } 89 90 // DestroyContainer implements container.Manager. 91 func (m *containerManager) DestroyContainer(id instance.Id) error { 92 return errors.Trace(m.server.RemoveContainer(string(id))) 93 } 94 95 // CreateContainer implements container.Manager. 96 func (m *containerManager) CreateContainer( 97 instanceConfig *instancecfg.InstanceConfig, 98 cons constraints.Value, 99 series string, 100 networkConfig *container.NetworkConfig, 101 storageConfig *container.StorageConfig, 102 callback environs.StatusCallbackFunc, 103 ) (instances.Instance, *instance.HardwareCharacteristics, error) { 104 callback(status.Provisioning, "Creating container spec", nil) 105 spec, err := m.getContainerSpec(instanceConfig, cons, series, networkConfig, storageConfig, callback) 106 if err != nil { 107 callback(status.ProvisioningError, fmt.Sprintf("Creating container spec: %v", err), nil) 108 return nil, nil, errors.Trace(err) 109 } 110 111 callback(status.Provisioning, "Creating container", nil) 112 c, err := m.server.CreateContainerFromSpec(spec) 113 if err != nil { 114 callback(status.ProvisioningError, fmt.Sprintf("Creating container: %v", err), nil) 115 return nil, nil, errors.Trace(err) 116 } 117 callback(status.Running, "Container started", nil) 118 119 return &lxdInstance{c.Name, m.server.ContainerServer}, 120 &instance.HardwareCharacteristics{AvailabilityZone: &m.availabilityZone}, nil 121 } 122 123 // ListContainers implements container.Manager. 124 func (m *containerManager) ListContainers() ([]instances.Instance, error) { 125 containers, err := m.server.FilterContainers(m.namespace.Prefix()) 126 if err != nil { 127 return nil, errors.Trace(err) 128 } 129 130 var result []instances.Instance 131 for _, i := range containers { 132 result = append(result, &lxdInstance{i.Name, m.server.ContainerServer}) 133 } 134 return result, nil 135 } 136 137 // IsInitialized implements container.Manager. 138 func (m *containerManager) IsInitialized() bool { 139 return m.server != nil 140 } 141 142 // getContainerSpec generates a spec for creating a new container. 143 // It sources an image based on the input series, and transforms the input 144 // config objects into LXD configuration, including cloud init user data. 145 func (m *containerManager) getContainerSpec( 146 instanceConfig *instancecfg.InstanceConfig, 147 cons constraints.Value, 148 series string, 149 networkConfig *container.NetworkConfig, 150 storageConfig *container.StorageConfig, 151 callback environs.StatusCallbackFunc, 152 ) (ContainerSpec, error) { 153 imageSources, err := m.getImageSources() 154 if err != nil { 155 return ContainerSpec{}, errors.Trace(err) 156 } 157 158 // Lock around finding an image. 159 // The provisioner works concurrently to create containers. 160 // If an image needs to be copied from a remote, we don't many goroutines 161 // attempting to do it at once. 162 m.imageMutex.Lock() 163 found, err := m.server.FindImage(series, jujuarch.HostArch(), imageSources, true, callback) 164 m.imageMutex.Unlock() 165 if err != nil { 166 return ContainerSpec{}, errors.Annotatef(err, "acquiring LXD image") 167 } 168 169 name, err := m.namespace.Hostname(instanceConfig.MachineId) 170 if err != nil { 171 return ContainerSpec{}, errors.Trace(err) 172 } 173 174 nics, unknown, err := m.networkDevicesFromConfig(networkConfig) 175 if err != nil { 176 return ContainerSpec{}, errors.Trace(err) 177 } 178 179 logger.Debugf("configuring container %q with network devices: %v", name, nics) 180 181 // If the default LXD bridge was supplied in network config, 182 // but without a CIDR, attempt to ensure it is configured for IPv4. 183 // If there are others with incomplete info, log a warning. 184 if len(unknown) > 0 { 185 if len(unknown) == 1 && unknown[0] == network.DefaultLXDBridge && m.server.networkAPISupport { 186 mod, err := m.server.EnsureIPv4(network.DefaultLXDBridge) 187 if err != nil { 188 return ContainerSpec{}, errors.Annotate(err, "ensuring default bridge IPv4 config") 189 } 190 if mod { 191 logger.Infof(`added "auto" IPv4 configuration to default LXD bridge`) 192 } 193 } else { 194 logger.Warningf("no CIDR was detected for the following networks: %v", unknown) 195 } 196 } 197 198 // If there was no incoming interface info, then at this point we know 199 // that nics were generated by falling back to either a single "eth0", 200 // or devices from the profile. 201 // Ensure that the devices are represented in the cloud-init user-data. 202 if len(networkConfig.Interfaces) == 0 { 203 interfaces, err := InterfaceInfoFromDevices(nics) 204 if err != nil { 205 return ContainerSpec{}, errors.Trace(err) 206 } 207 networkConfig.Interfaces = interfaces 208 } 209 210 // CloudInitUserData creates our own ENI/netplan. 211 // We need to disable cloud-init networking to make it work. 212 userData, err := containerinit.CloudInitUserData(instanceConfig, networkConfig) 213 if err != nil { 214 return ContainerSpec{}, errors.Trace(err) 215 } 216 217 cfg := map[string]string{ 218 UserDataKey: string(userData), 219 NetworkConfigKey: cloudinit.CloudInitNetworkConfigDisabled, 220 AutoStartKey: "true", 221 // Extra info to indicate the origin of this container. 222 JujuModelKey: m.modelUUID, 223 } 224 225 spec := ContainerSpec{ 226 Name: name, 227 Image: found, 228 Config: cfg, 229 Profiles: instanceConfig.Profiles, 230 Devices: nics, 231 } 232 spec.ApplyConstraints(cons) 233 234 return spec, nil 235 } 236 237 // getImageSources returns a list of LXD remote image sources based on the 238 // configuration that was passed into the container manager. 239 func (m *containerManager) getImageSources() ([]ServerSpec, error) { 240 imURL := m.imageMetadataURL 241 242 // Unless the configuration explicitly requests the daily stream, 243 // an empty image metadata URL results in a search of the default sources. 244 if imURL == "" && m.imageStream != "daily" { 245 logger.Debugf("checking default image metadata sources") 246 return []ServerSpec{CloudImagesRemote, CloudImagesDailyRemote}, nil 247 } 248 // Otherwise only check the daily stream. 249 if imURL == "" { 250 return []ServerSpec{CloudImagesDailyRemote}, nil 251 } 252 253 imURL, err := imagemetadata.ImageMetadataURL(imURL, m.imageStream) 254 if err != nil { 255 return nil, errors.Annotatef(err, "generating image metadata source") 256 } 257 imURL = EnsureHTTPS(imURL) 258 remote := ServerSpec{ 259 Name: strings.Replace(imURL, "https://", "", 1), 260 Host: imURL, 261 Protocol: SimpleStreamsProtocol, 262 } 263 264 // If the daily stream was configured with custom image metadata URL, 265 // only use the Ubuntu daily as a fallback. 266 if m.imageStream == "daily" { 267 return []ServerSpec{remote, CloudImagesDailyRemote}, nil 268 } 269 return []ServerSpec{remote, CloudImagesRemote, CloudImagesDailyRemote}, nil 270 } 271 272 // networkDevicesFromConfig uses the input container network configuration to 273 // create a map of network device configuration in the LXD format. 274 // If there are no interfaces in the input config, but there is a bridge device 275 // name, return a single "eth0" device with the bridge as its parent. 276 // The last fall-back is to return the NIC devices from the default profile. 277 // Names for any networks without a known CIDR are returned in a slice. 278 func (m *containerManager) networkDevicesFromConfig(netConfig *container.NetworkConfig) (map[string]device, []string, error) { 279 if len(netConfig.Interfaces) > 0 { 280 return DevicesFromInterfaceInfo(netConfig.Interfaces) 281 } else if netConfig.Device != "" { 282 return map[string]device{ 283 "eth0": newNICDevice("eth0", netConfig.Device, network.GenerateVirtualMACAddress(), netConfig.MTU), 284 }, nil, nil 285 } 286 287 nics, err := m.server.GetNICsFromProfile(lxdDefaultProfileName) 288 return nics, nil, errors.Trace(err) 289 } 290 291 // MaybeWriteLXDProfile implements container.LXDProfileManager. 292 func (m *containerManager) MaybeWriteLXDProfile(pName string, put *charm.LXDProfile) error { 293 hasProfile, err := m.server.HasProfile(pName) 294 if err != nil { 295 return errors.Trace(err) 296 } 297 if hasProfile { 298 logger.Debugf("lxd profile %q already exists, not written again", pName) 299 return nil 300 } 301 post := api.ProfilesPost{ 302 Name: pName, 303 ProfilePut: api.ProfilePut(*put), 304 } 305 if err = m.server.CreateProfile(post); err != nil { 306 return errors.Trace(err) 307 } 308 logger.Debugf("wrote lxd profile %q", pName) 309 return nil 310 } 311 312 // LXDProfileNames implements container.LXDProfileManager 313 func (m *containerManager) LXDProfileNames(containerName string) ([]string, error) { 314 return m.server.GetContainerProfiles(containerName) 315 } 316 317 // ReplaceOrAddLXDProfile implements environs.LXDProfiler. 318 func (m *containerManager) ReplaceOrAddInstanceProfile(instId, oldProfile, newProfile string, put *charm.LXDProfile) ([]string, error) { 319 if put != nil { 320 if err := m.MaybeWriteLXDProfile(newProfile, put); err != nil { 321 return []string{}, errors.Trace(err) 322 } 323 } 324 if err := m.server.ReplaceOrAddContainerProfile(instId, oldProfile, newProfile); err != nil { 325 return []string{}, errors.Trace(err) 326 } 327 if oldProfile != "" { 328 if err := m.server.DeleteProfile(oldProfile); err != nil { 329 // most likely the failure is because the profile is already in use 330 logger.Debugf("failed to delete profile %q: %s", oldProfile, err) 331 } 332 } 333 return m.LXDProfileNames(instId) 334 }