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

     1  // Package platform contains interface and implementation to run onet code
     2  // amongst multiple platforms. Such implementations include Localhost (run your
     3  // test locally) and Deterlab (similar to emulab).
     4  package platform
     5  
     6  import (
     7  	"bufio"
     8  	"bytes"
     9  	"errors"
    10  	"fmt"
    11  	"go/build"
    12  	"os"
    13  	"strconv"
    14  	"strings"
    15  	"time"
    16  
    17  	"sync"
    18  
    19  	"os/exec"
    20  
    21  	"io/ioutil"
    22  
    23  	"github.com/BurntSushi/toml"
    24  	"gopkg.in/dedis/onet.v2/app"
    25  	"gopkg.in/dedis/onet.v2/log"
    26  )
    27  
    28  // The Life of a simulation:
    29  //
    30  // 1. Configure
    31  //     * read configuration
    32  //     * compile eventual files
    33  // 2. Build
    34  //     * builds all files
    35  //     * eventually for different platforms
    36  // 3. Cleanup
    37  //     * send killall to applications
    38  // 4. Deploy
    39  //     * make sure the environment is up and running
    40  //     * copy files
    41  // 5. Start
    42  //     * start all logservers
    43  //     * start all nodes
    44  //     * start all clients
    45  // 6. Wait
    46  //     * wait for the applications to finish
    47  
    48  // Platform interface that has to be implemented to add another simulation-
    49  // platform.
    50  type Platform interface {
    51  	// Does the initial configuration of all structures needed for the platform
    52  	Configure(*Config)
    53  	// Build builds all necessary binaries
    54  	Build(build string, arg ...string) error
    55  	// Makes sure that there is no part of the application still running
    56  	Cleanup() error
    57  	// Copies the binaries to the appropriate directory/machines, together with
    58  	// the necessary configuration. RunConfig is a simple string that should
    59  	// be copied as 'app.toml' to the directory where the app resides
    60  	Deploy(*RunConfig) error
    61  	// Starts the application and returns - non-blocking!
    62  	Start(args ...string) error
    63  	// Waits for the application to quit
    64  	Wait() error
    65  }
    66  
    67  // Config is passed to Platform.Config and prepares the platform for
    68  // specific system-wide configurations
    69  type Config struct {
    70  	// string denoting the group used for simulations
    71  	// XXX find ways to remove that "one suite" assumption
    72  	Suite       string
    73  	MonitorPort int
    74  	Debug       int
    75  }
    76  
    77  var deterlab = "deterlab"
    78  var localhost = "localhost"
    79  var mininet = "mininet"
    80  
    81  // NewPlatform returns the appropriate platform
    82  // [deterlab,localhost]
    83  func NewPlatform(t string) Platform {
    84  	var p Platform
    85  	switch t {
    86  	case deterlab:
    87  		p = &Deterlab{}
    88  	case localhost:
    89  		p = &Localhost{}
    90  	case mininet:
    91  		p = &MiniNet{}
    92  		_, err := os.Stat("server_list")
    93  		if os.IsNotExist(err) {
    94  			path := build.Default.GOPATH + "/src/gopkg.in/dedis/onet.v2/simul/platform/mininet/"
    95  			var command string
    96  			if app.InputYN(true, "Do you want to run mininet on ICCluster?") {
    97  				command = path + "setup_iccluster.sh"
    98  			} else {
    99  				command = path + "setup_servers.sh"
   100  			}
   101  			numbers := app.Input("server1 server2 server3", "Please enter the space separated numbers of the servers")
   102  			split := strings.Split(numbers, " ")
   103  			cmd := exec.Command(command, split...)
   104  			out, err := cmd.CombinedOutput()
   105  			if err != nil {
   106  				log.Error(err)
   107  			}
   108  			log.Lvl1(string(out))
   109  		} else {
   110  			log.Lvl1("Using existing 'server_list'-file")
   111  			if log.DebugVisible() > 1 {
   112  				sl, err := ioutil.ReadFile("server_list")
   113  				log.ErrFatal(err)
   114  				servers := strings.Replace(string(sl), "\n", " ", -1)
   115  				log.Lvl2("Server_list is: ", servers)
   116  			}
   117  		}
   118  	}
   119  	return p
   120  }
   121  
   122  // ReadRunFile reads from a configuration-file for a run. The configuration-file has the
   123  // following syntax:
   124  // Name1 = value1
   125  // Name2 = value2
   126  // [empty line]
   127  // n1, n2, n3, n4
   128  // v11, v12, v13, v14
   129  // v21, v22, v23, v24
   130  //
   131  // The Name1...Namen are global configuration-options.
   132  // n1..nn are configuration-options for one run
   133  // Both the global and the run-configuration are copied to both
   134  // the platform and the app-configuration.
   135  func ReadRunFile(p Platform, filename string) []*RunConfig {
   136  	var runconfigs []*RunConfig
   137  	masterConfig := NewRunConfig()
   138  	log.Lvl3("Reading file", filename)
   139  
   140  	file, err := os.Open(filename)
   141  	defer func() {
   142  		if err := file.Close(); err != nil {
   143  			log.Error("Couldn' close", file.Name())
   144  		}
   145  	}()
   146  	if err != nil {
   147  		log.Fatal("Couldn't open file", filename, err)
   148  	}
   149  
   150  	// Decoding of the first part of the run config file
   151  	// where the config wont change for the whole set of the simulation's tests
   152  	scanner := bufio.NewScanner(file)
   153  	for scanner.Scan() {
   154  		text := scanner.Text()
   155  		log.Lvl3("Decoding", text)
   156  		// end of the first part
   157  		if text == "" {
   158  			break
   159  		}
   160  		if text[0] == '#' {
   161  			continue
   162  		}
   163  
   164  		// checking if format is good
   165  		vals := strings.Split(text, "=")
   166  		if len(vals) != 2 {
   167  			log.Fatal("Simulation file:", filename, " is not properly formatted ( key = value )")
   168  		}
   169  		// fill in the general config
   170  		masterConfig.Put(strings.TrimSpace(vals[0]), strings.TrimSpace(vals[1]))
   171  		// also put it in platform
   172  		if _, err := toml.Decode(text, p); err != nil {
   173  			log.Error("Error decoding", text)
   174  		}
   175  		log.Lvlf5("Platform is now %+v", p)
   176  	}
   177  
   178  	for scanner.Scan() {
   179  		if scanner.Text() != "" && scanner.Text()[0] != '#' {
   180  			break
   181  		}
   182  	}
   183  	args := strings.Split(scanner.Text(), ",")
   184  	for scanner.Scan() {
   185  		if scanner.Text()[0] == '#' {
   186  			continue
   187  		}
   188  		rc := masterConfig.Clone()
   189  		// put each individual test configs
   190  		for i, value := range strings.Split(scanner.Text(), ",") {
   191  			rc.Put(strings.TrimSpace(args[i]), strings.TrimSpace(value))
   192  		}
   193  		runconfigs = append(runconfigs, rc)
   194  	}
   195  
   196  	return runconfigs
   197  }
   198  
   199  // RunConfig is a struct that represent the configuration to apply for one "test"
   200  // Note: a "simulation" is a set of "tests"
   201  type RunConfig struct {
   202  	fields map[string]string
   203  	sync.RWMutex
   204  }
   205  
   206  // NewRunConfig returns an initialised config to be used for reading
   207  // in runconfig-files
   208  func NewRunConfig() *RunConfig {
   209  	rc := new(RunConfig)
   210  	rc.fields = make(map[string]string)
   211  	return rc
   212  }
   213  
   214  // One problem for now is RunConfig read also the ' " ' char (34 ASCII)
   215  // and thus when doing Get(), also return the value enclosed by ' " '
   216  // One fix is to each time we Get(), automatically delete those chars
   217  var replacer = strings.NewReplacer("\"", "", "'", "")
   218  
   219  // Get returns the associated value of the field in the config
   220  func (r *RunConfig) Get(field string) string {
   221  	r.RLock()
   222  	defer r.RUnlock()
   223  	return replacer.Replace(r.fields[strings.ToLower(field)])
   224  }
   225  
   226  // Delete a field from the runconfig (delete for example Simulation which we
   227  // dont care in the final csv)
   228  func (r *RunConfig) Delete(field string) {
   229  	r.Lock()
   230  	defer r.Unlock()
   231  	delete(r.fields, field)
   232  }
   233  
   234  // ErrorFieldNotPresent signals that a field is not in the RunConfig.
   235  var ErrorFieldNotPresent = errors.New("field not present")
   236  
   237  // GetInt returns the integer of the field, or error if not defined
   238  func (r *RunConfig) GetInt(field string) (int, error) {
   239  	val := r.Get(field)
   240  	if val == "" {
   241  		return 0, ErrorFieldNotPresent
   242  	}
   243  	ret, err := strconv.Atoi(val)
   244  	return ret, err
   245  }
   246  
   247  // GetDuration returns the field parsed as a duration, or error if a parse error occurs.
   248  func (r *RunConfig) GetDuration(field string) (time.Duration, error) {
   249  	val := r.Get(field)
   250  	if val == "" {
   251  		return 0, ErrorFieldNotPresent
   252  	}
   253  	return time.ParseDuration(val)
   254  }
   255  
   256  // Put inserts a new field - value relationship
   257  func (r *RunConfig) Put(field, value string) {
   258  	r.Lock()
   259  	defer r.Unlock()
   260  	r.fields[strings.ToLower(field)] = value
   261  }
   262  
   263  // Toml returns this config as bytes in a Toml format
   264  func (r *RunConfig) Toml() []byte {
   265  	r.RLock()
   266  	defer r.RUnlock()
   267  	var buf bytes.Buffer
   268  	for k, v := range r.fields {
   269  		fmt.Fprintf(&buf, "%s = %s\n", k, v)
   270  	}
   271  	return buf.Bytes()
   272  }
   273  
   274  // Map returns this config as a Map
   275  func (r *RunConfig) Map() map[string]string {
   276  	r.RLock()
   277  	defer r.RUnlock()
   278  	tomap := make(map[string]string)
   279  	for k := range r.fields {
   280  		tomap[k] = r.Get(k)
   281  	}
   282  	return tomap
   283  }
   284  
   285  // Clone this runconfig so it has all fields-value relationship already present
   286  func (r *RunConfig) Clone() *RunConfig {
   287  	r.RLock()
   288  	defer r.RUnlock()
   289  	rc := NewRunConfig()
   290  	for k, v := range r.fields {
   291  		rc.fields[k] = v
   292  	}
   293  	return rc
   294  }