github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/container/lxd/lxd.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 // +build go1.3 5 6 package lxd 7 8 import ( 9 "fmt" 10 11 "github.com/juju/errors" 12 "github.com/juju/loggo" 13 "github.com/juju/names" 14 "github.com/lxc/lxd" 15 16 "github.com/juju/juju/cloudconfig/containerinit" 17 "github.com/juju/juju/cloudconfig/instancecfg" 18 "github.com/juju/juju/container" 19 "github.com/juju/juju/instance" 20 "github.com/juju/juju/network" 21 "github.com/juju/juju/status" 22 "github.com/juju/juju/tools/lxdclient" 23 ) 24 25 var ( 26 logger = loggo.GetLogger("juju.container.lxd") 27 ) 28 29 // XXX: should we allow managing containers on other hosts? this is 30 // functionality LXD gives us and from discussion juju would use eventually for 31 // the local provider, so the APIs probably need to be changed to pass extra 32 // args around. I'm punting for now. 33 type containerManager struct { 34 name string 35 // A cached client. 36 client *lxdclient.Client 37 // Custom network profile 38 networkProfile string 39 } 40 41 // containerManager implements container.Manager. 42 var _ container.Manager = (*containerManager)(nil) 43 44 func ConnectLocal(namespace string) (*lxdclient.Client, error) { 45 cfg := lxdclient.Config{ 46 Namespace: namespace, 47 Remote: lxdclient.Local, 48 } 49 50 cfg, err := cfg.WithDefaults() 51 if err != nil { 52 return nil, errors.Trace(err) 53 } 54 55 client, err := lxdclient.Connect(cfg) 56 if err != nil { 57 return nil, errors.Trace(err) 58 } 59 60 return client, nil 61 } 62 63 // NewContainerManager creates the entity that knows how to create and manage 64 // LXD containers. 65 // TODO(jam): This needs to grow support for things like LXC's ImageURLGetter 66 // functionality. 67 func NewContainerManager(conf container.ManagerConfig) (container.Manager, error) { 68 name := conf.PopValue(container.ConfigName) 69 if name == "" { 70 return nil, errors.Errorf("name is required") 71 } 72 73 conf.WarnAboutUnused() 74 return &containerManager{name: name}, nil 75 } 76 77 func (manager *containerManager) CreateContainer( 78 instanceConfig *instancecfg.InstanceConfig, 79 series string, 80 networkConfig *container.NetworkConfig, 81 storageConfig *container.StorageConfig, 82 callback container.StatusCallback, 83 ) (inst instance.Instance, _ *instance.HardwareCharacteristics, err error) { 84 85 defer func() { 86 if err != nil { 87 manager.deleteNetworkProfile() 88 callback(status.StatusProvisioningError, fmt.Sprintf("Creating container: %v", err), nil) 89 } 90 }() 91 92 if manager.client == nil { 93 manager.client, err = ConnectLocal(manager.name) 94 if err != nil { 95 err = errors.Annotatef(err, "failed to connect to local LXD") 96 return 97 } 98 } 99 100 err = manager.client.EnsureImageExists(series, 101 lxdclient.DefaultImageSources, 102 func(progress string) { 103 callback(status.StatusProvisioning, progress, nil) 104 }) 105 if err != nil { 106 err = errors.Annotatef(err, "failed to ensure LXD image") 107 return 108 } 109 110 name := names.NewMachineTag(instanceConfig.MachineId).String() 111 if manager.name != "" { 112 name = fmt.Sprintf("%s-%s", manager.name, name) 113 } 114 115 userData, err := containerinit.CloudInitUserData(instanceConfig, networkConfig) 116 if err != nil { 117 return 118 } 119 120 metadata := map[string]string{ 121 lxdclient.UserdataKey: string(userData), 122 // An extra piece of info to let people figure out where this 123 // thing came from. 124 "user.juju-environment": manager.name, 125 126 // Make sure these come back up on host reboot. 127 "boot.autostart": "true", 128 } 129 130 networkProfile := fmt.Sprintf("%s-network", name) 131 132 if len(networkConfig.Interfaces) > 0 || networkConfig.Device != "" { 133 if err = createNetworkProfile(manager.client, networkProfile); err != nil { 134 return 135 } 136 137 manager.networkProfile = networkProfile 138 if len(networkConfig.Interfaces) > 0 { 139 err = networkProfileAddMultipleInterfaces(manager.client, networkProfile, networkConfig.Interfaces) 140 } else { 141 err = networkProfileAddSingleInterface(manager.client, networkProfile, networkConfig.Device, networkConfig.MTU) 142 } 143 if err != nil { 144 return 145 } 146 } else { 147 networkProfile = "default" 148 } 149 150 spec := lxdclient.InstanceSpec{ 151 Name: name, 152 Image: manager.client.ImageNameForSeries(series), 153 Metadata: metadata, 154 Profiles: []string{ 155 networkProfile, 156 }, 157 } 158 159 logger.Infof("starting instance %q (image %q)...", spec.Name, spec.Image) 160 callback(status.StatusProvisioning, "Starting container", nil) 161 _, err = manager.client.AddInstance(spec) 162 if err != nil { 163 manager.client.ProfileDelete(networkProfile) 164 return 165 } 166 167 callback(status.StatusRunning, "Container started", nil) 168 inst = &lxdInstance{name, manager.client} 169 return 170 } 171 172 func (manager *containerManager) DestroyContainer(id instance.Id) error { 173 if manager.client == nil { 174 var err error 175 manager.client, err = ConnectLocal(manager.name) 176 if err != nil { 177 return err 178 } 179 } 180 manager.deleteNetworkProfile() 181 return errors.Trace(manager.client.RemoveInstances(manager.name, string(id))) 182 } 183 184 func (manager *containerManager) ListContainers() (result []instance.Instance, err error) { 185 result = []instance.Instance{} 186 if manager.client == nil { 187 manager.client, err = ConnectLocal(manager.name) 188 if err != nil { 189 return 190 } 191 } 192 193 lxdInstances, err := manager.client.Instances(manager.name) 194 if err != nil { 195 return 196 } 197 198 for _, i := range lxdInstances { 199 result = append(result, &lxdInstance{i.Name, manager.client}) 200 } 201 202 return 203 } 204 205 func (manager *containerManager) IsInitialized() bool { 206 if manager.client != nil { 207 return true 208 } 209 210 // NewClient does a roundtrip to the server to make sure it understands 211 // the versions, so all we need to do is connect above and we're done. 212 var err error 213 manager.client, err = ConnectLocal(manager.name) 214 return err == nil 215 } 216 217 // HasLXDSupport returns false when this juju binary was not built with LXD 218 // support (i.e. it was built on a golang version < 1.2 219 func HasLXDSupport() bool { 220 return true 221 } 222 223 func nicProperties(parentDevice, deviceName, hwAddr string, mtu int) ([]string, error) { 224 var props = []string{"nictype=bridged"} 225 226 if parentDevice == "" { 227 return nil, errors.Errorf("invalid parent device") 228 } else { 229 props = append(props, fmt.Sprintf("parent=%v", parentDevice)) 230 } 231 232 if deviceName == "" { 233 return nil, errors.Errorf("invalid device name") 234 } else { 235 props = append(props, fmt.Sprintf("name=%v", deviceName)) 236 } 237 238 if hwAddr != "" { 239 props = append(props, fmt.Sprintf("hwaddr=%v", hwAddr)) 240 } 241 242 if mtu > 0 { 243 props = append(props, fmt.Sprintf("mtu=%v", mtu)) 244 } 245 246 return props, nil 247 } 248 249 func addNetworkDeviceToProfile(client *lxdclient.Client, profile, parentDevice, deviceName, hwAddr string, mtu int) (*lxd.Response, error) { 250 props, err := nicProperties(parentDevice, deviceName, hwAddr, mtu) 251 if err != nil { 252 return nil, errors.Trace(err) 253 } 254 logger.Infof("adding nic device %q with properties %+v to profile %q", deviceName, props, profile) 255 return client.ProfileDeviceAdd(profile, deviceName, "nic", props) 256 } 257 258 func networkProfileAddSingleInterface(client *lxdclient.Client, profile, deviceName string, mtu int) error { 259 _, err := addNetworkDeviceToProfile(client, profile, deviceName, "eth0", "", mtu) 260 return errors.Trace(err) 261 } 262 263 func networkProfileAddMultipleInterfaces(client *lxdclient.Client, profile string, interfaces []network.InterfaceInfo) error { 264 for _, v := range interfaces { 265 if v.InterfaceType == network.LoopbackInterface { 266 continue 267 } 268 269 if v.InterfaceType != network.EthernetInterface { 270 return errors.Errorf("interface type %q not supported", v.InterfaceType) 271 } 272 273 _, err := addNetworkDeviceToProfile(client, profile, v.ParentInterfaceName, v.InterfaceName, v.MACAddress, v.MTU) 274 275 if err != nil { 276 return errors.Trace(err) 277 } 278 } 279 280 return nil 281 } 282 283 func createNetworkProfile(client *lxdclient.Client, profile string) error { 284 found, err := client.HasProfile(profile) 285 286 if err != nil { 287 return errors.Trace(err) 288 } 289 290 if found { 291 logger.Infof("deleting existing container profile %q", profile) 292 if err := client.ProfileDelete(profile); err != nil { 293 return errors.Trace(err) 294 } 295 } 296 297 err = client.CreateProfile(profile, nil) 298 299 if err == nil { 300 logger.Infof("created new network container profile %q", profile) 301 } 302 303 return errors.Trace(err) 304 } 305 306 func (manager *containerManager) deleteNetworkProfile() { 307 if manager.client != nil && manager.networkProfile != "" { 308 logger.Infof("deleting container network profile %q", manager.networkProfile) 309 if err := manager.client.ProfileDelete(manager.networkProfile); err != nil { 310 logger.Warningf("discarding profile delete error: %v", err) 311 } 312 manager.networkProfile = "" 313 } 314 }