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")
40 URL_CONFIG_ORDM_TOPO = "{}/data/ietf-network:networks/network=openroadm-topology"
41 URL_PORTMAPPING = "{}/data/transportpce-portmapping:network/nodes="
43 TYPE_APPLICATION_JSON = {'Content-Type': 'application/json', 'Accept': 'application/json'}
44 TYPE_APPLICATION_XML = {'Content-Type': 'application/xml', 'Accept': 'application/xml'}
46 CODE_SHOULD_BE_200 = 'Http status code should be 200'
47 CODE_SHOULD_BE_201 = 'Http status code should be 201'
49 SIM_LOG_DIRECTORY = os.path.join(os.path.dirname(os.path.realpath(__file__)), "log")
54 if "USE_ODL_ALT_RESTCONF_PORT" in os.environ:
55 RESTCONF_BASE_URL = "http://localhost:" + os.environ['USE_ODL_ALT_RESTCONF_PORT'] + "/rests"
57 RESTCONF_BASE_URL = "http://localhost:8181/rests"
59 if "USE_ODL_ALT_KARAF_INSTALL_DIR" in os.environ:
60 KARAF_INSTALLDIR = os.environ['USE_ODL_ALT_KARAF_INSTALL_DIR']
62 KARAF_INSTALLDIR = "karaf"
64 KARAF_LOG = os.path.join(
65 os.path.dirname(os.path.realpath(__file__)),
66 "..", "..", "..", KARAF_INSTALLDIR, "target", "assembly", "data", "log", "karaf.log")
68 if "USE_LIGHTY" in os.environ and os.environ['USE_LIGHTY'] == 'True':
69 TPCE_LOG = 'odl-' + str(os.getpid()) + '.log'
74 # Basic HTTP operations
79 return requests.request(
80 "GET", url.format(RESTCONF_BASE_URL),
81 headers=TYPE_APPLICATION_JSON,
82 auth=(ODL_LOGIN, ODL_PWD))
85 def put_request(url, data):
86 return requests.request(
87 "PUT", url.format(RESTCONF_BASE_URL),
88 data=json.dumps(data),
89 headers=TYPE_APPLICATION_JSON,
90 auth=(ODL_LOGIN, ODL_PWD))
93 def delete_request(url):
94 return requests.request(
95 "DELETE", url.format(RESTCONF_BASE_URL),
96 headers=TYPE_APPLICATION_JSON,
97 auth=(ODL_LOGIN, ODL_PWD))
104 def start_sims(sims_list):
105 for sim in sims_list:
106 print("starting simulator " + sim[0] + " in OpenROADM device version " + sim[1] + "...")
107 log_file = os.path.join(SIM_LOG_DIRECTORY, SIMS[sim]['logfile'])
108 process = start_honeynode(log_file, sim)
109 if wait_until_log_contains(log_file, HONEYNODE_OK_START_MSG, 100):
110 print("simulator for " + sim[0] + " started")
112 print("simulator for " + sim[0] + " failed to start")
113 shutdown_process(process)
114 for pid in process_list:
115 shutdown_process(pid)
117 process_list.append(process)
122 print("starting OpenDaylight...")
123 if "USE_LIGHTY" in os.environ and os.environ['USE_LIGHTY'] == 'True':
124 process = start_lighty()
125 start_msg = LIGHTY_OK_START_MSG
127 process = start_karaf()
128 start_msg = KARAF_OK_START_MSG
129 if wait_until_log_contains(TPCE_LOG, start_msg, time_to_wait=300):
130 print("OpenDaylight started !")
132 print("OpenDaylight failed to start !")
133 shutdown_process(process)
134 for pid in process_list:
135 shutdown_process(pid)
137 process_list.append(process)
142 print("starting KARAF TransportPCE build...")
143 executable = os.path.join(
144 os.path.dirname(os.path.realpath(__file__)),
145 "..", "..", "..", KARAF_INSTALLDIR, "target", "assembly", "bin", "karaf")
146 with open('odl.log', 'w', encoding='utf-8') as outfile:
147 return subprocess.Popen(
148 ["sh", executable, "server"], stdout=outfile, stderr=outfile, stdin=None)
152 print("starting LIGHTY.IO TransportPCE build...")
153 executable = os.path.join(
154 os.path.dirname(os.path.realpath(__file__)),
155 "..", "..", "..", "lighty", "target", "tpce",
156 "clean-start-controller.sh")
157 with open(TPCE_LOG, 'w', encoding='utf-8') as outfile:
158 return subprocess.Popen(
159 ["sh", executable], stdout=outfile, stderr=outfile, stdin=None)
162 def install_karaf_feature(feature_name: str):
163 print("installing feature " + feature_name)
164 executable = os.path.join(
165 os.path.dirname(os.path.realpath(__file__)),
166 "..", "..", "..", KARAF_INSTALLDIR, "target", "assembly", "bin", "client")
167 return subprocess.run([executable],
168 input='feature:install ' + feature_name + '\n feature:list | grep '
169 + feature_name + ' \n logout \n',
170 universal_newlines=True, check=False)
173 def shutdown_process(process):
174 if process is not None:
175 for child in psutil.Process(process.pid).children():
176 child.send_signal(signal.SIGINT)
178 process.send_signal(signal.SIGINT)
181 def start_honeynode(log_file: str, sim):
182 executable = os.path.join(os.path.dirname(os.path.realpath(__file__)),
183 "..", "..", "honeynode", sim[1], "honeynode-simulator", "honeycomb-tpce")
184 sample_directory = os.path.join(os.path.dirname(os.path.realpath(__file__)),
185 "..", "..", "sample_configs", "openroadm", sim[1])
186 if os.path.isfile(executable):
187 with open(log_file, 'w', encoding='utf-8') as outfile:
188 return subprocess.Popen(
189 [executable, SIMS[sim]['port'], os.path.join(sample_directory, SIMS[sim]['configfile'])],
190 stdout=outfile, stderr=outfile)
194 def wait_until_log_contains(log_file, regexp, time_to_wait=60):
195 # pylint: disable=lost-exception
196 # pylint: disable=consider-using-with
201 with TimeOut(seconds=time_to_wait):
202 while not os.path.exists(log_file):
204 filelogs = open(log_file, 'r', encoding='utf-8')
207 print("Searching for pattern '" + regexp + "' in " + os.path.basename(log_file), end='... ', flush=True)
208 compiled_regexp = re.compile(regexp)
210 line = filelogs.readline()
211 if compiled_regexp.search(line):
212 print("Pattern found!", end=' ')
218 print("Pattern not found after " + str(time_to_wait), end=" seconds! ", flush=True)
219 except PermissionError:
220 print("Permission Error when trying to access the log file", end=" ... ", flush=True)
225 print("log file does not exist or is not accessible... ", flush=True)
230 def __init__(self, seconds=1, error_message='Timeout'):
231 self.seconds = seconds
232 self.error_message = error_message
234 def handle_timeout(self, signum, frame):
235 raise TimeoutError(self.error_message)
238 signal.signal(signal.SIGALRM, self.handle_timeout)
239 signal.alarm(self.seconds)
241 def __exit__(self, type, value, traceback):
242 # pylint: disable=W0622
246 # Basic NetCONF device operations
250 def mount_device(node_id, sim):
251 url = "{}/data/network-topology:network-topology/topology=topology-netconf/node={}"
254 "netconf-node-topology:username": NODES_LOGIN,
255 "netconf-node-topology:password": NODES_PWD,
256 "netconf-node-topology:host": "127.0.0.1",
257 "netconf-node-topology:port": SIMS[sim]['port'],
258 "netconf-node-topology:tcp-only": "false",
259 "netconf-node-topology:pass-through": {}}]}
260 response = put_request(url.format('{}', node_id), body)
261 if wait_until_log_contains(TPCE_LOG, re.escape("Triggering notification stream NETCONF for node " + node_id), 180):
262 print("Node " + node_id + " correctly added to tpce topology", end='... ', flush=True)
264 print("Node " + node_id + " still not added to tpce topology", end='... ', flush=True)
265 if response.status_code == requests.codes.ok:
266 print("It was probably loaded at start-up", end='... ', flush=True)
267 # TODO an else-clause to abort test would probably be nice here
271 def unmount_device(node_id):
272 url = "{}/data/network-topology:network-topology/topology=topology-netconf/node={}"
273 response = delete_request(url.format('{}', node_id))
274 if wait_until_log_contains(TPCE_LOG, re.escape("onDeviceDisConnected: " + node_id), 180):
275 print("Node " + node_id + " correctly deleted from tpce topology", end='... ', flush=True)
277 print("Node " + node_id + " still not deleted from tpce topology", end='... ', flush=True)
281 def check_device_connection(node: str):
282 url = "{}/data/network-topology:network-topology/topology=topology-netconf/node={}"
283 response = get_request(url.format('{}', node))
284 res = response.json()
285 key = 'network-topology:node'
286 if key in res.keys():
287 connection_status = res[key][0]['netconf-node-topology:connection-status']
289 connection_status = res['errors']['error']
290 return {'status_code': response.status_code,
291 'connection-status': connection_status}
294 # Portmapping operations
298 def get_portmapping(node: str):
299 url = "{}/data/transportpce-portmapping:network/nodes={}"
300 response = get_request(url.format('{}', node))
301 res = response.json()
303 nodes = res['transportpce-portmapping:nodes']
304 return {'status_code': response.status_code,
308 def get_portmapping_node_info(node: str):
309 url = "{}/data/transportpce-portmapping:network/nodes={}/node-info"
310 response = get_request(url.format('{}', node))
311 res = response.json()
312 key = 'transportpce-portmapping:node-info'
313 if key in res.keys():
316 node_info = res['errors']['error']
317 return {'status_code': response.status_code,
318 'node-info': node_info}
321 def portmapping_request(node: str, mapping: str):
322 url = "{}/data/transportpce-portmapping:network/nodes={}/mapping={}"
323 response = get_request(url.format('{}', node, mapping))
324 res = response.json()
325 mapping = res['transportpce-portmapping:mapping']
326 return {'status_code': response.status_code,
330 def portmapping_mc_capa_request(node: str, mc_capa: str):
331 url = "{}/data/transportpce-portmapping:network/nodes={}/mc-capabilities={}"
332 response = get_request(url.format('{}', node, mc_capa))
333 res = response.json()
334 capabilities = res['transportpce-portmapping:mc-capabilities']
335 return {'status_code': response.status_code,
336 'mc-capabilities': capabilities}