modification to use cluster version

Change-Id: Iaf169507577deba20d548348532a2e0b91a03249
refs: #3652
diff --git a/bin/minindn b/bin/minindn
index 9eca39c..950dc05 100755
--- a/bin/minindn
+++ b/bin/minindn
@@ -1,6 +1,6 @@
 # -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
 #
-# Copyright (C) 2015-2016, The University of Memphis,
+# Copyright (C) 2015-2017, The University of Memphis,
 #                          Arizona Board of Regents,
 #                          Regents of the University of California.
 #
@@ -24,9 +24,9 @@
 # This file incorporates work covered by the following copyright and
 # permission notice:
 #
-#   Mininet 2.2.1 License
+#   Mininet 2.3.0d1 License
 #
-#   Copyright (c) 2013-2015 Open Networking Laboratory
+#   Copyright (c) 2013-2016 Open Networking Laboratory
 #   Copyright (c) 2009-2012 Bob Lantz and The Board of Trustees of
 #   The Leland Stanford Junior University
 #
@@ -65,10 +65,15 @@
 from mininet.link import TCLink
 from mininet.util import ipStr, ipParse
 
+from mininet.examples.cluster import MininetCluster, RoundRobinPlacer, ClusterCleanup
+from mininet.examples.clustercli import ClusterCLI
 
 from ndn import ExperimentManager
-from ndn.ndn_host import NdnHost, CpuLimitedNdnHost
+from ndn.ndn_host import NdnHost, CpuLimitedNdnHost, RemoteNdnHost
 from ndn.conf_parser import parse_hosts, parse_switches, parse_links
+from ndn.remote_ndn_link import RemoteNdnLink, RemoteGRENdnLink
+from ndn.placer import GuidedPlacer, PopulatePlacement
+from ndn.util import ssh, scp
 
 import os.path, time
 import shutil
@@ -78,6 +83,9 @@
 import sys
 import signal
 from subprocess import call
+import glob
+from functools import partial
+import re
 
 from ndn.nlsr import Nlsr, NlsrConfigGenerator
 from ndn.nfd import Nfd
@@ -112,6 +120,11 @@
         self.workDir = "/tmp"
         self.resultDir = None
         self.pctTraffic = 1.0
+        self.cluster = None
+        self.servers = None
+        self.guided = None
+        self.placer = None
+        self.tunnelType = None
 
 def createResultsDir(resultDir, faces, hr):
     if faces == 0:
@@ -181,6 +194,19 @@
     parser.add_argument('--version', '-V', action='version', version='%(prog)s ' + VERSION_NUMBER,
                         help='Displays version information')
 
+    parser.add_argument("--cluster", metavar='localhost,server2,...',
+                        help="Run cluster edition")
+
+    parser.add_argument("--placement", default='guided',
+                        choices=['roundRobin', 'guided'])
+
+    parser.add_argument("--place-list", dest="placeList",
+                        help="""Provide corresponding number of nodes (comma separated) to put on
+                        each node respectively of --cluster when guided placement is used""")
+
+    parser.add_argument("--tunnel-type", dest="tunnelType", default='ssh',
+                        choices=['ssh', 'gre'])
+
     args = parser.parse_args()
 
     options = ProgramOptions()
@@ -197,6 +223,10 @@
     options.workDir = args.workDir
     options.resultDir = args.resultDir
     options.pctTraffic = args.pctTraffic
+    options.cluster = args.cluster
+    options.placement = args.placement
+    options.tunnelType = args.tunnelType
+    options.placeList = args.placeList
 
     if options.experimentName is not None and options.experimentName not in ExperimentManager.getExperimentNames():
         print("No experiment named %s" % options.experimentName)
@@ -205,6 +235,33 @@
     if options.experimentName is not None and options.resultDir is None:
         print("No results folder specified; experiment results will remain in the working directory")
 
