blob: 10bba5f98634a50834d65ce0125c5ff249d27d18 [file] [log] [blame]
Ayaka Koshibe4f23b0a2015-11-18 17:30:42 -08001#!/usr/bin/env python
2
HIGUCHI Yutad95f3cd2015-12-15 15:05:22 -08003import json
4
Ayaka Koshibe4f23b0a2015-11-18 17:30:42 -08005from mininet.net import Mininet
6from mininet.node import UserSwitch, DefaultController, RemoteController, Host
7from mininet.topo import Topo
HIGUCHI Yutad95f3cd2015-12-15 15:05:22 -08008from mininet.log import setLogLevel, info, error, warn
Ayaka Koshibe4f23b0a2015-11-18 17:30:42 -08009from mininet.cli import CLI
10from mininet.link import OVSIntf
HIGUCHI Yutad95f3cd2015-12-15 15:05:22 -080011from mininet.util import quietRun
Ayaka Koshibe4f23b0a2015-11-18 17:30:42 -080012
13from opticalUtils import LINCSwitch, LINCLink
14
15class 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 Yutad95f3cd2015-12-15 15:05:22 -080039 # Note: This method will return the name of the swich, not the switch object
Ayaka Koshibe4f23b0a2015-11-18 17:30:42 -080040 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
90class OpticalDomain(Domain):
91 """ An emulated optical metro core. It is Domain 0. """
92 def build(self):
Ayaka Koshibe4f23b0a2015-11-18 17:30:42 -080093 for i in range (1,4):
HIGUCHI Yuta099d75f2015-12-04 17:28:21 -080094 oean = { "optical.regens": 0 }
Ayaka Koshibe4f23b0a2015-11-18 17:30:42 -080095 self.addSwitch('OE%s' % i, dpid='0000ffffffffff0%s' % i, annotations=oean, cls=LINCSwitch)
96
HIGUCHI Yutad95f3cd2015-12-15 15:05:22 -080097 # 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 Leenheer32fc3d22015-12-15 21:36:39 -080099 an = { "durable": "true" }
HIGUCHI Yutad95f3cd2015-12-15 15:05:22 -0800100 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 Koshibe4f23b0a2015-11-18 17:30:42 -0800103
104class 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 Leenheercba25642015-12-17 15:13:58 -0800121 l_nsw.append(self.addSwitch('swn%s%s' % (self.getId(), sw+1), cls=UserSwitch, dpopts='--no-local-port'))
Ayaka Koshibe4f23b0a2015-11-18 17:30:42 -0800122
123 # create connection point to optical core (a leaf switch)
Marc De Leenheercba25642015-12-17 15:13:58 -0800124 tsw = self.addSwitch('swm%s01' % self.getId(), cls=UserSwitch, dpopts='--no-local-port')
Ayaka Koshibe4f23b0a2015-11-18 17:30:42 -0800125 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 Leenheercba25642015-12-17 15:13:58 -0800130 msw = self.addSwitch('swm%s0%s' % (self.getId(), sw+1), cls=UserSwitch, dpopts='--no-local-port')
Ayaka Koshibe4f23b0a2015-11-18 17:30:42 -0800131 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 Yutad95f3cd2015-12-15 15:05:22 -0800148 # Note: OVS port number '1' reserved for port facing the fabric
Ayaka Koshibe4f23b0a2015-11-18 17:30:42 -0800149 self.addLink(tname, name, port1=1)
150
151 def getTether(self):
HIGUCHI Yutad95f3cd2015-12-15 15:05:22 -0800152 """ get the switch name of this fabric facing the core """
Ayaka Koshibe4f23b0a2015-11-18 17:30:42 -0800153 return self.__tether
154
155
156class 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 Koshibe4f23b0a2015-11-18 17:30:42 -0800167def 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 Yutad95f3cd2015-12-15 15:05:22 -0800186 # 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 Koshibe4f23b0a2015-11-18 17:30:42 -0800196 # 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 Yutad95f3cd2015-12-15 15:05:22 -0800203 # adding cross-connect links
Ayaka Koshibe4f23b0a2015-11-18 17:30:42 -0800204 for i in range(1,len(domains)):
HIGUCHI Yutad95f3cd2015-12-15 15:05:22 -0800205 # 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 Koshibe4f23b0a2015-11-18 17:30:42 -0800216
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 Yutad95f3cd2015-12-15 15:05:22 -0800228 # 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
235 output = quietRun('%s/tools/test/bin/onos-netcfg %s %s &'\
236 % (LINCSwitch.onosDir,
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 Koshibe4f23b0a2015-11-18 17:30:42 -0800244 CLI(net)
245 net.stop()
246 LINCSwitch.shutdownOE()
247
248if __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)