b0daf88fb30551b38e9fd4a34d570a9c53358143
[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 post_request(url, data, username, password):
126     return requests.request(
127         "POST", url, data=json.dumps(data),
128         headers=TYPE_APPLICATION_JSON, auth=(username, password))
129
130
131 def put_request(url, data, username, password):
132     return requests.request(
133         "PUT", url, data=json.dumps(data), headers=TYPE_APPLICATION_JSON,
134         auth=(username, password))
135
136
137 def delete_request(url, username, password):
138     return requests.request(
139         "DELETE", url, headers=TYPE_APPLICATION_JSON,
140         auth=(username, password))
141
142
143 def mount_device(node_id, sim):
144     url = ("{}/config/network-topology:network-topology/topology/topology-netconf/node/"
145            + node_id).format(RESTCONF_BASE_URL)
146     headers = {"node": [{
147         "node-id": node_id,
148         "netconf-node-topology:username": NODES_LOGIN,
149         "netconf-node-topology:password": NODES_PWD,
150         "netconf-node-topology:host": "127.0.0.1",
151         "netconf-node-topology:port": SIMS[sim]['port'],
152         "netconf-node-topology:tcp-only": "false",
153         "netconf-node-topology:pass-through": {}}]}
154     response = put_request(url, headers, ODL_LOGIN, ODL_PWD)
155     if wait_until_log_contains(TPCE_LOG, re.escape("Triggering notification stream NETCONF for node "+node_id), 60):
156         print("Node "+node_id+" correctly added to tpce topology", end='... ', flush=True)
157     else:
158         print("Node "+node_id+" still not added to tpce topology", end='... ', flush=True)
159         if response.status_code == requests.codes.ok:
160             print("It was probably loaded at start-up", end='... ', flush=True)
161         # TODO an else-clause to abort test would probably be nice here
162     return response
163
164
165 def unmount_device(node_id):
166     url = ("{}/config/network-topology:network-topology/topology/topology-netconf/node/"
167            + node_id).format(RESTCONF_BASE_URL)
168     response = delete_request(url, ODL_LOGIN, ODL_PWD)
169     if wait_until_log_contains(TPCE_LOG, re.escape("onDeviceDisConnected: "+node_id), 60):
170         print("Node "+node_id+" correctly deleted from tpce topology", end='... ', flush=True)
171     else:
172         print("Node "+node_id+" still not deleted from tpce topology", end='... ', flush=True)
173     return response
174
175
176 def generate_link_data(xpdr_node: str, xpdr_num: str, network_num: str, rdm_node: str, srg_num: str,
177                        termination_num: str):
178     data = {
179         "networkutils:input": {
180             "networkutils:links-input": {
181                 "networkutils:xpdr-node": xpdr_node,
182                 "networkutils:xpdr-num": xpdr_num,
183                 "networkutils:network-num": network_num,
184                 "networkutils:rdm-node": rdm_node,
185                 "networkutils:srg-num": srg_num,
186                 "networkutils:termination-point-num": termination_num
187             }
188         }
189     }
190     return data
191
192
193 def shutdown_process(process):
194     if process is not None:
195         for child in psutil.Process(process.pid).children():
196             child.send_signal(signal.SIGINT)
197             child.wait()
198         process.send_signal(signal.SIGINT)
199
200
201 def start_honeynode(log_file: str, node_port: str, node_config_file_name: str):
202     if os.path.isfile(HONEYNODE_EXECUTABLE):
203         with open(log_file, 'w') as outfile:
204             return subprocess.Popen(
205                 [HONEYNODE_EXECUTABLE, node_port, os.path.join(SAMPLES_DIRECTORY, node_config_file_name)],
206                 stdout=outfile, stderr=outfile)
207
208
209 def wait_until_log_contains(log_file, regexp, time_to_wait=20):
210     stringfound = False
211     filefound = False
212     line = None
213     try:
214         with TimeOut(seconds=time_to_wait):
215             while not os.path.exists(log_file):
216                 time.sleep(0.2)
217             filelogs = open(log_file, 'r')
218             filelogs.seek(0, 2)
219             filefound = True
220             print("Searching for pattern '"+regexp+"' in "+os.path.basename(log_file), end='... ', flush=True)
221             compiled_regexp = re.compile(regexp)
222             while True:
223                 line = filelogs.readline()
224                 if compiled_regexp.search(line):
225                     print("String found!", end=' ')
226                     stringfound = True
227                     break
228                 if not line:
229                     time.sleep(0.1)
230     except TimeoutError:
231         print("String not found after "+str(time_to_wait), end=" seconds! ", flush=True)
232     except PermissionError:
233         print("Permission Error when trying to access the log file", end=" ... ", flush=True)
234     finally:
235         if filefound:
236             filelogs.close()
237         else:
238             print("log file does not exist or is not accessible... ", flush=True)
239         return stringfound
240
241
242 class TimeOut:
243     def __init__(self, seconds=1, error_message='Timeout'):
244         self.seconds = seconds
245         self.error_message = error_message
246
247     def handle_timeout(self, signum, frame):
248         raise TimeoutError(self.error_message)
249
250     def __enter__(self):
251         signal.signal(signal.SIGALRM, self.handle_timeout)
252         signal.alarm(self.seconds)
253
254     def __exit__(self, type, value, traceback):
255         signal.alarm(0)