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