Ayaka Koshibe | 4f23b0a | 2015-11-18 17:30:42 -0800 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | |
HIGUCHI Yuta | d95f3cd | 2015-12-15 15:05:22 -0800 | [diff] [blame] | 3 | import json |
| 4 | |
Ayaka Koshibe | 4f23b0a | 2015-11-18 17:30:42 -0800 | [diff] [blame] | 5 | from mininet.net import Mininet |
| 6 | from mininet.node import UserSwitch, DefaultController, RemoteController, Host |
| 7 | from mininet.topo import Topo |
HIGUCHI Yuta | d95f3cd | 2015-12-15 15:05:22 -0800 | [diff] [blame] | 8 | from mininet.log import setLogLevel, info, error, warn |
Ayaka Koshibe | 4f23b0a | 2015-11-18 17:30:42 -0800 | [diff] [blame] | 9 | from mininet.cli import CLI |
| 10 | from mininet.link import OVSIntf |
HIGUCHI Yuta | d95f3cd | 2015-12-15 15:05:22 -0800 | [diff] [blame] | 11 | from mininet.util import quietRun |
Ayaka Koshibe | 4f23b0a | 2015-11-18 17:30:42 -0800 | [diff] [blame] | 12 | |
| 13 | from opticalUtils import LINCSwitch, LINCLink |
| 14 | |
| 15 | class Domain(object): |
| 16 | """ |
| 17 | A container for switch, host, link, and controller information to be dumped |
| 18 | into the Mininet mid-level API. |
| 19 | """ |
| 20 | |
| 21 | def __init__ (self, did=0): |
| 22 | # each Domain has a numeric ID for sanity/convenience |
| 23 | self.__dId = did |
| 24 | |
| 25 | # information about network elements - for calling the "mid-level" APIs |
| 26 | self.__ctrls = {} |
| 27 | self.__switches = {} |
| 28 | self.__hosts = {} |
| 29 | self.__links = {} |
| 30 | # maps of devices, hosts, and controller names to actual objects |
| 31 | self.__smap = {} |
| 32 | self.__hmap = {} |
| 33 | self.__cmap = {} |
| 34 | |
| 35 | def addController(self, name, **args): |
| 36 | self.__ctrls[name] = args if args else {} |
| 37 | return name |
| 38 | |
HIGUCHI Yuta | d95f3cd | 2015-12-15 15:05:22 -0800 | [diff] [blame] | 39 | # Note: This method will return the name of the swich, not the switch object |
Ayaka Koshibe | 4f23b0a | 2015-11-18 17:30:42 -0800 | [diff] [blame] | 40 | def addSwitch(self, name, **args): |
| 41 | self.__switches[name] = args if args else {} |
| 42 | return name |
| 43 | |
| 44 | def addHost(self, name, **args): |
| 45 | self.__hosts[name] = args if args else {} |
| 46 | return name |
| 47 | |
| 48 | def addLink(self, src, dst, **args): |
| 49 | self.__links[(src, dst)] = args if args else {} |
| 50 | return (src, dst) |
| 51 | |
| 52 | def getId( self): |
| 53 | return self.__dId |
| 54 | |
| 55 | def getControllers(self, name=None): |
| 56 | return self.__cmap.values() if not name else self.__cmap.get(name) |
| 57 | |
| 58 | def getSwitches(self, name=None): |
| 59 | return self.__smap.values() if not name else self.__smap.get(name) |
| 60 | |
| 61 | def getHosts(self, name=None): |
| 62 | return self.__hmap.values() if not name else self.__hmap.get(name) |
| 63 | |
| 64 | def injectInto(self, net): |
| 65 | """ Adds available topology info to a supplied Mininet object. """ |
| 66 | # add switches, hosts, then links to mininet object |
| 67 | for sw, args in self.__switches.iteritems(): |
| 68 | self.__smap[sw] = net.addSwitch(sw, **args) |
| 69 | for h, args in self.__hosts.iteritems(): |
| 70 | self.__hmap[h] = net.addHost(h, **args) |
| 71 | for l, args in self.__links.iteritems(): |
| 72 | src = self.__smap.get(l[0]) |
| 73 | dst = self.__smap.get(l[1]) |
| 74 | net.addLink(src if src else self.__hmap.get(l[0]), |
| 75 | dst if dst else self.__hmap.get(l[1]), **args) |
| 76 | # then controllers |
| 77 | for c, args in self.__ctrls.iteritems(): |
| 78 | self.__cmap[c] = net.addController(c, **args) |
| 79 | |
| 80 | def start(self): |
| 81 | """ starts the switches with the correct controller. """ |
| 82 | map(lambda c: c.start(), self.__cmap.values()) |
| 83 | map(lambda s: s.start(self.__cmap.values()), self.__smap.values()) |
| 84 | |
| 85 | def build(self, *args): |
| 86 | """ override for custom topology, similar to Topo """ |
| 87 | pass |
| 88 | |
| 89 | |
| 90 | class OpticalDomain(Domain): |
| 91 | """ An emulated optical metro core. It is Domain 0. """ |
| 92 | def build(self): |
Ayaka Koshibe | 4f23b0a | 2015-11-18 17:30:42 -0800 | [diff] [blame] | 93 | for i in range (1,4): |
HIGUCHI Yuta | 099d75f | 2015-12-04 17:28:21 -0800 | [diff] [blame] | 94 | oean = { "optical.regens": 0 } |
Ayaka Koshibe | 4f23b0a | 2015-11-18 17:30:42 -0800 | [diff] [blame] | 95 | self.addSwitch('OE%s' % i, dpid='0000ffffffffff0%s' % i, annotations=oean, cls=LINCSwitch) |
| 96 | |
HIGUCHI Yuta | d95f3cd | 2015-12-15 15:05:22 -0800 | [diff] [blame] | 97 | # ROADM port number OE"1" -> OE'2' = "1"'2'00 |
| 98 | # leaving port number up to 100 open for use by Och port |
Marc De Leenheer | 32fc3d2 | 2015-12-15 21:36:39 -0800 | [diff] [blame] | 99 | an = { "durable": "true" } |
HIGUCHI Yuta | d95f3cd | 2015-12-15 15:05:22 -0800 | [diff] [blame] | 100 | self.addLink('OE1', 'OE2', port1=1200, port2=2100, annotations=an, cls=LINCLink) |
| 101 | self.addLink('OE2', 'OE3', port1=2300, port2=3200, annotations=an, cls=LINCLink) |
| 102 | self.addLink('OE3', 'OE1', port1=3100, port2=1300, annotations=an, cls=LINCLink) |
Ayaka Koshibe | 4f23b0a | 2015-11-18 17:30:42 -0800 | [diff] [blame] | 103 | |
| 104 | class FabricDomain(Domain): |
| 105 | """ |
| 106 | An emulated CO fabric, which is basically a K(n,m) bipartite graph. |
| 107 | |
| 108 | Each FabricDomain should be given a unique Domain ID (did) to ensure unique |
| 109 | names and addressing. |
| 110 | """ |
| 111 | def __init__(self, did): |
| 112 | Domain.__init__(self, did) |
| 113 | |
| 114 | def build(self, n=2, m=3, f=2): |
| 115 | # K(n,m) in bipartite graph |
| 116 | l_nsw=[] |
| 117 | l_msw=[] |
| 118 | |
| 119 | # create n spine switches |
| 120 | for sw in range(n): |
Marc De Leenheer | cba2564 | 2015-12-17 15:13:58 -0800 | [diff] [blame] | 121 | l_nsw.append(self.addSwitch('swn%s%s' % (self.getId(), sw+1), cls=UserSwitch, dpopts='--no-local-port')) |
Ayaka Koshibe | 4f23b0a | 2015-11-18 17:30:42 -0800 | [diff] [blame] | 122 | |
| 123 | # create connection point to optical core (a leaf switch) |
Marc De Leenheer | cba2564 | 2015-12-17 15:13:58 -0800 | [diff] [blame] | 124 | tsw = self.addSwitch('swm%s01' % self.getId(), cls=UserSwitch, dpopts='--no-local-port') |
Ayaka Koshibe | 4f23b0a | 2015-11-18 17:30:42 -0800 | [diff] [blame] | 125 | self.addTether(tsw, 'sw000%s' % self.getId(), '0000ffffffff000%s' % self.getId()) |
| 126 | l_msw.append(tsw) |
| 127 | |
| 128 | # attach f hosts to last m-1 leaves |
| 129 | for sw in range(1, m): |
Marc De Leenheer | cba2564 | 2015-12-17 15:13:58 -0800 | [diff] [blame] | 130 | msw = self.addSwitch('swm%s0%s' % (self.getId(), sw+1), cls=UserSwitch, dpopts='--no-local-port') |
Ayaka Koshibe | 4f23b0a | 2015-11-18 17:30:42 -0800 | [diff] [blame] | 131 | l_msw.append(msw) |
| 132 | for h in range(f): |
| 133 | host = self.addHost('h%s%s' % (self.getId(), sw * f+h+1), cls=IpHost, |
| 134 | ip='10.0.%s.%s/24' % ((self.getId()+sw+1), (f+1)), |
| 135 | gateway='10.0.%s.254' % (self.getId()+sw+1)) |
| 136 | self.addLink(host, msw) |
| 137 | # link up spines and leaves |
| 138 | for nsw in l_nsw: |
| 139 | for msw in l_msw: |
| 140 | self.addLink(nsw, msw) |
| 141 | |
| 142 | def addTether(self, name, tname, tdpid): |
| 143 | """ |
| 144 | add an OVS with name 'tname' and dpid 'tdpid' for connecting fabric |
| 145 | domains to the core. name: the UserSwitch to connect the OVS to. |
| 146 | """ |
| 147 | self.__tether = self.addSwitch(tname, dpid=tdpid) |
HIGUCHI Yuta | d95f3cd | 2015-12-15 15:05:22 -0800 | [diff] [blame] | 148 | # Note: OVS port number '1' reserved for port facing the fabric |
Ayaka Koshibe | 4f23b0a | 2015-11-18 17:30:42 -0800 | [diff] [blame] | 149 | self.addLink(tname, name, port1=1) |
| 150 | |
| 151 | def getTether(self): |
HIGUCHI Yuta | d95f3cd | 2015-12-15 15:05:22 -0800 | [diff] [blame] | 152 | """ get the switch name of this fabric facing the core """ |
Ayaka Koshibe | 4f23b0a | 2015-11-18 17:30:42 -0800 | [diff] [blame] | 153 | return self.__tether |
| 154 | |
| 155 | |
| 156 | class IpHost(Host): |
| 157 | def __init__(self, name, gateway, *args, **kwargs): |
| 158 | super(IpHost, self).__init__(name, *args, **kwargs) |
| 159 | self.gateway = gateway |
| 160 | |
| 161 | def config(self, **kwargs): |
| 162 | Host.config(self, **kwargs) |
| 163 | mtu = "ifconfig "+self.name+"-eth0 mtu 1490" |
| 164 | self.cmd(mtu) |
| 165 | self.cmd('ip route add default via %s' % self.gateway) |
| 166 | |
Ayaka Koshibe | 4f23b0a | 2015-11-18 17:30:42 -0800 | [diff] [blame] | 167 | def setup(argv): |
| 168 | domains = [] |
| 169 | ctlsets = sys.argv[1:] |
| 170 | |
| 171 | # the controllers for the optical domain |
| 172 | d0 = OpticalDomain() |
| 173 | domains.append(d0) |
| 174 | ctls = ctlsets[0].split(',') |
| 175 | for i in range (len(ctls)): |
| 176 | d0.addController('c0%s' % i, controller=RemoteController, ip=ctls[i]) |
| 177 | |
| 178 | # the fabric domains - position 1 for domain 1, 2 for 2 ... |
| 179 | for i in range (1,len(ctlsets)): |
| 180 | f = FabricDomain(i) |
| 181 | domains.append(f) |
| 182 | ctls = ctlsets[i].split(',') |
| 183 | for j in range (len(ctls)): |
| 184 | f.addController('c%s%s' % (i,j), controller=RemoteController, ip=ctls[j]) |
| 185 | |
HIGUCHI Yuta | d95f3cd | 2015-12-15 15:05:22 -0800 | [diff] [blame] | 186 | # netcfg for each domains |
| 187 | # Note: Separate netcfg for domain0 is created in opticalUtils |
| 188 | domainCfgs = [] |
| 189 | for i in range (0,len(ctlsets)): |
| 190 | cfg = {} |
| 191 | cfg['devices'] = {} |
| 192 | cfg['ports'] = {} |
| 193 | cfg['links'] = {} |
| 194 | domainCfgs.append(cfg) |
| 195 | |
Ayaka Koshibe | 4f23b0a | 2015-11-18 17:30:42 -0800 | [diff] [blame] | 196 | # make/setup Mininet object |
| 197 | net = Mininet() |
| 198 | for d in domains: |
| 199 | d.build() |
| 200 | d.injectInto(net) |
| 201 | |
| 202 | # connect COs to core - sort of hard-wired at this moment |
HIGUCHI Yuta | d95f3cd | 2015-12-15 15:05:22 -0800 | [diff] [blame] | 203 | # adding cross-connect links |
Ayaka Koshibe | 4f23b0a | 2015-11-18 17:30:42 -0800 | [diff] [blame] | 204 | for i in range(1,len(domains)): |
HIGUCHI Yuta | d95f3cd | 2015-12-15 15:05:22 -0800 | [diff] [blame] | 205 | # add 10 cross-connect links between domains |
| 206 | xcPortNo=2 |
| 207 | ochPortNo=10 |
| 208 | for j in range(0, 10): |
| 209 | an = { "bandwidth": 10, "durable": "true" } |
| 210 | net.addLink(domains[i].getTether(), d0.getSwitches('OE%s' % i), |
| 211 | port1=xcPortNo+j, port2=ochPortNo+j, speed=10000, annotations=an, cls=LINCLink) |
| 212 | |
| 213 | xcId = 'of:' + domains[i].getSwitches(name=domains[i].getTether()).dpid + '/' + str(xcPortNo+j) |
| 214 | ochId = 'of:' + d0.getSwitches('OE%s' % i).dpid + '/' + str(ochPortNo+j) |
| 215 | domainCfgs[i]['ports'][xcId] = {'cross-connect': {'remote': ochId}} |
Ayaka Koshibe | 4f23b0a | 2015-11-18 17:30:42 -0800 | [diff] [blame] | 216 | |
| 217 | # fire everything up |
| 218 | net.build() |
| 219 | map(lambda x: x.start(), domains) |
| 220 | |
| 221 | # create a minimal copy of the network for configuring LINC. |
| 222 | cfgnet = Mininet() |
| 223 | cfgnet.switches = net.switches |
| 224 | cfgnet.links = net.links |
| 225 | cfgnet.controllers = d0.getControllers() |
| 226 | LINCSwitch.bootOE(cfgnet, d0.getSwitches()) |
| 227 | |
HIGUCHI Yuta | d95f3cd | 2015-12-15 15:05:22 -0800 | [diff] [blame] | 228 | # send netcfg json to each CO-ONOS |
| 229 | for i in range(1,len(domains)): |
| 230 | info('*** Pushing Topology.json to CO-ONOS %d\n' % i) |
| 231 | filename = 'Topology%d.json' % i |
| 232 | with open(filename, 'w') as outfile: |
| 233 | json.dump(domainCfgs[i], outfile, indent=4, separators=(',', ': ')) |
| 234 | |
Devin Lim | 0d944e2 | 2017-06-23 15:17:53 -0700 | [diff] [blame] | 235 | output = quietRun('%s/onos-netcfg %s %s &'\ |
| 236 | % (LINCSwitch.runPackDir, |
HIGUCHI Yuta | d95f3cd | 2015-12-15 15:05:22 -0800 | [diff] [blame] | 237 | domains[i].getControllers()[0].ip, |
| 238 | filename), shell=True) |
| 239 | # successful output contains the two characters '{}' |
| 240 | # if there is more output than this, there is an issue |
| 241 | if output.strip('{}'): |
| 242 | warn('***WARNING: Could not push topology file to ONOS: %s\n' % output) |
| 243 | |
Ayaka Koshibe | 4f23b0a | 2015-11-18 17:30:42 -0800 | [diff] [blame] | 244 | CLI(net) |
| 245 | net.stop() |
| 246 | LINCSwitch.shutdownOE() |
| 247 | |
| 248 | if __name__ == '__main__': |
| 249 | setLogLevel('info') |
| 250 | import sys |
| 251 | if len(sys.argv) < 5: |
| 252 | print ("Usage: sudo -E ./metro.py ctl-set1 ... ctl-set4\n\n", |
| 253 | "Where ctl-set are comma-separated controller IP's") |
| 254 | else: |
| 255 | setup(sys.argv) |