github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/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 "os" 13 "os/exec" 14 "strconv" 15 "strings" 16 17 "github.com/juju/errors" 18 19 "github.com/juju/utils/packaging/config" 20 "github.com/juju/utils/packaging/manager" 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 60 if ci.series >= "xenial" { 61 configureZFS() 62 } 63 64 return nil 65 } 66 67 // getPackageManager is a helper function which returns the 68 // package manager implementation for the current system. 69 func getPackageManager(series string) (manager.PackageManager, error) { 70 return manager.NewPackageManager(series) 71 } 72 73 // getPackagingConfigurer is a helper function which returns the 74 // packaging configuration manager for the current system. 75 func getPackagingConfigurer(series string) (config.PackagingConfigurer, error) { 76 return config.NewPackagingConfigurer(series) 77 } 78 79 var configureZFS = func() { 80 /* create a 100 GB pool by default (sparse, so it won't actually fill 81 * that immediately) 82 */ 83 output, err := exec.Command( 84 "lxd", 85 "init", 86 "--auto", 87 "--storage-backend", "zfs", 88 "--storage-pool", "lxd", 89 "--storage-create-loop", "100", 90 ).CombinedOutput() 91 92 if err != nil { 93 logger.Warningf("configuring zfs failed with %s: %s", err, string(output)) 94 } 95 } 96 97 var configureLXDBridge = func() error { 98 f, err := os.OpenFile(lxdBridgeFile, os.O_RDWR, 0777) 99 if err != nil { 100 /* We're using an old version of LXD which doesn't have 101 * lxd-bridge; let's not fail here. 102 */ 103 if os.IsNotExist(err) { 104 logger.Warningf("couldn't find %s, not configuring it", lxdBridgeFile) 105 return nil 106 } 107 return errors.Trace(err) 108 } 109 defer f.Close() 110 111 output, err := exec.Command("ip", "addr", "show").CombinedOutput() 112 if err != nil { 113 return errors.Trace(err) 114 } 115 116 subnet, err := detectSubnet(string(output)) 117 if err != nil { 118 return errors.Trace(err) 119 } 120 121 existing, err := ioutil.ReadAll(f) 122 if err != nil { 123 return errors.Trace(err) 124 } 125 126 result := editLXDBridgeFile(string(existing), subnet) 127 _, err = f.Seek(0, 0) 128 if err != nil { 129 return errors.Trace(err) 130 } 131 132 _, err = f.WriteString(result) 133 if err != nil { 134 return errors.Trace(err) 135 } 136 137 /* non-systemd systems don't have the lxd-bridge service, so this always fails */ 138 _ = exec.Command("service", "lxd-bridge", "restart").Run() 139 return exec.Command("service", "lxd", "restart").Run() 140 } 141 142 func detectSubnet(ipAddrOutput string) (string, error) { 143 max := 0 144 145 for _, line := range strings.Split(ipAddrOutput, "\n") { 146 trimmed := strings.TrimSpace(line) 147 if trimmed == "" { 148 continue 149 } 150 151 columns := strings.Split(trimmed, " ") 152 153 if len(columns) < 2 { 154 return "", errors.Trace(fmt.Errorf("invalid ip addr output line %s", line)) 155 } 156 157 if columns[0] != "inet" { 158 continue 159 } 160 161 addr := columns[1] 162 if !strings.HasPrefix(addr, "10.0.") { 163 continue 164 } 165 166 tuples := strings.Split(addr, ".") 167 if len(tuples) < 4 { 168 return "", errors.Trace(fmt.Errorf("invalid ip addr %s", addr)) 169 } 170 171 subnet, err := strconv.Atoi(tuples[2]) 172 if err != nil { 173 return "", errors.Trace(err) 174 } 175 176 if subnet > max { 177 max = subnet 178 } 179 } 180 181 return fmt.Sprintf("%d", max+1), nil 182 } 183 184 func editLXDBridgeFile(input string, subnet string) string { 185 buffer := bytes.Buffer{} 186 187 newValues := map[string]string{ 188 "USE_LXD_BRIDGE": "true", 189 "EXISTING_BRIDGE": "", 190 "LXD_BRIDGE": "lxdbr0", 191 "LXD_IPV4_ADDR": fmt.Sprintf("10.0.%s.1", subnet), 192 "LXD_IPV4_NETMASK": "255.255.255.0", 193 "LXD_IPV4_NETWORK": fmt.Sprintf("10.0.%s.1/24", subnet), 194 "LXD_IPV4_DHCP_RANGE": fmt.Sprintf("10.0.%s.2,10.0.%s.254", subnet, subnet), 195 "LXD_IPV4_DHCP_MAX": "253", 196 "LXD_IPV4_NAT": "true", 197 "LXD_IPV6_PROXY": "false", 198 } 199 found := map[string]bool{} 200 201 for _, line := range strings.Split(input, "\n") { 202 out := line 203 204 for prefix, value := range newValues { 205 if strings.HasPrefix(line, prefix+"=") { 206 out = fmt.Sprintf(`%s="%s"`, prefix, value) 207 found[prefix] = true 208 break 209 } 210 } 211 212 buffer.WriteString(out) 213 buffer.WriteString("\n") 214 } 215 216 for prefix, value := range newValues { 217 if !found[prefix] { 218 buffer.WriteString(prefix) 219 buffer.WriteString("=") 220 buffer.WriteString(value) 221 buffer.WriteString("\n") 222 found[prefix] = true // not necessary but keeps "found" logically consistent 223 } 224 } 225 226 return buffer.String() 227 } 228 229 // ensureDependencies creates a set of install packages using 230 // apt.GetPreparePackages and runs each set of packages through 231 // apt.GetInstall. 232 func ensureDependencies(series string) error { 233 if series == "precise" { 234 return fmt.Errorf("LXD is not supported in precise.") 235 } 236 237 pacman, err := getPackageManager(series) 238 if err != nil { 239 return err 240 } 241 pacconfer, err := getPackagingConfigurer(series) 242 if err != nil { 243 return err 244 } 245 246 for _, pack := range requiredPackages { 247 pkg := pack 248 if config.SeriesRequiresCloudArchiveTools(series) && 249 pacconfer.IsCloudArchivePackage(pack) { 250 pkg = strings.Join(pacconfer.ApplyCloudArchiveTarget(pack), " ") 251 } 252 253 if config.RequiresBackports(series, pack) { 254 pkg = fmt.Sprintf("--target-release %s-backports %s", series, pkg) 255 } 256 257 if err := pacman.Install(pkg); err != nil { 258 return err 259 } 260 } 261 262 if series >= "xenial" { 263 for _, pack := range xenialPackages { 264 pacman.Install(fmt.Sprintf("--no-install-recommends %s", pack)) 265 } 266 } 267 268 return err 269 }