fix some pylint issues
[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
17 import psutil
18 import requests
19
20 import simulators
21
22 SIMS = simulators.SIMS
23 HONEYNODE_EXECUTABLE = simulators.HONEYNODE_EXECUTABLE
24 SAMPLES_DIRECTORY = simulators.SAMPLES_DIRECTORY
25
26 HONEYNODE_OK_START_MSG = "Netconf SSH endpoint started successfully at 0.0.0.0"
27 KARAF_OK_START_MSG = re.escape(
28     "Blueprint container for bundle org.opendaylight.netconf.restconf")+".* was successfully created"
29
30
31 RESTCONF_BASE_URL = "http://localhost:8181/restconf"
32 ODL_LOGIN = "admin"
33 ODL_PWD = "admin"
34 NODES_LOGIN = "admin"
35 NODES_PWD = "admin"
36
37 TYPE_APPLICATION_JSON = {'Content-Type': 'application/json', 'Accept': 'application/json'}
38 TYPE_APPLICATION_XML = {'Content-Type': 'application/xml', 'Accept': 'application/xml'}
39
40 CODE_SHOULD_BE_200 = 'Http status code should be 200'
41 CODE_SHOULD_BE_201 = 'Http status code should be 201'
42
43 LOG_DIRECTORY = os.path.dirname(os.path.realpath(__file__))
44
45 KARAF_LOG = os.path.join(
46     os.path.dirname(os.path.realpath(__file__)),
47     "..", "..", "..", "karaf", "target", "assembly", "data", "log", "karaf.log")
48
49 process_list = []
50
51 if "USE_LIGHTY" in os.environ and os.environ['USE_LIGHTY'] == 'True':
52     TPCE_LOG = 'odl.log'
53 else:
54     TPCE_LOG = KARAF_LOG
55
56
57 def start_sims(sims_list):
58     for sim in sims_list:
59         print("starting simulator for " + sim + "...")
60         log_file = os.path.join(LOG_DIRECTORY, SIMS[sim]['logfile'])
61         process = start_honeynode(log_file, SIMS[sim]['port'], SIMS[sim]['configfile'])
62         if wait_until_log_contains(log_file, HONEYNODE_OK_START_MSG, 100):
63             print("simulator for " + sim + " started")
64         else:
65             print("simulator for " + sim + " failed to start")
66             shutdown_process(process)
67             for pid in process_list:
68                 shutdown_process(pid)
69             sys.exit(3)
70         process_list.append(process)
71     return process_list
72
73
74 def start_tpce():
75     print("starting OpenDaylight...")
76     if "USE_LIGHTY" in os.environ and os.environ['USE_LIGHTY'] == 'True':
77         process = start_lighty()
78         # TODO: add some sort of health check similar to Karaf below
79     else:
80         process = start_karaf()
81         if wait_until_log_contains(KARAF_LOG, KARAF_OK_START_MSG, time_to_wait=60):
82             print("OpenDaylight started !")
83         else:
84             print("OpenDaylight failed to start !")
85             shutdown_process(process)
86             for pid in process_list:
87                 shutdown_process(pid)
88             sys.exit(1)
89     process_list.append(process)
90     return process_list
91
92
93 def start_karaf():
94     print("starting KARAF TransportPCE build...")
95     executable = os.path.join(
96         os.path.dirname(os.path.realpath(__file__)),
97         "..", "..", "..", "karaf", "target", "assembly", "bin", "karaf")
98     with open('odl.log', 'w') as outfile:
99         return subprocess.Popen(
100             ["sh", executable, "server"], stdout=outfile, stderr=outfile, stdin=None)
101
102
103 def start_lighty():
104     print("starting LIGHTY.IO TransportPCE build...")
105     executable = os.path.join(
106         os.path.dirname(os.path.realpath(__file__)),
107         "..", "..", "..", "lighty", "target", "tpce",
108         "clean-start-controller.sh")
109     with open('odl.log', 'w') as outfile:
110         return subprocess.Popen(
111             ["sh", executable], stdout=outfile, stderr=outfile, stdin=None)
112
113
114 def install_karaf_feature(feature_name: str):
115     print("installing feature " + feature_name)
116     executable = os.path.join(
117         os.path.dirname(os.path.realpath(__file__)),
118         "..", "..", "..", "karaf", "target", "assembly", "bin", "client")
119     return subprocess.run([executable],
120                           input='feature:install ' + feature_name + '\n feature:list | grep tapi \n logout \n',
121                           universal_newlines=True)
122
123
124 def post_request(url, data, username, password):
125     return requests.request(
126         "POST", url, data=json.dumps(data),
127         headers=TYPE_APPLICATION_JSON, auth=(username, password))
128
129
130 def put_request(url, data, username, password):
131     return requests.request(
132         "PUT", url, data=json.dumps(data), headers=TYPE_APPLICATION_JSON,
133         auth=(username, password))
134
135
136 def delete_request(url, username, password):
137     return requests.request(
138         "DELETE", url, headers=TYPE_APPLICATION_JSON,
139         auth=(username, password))
140
141
142 def mount_device(node_id, sim):
143     url = ("{}/config/network-topology:network-topology/topology/topology-netconf/node/"
144            + node_id).format(RESTCONF_BASE_URL)
145     headers = {"node": [{
146         "node-id": node_id,
147         "netconf-node-topology:username": NODES_LOGIN,
148         "netconf-node-topology:password": NODES_PWD,
149         "netconf-node-topology:host": "127.0.0.1",
150         "netconf-node-topology:port": SIMS[sim]['port'],
151         "netconf-node-topology:tcp-only": "false",
152         "netconf-node-topology:pass-through": {}}]}
153     response = put_request(url, headers, ODL_LOGIN, ODL_PWD)
154     if wait_until_log_contains(TPCE_LOG, re.escape("Triggering notification stream NETCONF for node "+node_id), 60):
155         print("Node "+node_id+" correctly added to tpce topology", end='... ', flush=True)
156     else:
157         print("Node "+node_id+" still not added to tpce topology", end='... ', flush=True)
158         if response.status_code == requests.codes.ok:
159             print("It was probably loaded at start-up", end='... ', flush=True)
160         # TODO an else-clause to abort test would probably be nice here
161     return response
162
163
164 def unmount_device(node_id):
165     url = ("{}/config/network-topology:network-topology/topology/topology-netconf/node/"
166            + node_id).format(RESTCONF_BASE_URL)
167     response = delete_request(url, ODL_LOGIN, ODL_PWD)
168     if wait_until_log_contains(TPCE_LOG, re.escape("onDeviceDisConnected: "+node_id), 60):
169         print("Node "+node_id+" correctly deleted from tpce topology", end='... ', flush=True)
170     else:
171         print("Node "+node_id+" still not deleted from tpce topology", end='... ', flush=True)
172     return response
173
174
175 def generate_link_data(xpdr_node: str, xpdr_num: str, network_num: str, rdm_node: str, srg_num: str,
176                        termination_num: str):
177     data = {
178         "networkutils:input": {
179             "networkutils:links-input": {
180                 "networkutils:xpdr-node": xpdr_node,
181                 "networkutils:xpdr-num": xpdr_num,
182                 "networkutils:network-num": network_num,
183                 "networkutils:rdm-node": rdm_node,
184                 "networkutils:srg-num": srg_num,
185                 "networkutils:termination-point-num": termination_num
186             }
187         }
188     }
189     return data
190
191
192 def shutdown_process(process):
193     if process is not None:
194         for child in psutil.Process(process.pid).children():
195             child.send_signal(signal.SIGINT)
196             child.wait()
197         process.send_signal(signal.SIGINT)
198
199
200 def start_honeynode(log_file: str, node_port: str, node_config_file_name: str):
201     if os.path.isfile(HONEYNODE_EXECUTABLE):
202         with open(log_file, 'w') as outfile:
203             return subprocess.Popen(
204                 [HONEYNODE_EXECUTABLE, node_port, os.path.join(SAMPLES_DIRECTORY, node_config_file_name)],
205                 stdout=outfile, stderr=outfile)
206
207
208 def wait_until_log_contains(log_file, regexp, time_to_wait=20):
209     found = False
210     tail = None
211     try:
212         with TimeOut(seconds=time_to_wait):
213             print("Searching for pattern '"+regexp+"' in "+os.path.basename(log_file), end='... ', flush=True)
214             tail = subprocess.Popen(['tail', '-F', log_file], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
215             compiled_regexp = re.compile(regexp)
216             while True:
217                 line = tail.stdout.readline().decode('utf-8')
218                 if compiled_regexp.search(line):
219                     print("String found!", end=' ')
220                     found = True
221                     break
222     except TimeoutError:
223         print("String not found after "+str(time_to_wait), end=" seconds! ", flush=True)
224     finally:
225         if tail is not None:
226             print("Stopping tail command", end='... ', flush=True)
227             tail.stderr.close()
228             tail.stdout.close()
229             tail.kill()
230             tail.wait()
231         return found
232 # TODO try to find an alternative to subprocess+tail -f (such as https://pypi.org/project/tailhead/)
233
234
235 class TimeOut:
236     def __init__(self, seconds=1, error_message='Timeout'):
237         self.seconds = seconds
238         self.error_message = error_message
239
240     def handle_timeout(self, signum, frame):
241         raise TimeoutError(self.error_message)
242
243     def __enter__(self):
244         signal.signal(signal.SIGALRM, self.handle_timeout)
245         signal.alarm(self.seconds)
246
247     def __exit__(self, type, value, traceback):
248         signal.alarm(0)