github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/worker/networker/networker.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package networker 5 6 import ( 7 "fmt" 8 "io/ioutil" 9 "net" 10 "os" 11 "path/filepath" 12 "sort" 13 "strings" 14 15 "github.com/juju/loggo" 16 "github.com/juju/names" 17 "launchpad.net/tomb" 18 19 "github.com/juju/juju/agent" 20 apinetworker "github.com/juju/juju/api/networker" 21 apiwatcher "github.com/juju/juju/api/watcher" 22 "github.com/juju/juju/network" 23 "github.com/juju/juju/state/watcher" 24 "github.com/juju/juju/worker" 25 ) 26 27 var logger = loggo.GetLogger("juju.networker") 28 29 // DefaultConfigBaseDir is the usual root directory where the 30 // network configuration is kept. 31 const DefaultConfigBaseDir = "/etc/network" 32 33 // Networker configures network interfaces on the machine, as needed. 34 type Networker struct { 35 tomb tomb.Tomb 36 37 st apinetworker.State 38 tag names.MachineTag 39 40 // isVLANSupportInstalled is set to true when the VLAN kernel 41 // module 8021q was installed. 42 isVLANSupportInstalled bool 43 44 // intrusiveMode determines whether to write any changes 45 // to the network config (intrusive mode) or not (non-intrusive mode). 46 intrusiveMode bool 47 48 // configBasePath is the root directory where the networking 49 // config is kept (usually /etc/network). 50 configBaseDir string 51 52 // primaryInterface is the name of the primary network interface 53 // on the machine (usually "eth0"). 54 primaryInterface string 55 56 // loopbackInterface is the name of the loopback interface on the 57 // machine (usually "lo"). 58 loopbackInterface string 59 60 // configFiles holds all loaded network config files, using the 61 // full file path as key. 62 configFiles map[string]*configFile 63 64 // interfaceInfo holds the info for all network interfaces 65 // discovered via the API, using the interface name as key. 66 interfaceInfo map[string]network.InterfaceInfo 67 68 // interfaces holds all known network interfaces on the machine, 69 // using their name as key. 70 interfaces map[string]net.Interface 71 72 // commands holds generated scripts (e.g. for bringing interfaces 73 // up or down, etc.) which were not executed yet. 74 commands []string 75 } 76 77 var _ worker.Worker = (*Networker)(nil) 78 79 // NewNetworker returns a Worker that handles machine networking 80 // configuration. If there is no <configBasePath>/interfaces file, an 81 // error is returned. 82 func NewNetworker( 83 st apinetworker.State, 84 agentConfig agent.Config, 85 intrusiveMode bool, 86 configBaseDir string, 87 ) (*Networker, error) { 88 tag, ok := agentConfig.Tag().(names.MachineTag) 89 if !ok { 90 // This should never happen, as there is a check for it in the 91 // machine agent. 92 return nil, fmt.Errorf("expected names.MachineTag, got %T", agentConfig.Tag()) 93 } 94 nw := &Networker{ 95 st: st, 96 tag: tag, 97 intrusiveMode: intrusiveMode, 98 configBaseDir: configBaseDir, 99 configFiles: make(map[string]*configFile), 100 interfaceInfo: make(map[string]network.InterfaceInfo), 101 interfaces: make(map[string]net.Interface), 102 } 103 go func() { 104 defer nw.tomb.Done() 105 nw.tomb.Kill(nw.loop()) 106 }() 107 return nw, nil 108 } 109 110 // Kill implements Worker.Kill(). 111 func (nw *Networker) Kill() { 112 nw.tomb.Kill(nil) 113 } 114 115 // Wait implements Worker.Wait(). 116 func (nw *Networker) Wait() error { 117 return nw.tomb.Wait() 118 } 119 120 // ConfigBaseDir returns the root directory where the networking config is 121 // kept. Usually, this is /etc/network. 122 func (nw *Networker) ConfigBaseDir() string { 123 return nw.configBaseDir 124 } 125 126 // ConfigSubDir returns the directory where individual config files 127 // for each network interface are kept. Usually, this is 128 // /etc/network/interfaces.d. 129 func (nw *Networker) ConfigSubDir() string { 130 return filepath.Join(nw.ConfigBaseDir(), "interfaces.d") 131 } 132 133 // ConfigFile returns the full path to the network config file for the 134 // given interface. If interfaceName is "", the path to the main 135 // network config file is returned (usually, this is 136 // /etc/network/interfaces). 137 func (nw *Networker) ConfigFile(interfaceName string) string { 138 if interfaceName == "" { 139 return filepath.Join(nw.ConfigBaseDir(), "interfaces") 140 } 141 return filepath.Join(nw.ConfigSubDir(), interfaceName+".cfg") 142 } 143 144 // IntrusiveMode returns whether the networker is changing networking 145 // configuration files (intrusive mode) or won't modify them on the 146 // machine (non-intrusive mode). 147 func (nw *Networker) IntrusiveMode() bool { 148 return nw.intrusiveMode 149 } 150 151 // IsPrimaryInterfaceOrLoopback returns whether the given 152 // interfaceName matches the primary or loopback network interface. 153 func (nw *Networker) IsPrimaryInterfaceOrLoopback(interfaceName string) bool { 154 return interfaceName == nw.primaryInterface || 155 interfaceName == nw.loopbackInterface 156 } 157 158 // loop is the worker's main loop. 159 func (nw *Networker) loop() error { 160 // TODO(dimitern) Networker is disabled until we have time to fix 161 // it so it's not overwriting /etc/network/interfaces 162 // indiscriminately for containers and possibly other cases. 163 logger.Infof("networker is disabled - not starting on machine %q", nw.tag) 164 return nil 165 166 logger.Debugf("starting on machine %q", nw.tag) 167 if !nw.IntrusiveMode() { 168 logger.Warningf("running in non-intrusive mode - no commands or changes to network config will be done") 169 } 170 w, err := nw.init() 171 if err != nil { 172 if w != nil { 173 // We don't bother to propagate an error, because we 174 // already have an error 175 w.Stop() 176 } 177 return err 178 } 179 defer watcher.Stop(w, &nw.tomb) 180 logger.Debugf("initialized and started watching") 181 for { 182 select { 183 case <-nw.tomb.Dying(): 184 logger.Debugf("shutting down") 185 return tomb.ErrDying 186 case _, ok := <-w.Changes(): 187 logger.Debugf("got change notification") 188 if !ok { 189 return watcher.EnsureErr(w) 190 } 191 if err := nw.handle(); err != nil { 192 return err 193 } 194 } 195 } 196 } 197 198 // init initializes the worker and starts a watcher for monitoring 199 // network interface changes. 200 func (nw *Networker) init() (apiwatcher.NotifyWatcher, error) { 201 // Discover all interfaces on the machine and populate internal 202 // maps, reading existing config files as well, and fetch the 203 // network info from the API.. 204 if err := nw.updateInterfaces(); err != nil { 205 return nil, err 206 } 207 208 // Apply changes (i.e. write managed config files and load the 209 // VLAN module if needed). 210 if err := nw.applyAndExecute(); err != nil { 211 return nil, err 212 } 213 return nw.st.WatchInterfaces(nw.tag) 214 } 215 216 // handle processes changes to network interfaces in state. 217 func (nw *Networker) handle() error { 218 // Update interfaces and config files as needed. 219 if err := nw.updateInterfaces(); err != nil { 220 return err 221 } 222 223 // Bring down disabled interfaces. 224 nw.prepareDownCommands() 225 226 // Bring up configured interfaces. 227 nw.prepareUpCommands() 228 229 // Apply any needed changes to config and run generated commands. 230 if err := nw.applyAndExecute(); err != nil { 231 return err 232 } 233 return nil 234 } 235 236 // updateInterfaces discovers all known network interfaces on the 237 // machine and caches the result internally. 238 func (nw *Networker) updateInterfaces() error { 239 interfaces, err := Interfaces() 240 if err != nil { 241 return fmt.Errorf("cannot retrieve network interfaces: %v", err) 242 } 243 logger.Debugf("updated machine network interfaces info") 244 245 // Read the main config file first. 246 mainConfig := nw.ConfigFile("") 247 if _, ok := nw.configFiles[mainConfig]; !ok { 248 if err := nw.readConfig("", mainConfig); err != nil { 249 return err 250 } 251 } 252 253 // Populate the internal maps for interfaces and configFiles and 254 // find the primary interface. 255 nw.interfaces = make(map[string]net.Interface) 256 for _, iface := range interfaces { 257 logger.Debugf( 258 "found interface %q with index %d and flags %s", 259 iface.Name, 260 iface.Index, 261 iface.Flags.String(), 262 ) 263 nw.interfaces[iface.Name] = iface 264 fullPath := nw.ConfigFile(iface.Name) 265 if _, ok := nw.configFiles[fullPath]; !ok { 266 if err := nw.readConfig(iface.Name, fullPath); err != nil { 267 return err 268 } 269 } 270 if iface.Flags&net.FlagLoopback != 0 && nw.loopbackInterface == "" { 271 nw.loopbackInterface = iface.Name 272 logger.Debugf("loopback interface is %q", iface.Name) 273 continue 274 } 275 276 // The first enabled, non-loopback interface should be the 277 // primary. 278 if iface.Flags&net.FlagUp != 0 && nw.primaryInterface == "" { 279 nw.primaryInterface = iface.Name 280 logger.Debugf("primary interface is %q", iface.Name) 281 } 282 } 283 284 // Fetch network info from the API and generate managed config as 285 // needed. 286 if err := nw.fetchInterfaceInfo(); err != nil { 287 return err 288 } 289 290 return nil 291 } 292 293 // fetchInterfaceInfo makes an API call to get all known 294 // *network.InterfaceInfo entries for each interface on the machine. 295 // If there are any VLAN interfaces to setup, it also generates 296 // commands to load the kernal 8021q VLAN module, if not already 297 // loaded and when not running inside an LXC container. 298 func (nw *Networker) fetchInterfaceInfo() error { 299 interfaceInfo, err := nw.st.MachineNetworkConfig(nw.tag) 300 if err != nil { 301 logger.Errorf("failed to retrieve network info: %v", err) 302 return err 303 } 304 logger.Debugf("fetched known network info from state") 305 306 haveVLANs := false 307 nw.interfaceInfo = make(map[string]network.InterfaceInfo) 308 for _, info := range interfaceInfo { 309 actualName := info.ActualInterfaceName() 310 logger.Debugf( 311 "have network info for %q: MAC=%q, disabled: %v, vlan-tag: %d", 312 actualName, 313 info.MACAddress, 314 info.Disabled, 315 info.VLANTag, 316 ) 317 if info.IsVLAN() { 318 haveVLANs = true 319 } 320 nw.interfaceInfo[actualName] = info 321 fullPath := nw.ConfigFile(actualName) 322 cfgFile, ok := nw.configFiles[fullPath] 323 if !ok { 324 // We have info for an interface which is was not 325 // discovered on the machine, so we need to add it 326 // list of managed interfaces. 327 logger.Debugf("no config for %q but network info exists; will generate", actualName) 328 if err := nw.readConfig(actualName, fullPath); err != nil { 329 return err 330 } 331 cfgFile = nw.configFiles[fullPath] 332 } 333 cfgFile.interfaceInfo = info 334 335 // Make sure we generate managed config, in case it changed. 336 cfgFile.UpdateData(cfgFile.RenderManaged()) 337 338 nw.configFiles[fullPath] = cfgFile 339 } 340 341 // Generate managed main config file. 342 cfgFile := nw.configFiles[nw.ConfigFile("")] 343 cfgFile.UpdateData(RenderMainConfig(nw.ConfigSubDir())) 344 345 if !haveVLANs { 346 return nil 347 } 348 349 if !nw.isVLANSupportInstalled { 350 if nw.isRunningInLXC() { 351 msg := "running inside LXC: " 352 msg += "cannot load the required 8021q kernel module for VLAN support; " 353 msg += "please ensure it is loaded on the host" 354 logger.Warningf(msg) 355 return nil 356 } 357 nw.prepareVLANModule() 358 nw.isVLANSupportInstalled = true 359 logger.Debugf("need to load VLAN 8021q kernel module") 360 } 361 return nil 362 } 363 364 // applyAndExecute updates or removes config files as needed, and runs 365 // all accumulated pending commands, and if all commands succeed, 366 // resets the commands slice. If the networker is running in "safe 367 // mode" nothing is changed. 368 func (nw *Networker) applyAndExecute() error { 369 if !nw.IntrusiveMode() { 370 logger.Warningf("running in non-intrusive mode - no changes made") 371 return nil 372 } 373 374 // Create the config subdir, if needed. 375 configSubDir := nw.ConfigSubDir() 376 if _, err := os.Stat(configSubDir); err != nil { 377 if err := os.Mkdir(configSubDir, 0755); err != nil { 378 logger.Errorf("failed to create directory %q: %v", configSubDir, err) 379 return err 380 } 381 } 382 383 // Read config subdir contents and remove any non-managed files. 384 files, err := ioutil.ReadDir(configSubDir) 385 if err != nil { 386 logger.Errorf("failed to read directory %q: %v", configSubDir, err) 387 return err 388 } 389 for _, info := range files { 390 if !info.Mode().IsRegular() { 391 // Skip special files and directories. 392 continue 393 } 394 fullPath := filepath.Join(configSubDir, info.Name()) 395 if _, ok := nw.configFiles[fullPath]; !ok { 396 if err := os.Remove(fullPath); err != nil { 397 logger.Errorf("failed to remove non-managed config %q: %v", fullPath, err) 398 return err 399 } 400 } 401 } 402 403 // Apply all changes needed for each config file. 404 logger.Debugf("applying changes to config files as needed") 405 for _, cfgFile := range nw.configFiles { 406 if err := cfgFile.Apply(); err != nil { 407 return err 408 } 409 } 410 if len(nw.commands) > 0 { 411 logger.Debugf("executing commands %v", nw.commands) 412 if err := ExecuteCommands(nw.commands); err != nil { 413 return err 414 } 415 nw.commands = []string{} 416 } 417 return nil 418 } 419 420 // isRunningInLXC returns whether the worker is running inside a LXC 421 // container or not. When running in LXC containers, we should not 422 // attempt to modprobe anything, as it's not possible and leads to 423 // run-time errors. See http://pad.lv/1353443. 424 func (nw *Networker) isRunningInLXC() bool { 425 // In case of nested containers, we need to check 426 // the last nesting level to ensure it's not LXC. 427 machineId := strings.ToLower(nw.tag.Id()) 428 parts := strings.Split(machineId, "/") 429 return len(parts) > 2 && parts[len(parts)-2] == "lxc" 430 } 431 432 // prepareVLANModule generates the necessary commands to load the VLAN 433 // kernel module 8021q. 434 func (nw *Networker) prepareVLANModule() { 435 commands := []string{ 436 `dpkg-query -s vlan || apt-get --option Dpkg::Options::=--force-confold --assume-yes install vlan`, 437 `lsmod | grep -q 8021q || modprobe 8021q`, 438 `grep -q 8021q /etc/modules || echo 8021q >> /etc/modules`, 439 `vconfig set_name_type DEV_PLUS_VID_NO_PAD`, 440 } 441 nw.commands = append(nw.commands, commands...) 442 } 443 444 // prepareUpCommands generates ifup commands to bring the needed 445 // interfaces up. 446 func (nw *Networker) prepareUpCommands() { 447 bringUp := []string{} 448 logger.Debugf("preparing to bring interfaces up") 449 for name, info := range nw.interfaceInfo { 450 if nw.IsPrimaryInterfaceOrLoopback(name) { 451 logger.Debugf("skipping primary or loopback interface %q", name) 452 continue 453 } 454 fullPath := nw.ConfigFile(name) 455 cfgFile := nw.configFiles[fullPath] 456 if info.Disabled && !cfgFile.IsPendingRemoval() { 457 cfgFile.MarkForRemoval() 458 logger.Debugf("disabled %q marked for removal", name) 459 } else if !info.Disabled && !InterfaceIsUp(name) { 460 bringUp = append(bringUp, name) 461 logger.Debugf("will bring %q up", name) 462 } 463 } 464 465 // Sort interfaces to ensure raw interfaces go before their 466 // virtual dependents (i.e. VLANs) 467 sort.Sort(sort.StringSlice(bringUp)) 468 for _, name := range bringUp { 469 nw.commands = append(nw.commands, "ifup "+name) 470 } 471 } 472 473 // prepareUpCommands generates ifdown commands to bring the needed 474 // interfaces down. 475 func (nw *Networker) prepareDownCommands() { 476 bringDown := []string{} 477 logger.Debugf("preparing to bring interfaces down") 478 for _, cfgFile := range nw.configFiles { 479 name := cfgFile.InterfaceName() 480 if name == "" { 481 // Skip the main config file. 482 continue 483 } 484 if nw.IsPrimaryInterfaceOrLoopback(name) { 485 logger.Debugf("skipping primary or loopback interface %q", name) 486 continue 487 } 488 info := cfgFile.InterfaceInfo() 489 if info.Disabled { 490 if InterfaceIsUp(name) { 491 bringDown = append(bringDown, name) 492 logger.Debugf("will bring %q down", name) 493 } 494 if !cfgFile.IsPendingRemoval() { 495 cfgFile.MarkForRemoval() 496 logger.Debugf("diabled %q marked for removal", name) 497 } 498 } 499 } 500 501 // Sort interfaces to ensure raw interfaces go after their virtual 502 // dependents (i.e. VLANs) 503 sort.Sort(sort.Reverse(sort.StringSlice(bringDown))) 504 for _, name := range bringDown { 505 nw.commands = append(nw.commands, "ifdown "+name) 506 } 507 } 508 509 // readConfig populates the configFiles map with an entry for the 510 // given interface and filename, and tries to read the file. If the 511 // config file is missing, that's OK, as it will be generated later 512 // and it's not considered an error. If configFiles already contains 513 // an entry for fileName, nothing is changed. 514 func (nw *Networker) readConfig(interfaceName, fileName string) error { 515 cfgFile := &configFile{ 516 interfaceName: interfaceName, 517 fileName: fileName, 518 } 519 if err := cfgFile.ReadData(); !os.IsNotExist(err) && err != nil { 520 return err 521 } 522 if _, ok := nw.configFiles[fileName]; !ok { 523 nw.configFiles[fileName] = cfgFile 524 } 525 return nil 526 }