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 }