gopkg.in/dedis/onet.v2@v2.0.0-20181115163211-c8f3724038a7/simul/platform/mininet.go (about)

     1  // Mininet is the platform-implementation that uses the MiniNet-framework
     2  // set in place by Marc-Andre Luthi from EPFL. It is based on MiniNet,
     3  // as it uses a lot of similar routines
     4  
     5  package platform
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"net"
    12  	"os"
    13  	"os/exec"
    14  	"path"
    15  	"path/filepath"
    16  	"runtime"
    17  	"sort"
    18  	"strings"
    19  	"time"
    20  
    21  	"github.com/BurntSushi/toml"
    22  	"gopkg.in/dedis/onet.v2"
    23  	"gopkg.in/dedis/onet.v2/app"
    24  	"gopkg.in/dedis/onet.v2/log"
    25  )
    26  
    27  // MiniNet represents all the configuration that is necessary to run a simulation
    28  // on remote hosts running Mininet.
    29  type MiniNet struct {
    30  	// *** Mininet-related configuration
    31  	// The login on the platform
    32  	Login string
    33  	// The outside host on the platform
    34  	External string
    35  	// Directory we start - the simulation-directory of the service/protocol
    36  	wd string
    37  	// Directory holding the simulation main-file
    38  	simulDir string
    39  	// Directory storing the additional files
    40  	mininetDir string
    41  	// Directory for building
    42  	buildDir string
    43  	// Directory for deploying
    44  	deployDir string
    45  	// IPs of all hosts
    46  	HostIPs []string
    47  	// Channel to communicate stopping of experiment
    48  	sshMininet chan string
    49  	// Whether the simulation is started
    50  	started bool
    51  	// RC-configuration
    52  	config string
    53  
    54  	// ProxyAddress : the proxy will redirect every traffic it
    55  	// receives to this address
    56  	ProxyAddress string
    57  	// Port number of the monitor and the proxy
    58  	MonitorPort int
    59  
    60  	// Simulation to be run
    61  	Simulation string
    62  	// Number of servers to be used
    63  	Servers int
    64  	// Number of machines
    65  	Hosts int
    66  	// Debugging-level: 0 is none - 5 is everything
    67  	Debug int
    68  	// Whether to show time in debugging messages
    69  	DebugTime bool
    70  	// Whether to show color debugging-messages
    71  	DebugColor bool
    72  	// Whether to pad debugging-messages
    73  	DebugPadding bool
    74  	// The number of seconds to wait for closing the connection
    75  	RunWait string
    76  	// Delay in ms of the network connection
    77  	Delay int
    78  	// Bandwidth in Mbps of the network connection
    79  	Bandwidth int
    80  	// Suite used for the simulation
    81  	Suite string
    82  	// PreScript defines a script that is run before the simulation
    83  	PreScript string
    84  	// Tags to use when compiling
    85  	Tags string
    86  }
    87  
    88  // Configure implements the Platform-interface. It is called once to set up
    89  // the necessary internal variables.
    90  func (m *MiniNet) Configure(pc *Config) {
    91  	// Directory setup - would also be possible in /tmp
    92  	m.wd, _ = os.Getwd()
    93  	m.simulDir = m.wd
    94  	_, filename, _, ok := runtime.Caller(0)
    95  	if !ok {
    96  		log.Fatal("Couldn't get my path")
    97  	}
    98  	var err error
    99  	m.Suite = pc.Suite
   100  	m.mininetDir, err = filepath.Abs(path.Dir(filename))
   101  	log.ErrFatal(err)
   102  	m.mininetDir = filepath.Join(m.mininetDir, "mininet")
   103  	m.buildDir = m.wd + "/build"
   104  	m.deployDir = m.wd + "/deploy"
   105  	m.Login = "root"
   106  	log.ErrFatal(m.parseServers())
   107  	m.External = m.HostIPs[0]
   108  	m.ProxyAddress = "localhost"
   109  	m.MonitorPort = pc.MonitorPort
   110  	m.Debug = pc.Debug
   111  	m.DebugTime = log.ShowTime()
   112  	m.DebugColor = log.UseColors()
   113  	m.DebugPadding = log.Padding()
   114  
   115  	m.Delay = 0
   116  	m.Bandwidth = 1000
   117  
   118  	// Clean the build- and deploy-dir, then (re-)create them
   119  	for _, d := range []string{m.buildDir, m.deployDir} {
   120  		os.RemoveAll(d)
   121  		log.ErrFatal(os.Mkdir(d, 0700))
   122  	}
   123  	onet.WriteTomlConfig(*m, "mininet.toml", m.buildDir)
   124  
   125  	if m.Simulation == "" {
   126  		log.Fatal("No simulation defined in runconfig")
   127  	}
   128  
   129  	// Setting up channel
   130  	m.sshMininet = make(chan string)
   131  }
   132  
   133  // Build implements the Platform interface and is called once per runlevel-file.
   134  // build is the name of the app to build
   135  // empty = all otherwise build specific package
   136  func (m *MiniNet) Build(build string, arg ...string) error {
   137  	log.Lvl1("Building for", m.Login, m.External, build, "simulDir=", m.simulDir)
   138  	start := time.Now()
   139  
   140  	// Start with a clean build-directory
   141  	processor := "amd64"
   142  	system := "linux"
   143  	srcRel, err := filepath.Rel(m.wd, m.simulDir)
   144  	if err != nil {
   145  		return err
   146  	}
   147  
   148  	log.Lvl3("Relative-path is", srcRel, ". Will build into", m.buildDir)
   149  	var tags []string
   150  	if m.Tags != "" {
   151  		tags = append([]string{"-tags"}, strings.Split(m.Tags, " ")...)
   152  	}
   153  	out, err := Build("./"+srcRel, m.buildDir+"/conode",
   154  		processor, system, append(arg, tags...)...)
   155  	if err != nil {
   156  		return fmt.Errorf(err.Error() + " " + out)
   157  	}
   158  
   159  	log.Lvl1("Build is finished after", time.Since(start))
   160  	return nil
   161  }
   162  
   163  // Cleanup kills all eventually remaining processes from the last Deploy-run
   164  func (m *MiniNet) Cleanup() error {
   165  	// Cleanup eventual ssh from the proxy-forwarding to the logserver
   166  	err := exec.Command("pkill", "-9", "-f", "ssh -nNTf").Run()
   167  	if err != nil {
   168  		log.Lvl3("Error stopping ssh:", err)
   169  	}
   170  
   171  	// SSH to the MiniNet-server and end all running users-processes
   172  	log.Lvl3("Going to stop everything")
   173  	err = m.parseServers()
   174  	if err != nil {
   175  		return err
   176  	}
   177  	for _, h := range m.HostIPs {
   178  		log.Lvl3("Cleaning up server", h)
   179  		_, err = SSHRun(m.Login, h, "pkill -9 -f start.py; killall sshd; pkill -f 'sshd[^ ]'; mn -c")
   180  		if err != nil {
   181  			log.Lvl2("Error while cleaning up:", err)
   182  		}
   183  	}
   184  	return nil
   185  }
   186  
   187  // Deploy creates the appropriate configuration-files and copies everything to the
   188  // MiniNet-installation.
   189  func (m *MiniNet) Deploy(rc *RunConfig) error {
   190  	log.Lvl2("Localhost: Deploying and writing config-files")
   191  	sim, err := onet.NewSimulation(m.Simulation, string(rc.Toml()))
   192  	if err != nil {
   193  		return err
   194  	}
   195  
   196  	// Check for PreScript and copy it to the deploy-dir
   197  	m.PreScript = rc.Get("PreScript")
   198  	if m.PreScript != "" {
   199  		_, err := os.Stat(m.PreScript)
   200  		if !os.IsNotExist(err) {
   201  			if err := app.Copy(m.deployDir, m.PreScript); err != nil {
   202  				return err
   203  			}
   204  		}
   205  	}
   206  
   207  	// Initialize the mininet-struct with our current structure (for debug-levels
   208  	// and such), then read in the app-configuration to overwrite eventual
   209  	// 'Servers', 'Hosts', '' or other fields
   210  	mininet := *m
   211  	mininetConfig := m.deployDir + "/mininet.toml"
   212  	_, err = toml.Decode(string(rc.Toml()), &mininet)
   213  	if err != nil {
   214  		return err
   215  	}
   216  	log.Lvl3("Writing the config file :", mininet)
   217  	onet.WriteTomlConfig(mininet, mininetConfig, m.deployDir)
   218  
   219  	log.Lvl3("Creating hosts")
   220  	if err = m.parseServers(); err != nil {
   221  		return err
   222  	}
   223  	hosts, list, err := m.getHostList(rc)
   224  	if err != nil {
   225  		return err
   226  	}
   227  	log.Lvl3("Hosts are:", hosts)
   228  	log.Lvl3("List is:", list)
   229  	err = ioutil.WriteFile(m.deployDir+"/list", []byte(list), 0660)
   230  	if err != nil {
   231  		return err
   232  	}
   233  	simulConfig, err := sim.Setup(m.deployDir, hosts)
   234  	if err != nil {
   235  		return err
   236  	}
   237  	simulConfig.Config = string(rc.Toml())
   238  	m.config = simulConfig.Config
   239  	log.Lvl3("Saving configuration")
   240  	simulConfig.Save(m.deployDir)
   241  
   242  	// Verify the installation is correct
   243  	gw := m.HostIPs[0]
   244  	log.Lvl2("Verifying configuration on", gw)
   245  	out, err := exec.Command("ssh", "root@"+gw, "which mn").Output()
   246  	if err != nil || !strings.HasSuffix(string(out), "mn\n") {
   247  		log.Error("While trying to connect to", gw, err)
   248  		log.Fatal("Please verify installation of mininet or run\n" +
   249  			"./platforms/mininet/setup_iccluster.sh")
   250  	}
   251  
   252  	// Copy our script
   253  	err = app.Copy(m.deployDir, m.mininetDir+"/start.py")
   254  	if err != nil {
   255  		log.Error(err)
   256  		return err
   257  	}
   258  
   259  	// Copy conode-binary
   260  	err = app.Copy(m.deployDir, m.buildDir+"/conode")
   261  	if err != nil {
   262  		log.Error(err)
   263  		return err
   264  	}
   265  
   266  	// Copy everything over to MiniNet
   267  	log.Lvl1("Copying over to", m.Login, "@", m.External)
   268  	err = Rsync(m.Login, m.External, m.deployDir+"/", "mininet_run/")
   269  	if err != nil {
   270  		log.Fatal(err)
   271  	}
   272  	log.Lvl2("Done copying")
   273  
   274  	return nil
   275  }
   276  
   277  // Start connects to the first of the remote servers to start the simulation.
   278  func (m *MiniNet) Start(args ...string) error {
   279  	// setup port forwarding for viewing log server
   280  	m.started = true
   281  	// Remote tunneling : the sink port is used both for the sink and for the
   282  	// proxy => the proxy redirects packets to the same port the sink is
   283  	// listening.
   284  	// -n = stdout == /Dev/null, -N => no command stream, -T => no tty
   285  	var exCmd *exec.Cmd
   286  	redirection := fmt.Sprintf("*:%d:%s:%d", m.MonitorPort, m.ProxyAddress, m.MonitorPort)
   287  	login := fmt.Sprintf("%s@%s", m.Login, m.External)
   288  	cmd := []string{"-nNTf", "-o", "StrictHostKeyChecking=no", "-o", "ExitOnForwardFailure=yes", "-R",
   289  		redirection, login}
   290  	exCmd = exec.Command("ssh", cmd...)
   291  	if err := exCmd.Start(); err != nil {
   292  		log.Fatal("Failed to start the ssh port forwarding:", err)
   293  	}
   294  	if err := exCmd.Wait(); err != nil {
   295  		log.Fatal("ssh port forwarding exited in failure:", err)
   296  	}
   297  	go func() {
   298  		config := strings.Split(m.config, "\n")
   299  		sort.Strings(config)
   300  		err := SSHRunStdout(m.Login, m.External, "cd mininet_run; ./start.py list go")
   301  		if err != nil {
   302  			log.Lvl3(err)
   303  		}
   304  		m.sshMininet <- "finished"
   305  	}()
   306  
   307  	return nil
   308  }
   309  
   310  // Wait blocks on the channel till the main-process finishes.
   311  func (m *MiniNet) Wait() error {
   312  	wait, err := time.ParseDuration(m.RunWait)
   313  	if wait == 0 || err != nil {
   314  		wait = 600 * time.Second
   315  		err = nil
   316  	}
   317  	if m.started {
   318  		log.Lvl3("Simulation is started")
   319  		select {
   320  		case msg := <-m.sshMininet:
   321  			if msg == "finished" {
   322  				log.Lvl3("Received finished-message, not killing users")
   323  				return nil
   324  			}
   325  			log.Lvl1("Received out-of-line message", msg)
   326  		case <-time.After(wait):
   327  			log.Lvl1("Quitting after waiting", wait)
   328  			m.started = false
   329  		}
   330  		m.started = false
   331  	}
   332  	return nil
   333  }
   334  
   335  // Returns the servers to use for mininet.
   336  func (m *MiniNet) parseServers() error {
   337  	slName := path.Join(m.wd, "server_list")
   338  	hosts, err := ioutil.ReadFile(slName)
   339  	if err != nil {
   340  		return fmt.Errorf("Couldn't find %s - you can produce one with\n"+
   341  			"\t\t%[2]s/setup_servers.sh\n\t\tor\n\t\t%[2]s/setup_iccluster.sh", slName, m.mininetDir)
   342  	}
   343  	m.HostIPs = []string{}
   344  	for _, hostRaw := range strings.Split(string(hosts), "\n") {
   345  		h := strings.Replace(hostRaw, " ", "", -1)
   346  		if len(h) > 0 {
   347  			ips, err := net.LookupIP(h)
   348  			if err != nil {
   349  				if err2 := CheckOutOfFileDescriptors(); err2 != nil {
   350  					return errors.New("couldn't look up hostname: " + err2.Error())
   351  				}
   352  				return errors.New("error while looking up hostname: " + err.Error())
   353  			}
   354  			log.Lvl3("Found IP for", h, ":", ips[0])
   355  			m.HostIPs = append(m.HostIPs, ips[0].String())
   356  		}
   357  	}
   358  	log.Lvl3("Nodes are:", m.HostIPs)
   359  	return nil
   360  }
   361  
   362  // getHostList prepares the mapping from physical hosts to mininet-hosts. Each
   363  // physical host holds a 10.x/16-network with the .0.1 being the gateway and
   364  // .0.2 the first usable conode.
   365  //
   366  // hosts holds all addresses for all conodes, attributed in a round-robin fashion
   367  // over all mininet-addresses.
   368  //
   369  // If the number of servers in MiniNet.HostsIP is bigger than MiniSet.Servers,
   370  // only the first MiniNet.Servers are taken into account.
   371  //
   372  // list is used by platform/mininet/start.py and has the following format:
   373  // SimulationName BandwidthMbps DelayMS
   374  // physicalIP1 MininetNet1/16 NumberConodes1
   375  // physicalIP2 MininetNet2/16 NumberConodes2
   376  func (m *MiniNet) getHostList(rc *RunConfig) (hosts []string, list string, err error) {
   377  	hosts = []string{}
   378  	list = ""
   379  	physicalServers := len(m.HostIPs)
   380  	nbrServers, err := rc.GetInt("Servers")
   381  	if err != nil {
   382  		return
   383  	}
   384  	if nbrServers > physicalServers {
   385  		log.Warn(nbrServers, "servers requested, but only", physicalServers,
   386  			"available - proceeding anyway.")
   387  		nbrServers = physicalServers
   388  	}
   389  	nets := make([]*net.IPNet, nbrServers)
   390  	ips := make([]net.IP, nbrServers)
   391  
   392  	// Create all mininet-networks
   393  	for n := range nets {
   394  		ips[n], nets[n], err = net.ParseCIDR(fmt.Sprintf("10.%d.0.0/16", n+1))
   395  		if err != nil {
   396  			return
   397  		}
   398  		// We'll have to start with 10.1.0.2 as the first host.
   399  		// So we set the LSByte to 1 which will be increased later.
   400  		ips[n][len(ips[n])-1] = byte(1)
   401  	}
   402  	hosts = []string{}
   403  	nbrHosts, err := rc.GetInt("Hosts")
   404  	if err != nil {
   405  		return
   406  	}
   407  
   408  	// Map all required conodes to Mininet-hosts
   409  	for i := 0; i < nbrHosts; i++ {
   410  		ip := ips[i%nbrServers]
   411  		for j := len(ip) - 1; j >= 0; j-- {
   412  			ip[j]++
   413  			if ip[j] > 0 {
   414  				break
   415  			}
   416  		}
   417  		ips[i%nbrServers] = ip
   418  		hosts = append(hosts, ip.String())
   419  	}
   420  
   421  	bandwidth := m.Bandwidth
   422  	if bw, err := rc.GetInt("Bandwidth"); err == nil {
   423  		bandwidth = bw
   424  	}
   425  	delay := m.Delay
   426  	if d, err := rc.GetInt("Delay"); err == nil {
   427  		delay = d
   428  	}
   429  	list = fmt.Sprintf("%s %s %d %d\n%d %t %t %t\n%s\n", m.Simulation, m.Suite, bandwidth, delay,
   430  		m.Debug, m.DebugTime, m.DebugColor, m.DebugPadding, m.PreScript)
   431  
   432  	// Add descriptions for `start.py` to know which mininet-network it has to
   433  	// run on what physical server with how many hosts.
   434  	for i, s := range nets {
   435  		if i >= nbrHosts {
   436  			break
   437  		}
   438  		// Magical formula to get how many hosts run on each
   439  		// physical server if we distribute them evenly, starting
   440  		// from the first server.
   441  		h := (nbrHosts + nbrServers - 1 - i) / nbrServers
   442  		list += fmt.Sprintf("%s %s %d\n",
   443  			m.HostIPs[i], s.String(), h)
   444  	}
   445  	return
   446  }