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  }