**breaking** mini-ndn: re-design

refs: #5062

Everything is now done through examples like Mininet.
bin/minindn no longer provided as a binary installed in the system
bin/minindnedit GUI: will no longer be maintained
Remove cluster edition, will be re-introduced later

Change-Id: Id4ef137cb2a04d1b0dd24d01941757363bbf7d26
diff --git a/minindn/apps/__init__.py b/minindn/apps/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/minindn/apps/__init__.py
diff --git a/minindn/apps/app_manager.py b/minindn/apps/app_manager.py
new file mode 100644
index 0000000..bfe2ddc
--- /dev/null
+++ b/minindn/apps/app_manager.py
@@ -0,0 +1,56 @@
+# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
+#
+# Copyright (C) 2015-2019, The University of Memphis,
+#                          Arizona Board of Regents,
+#                          Regents of the University of California.
+#
+# This file is part of Mini-NDN.
+# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
+#
+# Mini-NDN is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Mini-NDN is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Mini-NDN, e.g., in COPYING.md file.
+# If not, see <http://www.gnu.org/licenses/>.
+
+from mininet.node import Node
+
+class AppManager(object):
+    def __init__(self, minindn, hosts, cls, **appParams):
+        self.cls = cls
+        self.apps = []
+        for host in hosts:
+            # Don't run NDN apps on switches
+            if isinstance(host, Node):
+                self.startOnNode(host, **appParams)
+
+        minindn.cleanups.append(self.cleanup)
+
+    def startOnNode(self, host, **appParams):
+        app = self.cls(host, **appParams)
+        app.start()
+        self.apps.append(app)
+
+    def cleanup(self):
+        for app in self.apps:
+            app.stop()
+
+    def __getitem__(self, nodeName):
+        for app in self.apps:
+            if app.node.name == nodeName:
+                return app
+        return None
+
+    def __iter__(self):
+        return self.apps.__iter__()
+
+    def __next__(self):
+        return self.apps.__next__()
diff --git a/minindn/apps/application.py b/minindn/apps/application.py
new file mode 100644
index 0000000..6970cb7
--- /dev/null
+++ b/minindn/apps/application.py
@@ -0,0 +1,48 @@
+# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
+#
+# Copyright (C) 2015-2019, The University of Memphis,
+#                          Arizona Board of Regents,
+#                          Regents of the University of California.
+#
+# This file is part of Mini-NDN.
+# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
+#
+# Mini-NDN is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Mini-NDN is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Mini-NDN, e.g., in COPYING.md file.
+# If not, see <http://www.gnu.org/licenses/>.
+
+from minindn.util import getPopen
+
+class Application(object):
+    def __init__(self, node):
+        self.node = node
+        self.process = None
+        self.logfile = None
+        self.homeDir = self.node.params['params']['homeDir']
+
+        # Make directory for log file
+        self.logDir = '{}/log'.format(self.homeDir)
+        self.node.cmd('mkdir -p {}'.format(self.logDir))
+
+    def start(self, command, logfile, envDict=None):
+        if self.process is None:
+            self.logfile = open('{}/{}'.format(self.logDir, logfile), 'w')
+            self.process = getPopen(self.node, command.split(), envDict,
+                                    stdout=self.logfile, stderr=self.logfile)
+
+    def stop(self):
+        if self.process is not None:
+            self.process.kill()
+            self.process = None
+        if self.logfile is not None:
+            self.logfile.close()
diff --git a/minindn/apps/nfd.py b/minindn/apps/nfd.py
new file mode 100644
index 0000000..7130526
--- /dev/null
+++ b/minindn/apps/nfd.py
@@ -0,0 +1,74 @@
+# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
+#
+# Copyright (C) 2015-2019, The University of Memphis,
+#                          Arizona Board of Regents,
+#                          Regents of the University of California.
+#
+# This file is part of Mini-NDN.
+# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
+#
+# Mini-NDN is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Mini-NDN is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Mini-NDN, e.g., in COPYING.md file.
+# If not, see <http://www.gnu.org/licenses/>.
+
+from minindn.apps.application import Application
+from minindn.util import copyExistentFile
+from minindn.minindn import Minindn
+
+class Nfd(Application):
+
+    def __init__(self, node, logLevel='NONE', csSize=65536,
+                 csPolicy='lru', csUnsolicitedPolicy='drop-all'):
+        Application.__init__(self, node)
+
+        self.logLevel = node.params['params'].get('nfd-log-level', logLevel)
+
+        self.confFile = '{}/nfd.conf'.format(self.homeDir)
+        self.logFile = 'nfd.log'
+        self.sockFile = '/var/run/{}.sock'.format(node.name)
+        self.ndnFolder = '{}/.ndn'.format(self.homeDir)
+        self.clientConf = '{}/client.conf'.format(self.ndnFolder)
+
+        # Copy nfd.conf file from /usr/local/etc/ndn or /etc/ndn to the node's home directory
+        # Use nfd.conf as default configuration for NFD, else use the sample
+        possibleConfPaths = ['/usr/local/etc/ndn/nfd.conf.sample', '/usr/local/etc/ndn/nfd.conf',
+                             '/etc/ndn/nfd.conf.sample', '/etc/ndn/nfd.conf']
+        copyExistentFile(node, possibleConfPaths, self.confFile)
+
+        # Set log level
+        node.cmd('infoedit -f {} -s log.default_level -v {}'.format(self.confFile, self.logLevel))
+        # Open the conf file and change socket file name
+        node.cmd('infoedit -f {} -s face_system.unix.path -v {}'.format(self.confFile, self.sockFile))
+
+        # Set CS parameters
+        node.cmd('infoedit -f {} -s tables.cs_max_packets -v {}'.format(self.confFile, csSize))
+        node.cmd('infoedit -f {} -s tables.cs_policy -v {}'.format(self.confFile, csPolicy))
+        node.cmd('infoedit -f {} -s tables.cs_unsolicited_policy -v {}'.format(self.confFile, csUnsolicitedPolicy))
+
+        # Make NDN folder
+        node.cmd('mkdir -p {}'.format(self.ndnFolder))
+
+        # Copy client configuration to host
+        possibleClientConfPaths = ['/usr/local/etc/ndn/client.conf.sample', '/etc/ndn/client.conf.sample']
+        copyExistentFile(node, possibleClientConfPaths, self.clientConf)
+
+        # Change the unix socket
+        node.cmd('sudo sed -i "s|nfd.sock|{}.sock|g" {}'.format(node.name, self.clientConf))
+
+        if not Minindn.ndnSecurityDisabled:
+            # Generate key and install cert for /localhost/operator to be used by NFD
+            node.cmd('ndnsec-keygen /localhost/operator | ndnsec-install-cert -')
+
+    def start(self):
+        Application.start(self, 'nfd --config {}'.format(self.confFile), logfile=self.logFile)
+        Minindn.sleep(2)
diff --git a/minindn/apps/nlsr.py b/minindn/apps/nlsr.py
new file mode 100644
index 0000000..36ef971
--- /dev/null
+++ b/minindn/apps/nlsr.py
@@ -0,0 +1,258 @@
+# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
+#
+# Copyright (C) 2015-2019, The University of Memphis,
+#                          Arizona Board of Regents,
+#                          Regents of the University of California.
+#
+# This file is part of Mini-NDN.
+# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
+#
+# Mini-NDN is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Mini-NDN is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Mini-NDN, e.g., in COPYING.md file.
+# If not, see <http://www.gnu.org/licenses/>.
+
+import shutil
+import os, sys
+
+from mininet.clean import sh
+from mininet.examples.cluster import RemoteMixin
+from mininet.log import warn
+from mininet.node import Switch
+
+from minindn.apps.application import Application
+from minindn.util import scp, copyExistentFile
+from minindn.helpers.nfdc import Nfdc
+from minindn.minindn import Minindn
+
+class Nlsr(Application):
+    ROUTING_LINK_STATE = 'link-state'
+    ROUTING_HYPERBOLIC = 'hr'
+    ROUTING_DRY_RUN = 'dry'
+    SYNC_PSYNC = 'psync'
+    SYNC_CHRONOSYNC = 'chronosync'
+
+    def __init__(self, node, logLevel='NONE', security=False, sync=SYNC_PSYNC,
+                 faceType='udp', nFaces=3, routingType=ROUTING_LINK_STATE):
+        Application.__init__(self, node)
+
+        self.network = '/ndn/'
+        self.node = node
+        self.parameters = self.node.params['params']
+
+        if self.parameters.get('nlsr-log-level', None) != None:
+            logLevel = self.parameters.get('nlsr-log-level')
+
+        if logLevel in ['NONE', 'WARN', 'INFO', 'DEBUG', 'TRACE']:
+            self.envDict = {'NDN_LOG': 'nlsr.*={}'.format(logLevel)}
+        else:
+            self.envDict = {'NDN_LOG': logLevel}
+
+        self.logFile = 'nlsr.log'
+        self.routerName = '/{}C1.Router/cs/{}'.format('%', node.name)
+        self.confFile = '{}/nlsr.conf'.format(self.homeDir)
+        self.security = security
+        self.sync = sync
+        self.faceType = faceType
+        self.infocmd = 'infoedit -f nlsr.conf'
+
+        self.parameters = self.node.params['params']
+
+        self.nFaces = nFaces
+        if routingType == Nlsr.ROUTING_HYPERBOLIC:
+            self.hyperbolicState = 'on'
+        elif routingType == Nlsr.ROUTING_DRY_RUN:
+            self.hyperbolicState = 'dry-run'
+        else:
+            self.hyperbolicState = 'off'
+        self.hyperRadius = self.parameters.get('radius', 0.0)
+        self.hyperAngle = self.parameters.get('angle', 0.0)
+
+        if ((self.hyperbolicState == 'on' or self.hyperbolicState == 'dry-run') and
+            (self.hyperRadius == 0.0 or self.hyperAngle == 0.0)):
+            warn('Hyperbolic coordinates in topology file are either missing or misconfigured.')
+            warn('Check that each node has one radius value and one or two angle value(s).')
+            sys.exit(1)
+
+        self.neighborIPs = []
+        possibleConfPaths = ['/usr/local/etc/ndn/nlsr.conf.sample', '/etc/ndn/nlsr.conf.sample']
+        copyExistentFile(node, possibleConfPaths, '{}/nlsr.conf'.format(self.homeDir))
+
+        self.createConfigFile()
+
+        if security and not Minindn.ndnSecurityDisabled:
+            self.createKeysAndCertificates()
+
+    def start(self):
+        self.createFaces()
+        Application.start(self, 'nlsr -f {}'.format(self.confFile), self.logFile, self.envDict)
+        Minindn.sleep(1)
+
+    def createFaces(self):
+        for ip in self.neighborIPs:
+            Nfdc.createFace(self.node, ip, self.faceType, isPermanent=True)
+
+    @staticmethod
+    def createKey(host, name, outputFile):
+        host.cmd('ndnsec-keygen {} > {}'.format(name, outputFile))
+
+    @staticmethod
+    def createCertificate(host, signer, keyFile, outputFile):
+        host.cmd('ndnsec-certgen -s {} -r {} > {}'.format(signer, keyFile, outputFile))
+
+    def createKeysAndCertificates(self):
+        securityDir = '{}/security'.format(self.parameters['workDir'])
+
+        if not os.path.exists(securityDir):
+            os.mkdir(securityDir)
+
+        rootName = self.network
+        rootCertFile = '{}/root.cert'.format(securityDir)
+        if not os.path.isfile(rootCertFile):
+            # Create root certificate
+            sh('ndnsec-keygen {}'.format(rootName)) # Installs a self-signed cert into the system
+            sh('ndnsec-cert-dump -i {} > {}'.format(rootName, rootCertFile))
+
+        # Create necessary certificates for each site
+        nodeSecurityFolder = '{}/security'.format(self.homeDir)
+
+        self.node.cmd('mkdir -p {}'.format(nodeSecurityFolder))
+
+        # Create temp folders for remote nodes on this machine (localhost) to store site.key file
+        # from RemoteNodes
+        if not os.path.exists(nodeSecurityFolder) and \
+            isinstance(self.node, RemoteMixin) and self.node.isRemote:
+            os.makedirs(nodeSecurityFolder)
+
+        shutil.copyfile('{}/root.cert'.format(securityDir),
+                        '{}/root.cert'.format(nodeSecurityFolder))
+
+        # Create site certificate
+        siteName = '{}{}-site'.format(self.network, self.node.name)
+        siteKeyFile = '{}/site.keys'.format(nodeSecurityFolder)
+        siteCertFile = '{}/site.cert'.format(nodeSecurityFolder)
+        Nlsr.createKey(self.node, siteName, siteKeyFile)
+
+        # Copy siteKeyFile from remote for ndnsec-certgen
+        if isinstance(self.node, RemoteMixin) and self.node.isRemote:
+            login = 'mininet@{}'.format(self.node.server)
+            src = '{}:{}'.format(login, siteKeyFile)
+            dst = siteKeyFile
+            scp(src, dst)
+
+        # Root key is in root namespace, must sign site key and then install on host
+        sh('ndnsec-certgen -s {} -r {} > {}'.format(rootName, siteKeyFile, siteCertFile))
+
+        # Copy root.cert and site.cert from localhost to remote host
+        if isinstance(self.node, RemoteMixin) and self.node.isRemote:
+            login = 'mininet@{}'.format(self.node.server)
+            src = '{}/site.cert'.format(nodeSecurityFolder)
+            src2 = '{}/root.cert'.format(nodeSecurityFolder)
+            dst = '{}:/tmp/'.format(login)
+            scp(src, src2, dst)
+            self.node.cmd('mv /tmp/*.cert {}'.format(nodeSecurityFolder))
+
+        self.node.cmd('ndnsec-cert-install -f {}'.format(siteCertFile))
+
+        # Create and install operator certificate
+        opName = '{}/%C1.Operator/op'.format(siteName)
+        opKeyFile = '{}/op.keys'.format(nodeSecurityFolder)
+        opCertFile = '{}/op.cert'.format(nodeSecurityFolder)
+        Nlsr.createKey(self.node, opName, opKeyFile)
+        Nlsr.createCertificate(self.node, siteName, opKeyFile, opCertFile)
+        self.node.cmd('ndnsec-cert-install -f {}'.format(opCertFile))
+
+        # Create and install router certificate
+        routerName = '{}/%C1.Router/cs/{}'.format(siteName, self.node.name)
+        routerKeyFile = '{}/router.keys'.format(nodeSecurityFolder)
+        routerCertFile = '{}/router.cert'.format(nodeSecurityFolder)
+        Nlsr.createKey(self.node, routerName, routerKeyFile)
+        Nlsr.createCertificate(self.node, opName, routerKeyFile, routerCertFile)
+        self.node.cmd('ndnsec-cert-install -f {}'.format(routerCertFile))
+
+    def createConfigFile(self):
+
+        self.__editGeneralSection()
+        self.__editNeighborsSection()
+        self.__editHyperbolicSection()
+        self.__editFibSection()
+        self.__editAdvertisingSection()
+        self.__editSecuritySection()
+
+    def __editGeneralSection(self):
+
+        self.node.cmd('{} -s general.network -v {}'.format(self.infocmd, self.network))
+        self.node.cmd('{} -s general.site -v /{}-site'.format(self.infocmd, self.node.name))
+        self.node.cmd('{} -s general.router -v /%C1.Router/cs/{}'.format(self.infocmd, self.node.name))
+        self.node.cmd('{} -s general.state-dir -v {}/log'.format(self.infocmd, self.homeDir))
+        self.node.cmd('{} -s general.sync-protocol -v {}'.format(self.infocmd, self.sync))
+
+    def __editNeighborsSection(self):
+
+        self.node.cmd('{} -d neighbors.neighbor'.format(self.infocmd))
+        for intf in self.node.intfList():
+            link = intf.link
+            if not link:
+                continue
+
+            node1, node2 = link.intf1.node, link.intf2.node
+
+            # Todo: add some switch support
+            if isinstance(node1, Switch) or isinstance(node2, Switch):
+                continue
+
+            if node1 == self.node:
+                other = node2
+                ip = other.IP(str(link.intf2))
+            else:
+                other = node1
+                ip = other.IP(str(link.intf1))
+
+            linkCost = intf.params.get('delay', '10ms').replace('ms', '')
+
+            self.neighborIPs.append(ip)
+
+            self.node.cmd('{} -a neighbors.neighbor \
+                          <<<\'name {}{}-site/%C1.Router/cs/{} face-uri {}://{}\n link-cost {}\''
+                          .format(self.infocmd, self.network, other.name, other.name,
+                                  self.faceType, ip, linkCost))
+
+    def __editHyperbolicSection(self):
+
+        self.node.cmd('{} -s hyperbolic.state -v {}'.format(self.infocmd, self.hyperbolicState))
+        self.node.cmd('{} -s hyperbolic.radius -v {}'.format(self.infocmd, self.hyperRadius))
+        self.node.cmd('{} -s hyperbolic.angle -v {}'.format(self.infocmd, self.hyperAngle))
+
+    def __editFibSection(self):
+
+        self.node.cmd('{} -s fib.max-faces-per-prefix  -v {}'.format(self.infocmd, self.nFaces))
+
+    def __editAdvertisingSection(self):
+
+        self.node.cmd('{} -d advertising.prefix'.format(self.infocmd))
+        self.node.cmd('{} -s advertising.prefix -v {}{}-site/{}'
+                      .format(self.infocmd, self.network, self.node.name, self.node.name))
+
+    def __editSecuritySection(self):
+
+        self.node.cmd('{} -d security.cert-to-publish'.format(self.infocmd))
+        if not self.security:
+            self.node.cmd('{} -s security.validator.trust-anchor.type -v any'.format(self.infocmd))
+            self.node.cmd('{} -d security.validator.trust-anchor.file-name'.format(self.infocmd))
+            self.node.cmd('{} -s security.prefix-update-validator.trust-anchor.type -v any'.format(self.infocmd))
+            self.node.cmd('{} -d security.prefix-update-validator.trust-anchor.file-name'.format(self.infocmd))
+        else:
+            self.node.cmd('{} -s security.validator.trust-anchor.file-name -v security/root.cert'.format(self.infocmd))
+            self.node.cmd('{} -s security.prefix-update-validator.trust-anchor.file-name -v security/site.cert'.format(self.infocmd))
+            self.node.cmd('{} -p security.cert-to-publish -v security/site.cert'.format(self.infocmd))
+            self.node.cmd('{} -p security.cert-to-publish -v security/op.cert'.format(self.infocmd))
+            self.node.cmd('{} -p security.cert-to-publish -v security/router.cert'.format(self.infocmd))