github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/container/lxd/initialisation_linux.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package lxd 5 6 import ( 7 "bytes" 8 "fmt" 9 "io/ioutil" 10 "math/rand" 11 "net" 12 "os" 13 "os/exec" 14 "strings" 15 "syscall" 16 "time" 17 18 "github.com/juju/collections/set" 19 "github.com/juju/errors" 20 "github.com/juju/packaging/config" 21 "github.com/juju/packaging/manager" 22 "github.com/juju/proxy" 23 "github.com/juju/utils/series" 24 "github.com/lxc/lxd/shared" 25 26 "github.com/juju/juju/container" 27 "github.com/juju/juju/service" 28 "github.com/juju/juju/service/common" 29 ) 30 31 var hostSeries = series.HostSeries 32 33 var requiredPackages = []string{"lxd"} 34 35 type containerInitialiser struct { 36 getExecCommand func(string, ...string) *exec.Cmd 37 } 38 39 // containerInitialiser implements container.Initialiser. 40 var _ container.Initialiser = (*containerInitialiser)(nil) 41 42 // NewContainerInitialiser returns an instance used to perform the steps 43 // required to allow a host machine to run a LXC container. 44 func NewContainerInitialiser() container.Initialiser { 45 return &containerInitialiser{ 46 exec.Command, 47 } 48 } 49 50 // Initialise is specified on the container.Initialiser interface. 51 func (ci *containerInitialiser) Initialise() error { 52 localSeries, err := hostSeries() 53 if err != nil { 54 return errors.Trace(err) 55 } 56 57 if err := ensureDependencies(localSeries); err != nil { 58 return errors.Trace(err) 59 } 60 61 err = configureLXDBridge() 62 if err != nil { 63 return errors.Trace(err) 64 } 65 proxies := proxy.DetectProxies() 66 err = ConfigureLXDProxies(proxies) 67 if err != nil { 68 return errors.Trace(err) 69 } 70 71 // LXD init is only run on Xenial and later. 72 if localSeries == "trusty" { 73 return nil 74 } 75 76 output, err := ci.getExecCommand( 77 "lxd", 78 "init", 79 "--auto", 80 ).CombinedOutput() 81 82 if err == nil { 83 return nil 84 } 85 86 out := string(output) 87 if strings.Contains(out, "You have existing containers or images. lxd init requires an empty LXD.") { 88 // this error means we've already run lxd init. Just ignore it. 89 return nil 90 } 91 92 return errors.Annotate(err, "running lxd init --auto: "+out) 93 } 94 95 // getPackageManager is a helper function which returns the 96 // package manager implementation for the current system. 97 func getPackageManager(series string) (manager.PackageManager, error) { 98 return manager.NewPackageManager(series) 99 } 100 101 // getPackagingConfigurer is a helper function which returns the 102 // packaging configuration manager for the current system. 103 func getPackagingConfigurer(series string) (config.PackagingConfigurer, error) { 104 return config.NewPackagingConfigurer(series) 105 } 106 107 // ConfigureLXDProxies will try to set the lxc config core.proxy_http and 108 // core.proxy_https configuration values based on the current environment. 109 // If LXD is not installed, we skip the configuration. 110 func ConfigureLXDProxies(proxies proxy.Settings) error { 111 running, err := IsRunningLocally() 112 if err != nil { 113 return errors.Trace(err) 114 } 115 116 if !running { 117 logger.Debugf("LXD is not running; skipping proxy configuration") 118 return nil 119 } 120 121 svr, err := NewLocalServer() 122 if err != nil { 123 return errors.Trace(err) 124 } 125 126 return errors.Trace(svr.UpdateServerConfig(map[string]string{ 127 "core.proxy_http": proxies.Http, 128 "core.proxy_https": proxies.Https, 129 "core.proxy_ignore_hosts": proxies.NoProxy, 130 })) 131 } 132 133 // df returns the number of free bytes on the file system at the given path 134 var df = func(path string) (uint64, error) { 135 // Note: do not use golang.org/x/sys/unix for this, it is 136 // the best solution but will break the build in s390x 137 // and introduce cgo dependency lp:1632541 138 statfs := syscall.Statfs_t{} 139 err := syscall.Statfs(path, &statfs) 140 if err != nil { 141 return 0, err 142 } 143 return uint64(statfs.Bsize) * statfs.Bfree, nil 144 } 145 146 var configureLXDBridge = func() error { 147 server, err := NewLocalServer() 148 if err != nil { 149 return errors.Trace(err) 150 } 151 // If LXD itself supports managing networks (added in LXD 2.3) we can allow 152 // it to do all of the network configuration. 153 if server.networkAPISupport { 154 profile, eTag, err := server.GetProfile(lxdDefaultProfileName) 155 if err != nil { 156 return errors.Trace(err) 157 } 158 return server.ensureDefaultNetworking(profile, eTag) 159 } 160 return configureLXDBridgeForOlderLXD() 161 } 162 163 // configureLXDBridgeForOlderLXD is used for LXD agents that don't support the 164 // Network API (pre 2.3) 165 func configureLXDBridgeForOlderLXD() error { 166 f, err := os.OpenFile(BridgeConfigFile, os.O_RDWR, 0777) 167 if err != nil { 168 /* We're using an old version of LXD which doesn't have 169 * lxd-bridge; let's not fail here. 170 */ 171 if os.IsNotExist(err) { 172 logger.Debugf("couldn't find %s, not configuring it", BridgeConfigFile) 173 return nil 174 } 175 return errors.Trace(err) 176 } 177 defer f.Close() 178 179 existing, err := ioutil.ReadAll(f) 180 if err != nil { 181 return errors.Trace(err) 182 } 183 184 newBridgeCfg, err := bridgeConfiguration(string(existing)) 185 if err != nil { 186 return errors.Trace(err) 187 } 188 189 if newBridgeCfg == string(existing) { 190 return nil 191 } 192 193 _, err = f.Seek(0, 0) 194 if err != nil { 195 return errors.Trace(err) 196 } 197 _, err = f.WriteString(newBridgeCfg) 198 if err != nil { 199 return errors.Trace(err) 200 } 201 202 /* non-systemd systems don't have the lxd-bridge service, so this always fails */ 203 _ = exec.Command("service", "lxd-bridge", "restart").Run() 204 return exec.Command("service", "lxd", "restart").Run() 205 } 206 207 var interfaceAddrs = func() ([]net.Addr, error) { 208 return net.InterfaceAddrs() 209 } 210 211 func editLXDBridgeFile(input string, subnet string) string { 212 buffer := bytes.Buffer{} 213 214 newValues := map[string]string{ 215 "USE_LXD_BRIDGE": "true", 216 "EXISTING_BRIDGE": "", 217 "LXD_BRIDGE": "lxdbr0", 218 "LXD_IPV4_ADDR": fmt.Sprintf("10.0.%s.1", subnet), 219 "LXD_IPV4_NETMASK": "255.255.255.0", 220 "LXD_IPV4_NETWORK": fmt.Sprintf("10.0.%s.1/24", subnet), 221 "LXD_IPV4_DHCP_RANGE": fmt.Sprintf("10.0.%s.2,10.0.%s.254", subnet, subnet), 222 "LXD_IPV4_DHCP_MAX": "253", 223 "LXD_IPV4_NAT": "true", 224 "LXD_IPV6_PROXY": "false", 225 } 226 found := map[string]bool{} 227 228 for _, line := range strings.Split(input, "\n") { 229 out := line 230 231 for prefix, value := range newValues { 232 if strings.HasPrefix(line, prefix+"=") { 233 out = fmt.Sprintf(`%s="%s"`, prefix, value) 234 found[prefix] = true 235 break 236 } 237 } 238 239 buffer.WriteString(out) 240 buffer.WriteString("\n") 241 } 242 243 for prefix, value := range newValues { 244 if !found[prefix] { 245 buffer.WriteString(prefix) 246 buffer.WriteString("=") 247 buffer.WriteString(value) 248 buffer.WriteString("\n") 249 found[prefix] = true // not necessary but keeps "found" logically consistent 250 } 251 } 252 253 return buffer.String() 254 } 255 256 // ensureDependencies creates a set of install packages using 257 // apt.GetPreparePackages and runs each set of packages through 258 // apt.GetInstall. 259 func ensureDependencies(series string) error { 260 if series == "precise" { 261 return errors.NotSupportedf(`LXD containers on series "precise"`) 262 } 263 264 if lxdViaSnap() { 265 logger.Infof("LXD snap is installed; skipping package installation") 266 return nil 267 } 268 269 pacman, err := getPackageManager(series) 270 if err != nil { 271 return errors.Trace(err) 272 } 273 pacconfer, err := getPackagingConfigurer(series) 274 if err != nil { 275 return errors.Trace(err) 276 } 277 278 for _, pack := range requiredPackages { 279 pkg := pack 280 if config.SeriesRequiresCloudArchiveTools(series) && 281 pacconfer.IsCloudArchivePackage(pack) { 282 pkg = strings.Join(pacconfer.ApplyCloudArchiveTarget(pack), " ") 283 } 284 285 if config.RequiresBackports(series, pack) { 286 pkg = fmt.Sprintf("--target-release %s-backports %s", series, pkg) 287 } 288 289 if err := pacman.Install(pkg); err != nil { 290 return errors.Trace(err) 291 } 292 } 293 294 return errors.Trace(err) 295 } 296 297 // lxdViaSnap interrogates the location of the Snap LXD socket in order 298 // to determine if LXD is being provided via that method. 299 var lxdViaSnap = func() bool { 300 return shared.IsUnixSocket("/var/snap/lxd/common/lxd/unix.socket") 301 } 302 303 // randomizedOctetRange is a variable for testing purposes. 304 var randomizedOctetRange = func() []int { 305 rand.Seed(time.Now().UnixNano()) 306 return rand.Perm(255) 307 } 308 309 // getKnownV4IPsAndCIDRs iterates all of the known Addresses on this machine 310 // and groups them up into known CIDRs and IP addresses. 311 func getKnownV4IPsAndCIDRs(addrFunc func() ([]net.Addr, error)) ([]net.IP, []*net.IPNet, error) { 312 addrs, err := addrFunc() 313 if err != nil { 314 return nil, nil, errors.Annotate(err, "cannot get network interface addresses") 315 } 316 317 knownIPs := []net.IP{} 318 seenIPs := set.NewStrings() 319 knownCIDRs := []*net.IPNet{} 320 seenCIDRs := set.NewStrings() 321 for _, netAddr := range addrs { 322 ip, ipNet, err := net.ParseCIDR(netAddr.String()) 323 if err != nil { 324 continue 325 } 326 if ip.To4() == nil { 327 continue 328 } 329 if !seenIPs.Contains(ip.String()) { 330 knownIPs = append(knownIPs, ip) 331 seenIPs.Add(ip.String()) 332 } 333 if !seenCIDRs.Contains(ipNet.String()) { 334 knownCIDRs = append(knownCIDRs, ipNet) 335 seenCIDRs.Add(ipNet.String()) 336 } 337 } 338 return knownIPs, knownCIDRs, nil 339 } 340 341 // findNextAvailableIPv4Subnet scans the list of interfaces on the machine 342 // looking for 10.0.0.0/16 networks and returns the next subnet not in 343 // use, having first detected the highest subnet. The next subnet can 344 // actually be lower if we overflowed 255 whilst seeking out the next 345 // unused subnet. If all subnets are in use an error is returned. 346 // 347 // TODO(frobware): this is not an ideal solution as it doesn't take 348 // into account any static routes that may be set up on the machine. 349 // 350 // TODO(frobware): this only caters for IPv4 setups. 351 func findNextAvailableIPv4Subnet() (string, error) { 352 knownIPs, knownCIDRs, err := getKnownV4IPsAndCIDRs(interfaceAddrs) 353 if err != nil { 354 return "", errors.Trace(err) 355 } 356 357 randomized3rdSegment := randomizedOctetRange() 358 for _, i := range randomized3rdSegment { 359 // lxd randomizes the 2nd and 3rd segments, we should be fine with the 360 // 3rd only 361 ip, ip10network, err := net.ParseCIDR(fmt.Sprintf("10.0.%d.0/24", i)) 362 if err != nil { 363 return "", errors.Trace(err) 364 } 365 366 collides := false 367 for _, kIP := range knownIPs { 368 if ip10network.Contains(kIP) { 369 collides = true 370 break 371 } 372 } 373 if !collides { 374 for _, kNet := range knownCIDRs { 375 if kNet.Contains(ip) || ip10network.Contains(kNet.IP) { 376 collides = true 377 break 378 } 379 } 380 } 381 if !collides { 382 return fmt.Sprintf("%d", i), nil 383 } 384 } 385 return "", errors.New("could not find unused subnet") 386 } 387 388 func parseLXDBridgeConfigValues(input string) map[string]string { 389 values := make(map[string]string) 390 391 for _, line := range strings.Split(input, "\n") { 392 line = strings.TrimSpace(line) 393 if line == "" || strings.HasPrefix(line, "#") || !strings.Contains(line, "=") { 394 continue 395 } 396 397 tokens := strings.Split(line, "=") 398 if tokens[0] == "" { 399 continue // no key 400 } 401 402 value := "" 403 if len(tokens) > 1 { 404 value = tokens[1] 405 if strings.HasPrefix(value, `"`) && strings.HasSuffix(value, `"`) { 406 value = strings.Trim(value, `"`) 407 } 408 } 409 values[tokens[0]] = value 410 } 411 return values 412 } 413 414 // bridgeConfiguration ensures that input has a valid setting for 415 // LXD_IPV4_ADDR, returning the existing input if is already set, and 416 // allocating the next available subnet if it is not. 417 func bridgeConfiguration(input string) (string, error) { 418 values := parseLXDBridgeConfigValues(input) 419 ipAddr := net.ParseIP(values["LXD_IPV4_ADDR"]) 420 421 if ipAddr == nil || ipAddr.To4() == nil { 422 logger.Infof("LXD_IPV4_ADDR is not set; searching for unused subnet") 423 subnet, err := findNextAvailableIPv4Subnet() 424 if err != nil { 425 return "", errors.Trace(err) 426 } 427 logger.Infof("setting LXD_IPV4_ADDR=10.0.%s.1", subnet) 428 return editLXDBridgeFile(input, subnet), nil 429 } 430 return input, nil 431 } 432 433 // IsRunningLocally returns true if LXD is running locally. 434 var IsRunningLocally = isRunningLocally 435 436 func isRunningLocally() (bool, error) { 437 svcName, err := InstalledServiceName() 438 if svcName == "" || err != nil { 439 return false, errors.Trace(err) 440 } 441 442 hostSeries, err := series.HostSeries() 443 if err != nil { 444 return false, errors.Trace(err) 445 } 446 447 svc, err := service.NewService(svcName, common.Conf{}, hostSeries) 448 if err != nil { 449 return false, errors.Trace(err) 450 } 451 running, err := svc.Running() 452 if err != nil { 453 return running, errors.Trace(err) 454 } 455 return running, nil 456 } 457 458 // InstalledServiceName returns the name of the running service for the LXD 459 // daemon. If LXD is not installed, the return is an empty string. 460 func InstalledServiceName() (string, error) { 461 names, err := service.ListServices() 462 if err != nil { 463 return "", errors.Trace(err) 464 } 465 466 // Prefer the Snap service. 467 svcName := "" 468 for _, name := range names { 469 if name == "snap.lxd.daemon" { 470 return name, nil 471 } 472 if name == "lxd" { 473 svcName = name 474 } 475 } 476 return svcName, nil 477 }