Adding support for sdfabric topology deployment

Change-Id: If4f7a6016f8b3a91b47f68bc480f1cfa8ef5d5c3
diff --git a/mininet/Chart.yaml b/mininet/Chart.yaml
index 344b9a7..0526f6b 100644
--- a/mininet/Chart.yaml
+++ b/mininet/Chart.yaml
@@ -15,7 +15,7 @@
 
 icon: https://guide.opencord.org/logos/cord.svg
 apiVersion: v1
-appVersion: 1.0.0
+appVersion: 1.1.0
 description: A Helm chart for Mininet
 name: mininet
-version: 1.1.4
+version: 1.2.0
diff --git a/mininet/sdfabric-values.yaml b/mininet/sdfabric-values.yaml
new file mode 100644
index 0000000..3907a7b
--- /dev/null
+++ b/mininet/sdfabric-values.yaml
@@ -0,0 +1,40 @@
+---
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Put Mininet topology scripts in the toposcripts directory.
+# They will be mounted inside the container in /toposcripts
+
+replicaCount: 1
+
+nameOverride: ""
+fullnameOverride: ""
+
+images:
+  mininet:
+    repository: 'opennetworking/mn-stratum'
+    tag: 'latest'
+    pullPolicy: IfNotPresent
+
+global:
+  registry: ''
+
+resources: {}
+
+nodeSelector: {}
+
+tolerations: []
+
+mnStratumSwitchCount: 4
+topoScript: '/toposcripts/topo-leafspine.py'
diff --git a/mininet/toposcripts/mn_lib.py b/mininet/toposcripts/mn_lib.py
new file mode 100755
index 0000000..cbfad99
--- /dev/null
+++ b/mininet/toposcripts/mn_lib.py
@@ -0,0 +1,161 @@
+# SPDX-FileCopyrightText: 2022-present Intel Corporation
+# SPDX-License-Identifier: Apache-2.0
+
+from mininet.node import Host, Node
+import socket
+import struct
+
+DBUF_DROP_TIMEOUT_SEC = "30s"
+DBUF_NUM_QUEUES = 10
+DBUF_MAX_PKTS_PER_QUEUE = 16
+
+
+def ip2long(ip):
+    """
+    Convert an IP string to long
+    """
+    packedIP = socket.inet_aton(ip)
+    return struct.unpack("!L", packedIP)[0]
+
+
+class IPv4Host(Host):
+    """Host that can be configured with an IPv4 gateway (default route).
+    """
+
+    def config(self, mac=None, ip=None, defaultRoute=None, lo='up', gw=None, **_params):
+        super(IPv4Host, self).config(mac, ip, defaultRoute, lo, **_params)
+        self.cmd('ip -4 addr flush dev %s' % self.defaultIntf())
+        self.cmd('ip -6 addr flush dev %s' % self.defaultIntf())
+        self.cmd('sysctl -w net.ipv4.ip_forward=0')
+        self.cmd('ip -4 link set up %s' % self.defaultIntf())
+        self.cmd('ip -4 addr add %s dev %s' % (ip, self.defaultIntf()))
+        if gw:
+            self.cmd('ip -4 route add default via %s' % gw)
+        # Disable offload
+        for attr in ["rx", "tx", "sg"]:
+            cmd = "/sbin/ethtool --offload %s %s off" % (self.defaultIntf(), attr)
+            self.cmd(cmd)
+
+        def updateIP():
+            return ip.split('/')[0]
+
+        self.defaultIntf().updateIP = updateIP
+
+
+class TaggedIPv4Host(Host):
+    """VLAN-tagged host that can be configured with an IPv4 gateway
+    (default route).
+    """
+    vlanIntf = None
+
+    def config(self, mac=None, ip=None, defaultRoute=None, lo='up', gw=None,
+               vlan=None, **_params):
+        super(TaggedIPv4Host, self).config(mac, ip, defaultRoute, lo, **_params)
+        self.vlanIntf = "%s.%s" % (self.defaultIntf(), vlan)
+        # Replace default interface with a tagged one
+        self.cmd('ip -4 addr flush dev %s' % self.defaultIntf())
+        self.cmd('ip -6 addr flush dev %s' % self.defaultIntf())
+        self.cmd('ip -4 link add link %s name %s type vlan id %s' % (
+            self.defaultIntf(), self.vlanIntf, vlan))
+        self.cmd('ip -4 link set up %s' % self.vlanIntf)
+        self.cmd('ip -4 addr add %s dev %s' % (ip, self.vlanIntf))
+        if gw:
+            self.cmd('ip -4 route add default via %s' % gw)
+
+        self.defaultIntf().name = self.vlanIntf
+        self.nameToIntf[self.vlanIntf] = self.defaultIntf()
+
+        # Disable offload
+        for attr in ["rx", "tx", "sg"]:
+            cmd = "/sbin/ethtool --offload %s %s off" % (
+                self.defaultIntf(), attr)
+            self.cmd(cmd)
+
+        def updateIP():
+            return ip.split('/')[0]
+
+        self.defaultIntf().updateIP = updateIP
+
+    def terminate(self):
+        self.cmd('ip -4 link remove link %s' % self.vlanIntf)
+        super(TaggedIPv4Host, self).terminate()
+
+class DbufHost(IPv4Host):
+
+    def __init__(self, name, inNamespace=False, **params):
+        super(DbufHost, self).__init__(name, inNamespace, **params)
+
+    def config(self, drainIp=None, drainMac=None, **_params):
+        super(DbufHost, self).config(**_params)
+        self.setDrainIpAndMac(self.defaultIntf(), drainIp, drainMac)
+        self.startDbuf()
+
+    def startDbuf(self):
+        args = map(str, [
+            "-max_queues",
+            DBUF_NUM_QUEUES,
+            "-max_packet_slots_per_queue",
+            DBUF_MAX_PKTS_PER_QUEUE,
+            "-queue_drop_timeout",
+            DBUF_DROP_TIMEOUT_SEC,
+        ])
+        # Send to background
+        cmd = '/usr/local/bin/dbuf %s > /tmp/dbuf_%s.log 2>&1 &' \
+              % (" ".join(args), self.name)
+        print(cmd)
+        self.cmd(cmd)
+
+    def setDrainIpAndMac(self, intf, drainIp=None, drainMac=None):
+        if drainIp:
+            self.setHostRoute(drainIp, intf)
+            if drainMac:
+                self.setARP(drainIp, drainMac)
+
+
+class DualHomedIpv4Host(Host):
+    """A dual homed host that can be configured with an IPv4 gateway (default route).
+    """
+
+    def __init__(self, name, **kwargs):
+        super(DualHomedIpv4Host, self).__init__(name, **kwargs)
+        self.bond0 = None
+
+    def config(self, ip=None, gw=None, **kwargs):
+        super(DualHomedIpv4Host, self).config(**kwargs)
+        intf0 = self.intfs[0].name
+        intf1 = self.intfs[1].name
+        self.bond0 = "%s-bond0" % self.name
+        self.cmd('modprobe bonding')
+        self.cmd('ip link add %s type bond miimon 100 mode balance-xor xmit_hash_policy layer2+3' %
+                 self.bond0)
+        self.cmd('ip link set %s down' % intf0)
+        self.cmd('ip link set %s down' % intf1)
+        self.cmd('ip link set %s master %s' % (intf0, self.bond0))
+        self.cmd('ip link set %s master %s' % (intf1, self.bond0))
+        self.cmd('ip addr flush dev %s' % intf0)
+        self.cmd('ip addr flush dev %s' % intf1)
+        self.cmd('ip link set %s up' % self.bond0)
+
+        self.cmd('sysctl -w net.ipv4.ip_forward=0')
+        self.cmd('ip -4 addr add %s dev %s' % (ip, self.bond0))
+        if gw:
+            self.cmd('ip -4 route add default via %s' % gw)
+        # Disable offload
+        for attr in ["rx", "tx", "sg"]:
+            cmd = "/sbin/ethtool --offload %s %s off" % (self.defaultIntf(), attr)
+            self.cmd(cmd)
+
+    def terminate(self, **kwargs):
+        self.cmd('ip link set %s down' % self.bond0)
+        self.cmd('ip link delete %s' % self.bond0)
+        super(DualHomedIpv4Host, self).terminate()
+
+
+class DualHomedDbufHost(DualHomedIpv4Host, DbufHost):
+
+    def __init__(self, name, inNamespace=False, **params):
+        super(DualHomedDbufHost, self).__init__(name, inNamespace=inNamespace, **params)
+
+    def config(self, drainIp=None, drainMac=None, **_params):
+        super(DualHomedDbufHost, self).config(**_params)
+        self.setDrainIpAndMac(self.bond0, drainIp, drainMac)
diff --git a/mininet/toposcripts/topo-leafspine-param.py b/mininet/toposcripts/topo-leafspine-param.py
new file mode 100755
index 0000000..0b5fe2b
--- /dev/null
+++ b/mininet/toposcripts/topo-leafspine-param.py
@@ -0,0 +1,60 @@
+#!/usr/bin/python
+
+# SPDX-FileCopyrightText: 2022-present Intel Corporation
+# SPDX-License-Identifier: Apache-2.0
+
+import argparse
+
+from mininet.cli import CLI
+from mininet.log import setLogLevel, info
+from mininet.net import Mininet
+from mininet.topo import Topo
+from stratum import StratumBmv2Switch
+
+from mn_lib import IPv4Host
+from mn_lib import TaggedIPv4Host
+
+CPU_PORT = 255
+
+
+class TutorialTopo(Topo):
+    """2x2 fabric topology with IPv4 hosts"""
+
+    def __init__(self, *args, **kwargs):
+        Topo.__init__(self, *args, **kwargs)
+
+        spines = []
+        leaves = []
+{{- range $i, $junk := until (.Values.numLeaves|int) -}}
+{{- $leaf := printf "leaf%d" (add $i 1) }}
+        info( '*** Creating Leaf ' + '{{ $leaf }}\n' )
+        leaves.append(self.addSwitch(name='{{ $leaf }}', cls=StratumBmv2Switch, cpuport=CPU_PORT))
+{{- end }}
+
+{{- range $i, $junk := until (.Values.numSpines|int) -}}
+{{- $spine := printf "spine%d" (add $i 1) }}
+        info( '*** Creating Spine ' + '{{ $spine }}\n' )
+        spines.append(self.addSwitch(name='{{ $spine }}', cls=StratumBmv2Switch, cpuport=CPU_PORT))
+{{- end }}
+
+        for spine in spines:
+            for leaf in leaves:
+                info( '*** Creating link ' + str(spine) + ' ' + str(leaf) + '\n')
+                self.addLink(spine, leaf)
+                info( '*** Created link ' + str(spine) + ' ' + str(leaf) + '\n')
+
+
+def main():
+    net = Mininet(topo=TutorialTopo(), controller=None)
+    net.start()
+    CLI(net)
+    net.stop()
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(
+        description='Mininet topology script for 2x2 fabric with stratum_bmv2 and IPv4 hosts')
+    args = parser.parse_args()
+    setLogLevel('info')
+
+    main()
diff --git a/mininet/toposcripts/topo-leafspine.py b/mininet/toposcripts/topo-leafspine.py
new file mode 100755
index 0000000..d3f5941
--- /dev/null
+++ b/mininet/toposcripts/topo-leafspine.py
@@ -0,0 +1,80 @@
+#!/usr/bin/python
+
+# SPDX-FileCopyrightText: 2022-present Intel Corporation
+# SPDX-License-Identifier: Apache-2.0
+
+import argparse
+
+from mininet.cli import CLI
+from mininet.log import setLogLevel
+from mininet.net import Mininet
+from mininet.topo import Topo
+from stratum import StratumBmv2Switch
+
+from mn_lib import IPv4Host
+from mn_lib import TaggedIPv4Host
+
+CPU_PORT = 255
+
+
+class TutorialTopo(Topo):
+    """2x2 fabric topology with IPv4 hosts"""
+
+    def __init__(self, *args, **kwargs):
+        Topo.__init__(self, *args, **kwargs)
+
+        # Leaves
+        # gRPC port 50001
+        leaf1 = self.addSwitch('leaf1', cls=StratumBmv2Switch, cpuport=CPU_PORT)
+        # gRPC port 50002
+        leaf2 = self.addSwitch('leaf2', cls=StratumBmv2Switch, cpuport=CPU_PORT)
+
+        # Spines
+        # gRPC port 50003
+        spine1 = self.addSwitch('spine1', cls=StratumBmv2Switch, cpuport=CPU_PORT)
+        # gRPC port 50004
+        spine2 = self.addSwitch('spine2', cls=StratumBmv2Switch, cpuport=CPU_PORT)
+
+        # Switch Links
+        self.addLink(spine1, leaf1)
+        self.addLink(spine1, leaf2)
+        self.addLink(spine2, leaf1)
+        self.addLink(spine2, leaf2)
+
+        # IPv4 hosts attached to leaf 1
+        h1a = self.addHost('h1a', cls=IPv4Host, mac="00:00:00:00:00:1A",
+                           ip='172.16.1.1/24', gw='172.16.1.254')
+        h1b = self.addHost('h1b', cls=IPv4Host, mac="00:00:00:00:00:1B",
+                           ip='172.16.1.2/24', gw='172.16.1.254')
+        h1c = self.addHost('h1c', cls=TaggedIPv4Host, mac="00:00:00:00:00:1C",
+                           ip='172.16.1.3/24', gw='172.16.1.254', vlan=100)
+        h2 = self.addHost('h2', cls=TaggedIPv4Host, mac="00:00:00:00:00:20",
+                          ip='172.16.2.1/24', gw='172.16.2.254', vlan=200)
+        self.addLink(h1a, leaf1)  # port 3
+        self.addLink(h1b, leaf1)  # port 4
+        self.addLink(h1c, leaf1)  # port 5
+        self.addLink(h2, leaf1)  # port 6
+
+        # IPv4 hosts attached to leaf 2
+        h3 = self.addHost('h3', cls=TaggedIPv4Host, mac="00:00:00:00:00:30",
+                          ip='172.16.3.1/24', gw='172.16.3.254', vlan=300)
+        h4 = self.addHost('h4', cls=IPv4Host, mac="00:00:00:00:00:40",
+                          ip='172.16.4.1/24', gw='172.16.4.254')
+        self.addLink(h3, leaf2)  # port 3
+        self.addLink(h4, leaf2)  # port 4
+
+
+def main():
+    net = Mininet(topo=TutorialTopo(), controller=None)
+    net.start()
+    CLI(net)
+    net.stop()
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(
+        description='Mininet topology script for 2x2 fabric with stratum_bmv2 and IPv4 hosts')
+    args = parser.parse_args()
+    setLogLevel('info')
+
+    main()