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