+    if options.cluster is not None:
+        servers = options.cluster.split(',')
+        for server in servers:
+            ClusterCleanup.add(server)
+        options.servers = servers
+
+        if options.placement == "roundRobin":
+            options.placement = RoundRobinPlacer
+        elif options.placement == "guided":
+            if options.placeList is None or not re.match("^[0-9,]+$", options.placeList):
+                print("Please specify correctly how many nodes you want to place on each node!")
+                sys.exit(1)
+            else:
+                try:
+                    options.placeList = map(int, options.placeList.split(","))
+                except ValueError:
+                    print("Please specify the nodes correctly, no comma at the beginning/end!")
+                    sys.exit(1)
+
+                PopulatePlacement(options.placeList)
+                options.placement = GuidedPlacer
+
+        if options.tunnelType == "ssh":
+            options.tunnelType = RemoteNdnLink
+        else:
+            options.tunnelType = RemoteGRENdnLink
+
     return options
 
 class NdnTopo(Topo):
@@ -247,6 +304,17 @@
         info('Template file cannot be found. Exiting...\n')
         sys.exit(1)
 
+    if options.cluster is not None and options.placement == GuidedPlacer:
+        num_nodes = 0
+        with open(options.templateFile, 'r') as topo:
+            for line in topo:
+                if ': _' in line:
+                    num_nodes += 1
+
+        if sum(options.placeList) != num_nodes:
+            print("Placement list sum is not equal to number of nodes!")
+            sys.exit(1)
+
     # Use nfd.conf as default configuration for NFD, else use the sample
 
     nfdConfFile = "%s/nfd.conf" % INSTALL_DIR
@@ -259,6 +327,17 @@
 
     call(["sudo", "sed", "-i", 's|default_level [A-Z]*$|default_level $LOG_LEVEL|g', nfdConfFile])
 
+    # Copy nfd.conf to remote hosts - this assumes that NDN versions across
+    # the cluster are at least compatible if not the same
+    if options.cluster is not None:
+        for server in options.servers:
+            if server != "localhost":
+                login = "mininet@%s" % server
+                src = nfdConfFile
+                dst = "%s:/tmp/nfd.conf" % (login)
+                scp(src, dst)
+                ssh(login, "sudo cp /tmp/nfd.conf %s" % src)
+
     if options.resultDir is not None:
         options.resultDir = createResultsDir(options.resultDir, options.nFaces, options.hr)
 
@@ -269,11 +348,15 @@
     if topo.isTCLink == True and topo.isLimited == True:
         net = Mininet(topo,host=CpuLimitedNdnHost,link=TCLink)
     elif topo.isTCLink == True and topo.isLimited == False:
-        net = Mininet(topo,host=NdnHost,link=TCLink)
+        if options.cluster is not None:
+            mn = partial(MininetCluster, servers=options.servers, placement=options.placement)
+            net = mn(topo=topo, host=RemoteNdnHost, link=options.tunnelType)
+        else:
+            net = Mininet(topo, host=NdnHost, link=TCLink)
     elif topo.isTCLink == False and topo.isLimited == True:
-        net = Mininet(topo,host=CpuLimitedNdnHost)
+        net = Mininet(topo, host=CpuLimitedNdnHost)
     else:
-        net = Mininet(topo,host=NdnHost)
+        net = Mininet(topo, host=NdnHost)
 
     t2 = datetime.datetime.now()
 
@@ -369,7 +452,16 @@
 
     if options.resultDir is not None:
         print("Moving results to %s" % options.resultDir)
-        os.system("sudo mv /%s/* %s" % (options.workDir, options.resultDir))
+        for file in glob.glob('%s/*' % options.workDir):
+            shutil.move(file, options.resultDir)
+        if options.cluster is not None:
+            for server in options.servers:
+                if server != "localhost":
+                    login = "mininet@%s" % server
+                    src = "%s:%s/*" % (login, options.workDir)
+                    dst = options.resultDir
+                    scp(src, dst)
+            print("Please clean work directories of other machines before running the cluster again")
 
 def signal_handler(signal, frame):
     print('Cleaning up...')