blob: e6deb97712dd2fe9b2c444d9da524250ef6208dc [file] [log] [blame]
Jon Hall6cc1da82020-08-03 12:55:07 -07001# coding=utf-8
2# Copyright 2018-present Open Networking Foundation
3# SPDX-License-Identifier: Apache-2.0
4
5'''
6This module contains a switch class for Mininet: StratumBmv2Switch
7
8Prerequisites
9-------------
101. Docker- mininet+stratum_bmv2 image:
11$ cd stratum
12$ docker build -t <some tag> -f tools/mininet/Dockerfile .
13
14Usage
15-----
16From withing the Docker container, you can run Mininet using the following:
17$ mn --custom /root/stratum.py --switch stratum-bmv2 --controller none
18
19Advanced Usage
20--------------
21You can use this class in a Mininet topology script by including:
22
23from stratum import ONOSStratumBmv2Switch
24
25You will probably need to update your Python path. From within the Docker image:
26
27PYTHONPATH=$PYTHONPATH:/root ./<your script>.py
28
29Notes
30-----
31This code has been adapted from the ONOSBmv2Switch class defined in the ONOS project
32(tools/dev/mininet/bmv2.py).
33
34'''
35
36import json
37import multiprocessing
38import os
39import socket
40import threading
41import time
42
43from mininet.log import warn
44from mininet.node import Switch, Host
45
46DEFAULT_NODE_ID = 1
47DEFAULT_CPU_PORT = 255
48DEFAULT_PIPECONF = "org.onosproject.pipelines.basic"
49STRATUM_BMV2 = 'stratum_bmv2'
50STRATUM_INIT_PIPELINE = '/root/dummy.json'
51MAX_CONTROLLERS_PER_NODE = 10
52BMV2_LOG_LINES = 5
53
54
55def writeToFile(path, value):
56 with open(path, "w") as f:
57 f.write(str(value))
58
59
60def pickUnusedPort():
61 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
62 s.bind(('localhost', 0))
63 addr, port = s.getsockname()
64 s.close()
65 return port
66
67
68def watchdog(sw):
69 try:
70 writeToFile(sw.keepaliveFile,
71 "Remove this file to terminate %s" % sw.name)
72 while True:
73 if StratumBmv2Switch.mininet_exception == 1 \
74 or not os.path.isfile(sw.keepaliveFile):
75 sw.stop()
76 return
77 if sw.stopped:
78 return
79 if sw.bmv2popen.poll() is None:
80 # All good, no return code, still running.
81 time.sleep(1)
82 else:
83 warn("\n*** WARN: switch %s died ☠️ \n" % sw.name)
84 sw.printLog()
85 print("-" * 80) + "\n"
86 # Close log file, set as stopped etc.
87 sw.stop()
88 return
89 except Exception as e:
90 warn("*** ERROR: " + e.message)
91 sw.stop()
92
93
94class StratumBmv2Switch(Switch):
95 # Shared value used to notify to all instances of this class that a Mininet
96 # exception occurred. Mininet exception handling doesn't call the stop()
97 # method, so the mn process would hang after clean-up since Bmv2 would still
98 # be running.
99 mininet_exception = multiprocessing.Value('i', 0)
100
101 nextGrpcPort = 50001
102
103 def __init__(self, name, json=STRATUM_INIT_PIPELINE, loglevel="warn",
104 cpuport=DEFAULT_CPU_PORT, pipeconf=DEFAULT_PIPECONF,
105 onosdevid=None,
106 **kwargs):
107 Switch.__init__(self, name, **kwargs)
108 self.grpcPort = StratumBmv2Switch.nextGrpcPort
109 StratumBmv2Switch.nextGrpcPort += 1
110 self.cpuPort = cpuport
111 self.json = json
112 self.loglevel = loglevel
113 self.tmpDir = '/tmp/%s' % self.name
114 self.logfile = '%s/stratum_bmv2.log' % self.tmpDir
115 self.netcfgFile = '%s/onos-netcfg.json' % self.tmpDir
116 self.chassisConfigFile = '%s/chassis-config.txt' % self.tmpDir
117 self.pipeconfId = pipeconf
118 self.longitude = kwargs['longitude'] if 'longitude' in kwargs else None
119 self.latitude = kwargs['latitude'] if 'latitude' in kwargs else None
120 if onosdevid is not None and len(onosdevid) > 0:
121 self.onosDeviceId = onosdevid
122 else:
123 # The "device:" prefix is required by ONOS.
124 self.onosDeviceId = "device:%s" % self.name
125 self.nodeId = DEFAULT_NODE_ID
126 self.logfd = None
127 self.bmv2popen = None
128 self.stopped = True
129 # In case of exceptions, mininet removes *.out files from /tmp. We use
130 # this as a signal to terminate the switch instance (if active).
131 self.keepaliveFile = '/tmp/%s-watchdog.out' % self.name
132
133 # Remove files from previous executions
134 self.cleanupTmpFiles()
135 os.mkdir(self.tmpDir)
136
137 def getOnosNetcfg(self):
138 basicCfg = {
139 "managementAddress": "grpc://localhost:%d?device_id=%d" % (
140 self.grpcPort, self.nodeId),
141 "driver": "stratum-bmv2",
142 "pipeconf": self.pipeconfId
143 }
144
145 if self.longitude and self.latitude:
146 basicCfg["longitude"] = self.longitude
147 basicCfg["latitude"] = self.latitude
148
149 netcfg = {
150 "devices": {
151 self.onosDeviceId: {
152 "basic": basicCfg
153 }
154 }
155 }
156
157 return netcfg
158
159 def getChassisConfig(self):
160 config = """description: "stratum_bmv2 {name}"
161chassis {{
162 platform: PLT_P4_SOFT_SWITCH
163 name: "{name}"
164}}
165nodes {{
166 id: {nodeId}
167 name: "{name} node {nodeId}"
168 slot: 1
169 index: 1
170}}\n""".format(name=self.name, nodeId=self.nodeId)
171
172 intf_number = 1
173 for intf_name in self.intfNames():
174 if intf_name == 'lo':
175 continue
176 config = config + """singleton_ports {{
177 id: {intfNumber}
178 name: "{intfName}"
179 slot: 1
180 port: {intfNumber}
181 channel: 1
182 speed_bps: 10000000000
183 config_params {{
184 admin_state: ADMIN_STATE_ENABLED
185 }}
186 node: {nodeId}
187}}\n""".format(intfName=intf_name, intfNumber=intf_number, nodeId=self.nodeId)
188 intf_number += 1
189
190 return config
191
192 def start(self, controllers):
193
194 if not self.stopped:
195 warn("*** %s is already running!\n" % self.name)
196 return
197
198 writeToFile("%s/grpc-port.txt" % self.tmpDir, self.grpcPort)
199 with open(self.chassisConfigFile, 'w') as fp:
200 fp.write(self.getChassisConfig())
201 with open(self.netcfgFile, 'w') as fp:
202 json.dump(self.getOnosNetcfg(), fp, indent=2)
203
204 args = [
205 STRATUM_BMV2,
206 '-device_id=%d' % self.nodeId,
207 '-chassis_config_file=%s' % self.chassisConfigFile,
208 '-forwarding_pipeline_configs_file=%s/pipe.txt' % self.tmpDir,
209 '-persistent_config_dir=%s' % self.tmpDir,
210 '-initial_pipeline=%s' % STRATUM_INIT_PIPELINE,
211 '-cpu_port=%s' % self.cpuPort,
212 '-external_stratum_urls=0.0.0.0:%d' % self.grpcPort,
213 '-local_stratum_url=localhost:%d' % pickUnusedPort(),
214 '-max_num_controllers_per_node=%d' % MAX_CONTROLLERS_PER_NODE,
215 '-write_req_log_file=%s/write-reqs.txt' % self.tmpDir,
216 '-logtostderr=true',
217 '-bmv2_log_level=%s' % self.loglevel,
218 ]
219
220 cmd_string = " ".join(args)
221
222 try:
223 # Write cmd_string to log for debugging.
224 self.logfd = open(self.logfile, "w")
225 self.logfd.write(cmd_string + "\n\n" + "-" * 80 + "\n\n")
226 self.logfd.flush()
227
228 self.bmv2popen = self.popen(cmd_string, stdout=self.logfd, stderr=self.logfd)
229 print "⚡️ %s @ %d" % (STRATUM_BMV2, self.grpcPort)
230
231 # We want to be notified if stratum_bmv2 quits prematurely...
232 self.stopped = False
233 threading.Thread(target=watchdog, args=[self]).start()
234
235 except Exception:
236 StratumBmv2Switch.mininet_exception = 1
237 self.stop()
238 self.printLog()
239 raise
240
241 def printLog(self):
242 if os.path.isfile(self.logfile):
243 print "-" * 80
244 print "%s log (from %s):" % (self.name, self.logfile)
245 with open(self.logfile, 'r') as f:
246 lines = f.readlines()
247 if len(lines) > BMV2_LOG_LINES:
248 print "..."
249 for line in lines[-BMV2_LOG_LINES:]:
250 print line.rstrip()
251
252 def cleanupTmpFiles(self):
253 self.cmd("rm -rf %s" % self.tmpDir)
254
255 def stop(self, deleteIntfs=True):
256 """Terminate switch."""
257 self.stopped = True
258 if self.bmv2popen is not None:
259 if self.bmv2popen.poll() is None:
260 self.bmv2popen.terminate()
261 self.bmv2popen.wait()
262 self.bmv2popen = None
263 if self.logfd is not None:
264 self.logfd.close()
265 self.logfd = None
266 Switch.stop(self, deleteIntfs)
267
268
269class NoOffloadHost(Host):
270 def __init__(self, name, inNamespace=True, **params):
271 Host.__init__(self, name, inNamespace=inNamespace, **params)
272
273 def config(self, **params):
274 r = super(Host, self).config(**params)
275 for off in ["rx", "tx", "sg"]:
276 cmd = "/sbin/ethtool --offload %s %s off" \
277 % (self.defaultIntf(), off)
278 self.cmd(cmd)
279 return r
280
281
282# Exports for bin/mn
283switches = {'stratum-bmv2': StratumBmv2Switch}
284
285hosts = {'no-offload-host': NoOffloadHost}