go.dedis.ch/onet/v3@v3.2.11-0.20210930124529-e36530bca7ef/simul/platform/localhost.go (about)

     1  package platform
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"os/exec"
     7  	"runtime"
     8  	"strconv"
     9  	"sync"
    10  
    11  	"strings"
    12  
    13  	"time"
    14  
    15  	"go.dedis.ch/onet/v3"
    16  	"go.dedis.ch/onet/v3/app"
    17  	"go.dedis.ch/onet/v3/log"
    18  	"go.dedis.ch/onet/v3/simul/monitor"
    19  	"golang.org/x/xerrors"
    20  )
    21  
    22  // Localhost is responsible for launching the app with the specified number of nodes
    23  // directly on your machine, for local testing.
    24  
    25  // Localhost is the platform for launching thee apps locally
    26  type Localhost struct {
    27  	// Need mutex because build.go has a global variable that
    28  	// is used for multiple experiments
    29  	sync.Mutex
    30  
    31  	// Address of the logger (can be local or not)
    32  	logger string
    33  
    34  	// The simulation to run
    35  	Simulation string
    36  
    37  	// Where is the Localhost package located
    38  	localDir string
    39  	// Where to build the executables +
    40  	// where to read the config file
    41  	// it will be assembled like LocalDir/RunDir
    42  	runDir string
    43  
    44  	// Debug level 1 - 5
    45  	debug int
    46  
    47  	// The number of servers
    48  	servers int
    49  	// All addresses - we use 'localhost1'..'localhostn' to
    50  	// identify the different cothorities, but when opening the
    51  	// ports they will be converted to normal 'localhost'
    52  	addresses []string
    53  
    54  	// Whether we started a simulation
    55  	running bool
    56  	// WaitGroup for running processes
    57  	wgRun sync.WaitGroup
    58  
    59  	// errors go here:
    60  	errChan chan error
    61  
    62  	// Listening monitor port
    63  	monitorPort int
    64  
    65  	// Suite used for the simulation
    66  	Suite string
    67  
    68  	// SimulationConfig holds all things necessary for the run
    69  	sc *onet.SimulationConfig
    70  
    71  	// PreScript is run before the simulation is started
    72  	PreScript string
    73  
    74  	// RunWait for long simulations
    75  	RunWait string
    76  }
    77  
    78  // Configure various internal variables
    79  func (d *Localhost) Configure(pc *Config) {
    80  	d.Lock()
    81  	defer d.Unlock()
    82  	pwd, _ := os.Getwd()
    83  	d.runDir = pwd + "/build"
    84  	os.RemoveAll(d.runDir)
    85  	log.ErrFatal(os.Mkdir(d.runDir, 0770))
    86  	d.Suite = pc.Suite
    87  	d.localDir = pwd
    88  	d.debug = pc.Debug
    89  	d.running = false
    90  	d.monitorPort = pc.MonitorPort
    91  	if d.Simulation == "" {
    92  		log.Fatal("No simulation defined in simulation")
    93  	}
    94  	log.Lvl3(fmt.Sprintf("Localhost dirs: RunDir %s", d.runDir))
    95  	log.Lvl3("Localhost configured ...")
    96  }
    97  
    98  // Build does nothing, as we're using our own binary, no need to build
    99  func (d *Localhost) Build(build string, arg ...string) error {
   100  	return nil
   101  }
   102  
   103  // Cleanup kills all running cothority-binaryes
   104  func (d *Localhost) Cleanup() error {
   105  	log.Lvl1("Nothing to clean up")
   106  	return nil
   107  }
   108  
   109  // Deploy copies all files to the run-directory
   110  func (d *Localhost) Deploy(rc *RunConfig) error {
   111  	d.Lock()
   112  	defer d.Unlock()
   113  	if runtime.GOOS == "darwin" {
   114  		files, err := exec.Command("ulimit", "-n").Output()
   115  		if err != nil {
   116  			return xerrors.Errorf("ulimit: %v", err)
   117  		}
   118  		filesNbr, err := strconv.Atoi(strings.TrimSpace(string(files)))
   119  		if err != nil {
   120  			return xerrors.Errorf("atoi: %v", err)
   121  		}
   122  		hosts, _ := strconv.Atoi(rc.Get("hosts"))
   123  		if filesNbr < hosts*2 {
   124  			maxfiles := 10000 + hosts*2
   125  			return xerrors.Errorf("Maximum open files is too small. Please run the following command:\n"+
   126  				"sudo sysctl -w kern.maxfiles=%d\n"+
   127  				"sudo sysctl -w kern.maxfilesperproc=%d\n"+
   128  				"ulimit -n %d\n"+
   129  				"sudo sysctl -w kern.ipc.somaxconn=2048\n",
   130  				maxfiles, maxfiles, maxfiles)
   131  		}
   132  	}
   133  
   134  	// Check for PreScript and copy it to the deploy-dir
   135  	d.PreScript = rc.Get("PreScript")
   136  	if d.PreScript != "" {
   137  		_, err := os.Stat(d.PreScript)
   138  		if !os.IsNotExist(err) {
   139  			if err := app.Copy(d.runDir, d.PreScript); err != nil {
   140  				return xerrors.Errorf("copying: %v", err)
   141  			}
   142  		}
   143  	}
   144  
   145  	d.servers, _ = strconv.Atoi(rc.Get("servers"))
   146  	log.Lvl2("Localhost: Deploying and writing config-files for", d.servers, "servers")
   147  	sim, err := onet.NewSimulation(d.Simulation, string(rc.Toml()))
   148  	if err != nil {
   149  		return xerrors.Errorf("simulation error: %v", err)
   150  	}
   151  	d.addresses = make([]string, d.servers)
   152  	for i := range d.addresses {
   153  		d.addresses[i] = "127.0.0." + strconv.Itoa(i+1)
   154  	}
   155  	d.sc, err = sim.Setup(d.runDir, d.addresses)
   156  	if err != nil {
   157  		return xerrors.Errorf("simulation setup: %v", err)
   158  	}
   159  	d.sc.Config = string(rc.Toml())
   160  	if err := d.sc.Save(d.runDir); err != nil {
   161  		return xerrors.Errorf("saving folder: %v", err)
   162  	}
   163  	log.Lvl2("Localhost: Done deploying")
   164  	d.wgRun.Add(d.servers)
   165  	// add one to the channel length to indicate it's done
   166  	d.errChan = make(chan error, d.servers+1)
   167  	return nil
   168  }
   169  
   170  // Start will execute one cothority-binary for each server
   171  // configured
   172  func (d *Localhost) Start(args ...string) error {
   173  	d.Lock()
   174  	defer d.Unlock()
   175  	if err := os.Chdir(d.runDir); err != nil {
   176  		return err
   177  	}
   178  	log.Lvl4("Localhost: chdir into", d.runDir)
   179  	ex := d.runDir + "/" + d.Simulation
   180  	d.running = true
   181  	log.Lvl1("Starting", d.servers, "applications of", ex)
   182  	time.Sleep(100 * time.Millisecond)
   183  
   184  	// If PreScript is defined, run the appropriate script _before_ the simulation.
   185  	if d.PreScript != "" {
   186  		out, err := exec.Command("sh", "-c", "./"+d.PreScript+" localhost").CombinedOutput()
   187  		outStr := strings.TrimRight(string(out), "\n")
   188  		if err != nil {
   189  			return xerrors.Errorf("error deploying PreScript: " + err.Error() + " " + outStr)
   190  		}
   191  		log.Lvl1(outStr)
   192  	}
   193  
   194  	err := monitor.ConnectSink("localhost:" + strconv.Itoa(d.monitorPort))
   195  	if err != nil {
   196  		return xerrors.Errorf("monitor: %v", err)
   197  	}
   198  
   199  	for index := 0; index < d.servers; index++ {
   200  		log.Lvl3("Starting", index)
   201  		host := "127.0.0." + strconv.Itoa(index+1)
   202  		go func(i int, h string) {
   203  			log.Lvl3("Localhost: will start host", i, h)
   204  			err := Simulate(d.Suite, host, d.Simulation, "")
   205  			if err != nil {
   206  				log.Error("Error running localhost", h, ":", err)
   207  				d.errChan <- err
   208  			}
   209  			d.wgRun.Done()
   210  			log.Lvl3("host (index", i, ")", h, "done")
   211  		}(index, host)
   212  	}
   213  	return nil
   214  }
   215  
   216  // Wait for all processes to finish
   217  func (d *Localhost) Wait() error {
   218  	d.Lock()
   219  	defer d.Unlock()
   220  	log.Lvl3("Waiting for processes to finish")
   221  
   222  	wait, err := time.ParseDuration(d.RunWait)
   223  	if err != nil || wait == 0 {
   224  		wait = 600 * time.Second
   225  		err = nil
   226  	}
   227  
   228  	go func() {
   229  		d.wgRun.Wait()
   230  		log.Lvl3("WaitGroup is 0")
   231  		// write to error channel when done:
   232  		d.errChan <- nil
   233  	}()
   234  
   235  	// if one of the hosts fails, stop waiting and return the error:
   236  	select {
   237  	case e := <-d.errChan:
   238  		log.Lvl3("Finished waiting for hosts:", e)
   239  		if e != nil {
   240  			if err := d.Cleanup(); err != nil {
   241  				log.Errorf("Couldn't cleanup running instances: %+v", err)
   242  			}
   243  			err = xerrors.Errorf("localhost error: %v", err)
   244  		}
   245  	case <-time.After(wait):
   246  		log.Lvl1("Quitting after waiting", wait)
   247  	}
   248  
   249  	errCleanup := os.Chdir(d.localDir)
   250  	if errCleanup != nil {
   251  		log.Error("Fail to restore the cwd: " + errCleanup.Error())
   252  	}
   253  
   254  	monitor.EndAndCleanup()
   255  	log.Lvl2("Processes finished")
   256  	return err
   257  }