3 ##############################################################################
4 # Copyright (c) 2021 Orange, Inc. and others. All rights reserved.
6 # All rights reserved. This program and the accompanying materials
7 # are made available under the terms of the Apache License, Version 2.0
8 # which accompanies this distribution, and is available at
9 # http://www.apache.org/licenses/LICENSE-2.0
10 ##############################################################################
12 # pylint: disable=no-member
16 # pylint: disable=wrong-import-order
26 # pylint: disable=import-error
29 SIMS = simulators.SIMS
31 HONEYNODE_OK_START_MSG = "Netconf SSH endpoint started successfully at 0.0.0.0"
32 KARAF_OK_START_MSG = re.escape(
33 "Blueprint container for bundle org.opendaylight.netconf.restconf")+".* was successfully created"
34 LIGHTY_OK_START_MSG = re.escape("lighty.io and RESTCONF-NETCONF started")
41 TYPE_APPLICATION_JSON = {'Content-Type': 'application/json', 'Accept': 'application/json'}
42 TYPE_APPLICATION_XML = {'Content-Type': 'application/xml', 'Accept': 'application/xml'}
44 CODE_SHOULD_BE_200 = 'Http status code should be 200'
45 CODE_SHOULD_BE_201 = 'Http status code should be 201'
47 SIM_LOG_DIRECTORY = os.path.join(os.path.dirname(os.path.realpath(__file__)), "log")
52 if "USE_ODL_ALT_RESTCONF_PORT" in os.environ:
53 RESTCONF_BASE_URL = "http://localhost:" + os.environ['USE_ODL_ALT_RESTCONF_PORT'] + "/rests"
55 RESTCONF_BASE_URL = "http://localhost:8181/rests"
57 if "USE_ODL_ALT_KARAF_INSTALL_DIR" in os.environ:
58 KARAF_INSTALLDIR = os.environ['USE_ODL_ALT_KARAF_INSTALL_DIR']
60 KARAF_INSTALLDIR = "karaf"
62 KARAF_LOG = os.path.join(
63 os.path.dirname(os.path.realpath(__file__)),
64 "..", "..", "..", KARAF_INSTALLDIR, "target", "assembly", "data", "log", "karaf.log")
66 if "USE_LIGHTY" in os.environ and os.environ['USE_LIGHTY'] == 'True':
67 TPCE_LOG = 'odl-' + str(os.getpid()) + '.log'
72 # Basic HTTP operations
77 return requests.request(
78 "GET", url.format(RESTCONF_BASE_URL),
79 headers=TYPE_APPLICATION_JSON,
80 auth=(ODL_LOGIN, ODL_PWD))
83 def put_request(url, data):
84 return requests.request(
85 "PUT", url.format(RESTCONF_BASE_URL),
86 data=json.dumps(data),
87 headers=TYPE_APPLICATION_JSON,
88 auth=(ODL_LOGIN, ODL_PWD))
91 def delete_request(url):
92 return requests.request(
93 "DELETE", url.format(RESTCONF_BASE_URL),
94 headers=TYPE_APPLICATION_JSON,
95 auth=(ODL_LOGIN, ODL_PWD))
102 def start_sims(sims_list):
103 for sim in sims_list:
104 print("starting simulator " + sim[0] + " in OpenROADM device version " + sim[1] + "...")
105 log_file = os.path.join(SIM_LOG_DIRECTORY, SIMS[sim]['logfile'])
106 process = start_honeynode(log_file, sim)
107 if wait_until_log_contains(log_file, HONEYNODE_OK_START_MSG, 100):
108 print("simulator for " + sim[0] + " started")
110 print("simulator for " + sim[0] + " failed to start")
111 shutdown_process(process)
112 for pid in process_list:
113 shutdown_process(pid)
115 process_list.append(process)
120 print("starting OpenDaylight...")
121 if "USE_LIGHTY" in os.environ and os.environ['USE_LIGHTY'] == 'True':
122 process = start_lighty()
123 start_msg = LIGHTY_OK_START_MSG
125 process = start_karaf()
126 start_msg = KARAF_OK_START_MSG
127 if wait_until_log_contains(TPCE_LOG, start_msg, time_to_wait=300):
128 print("OpenDaylight started !")
130 print("OpenDaylight failed to start !")
131 shutdown_process(process)
132 for pid in process_list:
133 shutdown_process(pid)
135 process_list.append(process)
140 print("starting KARAF TransportPCE build...")
141 executable = os.path.join(
142 os.path.dirname(os.path.realpath(__file__)),
143 "..", "..", "..", KARAF_INSTALLDIR, "target", "assembly", "bin", "karaf")
144 with open('odl.log', 'w', encoding='utf-8') as outfile:
145 return subprocess.Popen(
146 ["sh", executable, "server"], stdout=outfile, stderr=outfile, stdin=None)
150 print("starting LIGHTY.IO TransportPCE build...")
151 executable = os.path.join(
152 os.path.dirname(os.path.realpath(__file__)),
153 "..", "..", "..", "lighty", "target", "tpce",
154 "clean-start-controller.sh")
155 with open(TPCE_LOG, 'w', encoding='utf-8') as outfile:
156 return subprocess.Popen(
157 ["sh", executable], stdout=outfile, stderr=outfile, stdin=None)
160 def install_karaf_feature(feature_name: str):
161 print("installing feature " + feature_name)
162 executable = os.path.join(
163 os.path.dirname(os.path.realpath(__file__)),
164 "..", "..", "..", KARAF_INSTALLDIR, "target", "assembly", "bin", "client")
165 return subprocess.run([executable],
166 input='feature:install ' + feature_name + '\n feature:list | grep '
167 + feature_name + ' \n logout \n',
168 universal_newlines=True, check=False)
171 def shutdown_process(process):
172 if process is not None:
173 for child in psutil.Process(process.pid).children():
174 child.send_signal(signal.SIGINT)
176 process.send_signal(signal.SIGINT)
179 def start_honeynode(log_file: str, sim):
180 executable = os.path.join(os.path.dirname(os.path.realpath(__file__)),
181 "..", "..", "honeynode", sim[1], "honeynode-simulator", "honeycomb-tpce")
182 sample_directory = os.path.join(os.path.dirname(os.path.realpath(__file__)),
183 "..", "..", "sample_configs", "openroadm", sim[1])
184 if os.path.isfile(executable):
185 with open(log_file, 'w', encoding='utf-8') as outfile:
186 return subprocess.Popen(
187 [executable, SIMS[sim]['port'], os.path.join(sample_directory, SIMS[sim]['configfile'])],
188 stdout=outfile, stderr=outfile)
192 def wait_until_log_contains(log_file, regexp, time_to_wait=60):
193 # pylint: disable=lost-exception
194 # pylint: disable=consider-using-with
199 with TimeOut(seconds=time_to_wait):
200 while not os.path.exists(log_file):
202 filelogs = open(log_file, 'r', encoding='utf-8')
205 print("Searching for pattern '" + regexp + "' in " + os.path.basename(log_file), end='... ', flush=True)
206 compiled_regexp = re.compile(regexp)
208 line = filelogs.readline()
209 if compiled_regexp.search(line):
210 print("Pattern found!", end=' ')
216 print("Pattern not found after " + str(time_to_wait), end=" seconds! ", flush=True)
217 except PermissionError:
218 print("Permission Error when trying to access the log file", end=" ... ", flush=True)
223 print("log file does not exist or is not accessible... ", flush=True)
228 def __init__(self, seconds=1, error_message='Timeout'):
229 self.seconds = seconds
230 self.error_message = error_message
232 def handle_timeout(self, signum, frame):
233 raise TimeoutError(self.error_message)
236 signal.signal(signal.SIGALRM, self.handle_timeout)
237 signal.alarm(self.seconds)
239 def __exit__(self, type, value, traceback):
240 # pylint: disable=W0622
244 # Basic NetCONF device operations
248 def mount_device(node_id, sim):
249 url = "{}/data/network-topology:network-topology/topology=topology-netconf/node={}"
252 "netconf-node-topology:username": NODES_LOGIN,
253 "netconf-node-topology:password": NODES_PWD,
254 "netconf-node-topology:host": "127.0.0.1",
255 "netconf-node-topology:port": SIMS[sim]['port'],
256 "netconf-node-topology:tcp-only": "false",
257 "netconf-node-topology:pass-through": {}}]}
258 response = put_request(url.format('{}', node_id), body)
259 if wait_until_log_contains(TPCE_LOG, re.escape("Triggering notification stream NETCONF for node " + node_id), 180):
260 print("Node " + node_id + " correctly added to tpce topology", end='... ', flush=True)
262 print("Node " + node_id + " still not added to tpce topology", end='... ', flush=True)
263 if response.status_code == requests.codes.ok:
264 print("It was probably loaded at start-up", end='... ', flush=True)
265 # TODO an else-clause to abort test would probably be nice here
269 def unmount_device(node_id):
270 url = "{}/data/network-topology:network-topology/topology=topology-netconf/node={}"
271 response = delete_request(url.format('{}', node_id))
272 if wait_until_log_contains(TPCE_LOG, re.escape("onDeviceDisConnected: " + node_id), 180):
273 print("Node " + node_id + " correctly deleted from tpce topology", end='... ', flush=True)
275 print("Node " + node_id + " still not deleted from tpce topology", end='... ', flush=True)
279 def check_device_connection(node: str):
280 url = "{}/data/network-topology:network-topology/topology=topology-netconf/node={}"
281 response = get_request(url.format('{}', node))
282 res = response.json()
283 key = 'network-topology:node'
284 if key in res.keys():
285 connection_status = res[key][0]['netconf-node-topology:connection-status']
287 connection_status = res['errors']['error']
288 return {'status_code': response.status_code,
289 'connection-status': connection_status}
292 # Portmapping operations
296 def get_portmapping(node: str):
297 url = "{}/data/transportpce-portmapping:network/nodes={}"
298 response = get_request(url.format('{}', node))
299 res = response.json()
300 nodes = res['transportpce-portmapping:nodes']
301 return {'status_code': response.status_code,
305 def get_portmapping_node_info(node: str):
306 url = "{}/data/transportpce-portmapping:network/nodes={}/node-info"
307 response = get_request(url.format('{}', node))
308 res = response.json()
309 key = 'transportpce-portmapping:node-info'
310 if key in res.keys():
313 node_info = res['errors']['error']
314 return {'status_code': response.status_code,
315 'node-info': node_info}
318 def portmapping_request(node: str, mapping: str):
319 url = "{}/data/transportpce-portmapping:network/nodes={}/mapping={}"
320 response = get_request(url.format('{}', node, mapping))
321 res = response.json()
322 mapping = res['transportpce-portmapping:mapping']
323 return {'status_code': response.status_code,
327 def portmapping_mc_capa_request(node: str, mc_capa: str):
328 url = "{}/data/transportpce-portmapping:network/nodes={}/mc-capabilities={}"
329 response = get_request(url.format('{}', node, mc_capa))
330 res = response.json()
331 capabilities = res['transportpce-portmapping:mc-capabilities']
332 return {'status_code': response.status_code,
333 'mc-capabilities': capabilities}
336 # Topology operations
340 def get_ietf_network_request(network: str, content: str):
341 url = "{}/data/ietf-network:networks/network={}?content={}"
342 response = get_request(url.format('{}', network, content))
343 res = response.json()
344 networks = res['ietf-network:network']
345 return {'status_code': response.status_code,