github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/container/lxd/initialisation_linux.go (about) 1 // Copyright 2016 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 "bytes" 10 "fmt" 11 "io/ioutil" 12 "net" 13 "os" 14 "os/exec" 15 "strings" 16 "syscall" 17 18 "github.com/juju/errors" 19 "github.com/juju/utils/packaging/config" 20 "github.com/juju/utils/packaging/manager" 21 "github.com/juju/utils/proxy" 22 "github.com/lxc/lxd/shared" 23 24 "github.com/juju/juju/container" 25 "github.com/juju/juju/tools/lxdclient" 26 ) 27 28 const lxdBridgeFile = "/etc/default/lxd-bridge" 29 30 var requiredPackages = []string{ 31 "lxd", 32 } 33 34 var xenialPackages = []string{ 35 "zfsutils-linux", 36 } 37 38 type containerInitialiser struct { 39 series string 40 } 41 42 // containerInitialiser implements container.Initialiser. 43 var _ container.Initialiser = (*containerInitialiser)(nil) 44 45 // NewContainerInitialiser returns an instance used to perform the steps 46 // required to allow a host machine to run a LXC container. 47 func NewContainerInitialiser(series string) container.Initialiser { 48 return &containerInitialiser{series} 49 } 50 51 // Initialise is specified on the container.Initialiser interface. 52 func (ci *containerInitialiser) Initialise() error { 53 err := ensureDependencies(ci.series) 54 if err != nil { 55 return err 56 } 57 58 err = configureLXDBridge() 59 if err != nil { 60 return err 61 } 62 proxies := proxy.DetectProxies() 63 err = ConfigureLXDProxies(proxies) 64 if err != nil { 65 return err 66 } 67 68 // Well... this will need to change soon once we are passed 17.04 as who 69 // knows what the series name will be. 70 if ci.series >= "xenial" { 71 configureZFS() 72 } 73 74 return nil 75 } 76 77 // getPackageManager is a helper function which returns the 78 // package manager implementation for the current system. 79 func getPackageManager(series string) (manager.PackageManager, error) { 80 return manager.NewPackageManager(series) 81 } 82 83 // getPackagingConfigurer is a helper function which returns the 84 // packaging configuration manager for the current system. 85 func getPackagingConfigurer(series string) (config.PackagingConfigurer, error) { 86 return config.NewPackagingConfigurer(series) 87 } 88 89 // ConfigureLXDProxies will try to set the lxc config core.proxy_http and core.proxy_https 90 // configuration values based on the current environment. 91 func ConfigureLXDProxies(proxies proxy.Settings) error { 92 setter, err := getLXDConfigSetter() 93 if err != nil { 94 return errors.Trace(err) 95 } 96 return errors.Trace(configureLXDProxies(setter, proxies)) 97 } 98 99 var getLXDConfigSetter = getConfigSetterConnect 100 101 func getConfigSetterConnect() (configSetter, error) { 102 return ConnectLocal() 103 } 104 105 type configSetter interface { 106 SetConfig(key, value string) error 107 } 108 109 func configureLXDProxies(setter configSetter, proxies proxy.Settings) error { 110 err := setter.SetConfig("core.proxy_http", proxies.Http) 111 if err != nil { 112 return errors.Trace(err) 113 } 114 err = setter.SetConfig("core.proxy_https", proxies.Https) 115 if err != nil { 116 return errors.Trace(err) 117 } 118 err = setter.SetConfig("core.proxy_ignore_hosts", proxies.NoProxy) 119 if err != nil { 120 return errors.Trace(err) 121 } 122 return nil 123 } 124 125 // df returns the number of free bytes on the file system at the given path 126 var df = func(path string) (uint64, error) { 127 // Note: do not use golang.org/x/sys/unix for this, it is 128 // the best solution but will break the build in s390x 129 // and introduce cgo dependency lp:1632541 130 statfs := syscall.Statfs_t{} 131 err := syscall.Statfs(path, &statfs) 132 if err != nil { 133 return 0, err 134 } 135 return uint64(statfs.Bsize) * statfs.Bfree, nil 136 } 137 138 var configureZFS = func() { 139 /* create a pool that will occupy 90% of the free disk space 140 (sparse, so it won't actually fill that immediately) 141 */ 142 143 // Find 90% of the free disk space 144 freeBytes, err := df("/") 145 if err != nil { 146 logger.Errorf("configuring zfs failed - unable to find file system size: %s", err) 147 } 148 GigaBytesToUse := freeBytes * 9 / (10 * 1024 * 1024 * 1024) 149 150 output, err := exec.Command( 151 "lxd", 152 "init", 153 "--auto", 154 "--storage-backend", "zfs", 155 "--storage-pool", "lxd", 156 "--storage-create-loop", fmt.Sprintf("%d", GigaBytesToUse), 157 ).CombinedOutput() 158 159 if err != nil { 160 logger.Errorf("configuring zfs failed with %s: %s", err, string(output)) 161 } 162 } 163 164 var configureLXDBridge = func() error { 165 client, err := ConnectLocal() 166 if err != nil { 167 return errors.Trace(err) 168 } 169 170 status, err := client.ServerStatus() 171 if err != nil { 172 return errors.Trace(err) 173 } 174 175 if shared.StringInSlice("network", status.APIExtensions) { 176 return lxdclient.CreateDefaultBridgeInDefaultProfile(client) 177 } 178 179 f, err := os.OpenFile(lxdBridgeFile, os.O_RDWR, 0777) 180 if err != nil { 181 /* We're using an old version of LXD which doesn't have 182 * lxd-bridge; let's not fail here. 183 */ 184 if os.IsNotExist(err) { 185 logger.Debugf("couldn't find %s, not configuring it", lxdBridgeFile) 186 return nil 187 } 188 return errors.Trace(err) 189 } 190 defer f.Close() 191 192 existing, err := ioutil.ReadAll(f) 193 if err != nil { 194 return errors.Trace(err) 195 } 196 197 newBridgeCfg, err := bridgeConfiguration(string(existing)) 198 if err != nil { 199 return errors.Trace(err) 200 } 201 202 if newBridgeCfg == string(existing) { 203 return nil 204 } 205 206 _, err = f.Seek(0, 0) 207 if err != nil { 208 return errors.Trace(err) 209 } 210 _, err = f.WriteString(newBridgeCfg) 211 if err != nil { 212 return errors.Trace(err) 213 } 214 215 /* non-systemd systems don't have the lxd-bridge service, so this always fails */ 216 _ = exec.Command("service", "lxd-bridge", "restart").Run() 217 return exec.Command("service", "lxd", "restart").Run() 218 } 219 220 var interfaceAddrs = func() ([]net.Addr, error) { 221 return net.InterfaceAddrs() 222 } 223 224 func editLXDBridgeFile(input string, subnet string) string { 225 buffer := bytes.Buffer{} 226 227 newValues := map[string]string{ 228 "USE_LXD_BRIDGE": "true", 229 "EXISTING_BRIDGE": "", 230 "LXD_BRIDGE": "lxdbr0", 231 "LXD_IPV4_ADDR": fmt.Sprintf("10.0.%s.1", subnet), 232 "LXD_IPV4_NETMASK": "255.255.255.0", 233 "LXD_IPV4_NETWORK": fmt.Sprintf("10.0.%s.1/24", subnet), 234 "LXD_IPV4_DHCP_RANGE": fmt.Sprintf("10.0.%s.2,10.0.%s.254", subnet, subnet), 235 "LXD_IPV4_DHCP_MAX": "253", 236 "LXD_IPV4_NAT": "true", 237 "LXD_IPV6_PROXY": "false", 238 } 239 found := map[string]bool{} 240 241 for _, line := range strings.Split(input, "\n") { 242 out := line 243 244 for prefix, value := range newValues { 245 if strings.HasPrefix(line, prefix+"=") { 246 out = fmt.Sprintf(`%s="%s"`, prefix, value) 247 found[prefix] = true 248 break 249 } 250 } 251 252 buffer.WriteString(out) 253 buffer.WriteString("\n") 254 } 255 256 for prefix, value := range newValues { 257 if !found[prefix] { 258 buffer.WriteString(prefix) 259 buffer.WriteString("=") 260 buffer.WriteString(value) 261 buffer.WriteString("\n") 262 found[prefix] = true // not necessary but keeps "found" logically consistent 263 } 264 } 265 266 return buffer.String() 267 } 268 269 // ensureDependencies creates a set of install packages using 270 // apt.GetPreparePackages and runs each set of packages through 271 // apt.GetInstall. 272 func ensureDependencies(series string) error { 273 if series == "precise" { 274 return fmt.Errorf("LXD is not supported in precise.") 275 } 276 277 pacman, err := getPackageManager(series) 278 if err != nil { 279 return err 280 } 281 pacconfer, err := getPackagingConfigurer(series) 282 if err != nil { 283 return err 284 } 285 286 for _, pack := range requiredPackages { 287 pkg := pack 288 if config.SeriesRequiresCloudArchiveTools(series) && 289 pacconfer.IsCloudArchivePackage(pack) { 290 pkg = strings.Join(pacconfer.ApplyCloudArchiveTarget(pack), " ") 291 } 292 293 if config.RequiresBackports(series, pack) { 294 pkg = fmt.Sprintf("--target-release %s-backports %s", series, pkg) 295 } 296 297 if err := pacman.Install(pkg); err != nil { 298 return err 299 } 300 } 301 302 if series >= "xenial" { 303 for _, pack := range xenialPackages { 304 pacman.Install(fmt.Sprintf("--no-install-recommends %s", pack)) 305 } 306 } 307 308 return err 309 } 310 311 // findNextAvailableIPv4Subnet scans the list of interfaces on the machine 312 // looking for 10.0.0.0/16 networks and returns the next subnet not in 313 // use, having first detected the highest subnet. The next subnet can 314 // actually be lower if we overflowed 255 whilst seeking out the next 315 // unused subnet. If all subnets are in use an error is returned. 316 // 317 // TODO(frobware): this is not an ideal solution as it doesn't take 318 // into account any static routes that may be set up on the machine. 319 // 320 // TODO(frobware): this only caters for IPv4 setups. 321 func findNextAvailableIPv4Subnet() (string, error) { 322 _, ip10network, err := net.ParseCIDR("10.0.0.0/16") 323 if err != nil { 324 return "", errors.Trace(err) 325 } 326 327 addrs, err := interfaceAddrs() 328 if err != nil { 329 return "", errors.Annotatef(err, "cannot get network interface addresses") 330 } 331 332 max := 0 333 usedSubnets := make(map[int]bool) 334 335 for _, address := range addrs { 336 addr, network, err := net.ParseCIDR(address.String()) 337 if err != nil { 338 logger.Debugf("cannot parse address %q: %v (ignoring)", address.String(), err) 339 continue 340 } 341 if !ip10network.Contains(addr) { 342 logger.Debugf("find available subnet, skipping %q", network.String()) 343 continue 344 } 345 subnet := int(network.IP[2]) 346 usedSubnets[subnet] = true 347 if subnet > max { 348 max = subnet 349 } 350 } 351 352 if len(usedSubnets) == 0 { 353 return "0", nil 354 } 355 356 for i := 0; i < 256; i++ { 357 max = (max + 1) % 256 358 if _, inUse := usedSubnets[max]; !inUse { 359 return fmt.Sprintf("%d", max), nil 360 } 361 } 362 363 return "", errors.New("could not find unused subnet") 364 } 365 366 func parseLXDBridgeConfigValues(input string) map[string]string { 367 values := make(map[string]string) 368 369 for _, line := range strings.Split(input, "\n") { 370 line = strings.TrimSpace(line) 371 372 if line == "" || strings.HasPrefix(line, "#") || !strings.Contains(line, "=") { 373 continue 374 } 375 376 tokens := strings.Split(line, "=") 377 378 if tokens[0] == "" { 379 continue // no key 380 } 381 382 value := "" 383 384 if len(tokens) > 1 { 385 value = tokens[1] 386 if strings.HasPrefix(value, `"`) && strings.HasSuffix(value, `"`) { 387 value = strings.Trim(value, `"`) 388 } 389 } 390 391 values[tokens[0]] = value 392 } 393 return values 394 } 395 396 // bridgeConfiguration ensures that input has a valid setting for 397 // LXD_IPV4_ADDR, returning the existing input if is already set, and 398 // allocating the next available subnet if it is not. 399 func bridgeConfiguration(input string) (string, error) { 400 values := parseLXDBridgeConfigValues(input) 401 ipAddr := net.ParseIP(values["LXD_IPV4_ADDR"]) 402 403 if ipAddr == nil || ipAddr.To4() == nil { 404 logger.Infof("LXD_IPV4_ADDR is not set; searching for unused subnet") 405 subnet, err := findNextAvailableIPv4Subnet() 406 if err != nil { 407 return "", errors.Trace(err) 408 } 409 logger.Infof("setting LXD_IPV4_ADDR=10.0.%s.1", subnet) 410 return editLXDBridgeFile(input, subnet), nil 411 } 412 return input, nil 413 }