launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/container/lxc/lxc.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package lxc 5 6 import ( 7 "fmt" 8 "io/ioutil" 9 "os" 10 "path/filepath" 11 "strings" 12 13 "github.com/loggo/loggo" 14 "launchpad.net/golxc" 15 16 "launchpad.net/juju-core/container" 17 "launchpad.net/juju-core/environs/cloudinit" 18 "launchpad.net/juju-core/instance" 19 "launchpad.net/juju-core/names" 20 "launchpad.net/juju-core/version" 21 ) 22 23 var logger = loggo.GetLogger("juju.container.lxc") 24 25 var ( 26 defaultTemplate = "ubuntu-cloud" 27 LxcContainerDir = "/var/lib/lxc" 28 LxcRestartDir = "/etc/lxc/auto" 29 LxcObjectFactory = golxc.Factory() 30 ) 31 32 const ( 33 // DefaultLxcBridge is the package created container bridge 34 DefaultLxcBridge = "lxcbr0" 35 ) 36 37 // DefaultNetworkConfig returns a valid NetworkConfig to use the 38 // defaultLxcBridge that is created by the lxc package. 39 func DefaultNetworkConfig() *container.NetworkConfig { 40 return container.BridgeNetworkConfig(DefaultLxcBridge) 41 } 42 43 type containerManager struct { 44 name string 45 logdir string 46 } 47 48 // containerManager implements container.Manager. 49 var _ container.Manager = (*containerManager)(nil) 50 51 // NewContainerManager returns a manager object that can start and stop lxc 52 // containers. The containers that are created are namespaced by the name 53 // parameter. 54 func NewContainerManager(conf container.ManagerConfig) container.Manager { 55 logdir := "/var/log/juju" 56 if conf.LogDir != "" { 57 logdir = conf.LogDir 58 } 59 return &containerManager{name: conf.Name, logdir: logdir} 60 } 61 62 func (manager *containerManager) StartContainer( 63 machineConfig *cloudinit.MachineConfig, 64 series string, 65 network *container.NetworkConfig) (instance.Instance, *instance.HardwareCharacteristics, error) { 66 67 name := names.MachineTag(machineConfig.MachineId) 68 if manager.name != "" { 69 name = fmt.Sprintf("%s-%s", manager.name, name) 70 } 71 // Note here that the lxcObjectFacotry only returns a valid container 72 // object, and doesn't actually construct the underlying lxc container on 73 // disk. 74 lxcContainer := LxcObjectFactory.New(name) 75 76 // Create the cloud-init. 77 directory, err := container.NewDirectory(name) 78 if err != nil { 79 return nil, nil, err 80 } 81 logger.Tracef("write cloud-init") 82 userDataFilename, err := container.WriteUserData(machineConfig, directory) 83 if err != nil { 84 logger.Errorf("failed to write user data: %v", err) 85 return nil, nil, err 86 } 87 logger.Tracef("write the lxc.conf file") 88 configFile, err := writeLxcConfig(network, directory, manager.logdir) 89 if err != nil { 90 logger.Errorf("failed to write config file: %v", err) 91 return nil, nil, err 92 } 93 templateParams := []string{ 94 "--debug", // Debug errors in the cloud image 95 "--userdata", userDataFilename, // Our groovey cloud-init 96 "--hostid", name, // Use the container name as the hostid 97 "-r", series, 98 } 99 // Create the container. 100 logger.Tracef("create the container") 101 if err := lxcContainer.Create(configFile, defaultTemplate, templateParams...); err != nil { 102 logger.Errorf("lxc container creation failed: %v", err) 103 return nil, nil, err 104 } 105 // Make sure that the mount dir has been created. 106 logger.Tracef("make the mount dir for the shard logs") 107 if err := os.MkdirAll(internalLogDir(name), 0755); err != nil { 108 logger.Errorf("failed to create internal /var/log/juju mount dir: %v", err) 109 return nil, nil, err 110 } 111 logger.Tracef("lxc container created") 112 // Now symlink the config file into the restart directory, if it exists. 113 // This is for backwards compatiblity. From Trusty onwards, the auto start 114 // option should be set in the LXC config file, this is done in the networkConfigTemplate 115 // function below. 116 if useRestartDir() { 117 containerConfigFile := filepath.Join(LxcContainerDir, name, "config") 118 if err := os.Symlink(containerConfigFile, restartSymlink(name)); err != nil { 119 return nil, nil, err 120 } 121 logger.Tracef("auto-restart link created") 122 } 123 124 // Start the lxc container with the appropriate settings for grabbing the 125 // console output and a log file. 126 consoleFile := filepath.Join(directory, "console.log") 127 lxcContainer.SetLogFile(filepath.Join(directory, "container.log"), golxc.LogDebug) 128 logger.Tracef("start the container") 129 // We explicitly don't pass through the config file to the container.Start 130 // method as we have passed it through at container creation time. This 131 // is necessary to get the appropriate rootfs reference without explicitly 132 // setting it ourselves. 133 if err = lxcContainer.Start("", consoleFile); err != nil { 134 logger.Errorf("container failed to start: %v", err) 135 return nil, nil, err 136 } 137 arch := version.Current.Arch 138 hardware := &instance.HardwareCharacteristics{ 139 Arch: &arch, 140 } 141 logger.Tracef("container started") 142 return &lxcInstance{lxcContainer, name}, hardware, nil 143 } 144 145 func (manager *containerManager) StopContainer(instance instance.Instance) error { 146 name := string(instance.Id()) 147 lxcContainer := LxcObjectFactory.New(name) 148 if useRestartDir() { 149 // Remove the autostart link. 150 if err := os.Remove(restartSymlink(name)); err != nil { 151 logger.Errorf("failed to remove restart symlink: %v", err) 152 return err 153 } 154 } 155 if err := lxcContainer.Destroy(); err != nil { 156 logger.Errorf("failed to destroy lxc container: %v", err) 157 return err 158 } 159 160 return container.RemoveDirectory(name) 161 } 162 163 func (manager *containerManager) ListContainers() (result []instance.Instance, err error) { 164 containers, err := LxcObjectFactory.List() 165 if err != nil { 166 logger.Errorf("failed getting all instances: %v", err) 167 return 168 } 169 managerPrefix := "" 170 if manager.name != "" { 171 managerPrefix = fmt.Sprintf("%s-", manager.name) 172 } 173 174 for _, container := range containers { 175 // Filter out those not starting with our name. 176 name := container.Name() 177 if !strings.HasPrefix(name, managerPrefix) { 178 continue 179 } 180 if container.IsRunning() { 181 result = append(result, &lxcInstance{container, name}) 182 } 183 } 184 return 185 } 186 187 const internalLogDirTemplate = "%s/%s/rootfs/var/log/juju" 188 189 func internalLogDir(containerName string) string { 190 return fmt.Sprintf(internalLogDirTemplate, LxcContainerDir, containerName) 191 } 192 193 func restartSymlink(name string) string { 194 return filepath.Join(LxcRestartDir, name+".conf") 195 } 196 197 const localConfig = `%s 198 lxc.mount.entry=%s var/log/juju none defaults,bind 0 0 199 ` 200 201 const networkTemplate = ` 202 lxc.network.type = %s 203 lxc.network.link = %s 204 lxc.network.flags = up 205 ` 206 207 func networkConfigTemplate(networkType, networkLink string) string { 208 networkConfig := fmt.Sprintf(networkTemplate, networkType, networkLink) 209 if !useRestartDir() { 210 networkConfig += "lxc.start.auto = 1\n" 211 logger.Tracef("Setting auto start to true in lxc config.") 212 } 213 return networkConfig 214 } 215 216 func generateNetworkConfig(network *container.NetworkConfig) string { 217 if network == nil { 218 logger.Warningf("network unspecified, using default networking config") 219 network = DefaultNetworkConfig() 220 } 221 switch network.NetworkType { 222 case container.PhysicalNetwork: 223 return networkConfigTemplate("phys", network.Device) 224 default: 225 logger.Warningf("Unknown network config type %q: using bridge", network.NetworkType) 226 fallthrough 227 case container.BridgeNetwork: 228 return networkConfigTemplate("veth", network.Device) 229 } 230 } 231 232 func writeLxcConfig(network *container.NetworkConfig, directory, logdir string) (string, error) { 233 networkConfig := generateNetworkConfig(network) 234 configFilename := filepath.Join(directory, "lxc.conf") 235 configContent := fmt.Sprintf(localConfig, networkConfig, logdir) 236 if err := ioutil.WriteFile(configFilename, []byte(configContent), 0644); err != nil { 237 return "", err 238 } 239 return configFilename, nil 240 } 241 242 // useRestartDir is used to determine whether or not to use a symlink to the 243 // container config as the restart mechanism. Older versions of LXC had the 244 // /etc/lxc/auto directory that would indicate that a container shoud auto- 245 // restart when the machine boots by having a symlink to the lxc.conf file. 246 // Newer versions don't do this, but instead have a config value inside the 247 // lxc.conf file. 248 func useRestartDir() bool { 249 if _, err := os.Stat(LxcRestartDir); err != nil { 250 if os.IsNotExist(err) { 251 return false 252 } 253 } 254 return true 255 }