Pyangbind NBI validation
[osm/NBI.git] / osm_nbi / tests / test.py
1 #! /usr/bin/python3
2 # -*- coding: utf-8 -*-
3
4 import getopt
5 import sys
6 import requests
7 import json
8 import logging
9 import yaml
10 # import json
11 # import tarfile
12 from time import sleep
13 import os
14
15 __author__ = "Alfonso Tierno, alfonso.tiernosepulveda@telefonica.com"
16 __date__ = "$2018-03-01$"
17 __version__ = "0.3"
18 version_date = "Oct 2018"
19
20
21 def usage():
22 print("Usage: ", sys.argv[0], "[options]")
23 print(" Performs system tests over running NBI. It can be used for real OSM test using option '--test-osm'")
24 print(" If this is the case env variables 'OSMNBITEST_VIM_NAME' must be suplied to create a VIM if not exist "
25 "where deployment is done")
26 print("OPTIONS")
27 print(" -h|--help: shows this help")
28 print(" --insecure: Allows non trusted https NBI server")
29 print(" --list: list available tests")
30 print(" --manual-check: Deployment tests stop after deployed to allow manual inspection. Only make sense with "
31 "'--test-osm'")
32 print(" -p|--password PASSWORD: NBI access password. 'admin' by default")
33 print(" ---project PROJECT: NBI access project. 'admin' by default")
34 print(" --test TEST[,...]: Execute only a test or a comma separated list of tests")
35 print(" --params key=val: params to the previous test. key can be vnfd-files, nsd-file, ns-name, ns-config")
36 print(" --test-osm: If missing this test is intended for NBI only, no other OSM components are expected. Use "
37 "this flag to test the system. LCM and RO components are expected to be up and running")
38 print(" --timeout TIMEOUT: General NBI timeout, by default {}s".format(timeout))
39 print(" --timeout-deploy TIMEOUT: Timeout used for getting NS deployed, by default {}s".format(timeout_deploy))
40 print(" --timeout-configure TIMEOUT: Timeout used for getting NS deployed and configured,"
41 " by default {}s".format(timeout_configure))
42 print(" -u|--user USERNAME: NBI access username. 'admin' by default")
43 print(" --url URL: complete NBI server URL. 'https//localhost:9999/osm' by default")
44 print(" -v|--verbose print debug information, can be used several times")
45 print(" --no-verbose remove verbosity")
46 print(" --version: prints current version")
47 print("ENV variables used for real deployment tests with option osm-test.")
48 print(" export OSMNBITEST_VIM_NAME=vim-name")
49 print(" export OSMNBITEST_VIM_URL=vim-url")
50 print(" export OSMNBITEST_VIM_TYPE=vim-type")
51 print(" export OSMNBITEST_VIM_TENANT=vim-tenant")
52 print(" export OSMNBITEST_VIM_USER=vim-user")
53 print(" export OSMNBITEST_VIM_PASSWORD=vim-password")
54 print(" export OSMNBITEST_VIM_CONFIG=\"vim-config\"")
55 print(" export OSMNBITEST_NS_NAME=\"vim-config\"")
56 return
57
58
59 r_header_json = {"Content-type": "application/json"}
60 headers_json = {
61 "Content-type": "application/json",
62 "Accept": "application/json",
63 }
64 r_header_yaml = {"Content-type": "application/yaml"}
65 headers_yaml = {
66 "Content-type": "application/yaml",
67 "Accept": "application/yaml",
68 }
69 r_header_text = {"Content-type": "text/plain"}
70 r_header_octect = {"Content-type": "application/octet-stream"}
71 headers_text = {
72 "Accept": "text/plain",
73 }
74 r_header_zip = {"Content-type": "application/zip"}
75 headers_zip = {
76 "Accept": "application/zip",
77 }
78 headers_zip_yaml = {
79 "Accept": "application/yaml", "Content-type": "application/zip"
80 }
81
82
83 # test ones authorized
84 test_authorized_list = (
85 ("AU1", "Invalid vnfd id", "GET", "/vnfpkgm/v1/vnf_packages/non-existing-id",
86 headers_json, None, 404, r_header_json, "json"),
87 ("AU2", "Invalid nsd id", "GET", "/nsd/v1/ns_descriptors/non-existing-id",
88 headers_yaml, None, 404, r_header_yaml, "yaml"),
89 ("AU3", "Invalid nsd id", "DELETE", "/nsd/v1/ns_descriptors_content/non-existing-id",
90 headers_yaml, None, 404, r_header_yaml, "yaml"),
91 )
92 timeout = 120 # general timeout
93 timeout_deploy = 60*10 # timeout for NS deploying without charms
94 timeout_configure = 60*20 # timeout for NS deploying and configuring
95
96
97 class TestException(Exception):
98 pass
99
100
101 class TestRest:
102 def __init__(self, url_base, header_base=None, verify=False, user="admin", password="admin", project="admin"):
103 self.url_base = url_base
104 if header_base is None:
105 self.header_base = {}
106 else:
107 self.header_base = header_base.copy()
108 self.s = requests.session()
109 self.s.headers = self.header_base
110 self.verify = verify
111 self.token = False
112 self.user = user
113 self.password = password
114 self.project = project
115 self.vim_id = None
116 # contains ID of tests obtained from Location response header. "" key contains last obtained id
117 self.test_ids = {}
118 self.old_test_description = ""
119
120 def set_header(self, header):
121 self.s.headers.update(header)
122
123 def unset_header(self, key):
124 if key in self.s.headers:
125 del self.s.headers[key]
126
127 def test(self, name, description, method, url, headers, payload, expected_codes, expected_headers,
128 expected_payload, store_file=None):
129 """
130 Performs an http request and check http code response. Exit if different than allowed. It get the returned id
131 that can be used by following test in the URL with {name} where name is the name of the test
132 :param name: short name of the test
133 :param description: description of the test
134 :param method: HTTP method: GET,PUT,POST,DELETE,...
135 :param url: complete URL or relative URL
136 :param headers: request headers to add to the base headers
137 :param payload: Can be a dict, transformed to json, a text or a file if starts with '@'
138 :param expected_codes: expected response codes, can be int, int tuple or int range
139 :param expected_headers: expected response headers, dict with key values
140 :param expected_payload: expected payload, 0 if empty, 'yaml', 'json', 'text', 'zip', 'octet-stream'
141 :param store_file: filename to store content
142 :return: requests response
143 """
144 r = None
145 try:
146 if not self.s:
147 self.s = requests.session()
148 # URL
149 if not url:
150 url = self.url_base
151 elif not url.startswith("http"):
152 url = self.url_base + url
153
154 var_start = url.find("<") + 1
155 while var_start:
156 var_end = url.find(">", var_start)
157 if var_end == -1:
158 break
159 var_name = url[var_start:var_end]
160 if var_name in self.test_ids:
161 url = url[:var_start-1] + self.test_ids[var_name] + url[var_end+1:]
162 var_start += len(self.test_ids[var_name])
163 var_start = url.find("<", var_start) + 1
164 if payload:
165 if isinstance(payload, str):
166 if payload.startswith("@"):
167 mode = "r"
168 file_name = payload[1:]
169 if payload.startswith("@b"):
170 mode = "rb"
171 file_name = payload[2:]
172 with open(file_name, mode) as f:
173 payload = f.read()
174 elif isinstance(payload, dict):
175 payload = json.dumps(payload)
176
177 test_description = "Test {} {} {} {}".format(name, description, method, url)
178 if self.old_test_description != test_description:
179 self.old_test_description = test_description
180 logger.warning(test_description)
181 stream = False
182 if expected_payload in ("zip", "octet-string") or store_file:
183 stream = True
184 r = getattr(self.s, method.lower())(url, data=payload, headers=headers, verify=self.verify, stream=stream)
185 if expected_payload in ("zip", "octet-string") or store_file:
186 logger.debug("RX {}".format(r.status_code))
187 else:
188 logger.debug("RX {}: {}".format(r.status_code, r.text))
189
190 # check response
191 if expected_codes:
192 if isinstance(expected_codes, int):
193 expected_codes = (expected_codes,)
194 if r.status_code not in expected_codes:
195 raise TestException(
196 "Got status {}. Expected {}. {}".format(r.status_code, expected_codes, r.text))
197
198 if expected_headers:
199 for header_key, header_val in expected_headers.items():
200 if header_key.lower() not in r.headers:
201 raise TestException("Header {} not present".format(header_key))
202 if header_val and header_val.lower() not in r.headers[header_key]:
203 raise TestException("Header {} does not contain {} but {}".format(header_key, header_val,
204 r.headers[header_key]))
205
206 if expected_payload is not None:
207 if expected_payload == 0 and len(r.content) > 0:
208 raise TestException("Expected empty payload")
209 elif expected_payload == "json":
210 try:
211 r.json()
212 except Exception as e:
213 raise TestException("Expected json response payload, but got Exception {}".format(e))
214 elif expected_payload == "yaml":
215 try:
216 yaml.safe_load(r.text)
217 except Exception as e:
218 raise TestException("Expected yaml response payload, but got Exception {}".format(e))
219 elif expected_payload in ("zip", "octet-string"):
220 if len(r.content) == 0:
221 raise TestException("Expected some response payload, but got empty")
222 # try:
223 # tar = tarfile.open(None, 'r:gz', fileobj=r.raw)
224 # for tarinfo in tar:
225 # tarname = tarinfo.name
226 # print(tarname)
227 # except Exception as e:
228 # raise TestException("Expected zip response payload, but got Exception {}".format(e))
229 elif expected_payload == "text":
230 if len(r.content) == 0:
231 raise TestException("Expected some response payload, but got empty")
232 # r.text
233 if store_file:
234 with open(store_file, 'wb') as fd:
235 for chunk in r.iter_content(chunk_size=128):
236 fd.write(chunk)
237
238 location = r.headers.get("Location")
239 if location:
240 _id = location[location.rfind("/") + 1:]
241 if _id:
242 self.test_ids[name] = str(_id)
243 self.test_ids[""] = str(_id) # last id
244 return r
245 except TestException as e:
246 r_status_code = None
247 r_text = None
248 if r:
249 r_status_code = r.status_code
250 r_text = r.text
251 logger.error("{} \nRX code{}: {}".format(e, r_status_code, r_text))
252 exit(1)
253 except IOError as e:
254 logger.error("Cannot open file {}".format(e))
255 exit(1)
256
257 def get_autorization(self): # user=None, password=None, project=None):
258 if self.token: # and self.user == user and self.password == password and self.project == project:
259 return
260 # self.user = user
261 # self.password = password
262 # self.project = project
263 r = self.test("TOKEN", "Obtain token", "POST", "/admin/v1/tokens", headers_json,
264 {"username": self.user, "password": self.password, "project_id": self.project},
265 (200, 201), r_header_json, "json")
266 response = r.json()
267 self.token = response["id"]
268 self.set_header({"Authorization": "Bearer {}".format(self.token)})
269
270 def remove_authorization(self):
271 if self.token:
272 self.test("TOKEN_DEL", "Delete token", "DELETE", "/admin/v1/tokens/{}".format(self.token), headers_json,
273 None, (200, 201, 204), None, None)
274 self.token = None
275 self.unset_header("Authorization")
276
277 def get_create_vim(self, test_osm):
278 if self.vim_id:
279 return self.vim_id
280 self.get_autorization()
281 if test_osm:
282 vim_name = os.environ.get("OSMNBITEST_VIM_NAME")
283 if not vim_name:
284 raise TestException(
285 "Needed to define OSMNBITEST_VIM_XXX variables to create a real VIM for deployment")
286 else:
287 vim_name = "fakeVim"
288 # Get VIM
289 r = self.test("_VIMGET1", "Get VIM ID", "GET", "/admin/v1/vim_accounts?name={}".format(vim_name), headers_json,
290 None, 200, r_header_json, "json")
291 vims = r.json()
292 if vims:
293 return vims[0]["_id"]
294 # Add VIM
295 if test_osm:
296 # check needed environ parameters:
297 if not os.environ.get("OSMNBITEST_VIM_URL") or not os.environ.get("OSMNBITEST_VIM_TENANT"):
298 raise TestException("Env OSMNBITEST_VIM_URL and OSMNBITEST_VIM_TENANT are needed for create a real VIM"
299 " to deploy on whit the --test-osm option")
300 vim_data = "{{schema_version: '1.0', name: '{}', vim_type: {}, vim_url: '{}', vim_tenant_name: '{}', "\
301 "vim_user: {}, vim_password: {}".format(vim_name,
302 os.environ.get("OSMNBITEST_VIM_TYPE", "openstack"),
303 os.environ.get("OSMNBITEST_VIM_URL"),
304 os.environ.get("OSMNBITEST_VIM_TENANT"),
305 os.environ.get("OSMNBITEST_VIM_USER"),
306 os.environ.get("OSMNBITEST_VIM_PASSWORD"))
307 if os.environ.get("OSMNBITEST_VIM_CONFIG"):
308 vim_data += " ,config: {}".format(os.environ.get("OSMNBITEST_VIM_CONFIG"))
309 vim_data += "}"
310 else:
311 vim_data = "{schema_version: '1.0', name: fakeVim, vim_type: openstack, vim_url: 'http://10.11.12.13/fake'"\
312 ", vim_tenant_name: 'vimtenant', vim_user: vimuser, vim_password: vimpassword}"
313 r = self.test("_VIMGET2", "Create VIM", "POST", "/admin/v1/vim_accounts", headers_yaml, vim_data,
314 (201), {"Location": "/admin/v1/vim_accounts/", "Content-Type": "application/yaml"}, "yaml")
315 location = r.headers.get("Location")
316 return location[location.rfind("/") + 1:]
317
318
319 class TestNonAuthorized:
320 description = "test invalid URLs. methods and no authorization"
321
322 @staticmethod
323 def run(engine, test_osm, manual_check, test_params=None):
324 engine.remove_authorization()
325 test_not_authorized_list = (
326 ("NA1", "Invalid token", "GET", "/admin/v1/users", headers_json, None, 401, r_header_json, "json"),
327 ("NA2", "Invalid URL", "POST", "/admin/v1/nonexist", headers_yaml, None, 405, r_header_yaml, "yaml"),
328 ("NA3", "Invalid version", "DELETE", "/admin/v2/users", headers_yaml, None, 405, r_header_yaml, "yaml"),
329 )
330 for t in test_not_authorized_list:
331 engine.test(*t)
332
333
334 class TestUsersProjects:
335 description = "test project and user creation"
336
337 @staticmethod
338 def run(engine, test_osm, manual_check, test_params=None):
339 engine.get_autorization()
340 engine.test("PU1", "Create project non admin", "POST", "/admin/v1/projects", headers_json, {"name": "P1"},
341 (201, 204), {"Location": "/admin/v1/projects/", "Content-Type": "application/json"}, "json")
342 engine.test("PU2", "Create project admin", "POST", "/admin/v1/projects", headers_json,
343 {"name": "Padmin", "admin": True}, (201, 204),
344 {"Location": "/admin/v1/projects/", "Content-Type": "application/json"}, "json")
345 engine.test("PU3", "Create project bad format", "POST", "/admin/v1/projects", headers_json, {"name": 1}, 422,
346 r_header_json, "json")
347 engine.test("PU4", "Create user with bad project", "POST", "/admin/v1/users", headers_json,
348 {"username": "U1", "projects": ["P1", "P2", "Padmin"], "password": "pw1"}, 409,
349 r_header_json, "json")
350 engine.test("PU5", "Create user with bad project and force", "POST", "/admin/v1/users?FORCE=True", headers_json,
351 {"username": "U1", "projects": ["P1", "P2", "Padmin"], "password": "pw1"}, 201,
352 {"Location": "/admin/v1/users/", "Content-Type": "application/json"}, "json")
353 engine.test("PU6", "Create user 2", "POST", "/admin/v1/users", headers_json,
354 {"username": "U2", "projects": ["P1"], "password": "pw2"}, 201,
355 {"Location": "/admin/v1/users/", "Content-Type": "application/json"}, "json")
356
357 engine.test("PU7", "Edit user U1, delete P2 project", "PATCH", "/admin/v1/users/U1", headers_json,
358 {"projects": {"$'P2'": None}}, 204, None, None)
359 res = engine.test("PU1", "Check user U1, contains the right projects", "GET", "/admin/v1/users/U1",
360 headers_json, None, 200, None, json)
361 u1 = res.json()
362 # print(u1)
363 expected_projects = ["P1", "Padmin"]
364 if u1["projects"] != expected_projects:
365 raise TestException("User content projects '{}' different than expected '{}'. Edition has not done"
366 " properly".format(u1["projects"], expected_projects))
367
368 engine.test("PU8", "Edit user U1, set Padmin as default project", "PUT", "/admin/v1/users/U1", headers_json,
369 {"projects": {"$'Padmin'": None, "$+[0]": "Padmin"}}, 204, None, None)
370 res = engine.test("PU1", "Check user U1, contains the right projects", "GET", "/admin/v1/users/U1",
371 headers_json, None, 200, None, json)
372 u1 = res.json()
373 # print(u1)
374 expected_projects = ["Padmin", "P1"]
375 if u1["projects"] != expected_projects:
376 raise TestException("User content projects '{}' different than expected '{}'. Edition has not done"
377 " properly".format(u1["projects"], expected_projects))
378
379 engine.test("PU9", "Edit user U1, change password", "PATCH", "/admin/v1/users/U1", headers_json,
380 {"password": "pw1_new"}, 204, None, None)
381
382 engine.test("PU10", "Change to project P1 non existing", "POST", "/admin/v1/tokens/", headers_json,
383 {"project_id": "P1"}, 401, r_header_json, "json")
384
385 res = engine.test("PU1", "Change to user U1 project P1", "POST", "/admin/v1/tokens", headers_json,
386 {"username": "U1", "password": "pw1_new", "project_id": "P1"}, (200, 201),
387 r_header_json, "json")
388 response = res.json()
389 engine.set_header({"Authorization": "Bearer {}".format(response["id"])})
390
391 engine.test("PU11", "Edit user projects non admin", "PUT", "/admin/v1/users/U1", headers_json,
392 {"projects": {"$'P1'": None}}, 401, r_header_json, "json")
393 engine.test("PU12", "Add new project non admin", "POST", "/admin/v1/projects", headers_json,
394 {"name": "P2"}, 401, r_header_json, "json")
395 engine.test("PU13", "Add new user non admin", "POST", "/admin/v1/users", headers_json,
396 {"username": "U3", "projects": ["P1"], "password": "pw3"}, 401,
397 r_header_json, "json")
398
399 res = engine.test("PU14", "Change to user U1 project Padmin", "POST", "/admin/v1/tokens", headers_json,
400 {"project_id": "Padmin"}, (200, 201), r_header_json, "json")
401 response = res.json()
402 engine.set_header({"Authorization": "Bearer {}".format(response["id"])})
403
404 engine.test("PU15", "Add new project admin", "POST", "/admin/v1/projects", headers_json, {"name": "P2"},
405 (201, 204), {"Location": "/admin/v1/projects/", "Content-Type": "application/json"}, "json")
406 engine.test("PU16", "Add new user U3 admin", "POST", "/admin/v1/users",
407 headers_json, {"username": "U3", "projects": ["P2"], "password": "pw3"}, (201, 204),
408 {"Location": "/admin/v1/users/", "Content-Type": "application/json"}, "json")
409 engine.test("PU17", "Edit user projects admin", "PUT", "/admin/v1/users/U3", headers_json,
410 {"projects": ["P2"]}, 204, None, None)
411
412 engine.test("PU18", "Delete project P2 conflict", "DELETE", "/admin/v1/projects/P2", headers_json, None, 409,
413 r_header_json, "json")
414 engine.test("PU19", "Delete project P2 forcing", "DELETE", "/admin/v1/projects/P2?FORCE=True", headers_json,
415 None, 204, None, None)
416
417 engine.test("PU20", "Delete user U1. Conflict deleting own user", "DELETE", "/admin/v1/users/U1", headers_json,
418 None, 409, r_header_json, "json")
419 engine.test("PU21", "Delete user U2", "DELETE", "/admin/v1/users/U2", headers_json, None, 204, None, None)
420 engine.test("PU22", "Delete user U3", "DELETE", "/admin/v1/users/U3", headers_json, None, 204, None, None)
421 # change to admin
422 engine.remove_authorization() # To force get authorization
423 engine.get_autorization()
424 engine.test("PU23", "Delete user U1", "DELETE", "/admin/v1/users/U1", headers_json, None, 204, None, None)
425 engine.test("PU24", "Delete project P1", "DELETE", "/admin/v1/projects/P1", headers_json, None, 204, None, None)
426 engine.test("PU25", "Delete project Padmin", "DELETE", "/admin/v1/projects/Padmin", headers_json, None, 204,
427 None, None)
428
429
430 class TestFakeVim:
431 description = "Creates/edit/delete fake VIMs and SDN controllers"
432
433 def __init__(self):
434 self.vim = {
435 "schema_version": "1.0",
436 "schema_type": "No idea",
437 "name": "myVim",
438 "description": "Descriptor name",
439 "vim_type": "openstack",
440 "vim_url": "http://localhost:/vim",
441 "vim_tenant_name": "vimTenant",
442 "vim_user": "user",
443 "vim_password": "password",
444 "config": {"config_param": 1}
445 }
446 self.sdn = {
447 "name": "sdn-name",
448 "description": "sdn-description",
449 "dpid": "50:50:52:54:00:94:21:21",
450 "ip": "192.168.15.17",
451 "port": 8080,
452 "type": "opendaylight",
453 "version": "3.5.6",
454 "user": "user",
455 "password": "passwd"
456 }
457 self.port_mapping = [
458 {"compute_node": "compute node 1",
459 "ports": [{"pci": "0000:81:00.0", "switch_port": "port-2/1", "switch_mac": "52:54:00:94:21:21"},
460 {"pci": "0000:81:00.1", "switch_port": "port-2/2", "switch_mac": "52:54:00:94:21:22"}
461 ]},
462 {"compute_node": "compute node 2",
463 "ports": [{"pci": "0000:81:00.0", "switch_port": "port-2/3", "switch_mac": "52:54:00:94:21:23"},
464 {"pci": "0000:81:00.1", "switch_port": "port-2/4", "switch_mac": "52:54:00:94:21:24"}
465 ]}
466 ]
467
468 def run(self, engine, test_osm, manual_check, test_params=None):
469
470 vim_bad = self.vim.copy()
471 vim_bad.pop("name")
472
473 engine.get_autorization()
474 engine.test("FVIM1", "Create VIM", "POST", "/admin/v1/vim_accounts", headers_json, self.vim, (201, 204),
475 {"Location": "/admin/v1/vim_accounts/", "Content-Type": "application/json"}, "json")
476 engine.test("FVIM2", "Create VIM without name, bad schema", "POST", "/admin/v1/vim_accounts", headers_json,
477 vim_bad, 422, None, headers_json)
478 engine.test("FVIM3", "Create VIM name repeated", "POST", "/admin/v1/vim_accounts", headers_json, self.vim,
479 409, None, headers_json)
480 engine.test("FVIM4", "Show VIMs", "GET", "/admin/v1/vim_accounts", headers_yaml, None, 200, r_header_yaml,
481 "yaml")
482 engine.test("FVIM5", "Show VIM", "GET", "/admin/v1/vim_accounts/<FVIM1>", headers_yaml, None, 200,
483 r_header_yaml, "yaml")
484 if not test_osm:
485 # delete with FORCE
486 engine.test("FVIM6", "Delete VIM", "DELETE", "/admin/v1/vim_accounts/<FVIM1>?FORCE=True", headers_yaml,
487 None, 202, None, 0)
488 engine.test("FVIM7", "Check VIM is deleted", "GET", "/admin/v1/vim_accounts/<FVIM1>", headers_yaml, None,
489 404, r_header_yaml, "yaml")
490 else:
491 # delete and wait until is really deleted
492 engine.test("FVIM6", "Delete VIM", "DELETE", "/admin/v1/vim_accounts/<FVIM1>", headers_yaml, None, 202,
493 None, 0)
494 wait = timeout
495 while wait >= 0:
496 r = engine.test("FVIM7", "Check VIM is deleted", "GET", "/admin/v1/vim_accounts/<FVIM1>", headers_yaml,
497 None, None, r_header_yaml, "yaml")
498 if r.status_code == 404:
499 break
500 elif r.status_code == 200:
501 wait -= 5
502 sleep(5)
503 else:
504 raise TestException("Vim created at 'FVIM1' is not delete after {} seconds".format(timeout))
505
506
507 class TestVIMSDN(TestFakeVim):
508 description = "Creates VIM with SDN editing SDN controllers and port_mapping"
509
510 def __init__(self):
511 TestFakeVim.__init__(self)
512
513 def run(self, engine, test_osm, manual_check, test_params=None):
514 engine.get_autorization()
515 # Added SDN
516 engine.test("VIMSDN1", "Create SDN", "POST", "/admin/v1/sdns", headers_json, self.sdn, (201, 204),
517 {"Location": "/admin/v1/sdns/", "Content-Type": "application/json"}, "json")
518 # sleep(5)
519 # Edit SDN
520 engine.test("VIMSDN2", "Edit SDN", "PATCH", "/admin/v1/sdns/<VIMSDN1>", headers_json, {"name": "new_sdn_name"},
521 204, None, None)
522 # sleep(5)
523 # VIM with SDN
524 self.vim["config"]["sdn-controller"] = engine.test_ids["VIMSDN1"]
525 self.vim["config"]["sdn-port-mapping"] = self.port_mapping
526 engine.test("VIMSDN3", "Create VIM", "POST", "/admin/v1/vim_accounts", headers_json, self.vim, (200, 204, 201),
527 {"Location": "/admin/v1/vim_accounts/", "Content-Type": "application/json"}, "json"),
528
529 self.port_mapping[0]["compute_node"] = "compute node XX"
530 engine.test("VIMSDN4", "Edit VIM change port-mapping", "PUT", "/admin/v1/vim_accounts/<VIMSDN3>", headers_json,
531 {"config": {"sdn-port-mapping": self.port_mapping}}, 204, None, None)
532 engine.test("VIMSDN5", "Edit VIM remove port-mapping", "PUT", "/admin/v1/vim_accounts/<VIMSDN3>", headers_json,
533 {"config": {"sdn-port-mapping": None}}, 204, None, None)
534
535 if not test_osm:
536 # delete with FORCE
537 engine.test("VIMSDN6", "Delete VIM remove port-mapping", "DELETE",
538 "/admin/v1/vim_accounts/<VIMSDN3>?FORCE=True", headers_json, None, 202, None, 0)
539 engine.test("VIMSDN7", "Delete SDNC", "DELETE", "/admin/v1/sdns/<VIMSDN1>?FORCE=True", headers_json, None,
540 202, None, 0)
541
542 engine.test("VIMSDN8", "Check VIM is deleted", "GET", "/admin/v1/vim_accounts/<VIMSDN3>", headers_yaml,
543 None, 404, r_header_yaml, "yaml")
544 engine.test("VIMSDN9", "Check SDN is deleted", "GET", "/admin/v1/sdns/<VIMSDN1>", headers_yaml, None,
545 404, r_header_yaml, "yaml")
546 else:
547 # delete and wait until is really deleted
548 engine.test("VIMSDN6", "Delete VIM remove port-mapping", "DELETE", "/admin/v1/vim_accounts/<VIMSDN3>",
549 headers_json, None, (202, 201, 204), None, 0)
550 engine.test("VIMSDN7", "Delete SDN", "DELETE", "/admin/v1/sdns/<VIMSDN1>", headers_json, None,
551 (202, 201, 204), None, 0)
552 wait = timeout
553 while wait >= 0:
554 r = engine.test("VIMSDN8", "Check VIM is deleted", "GET", "/admin/v1/vim_accounts/<VIMSDN3>",
555 headers_yaml, None, None, r_header_yaml, "yaml")
556 if r.status_code == 404:
557 break
558 elif r.status_code == 200:
559 wait -= 5
560 sleep(5)
561 else:
562 raise TestException("Vim created at 'VIMSDN3' is not delete after {} seconds".format(timeout))
563 while wait >= 0:
564 r = engine.test("VIMSDN9", "Check SDNC is deleted", "GET", "/admin/v1/sdns/<VIMSDN1>",
565 headers_yaml, None, None, r_header_yaml, "yaml")
566 if r.status_code == 404:
567 break
568 elif r.status_code == 200:
569 wait -= 5
570 sleep(5)
571 else:
572 raise TestException("SDNC created at 'VIMSDN1' is not delete after {} seconds".format(timeout))
573
574
575 class TestDeploy:
576 description = "Base class for downloading descriptors from ETSI, onboard and deploy in real VIM"
577
578 def __init__(self):
579 self.step = 0
580 self.nsd_id = None
581 self.vim_id = None
582 self.nsd_test = None
583 self.ns_test = None
584 self.ns_id = None
585 self.vnfds_test = []
586 self.descriptor_url = "https://osm-download.etsi.org/ftp/osm-3.0-three/2nd-hackfest/packages/"
587 self.vnfd_filenames = ("cirros_vnf.tar.gz",)
588 self.nsd_filename = "cirros_2vnf_ns.tar.gz"
589 self.uses_configuration = False
590 self.uss = {}
591 self.passwds = {}
592 self.cmds = {}
593 self.keys = {}
594 self.timeout = 120
595 self.passed_tests = 0
596 self.total_tests = 0
597
598 def create_descriptors(self, engine):
599 temp_dir = os.path.dirname(os.path.abspath(__file__)) + "/temp/"
600 if not os.path.exists(temp_dir):
601 os.makedirs(temp_dir)
602 for vnfd_filename in self.vnfd_filenames:
603 if "/" in vnfd_filename:
604 vnfd_filename_path = vnfd_filename
605 if not os.path.exists(vnfd_filename_path):
606 raise TestException("File '{}' does not exist".format(vnfd_filename_path))
607 else:
608 vnfd_filename_path = temp_dir + vnfd_filename
609 if not os.path.exists(vnfd_filename_path):
610 with open(vnfd_filename_path, "wb") as file:
611 response = requests.get(self.descriptor_url + vnfd_filename)
612 if response.status_code >= 300:
613 raise TestException("Error downloading descriptor from '{}': {}".format(
614 self.descriptor_url + vnfd_filename, response.status_code))
615 file.write(response.content)
616 if vnfd_filename_path.endswith(".yaml"):
617 headers = headers_yaml
618 else:
619 headers = headers_zip_yaml
620 if self.step % 2 == 0:
621 # vnfd CREATE AND UPLOAD in one step:
622 engine.test("DEPLOY{}".format(self.step), "Onboard VNFD in one step", "POST",
623 "/vnfpkgm/v1/vnf_packages_content", headers, "@b" + vnfd_filename_path, 201,
624 {"Location": "/vnfpkgm/v1/vnf_packages_content/", "Content-Type": "application/yaml"}, yaml)
625 self.vnfds_test.append("DEPLOY" + str(self.step))
626 self.step += 1
627 else:
628 # vnfd CREATE AND UPLOAD ZIP
629 engine.test("DEPLOY{}".format(self.step), "Onboard VNFD step 1", "POST", "/vnfpkgm/v1/vnf_packages",
630 headers_json, None, 201,
631 {"Location": "/vnfpkgm/v1/vnf_packages/", "Content-Type": "application/json"}, "json")
632 self.vnfds_test.append("DEPLOY" + str(self.step))
633 self.step += 1
634 # location = r.headers["Location"]
635 # vnfd_id = location[location.rfind("/")+1:]
636 engine.test("DEPLOY{}".format(self.step), "Onboard VNFD step 2 as ZIP", "PUT",
637 "/vnfpkgm/v1/vnf_packages/<>/package_content",
638 headers, "@b" + vnfd_filename_path, 204, None, 0)
639 self.step += 2
640
641 if "/" in self.nsd_filename:
642 nsd_filename_path = self.nsd_filename
643 if not os.path.exists(nsd_filename_path):
644 raise TestException("File '{}' does not exist".format(nsd_filename_path))
645 else:
646 nsd_filename_path = temp_dir + self.nsd_filename
647 if not os.path.exists(nsd_filename_path):
648 with open(nsd_filename_path, "wb") as file:
649 response = requests.get(self.descriptor_url + self.nsd_filename)
650 if response.status_code >= 300:
651 raise TestException("Error downloading descriptor from '{}': {}".format(
652 self.descriptor_url + self.nsd_filename, response.status_code))
653 file.write(response.content)
654 if nsd_filename_path.endswith(".yaml"):
655 headers = headers_yaml
656 else:
657 headers = headers_zip_yaml
658
659 self.nsd_test = "DEPLOY" + str(self.step)
660 if self.step % 2 == 0:
661 # nsd CREATE AND UPLOAD in one step:
662 engine.test("DEPLOY{}".format(self.step), "Onboard NSD in one step", "POST",
663 "/nsd/v1/ns_descriptors_content", headers, "@b" + nsd_filename_path, 201,
664 {"Location": "/nsd/v1/ns_descriptors_content/", "Content-Type": "application/yaml"}, yaml)
665 self.step += 1
666 else:
667 # nsd CREATE AND UPLOAD ZIP
668 engine.test("DEPLOY{}".format(self.step), "Onboard NSD step 1", "POST", "/nsd/v1/ns_descriptors",
669 headers_json, None, 201,
670 {"Location": "/nsd/v1/ns_descriptors/", "Content-Type": "application/json"}, "json")
671 self.step += 1
672 # location = r.headers["Location"]
673 # vnfd_id = location[location.rfind("/")+1:]
674 engine.test("DEPLOY{}".format(self.step), "Onboard NSD step 2 as ZIP", "PUT",
675 "/nsd/v1/ns_descriptors/<>/nsd_content",
676 headers, "@b" + nsd_filename_path, 204, None, 0)
677 self.step += 2
678 self.nsd_id = engine.test_ids[self.nsd_test]
679
680 def delete_descriptors(self, engine):
681 # delete descriptors
682 engine.test("DEPLOY{}".format(self.step), "Delete NSSD SOL005", "DELETE",
683 "/nsd/v1/ns_descriptors/<{}>".format(self.nsd_test),
684 headers_yaml, None, 204, None, 0)
685 self.step += 1
686 for vnfd_test in self.vnfds_test:
687 engine.test("DEPLOY{}".format(self.step), "Delete VNFD SOL005", "DELETE",
688 "/vnfpkgm/v1/vnf_packages/<{}>".format(vnfd_test), headers_yaml, None, 204, None, 0)
689 self.step += 1
690
691 def instantiate(self, engine, ns_data):
692 ns_data_text = yaml.safe_dump(ns_data, default_flow_style=True, width=256)
693 # create NS Two steps
694 r = engine.test("DEPLOY{}".format(self.step), "Create NS step 1", "POST", "/nslcm/v1/ns_instances",
695 headers_yaml, ns_data_text, 201,
696 {"Location": "nslcm/v1/ns_instances/", "Content-Type": "application/yaml"}, "yaml")
697 self.ns_test = "DEPLOY{}".format(self.step)
698 self.ns_id = engine.test_ids[self.ns_test]
699 engine.test_ids[self.ns_test]
700 self.step += 1
701 r = engine.test("DEPLOY{}".format(self.step), "Instantiate NS step 2", "POST",
702 "/nslcm/v1/ns_instances/<{}>/instantiate".format(self.ns_test), headers_yaml, ns_data_text,
703 201, {"Location": "nslcm/v1/ns_lcm_op_occs/", "Content-Type": "application/yaml"}, "yaml")
704 nslcmop_test = "DEPLOY{}".format(self.step)
705 self.step += 1
706
707 if test_osm:
708 # Wait until status is Ok
709 wait = timeout_configure if self.uses_configuration else timeout_deploy
710 while wait >= 0:
711 r = engine.test("DEPLOY{}".format(self.step), "Wait until NS is deployed and configured", "GET",
712 "/nslcm/v1/ns_lcm_op_occs/<{}>".format(nslcmop_test), headers_json, None,
713 200, r_header_json, "json")
714 nslcmop = r.json()
715 if "COMPLETED" in nslcmop["operationState"]:
716 break
717 elif "FAILED" in nslcmop["operationState"]:
718 raise TestException("NS instantiate has failed: {}".format(nslcmop["detailed-status"]))
719 wait -= 5
720 sleep(5)
721 else:
722 raise TestException("NS instantiate is not done after {} seconds".format(timeout_deploy))
723 self.step += 1
724
725 def _wait_nslcmop_ready(self, engine, nslcmop_test, timeout_deploy):
726 wait = timeout
727 while wait >= 0:
728 r = engine.test("DEPLOY{}".format(self.step), "Wait to ns lcm operation complete", "GET",
729 "/nslcm/v1/ns_lcm_op_occs/<{}>".format(nslcmop_test), headers_json, None,
730 200, r_header_json, "json")
731 nslcmop = r.json()
732 if "COMPLETED" in nslcmop["operationState"]:
733 break
734 elif "FAILED" in nslcmop["operationState"]:
735 raise TestException("NS terminate has failed: {}".format(nslcmop["detailed-status"]))
736 wait -= 5
737 sleep(5)
738 else:
739 raise TestException("NS instantiate is not terminate after {} seconds".format(timeout))
740
741 def terminate(self, engine):
742 # remove deployment
743 if test_osm:
744 r = engine.test("DEPLOY{}".format(self.step), "Terminate NS", "POST",
745 "/nslcm/v1/ns_instances/<{}>/terminate".format(self.ns_test), headers_yaml, None,
746 201, {"Location": "nslcm/v1/ns_lcm_op_occs/", "Content-Type": "application/yaml"}, "yaml")
747 nslcmop2_test = "DEPLOY{}".format(self.step)
748 self.step += 1
749 # Wait until status is Ok
750 self._wait_nslcmop_ready(engine, nslcmop2_test, timeout_deploy)
751
752 r = engine.test("DEPLOY{}".format(self.step), "Delete NS", "DELETE",
753 "/nslcm/v1/ns_instances/<{}>".format(self.ns_test), headers_yaml, None,
754 204, None, 0)
755 self.step += 1
756 else:
757 r = engine.test("DEPLOY{}".format(self.step), "Delete NS with FORCE", "DELETE",
758 "/nslcm/v1/ns_instances/<{}>?FORCE=True".format(self.ns_test), headers_yaml, None,
759 204, None, 0)
760 self.step += 1
761
762 # check all it is deleted
763 r = engine.test("DEPLOY{}".format(self.step), "Check NS is deleted", "GET",
764 "/nslcm/v1/ns_instances/<{}>".format(self.ns_test), headers_yaml, None,
765 404, None, "yaml")
766 self.step += 1
767 r = engine.test("DEPLOY{}".format(self.step), "Check NSLCMOPs are deleted", "GET",
768 "/nslcm/v1/ns_lcm_op_occs?nsInstanceId=<{}>".format(self.ns_test), headers_json, None,
769 200, None, "json")
770 nslcmops = r.json()
771 if not isinstance(nslcmops, list) or nslcmops:
772 raise TestException("NS {} deleted but with ns_lcm_op_occ active: {}".format(self.ns_test, nslcmops))
773
774 def test_ns(self, engine, test_osm, commands=None, users=None, passwds=None, keys=None, timeout=0):
775
776 n = 0
777 r = engine.test("TEST_NS{}".format(n), "GET VNFR_IDs", "GET",
778 "/nslcm/v1/ns_instances/{}".format(self.ns_id), headers_json, None,
779 200, r_header_json, "json")
780 n += 1
781 ns_data = r.json()
782
783 vnfr_list = ns_data['constituent-vnfr-ref']
784 time = 0
785
786 for vnfr_id in vnfr_list:
787 self.total_tests += 1
788 r = engine.test("TEST_NS{}".format(n), "GET IP_ADDRESS OF VNFR", "GET",
789 "/nslcm/v1/vnfrs/{}".format(vnfr_id), headers_json, None,
790 200, r_header_json, "json")
791 n += 1
792 vnfr_data = r.json()
793
794 if vnfr_data.get("ip-address"):
795 name = "TEST_NS{}".format(n)
796 description = "Run tests in VNFR with IP {}".format(vnfr_data['ip-address'])
797 n += 1
798 test_description = "Test {} {}".format(name, description)
799 logger.warning(test_description)
800 vnf_index = str(vnfr_data["member-vnf-index-ref"])
801 while timeout >= time:
802 result, message = self.do_checks([vnfr_data["ip-address"]],
803 vnf_index=vnfr_data["member-vnf-index-ref"],
804 commands=commands.get(vnf_index), user=users.get(vnf_index),
805 passwd=passwds.get(vnf_index), key=keys.get(vnf_index))
806 if result == 1:
807 logger.warning(message)
808 break
809 elif result == 0:
810 time += 20
811 sleep(20)
812 elif result == -1:
813 logger.critical(message)
814 break
815 else:
816 time -= 20
817 logger.critical(message)
818 else:
819 logger.critical("VNFR {} has not mgmt address. Check failed".format(vnfr_id))
820
821 def do_checks(self, ip, vnf_index, commands=[], user=None, passwd=None, key=None):
822 try:
823 import urllib3
824 from pssh.clients import ParallelSSHClient
825 from pssh.utils import load_private_key
826 from ssh2 import exceptions as ssh2Exception
827 except ImportError as e:
828 logger.critical("package <pssh> or/and <urllib3> is not installed. Please add it with 'pip3 install "
829 "parallel-ssh' and/or 'pip3 install urllib3': {}".format(e))
830 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
831 try:
832 p_host = os.environ.get("PROXY_HOST")
833 p_user = os.environ.get("PROXY_USER")
834 p_password = os.environ.get("PROXY_PASSWD")
835
836 if key:
837 pkey = load_private_key(key)
838 else:
839 pkey = None
840
841 client = ParallelSSHClient(ip, user=user, password=passwd, pkey=pkey, proxy_host=p_host,
842 proxy_user=p_user, proxy_password=p_password, timeout=10, num_retries=0)
843 for cmd in commands:
844 output = client.run_command(cmd)
845 client.join(output)
846 if output[ip[0]].exit_code:
847 return -1, " VNFR {} could not be checked: {}".format(ip[0], output[ip[0]].stderr)
848 else:
849 self.passed_tests += 1
850 return 1, " Test successful"
851 except (ssh2Exception.ChannelFailure, ssh2Exception.SocketDisconnectError, ssh2Exception.SocketTimeout,
852 ssh2Exception.SocketRecvError) as e:
853 return 0, "Timeout accessing the VNFR {}: {}".format(ip[0], str(e))
854 except Exception as e:
855 return -1, "ERROR checking the VNFR {}: {}".format(ip[0], str(e))
856
857 def aditional_operations(self, engine, test_osm, manual_check):
858 pass
859
860 def run(self, engine, test_osm, manual_check, test_params=None):
861 engine.get_autorization()
862 nsname = os.environ.get("OSMNBITEST_NS_NAME", "OSMNBITEST")
863 if test_params:
864 if "vnfd-files" in test_params:
865 self.vnfd_filenames = test_params["vnfd-files"].split(",")
866 if "nsd-file" in test_params:
867 self.nsd_filename = test_params["nsd-file"]
868 if test_params.get("ns-name"):
869 nsname = test_params["ns-name"]
870 self.create_descriptors(engine)
871
872 # create real VIM if not exist
873 self.vim_id = engine.get_create_vim(test_osm)
874 ns_data = {"nsDescription": "default description", "nsName": nsname, "nsdId": self.nsd_id,
875 "vimAccountId": self.vim_id}
876 if test_params and test_params.get("ns-config"):
877 if isinstance(test_params["ns-config"], str):
878 ns_data.update(yaml.load(test_params["ns-config"]))
879 else:
880 ns_data.update(test_params["ns-config"])
881 self.instantiate(engine, ns_data)
882
883 if manual_check:
884 input('NS has been deployed. Perform manual check and press enter to resume')
885 else:
886 self.test_ns(engine, test_osm, self.cmds, self.uss, self.pss, self.keys, self.timeout)
887 self.aditional_operations(engine, test_osm, manual_check)
888 self.terminate(engine)
889 self.delete_descriptors(engine)
890 self.print_results()
891
892 def print_results(self):
893 print("\n\n\n--------------------------------------------")
894 print("TEST RESULTS:\n PASSED TESTS: {} - TOTAL TESTS: {}".format(self.total_tests, self.passed_tests))
895 print("--------------------------------------------")
896
897
898 class TestDeployHackfestCirros(TestDeploy):
899 description = "Load and deploy Hackfest cirros_2vnf_ns example"
900
901 def __init__(self):
902 super().__init__()
903 self.vnfd_filenames = ("cirros_vnf.tar.gz",)
904 self.nsd_filename = "cirros_2vnf_ns.tar.gz"
905 self.cmds = {'1': ['ls -lrt', ], '2': ['ls -lrt', ]}
906 self.uss = {'1': "cirros", '2': "cirros"}
907 self.pss = {'1': "cubswin:)", '2': "cubswin:)"}
908
909
910 class TestDeployIpMac(TestDeploy):
911 description = "Load and deploy descriptor examples setting mac, ip address at descriptor and instantiate params"
912
913 def __init__(self):
914 super().__init__()
915 self.vnfd_filenames = ("vnfd_2vdu_set_ip_mac2.yaml", "vnfd_2vdu_set_ip_mac.yaml")
916 self.nsd_filename = "scenario_2vdu_set_ip_mac.yaml"
917 self.descriptor_url = \
918 "https://osm.etsi.org/gitweb/?p=osm/RO.git;a=blob_plain;f=test/RO_tests/v3_2vdu_set_ip_mac/"
919 self.cmds = {'1': ['ls -lrt', ], '2': ['ls -lrt', ]}
920 self.uss = {'1': "osm", '2': "osm"}
921 self.pss = {'1': "osm4u", '2': "osm4u"}
922 self.timeout = 360
923
924 def run(self, engine, test_osm, manual_check, test_params=None):
925 # super().run(engine, test_osm, manual_check, test_params)
926 # run again setting IPs with instantiate parameters
927 instantiation_params = {
928 "vnf": [
929 {
930 "member-vnf-index": "1",
931 "internal-vld": [
932 {
933 "name": "internal_vld1", # net_internal
934 "ip-profile": {
935 "ip-version": "ipv4",
936 "subnet-address": "10.9.8.0/24",
937 "dhcp-params": {"count": 100, "start-address": "10.9.8.100"}
938 },
939 "internal-connection-point": [
940 {
941 "id-ref": "eth2",
942 "ip-address": "10.9.8.2",
943 },
944 {
945 "id-ref": "eth3",
946 "ip-address": "10.9.8.3",
947 }
948 ]
949 },
950 ],
951
952 "vdu": [
953 {
954 "id": "VM1",
955 "interface": [
956 # {
957 # "name": "iface11",
958 # "floating-ip-required": True,
959 # },
960 {
961 "name": "iface13",
962 "mac-address": "52:33:44:55:66:13"
963 },
964 ],
965 },
966 {
967 "id": "VM2",
968 "interface": [
969 {
970 "name": "iface21",
971 "ip-address": "10.31.31.22",
972 "mac-address": "52:33:44:55:66:21"
973 },
974 ],
975 },
976 ]
977 },
978 ]
979 }
980
981 super().run(engine, test_osm, manual_check, test_params={"ns-config": instantiation_params})
982
983
984 class TestDeployHackfest4(TestDeploy):
985 description = "Load and deploy Hackfest 4 example."
986
987 def __init__(self):
988 super().__init__()
989 self.vnfd_filenames = ("hackfest_4_vnfd.tar.gz",)
990 self.nsd_filename = "hackfest_4_nsd.tar.gz"
991 self.uses_configuration = True
992 self.cmds = {'1': ['ls -lrt', ], '2': ['ls -lrt', ]}
993 self.uss = {'1': "ubuntu", '2': "ubuntu"}
994 self.pss = {'1': "osm4u", '2': "osm4u"}
995
996 def create_descriptors(self, engine):
997 super().create_descriptors(engine)
998 # Modify VNFD to add scaling
999 payload = """
1000 scaling-group-descriptor:
1001 - name: "scale_dataVM"
1002 max-instance-count: 10
1003 scaling-policy:
1004 - name: "auto_cpu_util_above_threshold"
1005 scaling-type: "automatic"
1006 threshold-time: 0
1007 cooldown-time: 60
1008 scaling-criteria:
1009 - name: "cpu_util_above_threshold"
1010 scale-in-threshold: 15
1011 scale-in-relational-operation: "LE"
1012 scale-out-threshold: 60
1013 scale-out-relational-operation: "GE"
1014 vnf-monitoring-param-ref: "all_aaa_cpu_util"
1015 vdu:
1016 - vdu-id-ref: dataVM
1017 count: 1
1018 scaling-config-action:
1019 - trigger: post-scale-out
1020 vnf-config-primitive-name-ref: touch
1021 - trigger: pre-scale-in
1022 vnf-config-primitive-name-ref: touch
1023 vnf-configuration:
1024 config-primitive:
1025 - name: touch
1026 parameter:
1027 - name: filename
1028 data-type: STRING
1029 default-value: '/home/ubuntu/touched'
1030 """
1031 engine.test("DEPLOY{}".format(self.step), "Edit VNFD ", "PATCH",
1032 "/vnfpkgm/v1/vnf_packages/<{}>".format(self.vnfds_test[0]), headers_yaml, payload, 204, None, None)
1033 self.step += 1
1034
1035
1036 class TestDeployHackfest3Charmed(TestDeploy):
1037 description = "Load and deploy Hackfest 3charmed_ns example. Modifies it for adding scaling and performs " \
1038 "primitive actions and scaling"
1039
1040 def __init__(self):
1041 super().__init__()
1042 self.vnfd_filenames = ("hackfest_3charmed_vnfd.tar.gz",)
1043 self.nsd_filename = "hackfest_3charmed_nsd.tar.gz"
1044 self.uses_configuration = True
1045 self.cmds = {'1': [''], '2': ['ls -lrt /home/ubuntu/first-touch', ]}
1046 self.uss = {'1': "ubuntu", '2': "ubuntu"}
1047 self.pss = {'1': "osm4u", '2': "osm4u"}
1048
1049 # def create_descriptors(self, engine):
1050 # super().create_descriptors(engine)
1051 # # Modify VNFD to add scaling
1052 # payload = """
1053 # scaling-group-descriptor:
1054 # - name: "scale_dataVM"
1055 # max-instance-count: 10
1056 # scaling-policy:
1057 # - name: "auto_cpu_util_above_threshold"
1058 # scaling-type: "automatic"
1059 # threshold-time: 0
1060 # cooldown-time: 60
1061 # scaling-criteria:
1062 # - name: "cpu_util_above_threshold"
1063 # scale-in-threshold: 15
1064 # scale-in-relational-operation: "LE"
1065 # scale-out-threshold: 60
1066 # scale-out-relational-operation: "GE"
1067 # vnf-monitoring-param-ref: "all_aaa_cpu_util"
1068 # vdu:
1069 # - vdu-id-ref: dataVM
1070 # count: 1
1071 # scaling-config-action:
1072 # - trigger: post-scale-out
1073 # vnf-config-primitive-name-ref: touch
1074 # - trigger: pre-scale-in
1075 # vnf-config-primitive-name-ref: touch
1076 # vnf-configuration:
1077 # config-primitive:
1078 # - name: touch
1079 # parameter:
1080 # - name: filename
1081 # data-type: STRING
1082 # default-value: '/home/ubuntu/touched'
1083 # """
1084 # engine.test("DEPLOY{}".format(self.step), "Edit VNFD ", "PATCH",
1085 # "/vnfpkgm/v1/vnf_packages/<{}>".format(self.vnfds_test[0]),
1086 # headers_yaml, payload, 200,
1087 # r_header_yaml, yaml)
1088 # self.vnfds_test.append("DEPLOY" + str(self.step))
1089 # self.step += 1
1090
1091 def aditional_operations(self, engine, test_osm, manual_check):
1092 if not test_osm:
1093 return
1094 # 1 perform action
1095 payload = '{member_vnf_index: "2", primitive: touch, primitive_params: { filename: /home/ubuntu/OSMTESTNBI }}'
1096 engine.test("DEPLOY{}".format(self.step), "Executer service primitive over NS", "POST",
1097 "/nslcm/v1/ns_instances/<{}>/action".format(self.ns_test), headers_yaml, payload,
1098 201, {"Location": "nslcm/v1/ns_lcm_op_occs/", "Content-Type": "application/yaml"}, "yaml")
1099 nslcmop2_action = "DEPLOY{}".format(self.step)
1100 self.step += 1
1101 # Wait until status is Ok
1102 self._wait_nslcmop_ready(engine, nslcmop2_action, timeout_deploy)
1103 if manual_check:
1104 input('NS service primitive has been executed. Check that file /home/ubuntu/OSMTESTNBI is present at '
1105 'TODO_PUT_IP')
1106 else:
1107 cmds = {'1': [''], '2': ['ls -lrt /home/ubuntu/OSMTESTNBI', ]}
1108 uss = {'1': "ubuntu", '2': "ubuntu"}
1109 pss = {'1': "osm4u", '2': "osm4u"}
1110 self.test_ns(engine, test_osm, cmds, uss, pss, self.keys, self.timeout)
1111
1112 # # 2 perform scale out
1113 # payload = '{scaleType: SCALE_VNF, scaleVnfData: {scaleVnfType: SCALE_OUT, scaleByStepData: ' \
1114 # '{scaling-group-descriptor: scale_dataVM, member-vnf-index: "1"}}}'
1115 # engine.test("DEPLOY{}".format(self.step), "Execute scale action over NS", "POST",
1116 # "/nslcm/v1/ns_instances/<{}>/scale".format(self.ns_test), headers_yaml, payload,
1117 # 201, {"Location": "nslcm/v1/ns_lcm_op_occs/", "Content-Type": "application/yaml"}, "yaml")
1118 # nslcmop2_scale_out = "DEPLOY{}".format(self.step)
1119 # self._wait_nslcmop_ready(engine, nslcmop2_scale_out, timeout_deploy)
1120 # if manual_check:
1121 # input('NS scale out done. Check that file /home/ubuntu/touched is present and new VM is created')
1122 # # TODO check automatic
1123 #
1124 # # 2 perform scale in
1125 # payload = '{scaleType: SCALE_VNF, scaleVnfData: {scaleVnfType: SCALE_IN, scaleByStepData: ' \
1126 # '{scaling-group-descriptor: scale_dataVM, member-vnf-index: "1"}}}'
1127 # engine.test("DEPLOY{}".format(self.step), "Execute scale action over NS", "POST",
1128 # "/nslcm/v1/ns_instances/<{}>/scale".format(self.ns_test), headers_yaml, payload,
1129 # 201, {"Location": "nslcm/v1/ns_lcm_op_occs/", "Content-Type": "application/yaml"}, "yaml")
1130 # nslcmop2_scale_in = "DEPLOY{}".format(self.step)
1131 # self._wait_nslcmop_ready(engine, nslcmop2_scale_in, timeout_deploy)
1132 # if manual_check:
1133 # input('NS scale in done. Check that file /home/ubuntu/touched is updated and new VM is deleted')
1134 # # TODO check automatic
1135
1136
1137 class TestDescriptors:
1138 description = "Test VNFD, NSD, PDU descriptors CRUD and dependencies"
1139
1140 def __init__(self):
1141 self.step = 0
1142 self.vnfd_filename = "hackfest_3charmed_vnfd.tar.gz"
1143 self.nsd_filename = "hackfest_3charmed_nsd.tar.gz"
1144 self.descriptor_url = "https://osm-download.etsi.org/ftp/osm-3.0-three/2nd-hackfest/packages/"
1145 self.vnfd_id = None
1146 self.nsd_id = None
1147
1148 def run(self, engine, test_osm, manual_check, test_params=None):
1149 engine.get_autorization()
1150 temp_dir = os.path.dirname(os.path.abspath(__file__)) + "/temp/"
1151 if not os.path.exists(temp_dir):
1152 os.makedirs(temp_dir)
1153
1154 # download files
1155 for filename in (self.vnfd_filename, self.nsd_filename):
1156 filename_path = temp_dir + filename
1157 if not os.path.exists(filename_path):
1158 with open(filename_path, "wb") as file:
1159 response = requests.get(self.descriptor_url + filename)
1160 if response.status_code >= 300:
1161 raise TestException("Error downloading descriptor from '{}': {}".format(
1162 self.descriptor_url + filename, response.status_code))
1163 file.write(response.content)
1164
1165 vnfd_filename_path = temp_dir + self.vnfd_filename
1166 nsd_filename_path = temp_dir + self.nsd_filename
1167
1168 # vnfd CREATE AND UPLOAD in one step:
1169 test_name = "DESCRIPTOR{}".format(self.step)
1170 engine.test(test_name, "Onboard VNFD in one step", "POST",
1171 "/vnfpkgm/v1/vnf_packages_content", headers_zip_yaml, "@b" + vnfd_filename_path, 201,
1172 {"Location": "/vnfpkgm/v1/vnf_packages_content/", "Content-Type": "application/yaml"}, "yaml")
1173 self.vnfd_id = engine.test_ids[test_name]
1174 self.step += 1
1175
1176 # get vnfd descriptor
1177 engine.test("DESCRIPTOR" + str(self.step), "Get VNFD descriptor", "GET",
1178 "/vnfpkgm/v1/vnf_packages/{}".format(self.vnfd_id), headers_yaml, None, 200, r_header_yaml, "yaml")
1179 self.step += 1
1180
1181 # get vnfd file descriptor
1182 engine.test("DESCRIPTOR" + str(self.step), "Get VNFD file descriptor", "GET",
1183 "/vnfpkgm/v1/vnf_packages/{}/vnfd".format(self.vnfd_id), headers_text, None, 200,
1184 r_header_text, "text", temp_dir+"vnfd-yaml")
1185 self.step += 1
1186 # TODO compare files: diff vnfd-yaml hackfest_3charmed_vnfd/hackfest_3charmed_vnfd.yaml
1187
1188 # get vnfd zip file package
1189 engine.test("DESCRIPTOR" + str(self.step), "Get VNFD zip package", "GET",
1190 "/vnfpkgm/v1/vnf_packages/{}/package_content".format(self.vnfd_id), headers_zip, None, 200,
1191 r_header_zip, "zip", temp_dir+"vnfd-zip")
1192 self.step += 1
1193 # TODO compare files: diff vnfd-zip hackfest_3charmed_vnfd.tar.gz
1194
1195 # get vnfd artifact
1196 engine.test("DESCRIPTOR" + str(self.step), "Get VNFD artifact package", "GET",
1197 "/vnfpkgm/v1/vnf_packages/{}/artifacts/icons/osm.png".format(self.vnfd_id), headers_zip, None, 200,
1198 r_header_octect, "octet-string", temp_dir+"vnfd-icon")
1199 self.step += 1
1200 # TODO compare files: diff vnfd-icon hackfest_3charmed_vnfd/icons/osm.png
1201
1202 # nsd CREATE AND UPLOAD in one step:
1203 test_name = "DESCRIPTOR{}".format(self.step)
1204 engine.test(test_name, "Onboard NSD in one step", "POST",
1205 "/nsd/v1/ns_descriptors_content", headers_zip_yaml, "@b" + nsd_filename_path, 201,
1206 {"Location": "/nsd/v1/ns_descriptors_content/", "Content-Type": "application/yaml"}, "yaml")
1207 self.nsd_id = engine.test_ids[test_name]
1208 self.step += 1
1209
1210 # get nsd descriptor
1211 engine.test("DESCRIPTOR" + str(self.step), "Get NSD descriptor", "GET",
1212 "/nsd/v1/ns_descriptors/{}".format(self.nsd_id), headers_yaml, None, 200, r_header_yaml, "yaml")
1213 self.step += 1
1214
1215 # get nsd file descriptor
1216 engine.test("DESCRIPTOR" + str(self.step), "Get NSD file descriptor", "GET",
1217 "/nsd/v1/ns_descriptors/{}/nsd".format(self.nsd_id), headers_text, None, 200,
1218 r_header_text, "text", temp_dir+"nsd-yaml")
1219 self.step += 1
1220 # TODO compare files: diff nsd-yaml hackfest_3charmed_nsd/hackfest_3charmed_nsd.yaml
1221
1222 # get nsd zip file package
1223 engine.test("DESCRIPTOR" + str(self.step), "Get NSD zip package", "GET",
1224 "/nsd/v1/ns_descriptors/{}/nsd_content".format(self.nsd_id), headers_zip, None, 200,
1225 r_header_zip, "zip", temp_dir+"nsd-zip")
1226 self.step += 1
1227 # TODO compare files: diff nsd-zip hackfest_3charmed_nsd.tar.gz
1228
1229 # get nsd artifact
1230 engine.test("DESCRIPTOR" + str(self.step), "Get NSD artifact package", "GET",
1231 "/nsd/v1/ns_descriptors/{}/artifacts/icons/osm.png".format(self.nsd_id), headers_zip, None, 200,
1232 r_header_octect, "octet-string", temp_dir+"nsd-icon")
1233 self.step += 1
1234 # TODO compare files: diff nsd-icon hackfest_3charmed_nsd/icons/osm.png
1235
1236 # vnfd DELETE
1237 test_rest.test("DESCRIPTOR" + str(self.step), "Delete VNFD conflict", "DELETE",
1238 "/vnfpkgm/v1/vnf_packages/{}".format(self.vnfd_id), headers_yaml, None, 409, None, None)
1239 self.step += 1
1240
1241 test_rest.test("DESCRIPTOR" + str(self.step), "Delete VNFD force", "DELETE",
1242 "/vnfpkgm/v1/vnf_packages/{}?FORCE=TRUE".format(self.vnfd_id), headers_yaml, None, 204, None, 0)
1243 self.step += 1
1244
1245 # nsd DELETE
1246 test_rest.test("DESCRIPTOR" + str(self.step), "Delete NSD", "DELETE",
1247 "/nsd/v1/ns_descriptors/{}".format(self.nsd_id), headers_yaml, None, 204, None, 0)
1248 self.step += 1
1249
1250
1251 if __name__ == "__main__":
1252 global logger
1253 test = ""
1254
1255 # Disable warnings from self-signed certificates.
1256 requests.packages.urllib3.disable_warnings()
1257 try:
1258 logging.basicConfig(format="%(levelname)s %(message)s", level=logging.ERROR)
1259 logger = logging.getLogger('NBI')
1260 # load parameters and configuration
1261 opts, args = getopt.getopt(sys.argv[1:], "hvu:p:",
1262 ["url=", "user=", "password=", "help", "version", "verbose", "no-verbose",
1263 "project=", "insecure", "timeout", "timeout-deploy", "timeout-configure",
1264 "test=", "list", "test-osm", "manual-check", "params="])
1265 url = "https://localhost:9999/osm"
1266 user = password = project = "admin"
1267 test_osm = False
1268 manual_check = False
1269 verbose = 0
1270 verify = True
1271 test_classes = {
1272 "NonAuthorized": TestNonAuthorized,
1273 "FakeVIM": TestFakeVim,
1274 "TestUsersProjects": TestUsersProjects,
1275 "VIM-SDN": TestVIMSDN,
1276 "Deploy-Custom": TestDeploy,
1277 "Deploy-Hackfest-Cirros": TestDeployHackfestCirros,
1278 "Deploy-Hackfest-3Charmed": TestDeployHackfest3Charmed,
1279 "Deploy-Hackfest-4": TestDeployHackfest4,
1280 "Deploy-CirrosMacIp": TestDeployIpMac,
1281 "TestDescriptors": TestDescriptors,
1282 # "Deploy-MultiVIM": TestDeployMultiVIM,
1283 }
1284 test_to_do = []
1285 test_params = {}
1286
1287 for o, a in opts:
1288 # print("parameter:", o, a)
1289 if o == "--version":
1290 print("test version " + __version__ + ' ' + version_date)
1291 exit()
1292 elif o == "--list":
1293 for test, test_class in test_classes.items():
1294 print("{:20} {}".format(test + ":", test_class.description))
1295 exit()
1296 elif o in ("-v", "--verbose"):
1297 verbose += 1
1298 elif o == "no-verbose":
1299 verbose = -1
1300 elif o in ("-h", "--help"):
1301 usage()
1302 sys.exit()
1303 elif o == "--test-osm":
1304 test_osm = True
1305 elif o == "--manual-check":
1306 manual_check = True
1307 elif o == "--url":
1308 url = a
1309 elif o in ("-u", "--user"):
1310 user = a
1311 elif o in ("-p", "--password"):
1312 password = a
1313 elif o == "--project":
1314 project = a
1315 elif o == "--test":
1316 # print("asdfadf", o, a, a.split(","))
1317 for _test in a.split(","):
1318 if _test not in test_classes:
1319 print("Invalid test name '{}'. Use option '--list' to show available tests".format(_test),
1320 file=sys.stderr)
1321 exit(1)
1322 test_to_do.append(_test)
1323 elif o == "--params":
1324 param_key, _, param_value = a.partition("=")
1325 text_index = len(test_to_do)
1326 if text_index not in test_params:
1327 test_params[text_index] = {}
1328 test_params[text_index][param_key] = param_value
1329 elif o == "--insecure":
1330 verify = False
1331 elif o == "--timeout":
1332 timeout = int(a)
1333 elif o == "--timeout-deploy":
1334 timeout_deploy = int(a)
1335 elif o == "--timeout-configure":
1336 timeout_configure = int(a)
1337 else:
1338 assert False, "Unhandled option"
1339 if verbose == 0:
1340 logger.setLevel(logging.WARNING)
1341 elif verbose > 1:
1342 logger.setLevel(logging.DEBUG)
1343 else:
1344 logger.setLevel(logging.ERROR)
1345
1346 test_rest = TestRest(url, user=user, password=password, project=project)
1347 # print("tests to do:", test_to_do)
1348 if test_to_do:
1349 text_index = 0
1350 for test in test_to_do:
1351 text_index += 1
1352 test_class = test_classes[test]
1353 test_class().run(test_rest, test_osm, manual_check, test_params.get(text_index))
1354 else:
1355 for test, test_class in test_classes.items():
1356 test_class().run(test_rest, test_osm, manual_check, test_params.get(0))
1357 exit(0)
1358
1359 # get token
1360 print("PASS")
1361
1362 except TestException as e:
1363 logger.error(test + "Test {} Exception: {}".format(test, str(e)))
1364 exit(1)
1365 except getopt.GetoptError as e:
1366 logger.error(e)
1367 print(e, file=sys.stderr)
1368 exit(1)
1369 except Exception as e:
1370 logger.critical(test + " Exception: " + str(e), exc_info=True)