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  }