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