9dfd694483a7e09b87d4e469d38d2e934c59d35d
[transportpce.git] / tests / transportpce_tests / common / test_utils.py
1 #!/usr/bin/env python
2 ##############################################################################
3 # Copyright (c) 2020 Orange, Inc. and others.  All rights reserved.
4 #
5 # All rights reserved. This program and the accompanying materials
6 # are made available under the terms of the Apache License, Version 2.0
7 # which accompanies this distribution, and is available at
8 # http://www.apache.org/licenses/LICENSE-2.0
9 ##############################################################################
10 import json
11 import os
12 import sys
13 import re
14 import signal
15 import subprocess
16 import time
17
18 import psutil
19 import requests
20
21 import simulators
22
23 SIMS = simulators.SIMS
24 HONEYNODE_EXECUTABLE = simulators.HONEYNODE_EXECUTABLE
25 SAMPLES_DIRECTORY = simulators.SAMPLES_DIRECTORY
26
27 HONEYNODE_OK_START_MSG = "Netconf SSH endpoint started successfully at 0.0.0.0"
28 KARAF_OK_START_MSG = re.escape(
29     "Blueprint container for bundle org.opendaylight.netconf.restconf")+".* was successfully created"
30
31
32 RESTCONF_BASE_URL = "http://localhost:8181/restconf"
33 ODL_LOGIN = "admin"
34 ODL_PWD = "admin"
35 NODES_LOGIN = "admin"
36 NODES_PWD = "admin"
37
38 TYPE_APPLICATION_JSON = {'Content-Type': 'application/json', 'Accept': 'application/json'}
39 TYPE_APPLICATION_XML = {'Content-Type': 'application/xml', 'Accept': 'application/xml'}
40
41 CODE_SHOULD_BE_200 = 'Http status code should be 200'
42 CODE_SHOULD_BE_201 = 'Http status code should be 201'
43
44 LOG_DIRECTORY = os.path.dirname(os.path.realpath(__file__))
45
46 KARAF_LOG = os.path.join(
47     os.path.dirname(os.path.realpath(__file__)),
48     "..", "..", "..", "karaf", "target", "assembly", "data", "log", "karaf.log")
49
50 process_list = []
51
52 if "USE_LIGHTY" in os.environ and os.environ['USE_LIGHTY'] == 'True':
53     TPCE_LOG = 'odl.log'
54 else:
55     TPCE_LOG = KARAF_LOG
56
57
58 def start_sims(sims_list):
59     for sim in sims_list:
60         print("starting simulator for " + sim + "...")
61         log_file = os.path.join(LOG_DIRECTORY, SIMS[sim]['logfile'])
62         process = start_honeynode(log_file, SIMS[sim]['port'], SIMS[sim]['configfile'])
63         if wait_until_log_contains(log_file, HONEYNODE_OK_START_MSG, 100):
64             print("simulator for " + sim + " started")
65         else:
66             print("simulator for " + sim + " failed to start")
67             shutdown_process(process)
68             for pid in process_list:
69                 shutdown_process(pid)
70             sys.exit(3)
71         process_list.append(process)
72     return process_list
73
74
75 def start_tpce():
76     print("starting OpenDaylight...")
77     if "USE_LIGHTY" in os.environ and os.environ['USE_LIGHTY'] == 'True':
78         process = start_lighty()
79         # TODO: add some sort of health check similar to Karaf below
80     else:
81         process = start_karaf()
82         if wait_until_log_contains(KARAF_LOG, KARAF_OK_START_MSG, time_to_wait=60):
83             print("OpenDaylight started !")
84         else:
85             print("OpenDaylight failed to start !")
86             shutdown_process(process)
87             for pid in process_list:
88                 shutdown_process(pid)
89             sys.exit(1)
90     process_list.append(process)
91     return process_list
92
93
94 def start_karaf():
95     print("starting KARAF TransportPCE build...")
96     executable = os.path.join(
97         os.path.dirname(os.path.realpath(__file__)),
98         "..", "..", "..", "karaf", "target", "assembly", "bin", "karaf")
99     with open('odl.log', 'w') as outfile:
100         return subprocess.Popen(
101             ["sh", executable, "server"], stdout=outfile, stderr=outfile, stdin=None)
102
103
104 def start_lighty():
105     print("starting LIGHTY.IO TransportPCE build...")
106     executable = os.path.join(
107         os.path.dirname(os.path.realpath(__file__)),
108         "..", "..", "..", "lighty", "target", "tpce",
109         "clean-start-controller.sh")
110     with open('odl.log', 'w') as outfile:
111         return subprocess.Popen(
112             ["sh", executable], stdout=outfile, stderr=outfile, stdin=None)
113
114
115 def install_karaf_feature(feature_name: str):
116     print("installing feature " + feature_name)
117     executable = os.path.join(
118         os.path.dirname(os.path.realpath(__file__)),
119         "..", "..", "..", "karaf", "target", "assembly", "bin", "client")
120     return subprocess.run([executable],
121                           input='feature:install ' + feature_name + '\n feature:list | grep tapi \n logout \n',
122                           universal_newlines=True)
123
124
125 def get_request(url):
126     return requests.request(
127         "GET", url.format(RESTCONF_BASE_URL),
128         headers=TYPE_APPLICATION_JSON,
129         auth=(ODL_LOGIN, ODL_PWD))
130
131
132 def post_request(url, data):
133     if data:
134         return requests.request(
135             "POST", url.format(RESTCONF_BASE_URL),
136             data=json.dumps(data),
137             headers=TYPE_APPLICATION_JSON,
138             auth=(ODL_LOGIN, ODL_PWD))
139     else:
140         return requests.request(
141             "POST", url.format(RESTCONF_BASE_URL),
142             headers=TYPE_APPLICATION_JSON,
143             auth=(ODL_LOGIN, ODL_PWD))
144
145
146 def post_xmlrequest(url, data):
147     if data:
148         return requests.request(
149             "POST", url.format(RESTCONF_BASE_URL),
150             data=data,
151             headers=TYPE_APPLICATION_XML,
152             auth=(ODL_LOGIN, ODL_PWD))
153
154
155 def put_request(url, data):
156     return requests.request(
157         "PUT", url.format(RESTCONF_BASE_URL),
158         data=json.dumps(data),
159         headers=TYPE_APPLICATION_JSON,
160         auth=(ODL_LOGIN, ODL_PWD))
161
162
163 def put_xmlrequest(url, data):
164     return requests.request(
165         "PUT", url.format(RESTCONF_BASE_URL),
166         data=data,
167         headers=TYPE_APPLICATION_XML,
168         auth=(ODL_LOGIN, ODL_PWD))
169
170
171 def rawput_request(url, data):
172     return requests.request(
173         "PUT", url.format(RESTCONF_BASE_URL),
174         data=data,
175         headers=TYPE_APPLICATION_JSON,
176         auth=(ODL_LOGIN, ODL_PWD))
177
178
179 def delete_request(url):
180     return requests.request(
181         "DELETE", url.format(RESTCONF_BASE_URL),
182         headers=TYPE_APPLICATION_JSON,
183         auth=(ODL_LOGIN, ODL_PWD))
184
185
186 def mount_device(node_id, sim):
187     url = "{}/config/network-topology:network-topology/topology/topology-netconf/node/"+node_id
188     body = {"node": [{
189         "node-id": node_id,
190         "netconf-node-topology:username": NODES_LOGIN,
191         "netconf-node-topology:password": NODES_PWD,
192         "netconf-node-topology:host": "127.0.0.1",
193         "netconf-node-topology:port": SIMS[sim]['port'],
194         "netconf-node-topology:tcp-only": "false",
195         "netconf-node-topology:pass-through": {}}]}
196     response = put_request(url, body)
197     if wait_until_log_contains(TPCE_LOG, re.escape("Triggering notification stream NETCONF for node "+node_id), 60):
198         print("Node "+node_id+" correctly added to tpce topology", end='... ', flush=True)
199     else:
200         print("Node "+node_id+" still not added to tpce topology", end='... ', flush=True)
201         if response.status_code == requests.codes.ok:
202             print("It was probably loaded at start-up", end='... ', flush=True)
203         # TODO an else-clause to abort test would probably be nice here
204     return response
205
206
207 def unmount_device(node_id):
208     url = "{}/config/network-topology:network-topology/topology/topology-netconf/node/"+node_id
209     response = delete_request(url)
210     if wait_until_log_contains(TPCE_LOG, re.escape("onDeviceDisConnected: "+node_id), 60):
211         print("Node "+node_id+" correctly deleted from tpce topology", end='... ', flush=True)
212     else:
213         print("Node "+node_id+" still not deleted from tpce topology", end='... ', flush=True)
214     return response
215
216
217 def connect_xpdr_to_rdm_request(xpdr_node: str, xpdr_num: str, network_num: str,
218                                 rdm_node: str, srg_num: str, termination_num: str):
219     url = "{}/operations/transportpce-networkutils:init-xpdr-rdm-links"
220     data = {
221         "networkutils:input": {
222             "networkutils:links-input": {
223                 "networkutils:xpdr-node": xpdr_node,
224                 "networkutils:xpdr-num": xpdr_num,
225                 "networkutils:network-num": network_num,
226                 "networkutils:rdm-node": rdm_node,
227                 "networkutils:srg-num": srg_num,
228                 "networkutils:termination-point-num": termination_num
229             }
230         }
231     }
232     return post_request(url, data)
233
234
235 def connect_rdm_to_xpdr_request(xpdr_node: str, xpdr_num: str, network_num: str,
236                                 rdm_node: str, srg_num: str, termination_num: str):
237     url = "{}/operations/transportpce-networkutils:init-rdm-xpdr-links"
238     data = {
239         "networkutils:input": {
240             "networkutils:links-input": {
241                 "networkutils:xpdr-node": xpdr_node,
242                 "networkutils:xpdr-num": xpdr_num,
243                 "networkutils:network-num": network_num,
244                 "networkutils:rdm-node": rdm_node,
245                 "networkutils:srg-num": srg_num,
246                 "networkutils:termination-point-num": termination_num
247             }
248         }
249     }
250     return post_request(url, data)
251
252
253 def shutdown_process(process):
254     if process is not None:
255         for child in psutil.Process(process.pid).children():
256             child.send_signal(signal.SIGINT)
257             child.wait()
258         process.send_signal(signal.SIGINT)
259
260
261 def start_honeynode(log_file: str, node_port: str, node_config_file_name: str):
262     if os.path.isfile(HONEYNODE_EXECUTABLE):
263         with open(log_file, 'w') as outfile:
264             return subprocess.Popen(
265                 [HONEYNODE_EXECUTABLE, node_port, os.path.join(SAMPLES_DIRECTORY, node_config_file_name)],
266                 stdout=outfile, stderr=outfile)
267
268
269 def wait_until_log_contains(log_file, regexp, time_to_wait=20):
270     stringfound = False
271     filefound = False
272     line = None
273     try:
274         with TimeOut(seconds=time_to_wait):
275             while not os.path.exists(log_file):
276                 time.sleep(0.2)
277             filelogs = open(log_file, 'r')
278             filelogs.seek(0, 2)
279             filefound = True
280             print("Searching for pattern '"+regexp+"' in "+os.path.basename(log_file), end='... ', flush=True)
281             compiled_regexp = re.compile(regexp)
282             while True:
283                 line = filelogs.readline()
284                 if compiled_regexp.search(line):
285                     print("Pattern found!", end=' ')
286                     stringfound = True
287                     break
288                 if not line:
289                     time.sleep(0.1)
290     except TimeoutError:
291         print("Pattern not found after "+str(time_to_wait), end=" seconds! ", flush=True)
292     except PermissionError:
293         print("Permission Error when trying to access the log file", end=" ... ", flush=True)
294     finally:
295         if filefound:
296             filelogs.close()
297         else:
298             print("log file does not exist or is not accessible... ", flush=True)
299         return stringfound
300
301
302 class TimeOut:
303     def __init__(self, seconds=1, error_message='Timeout'):
304         self.seconds = seconds
305         self.error_message = error_message
306
307     def handle_timeout(self, signum, frame):
308         raise TimeoutError(self.error_message)
309
310     def __enter__(self):
311         signal.signal(signal.SIGALRM, self.handle_timeout)
312         signal.alarm(self.seconds)
313
314     def __exit__(self, type, value, traceback):
315         signal.alarm(0)