1 # Copyright (c) 2018 Intel Corporation
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
19 from collections import Mapping
21 from yardstick.common import exceptions
22 from yardstick.common import utils as common_utils
23 from yardstick.network_services import utils as net_serv_utils
24 from yardstick.network_services.vnf_generic.vnf import sample_vnf
27 from lsapi import LsApi
29 LsApi = common_utils.ErrorClass
31 LOG = logging.getLogger(__name__)
34 class LandslideResourceHelper(sample_vnf.ClientResourceHelper):
35 """Landslide TG helper class"""
37 REST_STATUS_CODES = {'OK': 200, 'CREATED': 201, 'NO CHANGE': 409}
38 REST_API_CODES = {'NOT MODIFIED': 500810}
40 def __init__(self, setup_helper):
41 super(LandslideResourceHelper, self).__init__(setup_helper)
43 self.vnfd_helper = setup_helper.vnfd_helper
44 self.scenario_helper = setup_helper.scenario_helper
46 # TAS Manager config initialization
50 self.license_data = {}
52 # TCL session initialization
53 self._tcl = LandslideTclClient(LsTclHandler(), self)
55 self.session = requests.Session()
56 self.running_tests_uri = 'runningTests'
57 self.test_session_uri = 'testSessions'
58 self.test_serv_uri = 'testServers'
59 self.suts_uri = 'suts'
60 self.users_uri = 'users'
61 self.user_lib_uri = None
64 def abort_running_tests(self, timeout=60, delay=5):
65 """ Abort running test sessions, if any """
66 _start_time = time.time()
67 while time.time() < _start_time + timeout:
68 run_tests_states = {x['id']: x['testStateOrStep']
69 for x in self.get_running_tests()}
70 if not set(run_tests_states.values()).difference(
71 {'COMPLETE', 'COMPLETE_ERROR'}):
74 [self.stop_running_tests(running_test_id=_id, force=True)
75 for _id, _state in run_tests_states.items()
76 if 'COMPLETE' not in _state]
80 'Some test runs not stopped during {} seconds'.format(timeout))
82 def _build_url(self, resource, action=None):
85 :param resource: REST API resource name
87 :param action: actions name and value
88 :type action: dict('name': <str>, 'value': <str>)
89 :returns str: REST API resource name with optional action info
91 # Action is optional and accepted only in presence of resource param
92 if action and not resource:
93 raise ValueError("Resource name not provided")
95 _action = ''.join(['?{}={}'.format(k, v) for k, v in
96 action.items()]) if action else ''
98 return ''.join([self._url, resource, _action])
100 def get_response_params(self, method, resource, params=None):
101 """ Retrieve params from JSON response of specific resource URL
103 :param method: one of supported REST API methods
105 :param resource: URI, requested resource name
107 :param params: attributes to be found in JSON response
108 :type params: list(str)
111 params = params if params else []
112 response = self.exec_rest_request(method, resource)
113 # Get substring between last slash sign and question mark (if any)
114 url_last_part = resource.rsplit('/', 1)[-1].rsplit('?', 1)[0]
115 _response_json = response.json()
116 # Expect dict(), if URL last part and top dict key don't match
117 # Else, if they match, expect list()
118 k, v = list(_response_json.items())[0]
119 if k != url_last_part:
120 v = [v] # v: list(dict(str: str))
121 # Extract params, or whole list of dicts (without top level key)
123 _res.append({param: x[param] for param in params} if params else x)
126 def _create_user(self, auth, level=1):
129 :param auth: data to create user account on REST server
131 :param level: Landslide user permissions level
133 :returns int: user id
135 # Set expiration date in two years since account creation date
136 _exp_date = time.strftime(
137 '{}/%m/%d %H:%M %Z'.format(time.gmtime().tm_year + 2))
138 _username = auth['user']
139 _fields = {"contactInformation": "", "expiresOn": _exp_date,
140 "fullName": "Test User",
141 "isActive": "true", "level": level,
142 "password": auth['password'],
143 "username": _username}
144 _response = self.exec_rest_request('post', self.users_uri,
145 json_data=_fields, raise_exc=False)
146 _resp_json = _response.json()
147 if _response.status_code == self.REST_STATUS_CODES['CREATED']:
149 _id = _resp_json['id']
150 LOG.info("New user created: username='%s', id='%s'", _username,
152 elif _resp_json.get('apiCode') == self.REST_API_CODES['NOT MODIFIED']:
153 # User already exists
154 LOG.info("Account '%s' already exists.", _username)
156 _id = self._modify_user(_username, {"isActive": "true"})['id']
158 raise exceptions.RestApiError(
159 'Error during new user "{}" creation'.format(_username))
162 def _modify_user(self, username, fields):
163 """ Modify information about existing user
165 :param username: user name of account to be modified
167 :param fields: data to modify user account on REST server
169 :returns dict: user info
171 _response = self.exec_rest_request('post', self.users_uri,
172 action={'username': username},
173 json_data=fields, raise_exc=False)
174 if _response.status_code == self.REST_STATUS_CODES['OK']:
175 _response = _response.json()
177 raise exceptions.RestApiError(
178 'Error during user "{}" data update: {}'.format(
180 _response.status_code))
181 LOG.info("User account '%s' modified: '%s'", username, _response)
184 def _delete_user(self, username):
185 """ Delete user account
187 :param username: username field
189 :returns bool: True if succeeded
191 self.exec_rest_request('delete', self.users_uri,
192 action={'username': username})
194 def _get_users(self, username=None):
195 """ Get user records from REST server
197 :param username: username field
198 :type username: None|str
199 :returns list(dict): empty list, or user record, or list of all users
201 _response = self.get_response_params('get', self.users_uri)
202 _res = [u for u in _response if
203 u['username'] == username] if username else _response
206 def exec_rest_request(self, method, resource, action=None, json_data=None,
207 logs=True, raise_exc=True):
208 """ Execute REST API request, return response object
210 :param method: one of supported requests ('post', 'get', 'delete')
212 :param resource: URL of resource
214 :param action: data used to provide URI located after question mark
216 :param json_data: mandatory only for 'post' method
217 :type json_data: dict
218 :param logs: debug logs display flag
219 :type raise_exc: bool
220 :param raise_exc: if True, raise exception on REST API call error
221 :returns requests.Response(): REST API call response object
223 json_data = json_data if json_data else {}
224 action = action if action else {}
225 _method = method.upper()
226 method = method.lower()
227 if method not in ('post', 'get', 'delete'):
228 raise ValueError("Method '{}' not supported".format(_method))
230 if method == 'post' and not action:
231 if not (json_data and isinstance(json_data, Mapping)):
233 'JSON data missing in {} request'.format(_method))
235 r = getattr(self.session, method)(self._build_url(resource, action),
237 if raise_exc and not r.ok:
238 msg = 'Failed to "{}" resource "{}". Reason: "{}"'.format(
239 method, self._build_url(resource, action), r.reason)
240 raise exceptions.RestApiError(msg)
243 LOG.debug("RC: %s | Request: %s | URL: %s", r.status_code, method,
245 LOG.debug("Response: %s", r.json())
249 """Connect to RESTful server using test user account"""
250 tas_info = self.vnfd_helper['mgmt-interface']
251 # Supported REST Server ports: HTTP - 8080, HTTPS - 8181
252 _port = '8080' if tas_info['proto'] == 'http' else '8181'
253 tas_info.update({'port': _port})
254 self._url = '{proto}://{ip}:{port}/api/'.format(**tas_info)
255 self.session.headers.update({'Accept': 'application/json',
256 'Content-type': 'application/json'})
257 # Login with super user to create test user
258 self.session.auth = (
259 tas_info['super-user'], tas_info['super-user-password'])
260 LOG.info("Connect using superuser: server='%s'", self._url)
261 auth = {x: tas_info[x] for x in ('user', 'password')}
262 self._user_id = self._create_user(auth)
263 # Login with test user
264 self.session.auth = auth['user'], auth['password']
266 self.exec_rest_request('get', '')
268 self.user_lib_uri = 'libraries/{{}}/{}'.format(self.test_session_uri)
269 LOG.info("Login with test user: server='%s'", self._url)
270 # Read existing license
271 self.license_data['lic_id'] = tas_info['license']
274 self._tcl.connect(tas_info['ip'], *self.session.auth)
278 def disconnect(self):
280 self._tcl.disconnect()
283 self._terminated.value = 1
285 def create_dmf(self, dmf):
286 if isinstance(dmf, list):
288 self._tcl.create_dmf(_dmf)
290 self._tcl.create_dmf(dmf)
292 def delete_dmf(self, dmf):
293 if isinstance(dmf, list):
295 self._tcl.delete_dmf(_dmf)
297 self._tcl.delete_dmf(dmf)
299 def create_suts(self, suts):
300 # Keep only supported keys in suts object
302 sut_entry = {k: v for k, v in _sut.items()
303 if k not in {'phy', 'nextHop', 'role'}}
304 _response = self.exec_rest_request(
305 'post', self.suts_uri, json_data=sut_entry,
306 logs=False, raise_exc=False)
307 if _response.status_code != self.REST_STATUS_CODES['CREATED']:
308 LOG.info(_response.reason) # Failed to create
309 _name = sut_entry.pop('name')
310 # Modify existing SUT
311 self.configure_sut(sut_name=_name, json_data=sut_entry)
313 LOG.info("SUT created: %s", sut_entry)
315 def get_suts(self, suts_id=None):
317 _suts = self.exec_rest_request(
318 'get', '{}/{}'.format(self.suts_uri, suts_id)).json()
320 _suts = self.get_response_params('get', self.suts_uri)
324 def configure_sut(self, sut_name, json_data):
325 """ Modify information of specific SUTs
327 :param sut_name: name of existing SUT
329 :param json_data: SUT settings
330 :type json_data: dict()
332 LOG.info("Modifying SUT information...")
333 _response = self.exec_rest_request('post',
335 action={'name': sut_name},
338 if _response.status_code not in {self.REST_STATUS_CODES[x] for x in
339 {'OK', 'NO CHANGE'}}:
340 raise exceptions.RestApiError(_response.reason)
342 LOG.info("Modified SUT: %s", sut_name)
344 def delete_suts(self, suts_ids=None):
346 _curr_suts = self.get_response_params('get', self.suts_uri)
347 suts_ids = [x['id'] for x in _curr_suts]
348 LOG.info("Deleting SUTs with following IDs: %s", suts_ids)
350 self.exec_rest_request('delete',
351 '{}/{}'.format(self.suts_uri, _id))
352 LOG.info("\tDone for SUT id: %s", _id)
354 def _check_test_servers_state(self, test_servers_ids=None, delay=10,
356 LOG.info("Waiting for related test servers state change to READY...")
357 # Wait on state change
358 _start_time = time.time()
359 while time.time() - _start_time < timeout:
360 ts_ids_not_ready = {x['id'] for x in
361 self.get_test_servers(test_servers_ids)
362 if x['state'] != 'READY'}
363 if ts_ids_not_ready == set():
368 'Test servers not in READY state after {} seconds.'.format(
371 def create_test_servers(self, test_servers):
372 """ Create test servers
374 :param test_servers: input data for test servers creation
375 mandatory fields: managementIp
376 optional fields: name
377 :type test_servers: list(dict)
380 for _ts in test_servers:
381 _msg = 'Created test server "%(name)s"'
382 _ts_ids.append(self._tcl.create_test_server(_ts))
383 if _ts.get('thread_model'):
384 _msg += ' in mode: "%(thread_model)s"'
387 self._check_test_servers_state(_ts_ids)
389 def get_test_servers(self, test_server_ids=None):
390 if not test_server_ids: # Get all test servers info
391 _test_servers = self.exec_rest_request(
392 'get', self.test_serv_uri).json()[self.test_serv_uri]
393 LOG.info("Current test servers configuration: %s", _test_servers)
397 for _id in test_server_ids:
398 _test_servers.append(self.exec_rest_request(
399 'get', '{}/{}'.format(self.test_serv_uri, _id)).json())
400 LOG.info("Current test servers configuration: %s", _test_servers)
403 def configure_test_servers(self, action, json_data=None,
404 test_server_ids=None):
405 if not test_server_ids:
406 test_server_ids = [x['id'] for x in self.get_test_servers()]
407 elif isinstance(test_server_ids, int):
408 test_server_ids = [test_server_ids]
409 for _id in test_server_ids:
410 self.exec_rest_request('post',
411 '{}/{}'.format(self.test_serv_uri, _id),
412 action=action, json_data=json_data)
413 LOG.info("Test server (id: %s) configuration done: %s", _id,
415 return test_server_ids
417 def delete_test_servers(self, test_servers_ids=None):
418 # Delete test servers
419 for _ts in self.get_test_servers(test_servers_ids):
420 self.exec_rest_request('delete', '{}/{}'.format(self.test_serv_uri,
422 LOG.info("Deleted test server: %s", _ts['name'])
424 def create_test_session(self, test_session):
425 # Use tcl client to create session
426 test_session['library'] = self._user_id
427 LOG.debug("Creating session='%s'", test_session['name'])
428 self._tcl.create_test_session(test_session)
430 def get_test_session(self, test_session_name=None):
431 if test_session_name:
432 uri = 'libraries/{}/{}/{}'.format(self._user_id,
433 self.test_session_uri,
436 uri = self.user_lib_uri.format(self._user_id)
437 _test_sessions = self.exec_rest_request('get', uri).json()
438 return _test_sessions
440 def configure_test_session(self, template_name, test_session):
441 # Override specified test session parameters
442 LOG.info('Update test session parameters: %s', test_session['name'])
443 test_session.update({'library': self._user_id})
444 return self.exec_rest_request(
446 action={'action': 'overrideAndSaveAs'},
447 json_data=test_session,
448 resource='{}/{}'.format(self.user_lib_uri.format(self._user_id),
451 def delete_test_session(self, test_session):
452 return self.exec_rest_request('delete', '{}/{}'.format(
453 self.user_lib_uri.format(self._user_id), test_session))
455 def create_running_tests(self, test_session_name):
456 r = self.exec_rest_request('post',
457 self.running_tests_uri,
458 json_data={'library': self._user_id,
459 'name': test_session_name})
460 if r.status_code != self.REST_STATUS_CODES['CREATED']:
461 raise exceptions.RestApiError('Failed to start test session.')
462 self.run_id = r.json()['id']
464 def get_running_tests(self, running_test_id=None):
465 """Get JSON structure of specified running test entity
467 :param running_test_id: ID of created running test entity
468 :type running_test_id: int
469 :returns list: running tests entity
471 if not running_test_id:
473 _res_name = '{}/{}'.format(self.running_tests_uri, running_test_id)
474 _res = self.exec_rest_request('get', _res_name, logs=False).json()
475 # If no run_id specified, skip top level key in response dict.
476 # Else return JSON as list
477 return _res.get('runningTests', [_res])
479 def delete_running_tests(self, running_test_id=None):
480 if not running_test_id:
482 _res_name = '{}/{}'.format(self.running_tests_uri, running_test_id)
483 self.get_response_params('delete', _res_name)
484 LOG.info("Deleted running test with id: %s", running_test_id)
486 def _running_tests_action(self, running_test_id, action, json_data=None):
490 # 'stop', 'abort', 'continue', 'update', 'sendTcCommand', 'sendOdc'
491 _res_name = '{}/{}'.format(self.running_tests_uri, running_test_id)
492 self.exec_rest_request('post', _res_name, {'action': action},
494 LOG.debug("Executed action: '%s' on running test id: %s", action,
497 def stop_running_tests(self, running_test_id, json_data=None, force=False):
498 _action = 'abort' if force else 'stop'
499 self._running_tests_action(running_test_id, _action,
501 LOG.info('Performed action: "%s" to test run with id: %s', _action,
504 def check_running_test_state(self, run_id):
505 r = self.exec_rest_request('get',
506 '{}/{}'.format(self.running_tests_uri,
508 return r.json().get("testStateOrStep")
510 def get_running_tests_results(self, run_id):
511 _res = self.exec_rest_request(
513 '{}/{}/{}'.format(self.running_tests_uri,
515 'measurements')).json()
518 def _write_results(self, results):
519 # Avoid None value at test session start
520 _elapsed_time = results['elapsedTime'] if results['elapsedTime'] else 0
522 _res_tabs = results.get('tabs')
523 # Avoid parsing 'tab' dict key initially (missing or empty)
527 # Flatten nested dict holding Landslide KPIs of current test run
529 for _tab, _kpis in six.iteritems(_res_tabs):
530 for _kpi, _value in six.iteritems(_kpis):
531 # Combine table name and KPI name using delimiter "::"
532 _key = '::'.join([_tab, _kpi])
534 # Cast value from str to float
535 # Remove comma and/or measure units, e.g. "us"
536 flat_kpis_dict[_key] = float(
537 _value.split(' ')[0].replace(',', ''))
538 except ValueError: # E.g. if KPI represents datetime
540 LOG.info("Polling test results of test run id: %s. Elapsed time: %s "
541 "seconds", self.run_id, _elapsed_time)
542 return flat_kpis_dict
544 def collect_kpi(self):
545 if 'COMPLETE' in self.check_running_test_state(self.run_id):
546 self._result.update({'done': True})
548 _res = self.get_running_tests_results(self.run_id)
549 _kpis = self._write_results(_res)
551 _kpis.update({'run_id': int(self.run_id)})
552 _kpis.update({'iteration': _res['iteration']})
553 self._result.update(_kpis)
557 class LandslideTclClient(object):
558 """Landslide TG TCL client class"""
560 DEFAULT_TEST_NODE = {
561 'ethStatsEnabled': True,
562 'forcedEthInterface': '',
568 'numLinksOrNodes': 1,
571 'uniqueVlanAddr': False,
574 'vlanUserPriority': 0,
579 'ls::create -TestNode-{} -under $p_ -Type "eth"' \
580 ' -Phy "{phy}" -Ip "{ip}" -NumLinksOrNodes {numLinksOrNodes}' \
581 ' -NextHop "{nextHop}" -Mac "{mac}" -MTU {mtu} ' \
582 ' -ForcedEthInterface "{forcedEthInterface}"' \
583 ' -EthStatsEnabled {ethStatsEnabled}' \
584 ' -VlanId {vlanId} -VlanUserPriority {vlanUserPriority}' \
585 ' -NumVlan {numVlan} -UniqueVlanAddr {uniqueVlanAddr}' \
588 def __init__(self, tcl_handler, ts_context):
589 self.tcl_server_ip = None
591 self._library_id = None
592 self._basic_library_id = None
593 self._tcl = tcl_handler
594 self._ts_context = ts_context
597 # Test types names expected in session profile, test case and pod files
598 self._tc_types = {"SGW_Nodal", "SGW_Node", "MME_Nodal", "PGW_Node",
601 self._class_param_config_handler = {
602 "Array": self._configure_array_param,
603 "TestNode": self._configure_test_node_param,
604 "Sut": self._configure_sut_param,
605 "Dmf": self._configure_dmf_param
608 def connect(self, tcl_server_ip, username, password):
609 """ Connect to TCL server with username and password
611 :param tcl_server_ip: TCL server IP address
612 :type tcl_server_ip: str
613 :param username: existing username on TCL server
615 :param password: password related to username on TCL server
618 LOG.info("connect: server='%s' user='%s'", tcl_server_ip, username)
619 res = self._tcl.execute(
620 "ls::login {} {} {}".format(tcl_server_ip, username, password))
621 if 'java0x' not in res: # handle assignment reflects login success
622 raise exceptions.LandslideTclException(
623 "connect: login failed ='{}'.".format(res))
624 self._library_id = self._tcl.execute(
625 "ls::get [ls::query LibraryInfo -userLibraryName {}] -Id".format(
627 self._basic_library_id = self._get_library_id('Basic')
628 self.tcl_server_ip = tcl_server_ip
629 self._user = username
630 LOG.debug("connect: user='%s' me='%s' basic='%s'", self._user,
632 self._basic_library_id)
634 def disconnect(self):
635 """ Disconnect from TCL server. Drop TCL connection configuration """
636 LOG.info("disconnect: server='%s' user='%s'",
637 self.tcl_server_ip, self._user)
638 self._tcl.execute("ls::logout")
639 self.tcl_server_ip = None
641 self._library_id = None
642 self._basic_library_id = None
644 def _add_test_server(self, name, ip):
646 # Check if test server exists with name equal to _ts_name
647 ts_id = int(self.resolve_test_server_name(name))
649 # Such test server does not exist. Attempt to create it
650 ts_id = self._tcl.execute(
651 'ls::perform AddTs -Name "{}" -Ip "{}"'.format(name, ip))
655 # Failed to create test server, e.g. limit reached
657 'Failed to create test server: "{}". {}'.format(name,
661 def _update_license(self, name):
662 """ Setup/update test server license
664 :param name: test server name
667 # Retrieve current TsInfo configuration, result stored in handle "ts"
669 'set ts [ls::retrieve TsInfo -Name "{}"]'.format(name))
671 # Set license ID, if it differs from current one, update test server
672 _curr_lic_id = self._tcl.execute('ls::get $ts -RequestedLicense')
673 if _curr_lic_id != self._ts_context.license_data['lic_id']:
674 self._tcl.execute('ls::config $ts -RequestedLicense {}'.format(
675 self._ts_context.license_data['lic_id']))
676 self._tcl.execute('ls::perform ModifyTs $ts')
678 def _set_thread_model(self, name, thread_model):
679 # Retrieve test server configuration, store it in handle "tsc"
680 _cfguser_password = self._ts_context.vnfd_helper['mgmt-interface'][
683 'set tsc [ls::perform RetrieveTsConfiguration '
684 '-name "{}" {}]'.format(name, _cfguser_password))
685 # Configure ThreadModel, if it differs from current one
686 thread_model_map = {'Legacy': 'V0',
688 'Fireball': 'V1_FB3'}
689 _model = thread_model_map[thread_model]
690 _curr_model = self._tcl.execute('ls::get $tsc -ThreadModel')
691 if _curr_model != _model:
693 'ls::config $tsc -ThreadModel "{}"'.format(_model))
695 'ls::perform ApplyTsConfiguration $tsc {}'.format(
698 def create_test_server(self, test_server):
699 _ts_thread_model = test_server.get('thread_model')
700 _ts_name = test_server['name']
702 ts_id = self._add_test_server(_ts_name, test_server['ip'])
704 self._update_license(_ts_name)
706 # Skip below code modifying thread_model if it is not defined
708 self._set_thread_model(_ts_name, _ts_thread_model)
712 def create_test_session(self, test_session):
713 """ Create, configure and save Landslide test session object.
715 :param test_session: Landslide TestSession object
716 :type test_session: dict
718 LOG.info("create_test_session: name='%s'", test_session['name'])
719 self._tcl.execute('set test_ [ls::create TestSession]')
720 self._tcl.execute('ls::config $test_ -Library {} -Name "{}"'.format(
721 self._library_id, test_session['name']))
722 self._tcl.execute('ls::config $test_ -Description "{}"'.format(
723 test_session['description']))
724 if 'keywords' in test_session:
725 self._tcl.execute('ls::config $test_ -Keywords "{}"'.format(
726 test_session['keywords']))
727 if 'duration' in test_session:
728 self._tcl.execute('ls::config $test_ -Duration "{}"'.format(
729 test_session['duration']))
730 if 'iterations' in test_session:
731 self._tcl.execute('ls::config $test_ -Iterations "{}"'.format(
732 test_session['iterations']))
733 if 'reservePorts' in test_session:
734 if test_session['reservePorts']:
735 self._tcl.execute('ls::config $test_ -Reserve Ports')
737 if 'reservations' in test_session:
738 for _reservation in test_session['reservations']:
739 self._configure_reservation(_reservation)
741 if 'reportOptions' in test_session:
742 self._configure_report_options(test_session['reportOptions'])
744 for _index, _group in enumerate(test_session['tsGroups']):
745 self._configure_ts_group(_group, _index)
747 self._save_test_session()
749 def create_dmf(self, dmf):
750 """ Create, configure and save Landslide Data Message Flow object.
752 :param dmf: Landslide Data Message Flow object
755 self._tcl.execute('set dmf_ [ls::create Dmf]')
756 _lib_id = self._get_library_id(dmf['dmf']['library'])
757 self._tcl.execute('ls::config $dmf_ -Library {} -Name "{}"'.format(
760 for _param_key in dmf:
761 if _param_key == 'dmf':
763 _param_value = dmf[_param_key]
764 if isinstance(_param_value, dict):
765 # Configure complex parameter
766 _tcl_cmd = 'ls::config $dmf_'
767 for _sub_param_key in _param_value:
768 _sub_param_value = _param_value[_sub_param_key]
769 if isinstance(_sub_param_value, str):
770 _tcl_cmd += ' -{} "{}"'.format(_sub_param_key,
773 _tcl_cmd += ' -{} {}'.format(_sub_param_key,
776 self._tcl.execute(_tcl_cmd)
778 # Configure simple parameter
779 if isinstance(_param_value, str):
781 'ls::config $dmf_ -{} "{}"'.format(_param_key,
785 'ls::config $dmf_ -{} {}'.format(_param_key,
789 def configure_dmf(self, dmf):
790 # Use create to reconfigure and overwrite existing dmf
793 def delete_dmf(self, dmf):
794 raise NotImplementedError
797 # Call 'Validate' to set default values for missing parameters
798 res = self._tcl.execute('ls::perform Validate -Dmf $dmf_')
800 res = self._tcl.execute('ls::get $dmf_ -ErrorsAndWarnings')
801 LOG.error("_save_dmf: %s", res)
802 raise exceptions.LandslideTclException("_save_dmf: {}".format(res))
804 res = self._tcl.execute('ls::save $dmf_ -overwrite')
805 LOG.debug("_save_dmf: result (%s)", res)
807 def _configure_report_options(self, options):
808 for _option_key in options:
809 _option_value = options[_option_key]
810 if _option_key == 'format':
812 if _option_value == 'CSV':
815 'ls::config $test_.ReportOptions -Format {} '
816 '-Ts -3 -Tc -3'.format(_format))
819 'ls::config $test_.ReportOptions -{} {}'.format(
823 def _configure_ts_group(self, ts_group, ts_group_index):
825 _ts_id = int(self.resolve_test_server_name(ts_group['tsId']))
827 raise RuntimeError('Test server name "{}" does not exist.'.format(
829 if _ts_id not in self.ts_ids:
831 'set tss_ [ls::create TsGroup -under $test_ -tsId {} ]'.format(
833 self.ts_ids.add(_ts_id)
834 for _case in ts_group.get('testCases', []):
835 self._configure_tc_type(_case, ts_group_index)
837 self._configure_preresolved_arp(ts_group.get('preResolvedArpAddress'))
839 def _configure_tc_type(self, tc, ts_group_index):
840 if tc['type'] not in self._tc_types:
841 raise RuntimeError('Test type {} not supported.'.format(
843 tc['type'] = tc['type'].replace('_', ' ')
844 res = self._tcl.execute(
845 'set tc_ [ls::retrieve testcase -libraryId {0} "{1}"]'.format(
846 self._basic_library_id, tc['type']))
848 raise RuntimeError('Test type {} not found in "Basic" '
849 'library.'.format(tc['type']))
851 'ls::config $test_.TsGroup({}) -children-Tc $tc_'.format(
853 self._tcl.execute('ls::config $tc_ -Library {0} -Name "{1}"'.format(
854 self._basic_library_id, tc['name']))
856 'ls::config $tc_ -Description "{}"'.format(tc['type']))
858 'ls::config $tc_ -Keywords "GTP LTE {}"'.format(tc['type']))
861 'ls::config $tc_ -Linked {}'.format(tc['linked']))
862 if 'AssociatedPhys' in tc:
863 self._tcl.execute('ls::config $tc_ -AssociatedPhys "{}"'.format(
864 tc['AssociatedPhys']))
865 if 'parameters' in tc:
866 self._configure_parameters(tc['parameters'])
868 def _configure_parameters(self, params):
869 self._tcl.execute('set p_ [ls::get $tc_ -children-Parameters(0)]')
870 for _param_key in sorted(params):
871 _param_value = params[_param_key]
872 if isinstance(_param_value, dict):
873 # Configure complex parameter
874 if _param_value['class'] in self._class_param_config_handler:
875 self._class_param_config_handler[_param_value['class']](
879 # Configure simple parameter
881 'ls::create {} -under $p_ -Value "{}"'.format(
885 def _configure_array_param(self, name, params):
886 self._tcl.execute('ls::create -Array-{} -under $p_ ;'.format(name))
887 for param in params['array']:
889 'ls::create ArrayItem -under $p_.{} -Value "{}"'.format(name,
892 def _configure_test_node_param(self, name, params):
893 _params = self.DEFAULT_TEST_NODE
894 _params.update(params)
896 # TCL command expects lower case 'true' or 'false'
897 _params['ethStatsEnabled'] = str(_params['ethStatsEnabled']).lower()
898 _params['uniqueVlanAddr'] = str(_params['uniqueVlanAddr']).lower()
900 cmd = self.TEST_NODE_CMD.format(name, **_params)
901 self._tcl.execute(cmd)
903 def _configure_sut_param(self, name, params):
905 'ls::create -Sut-{} -under $p_ -Name "{}";'.format(name,
908 def _configure_dmf_param(self, name, params):
909 self._tcl.execute('ls::create -Dmf-{} -under $p_ ;'.format(name))
911 for _flow_index, _flow in enumerate(params['mainflows']):
912 _lib_id = self._get_library_id(_flow['library'])
914 'ls::perform AddDmfMainflow $p_.Dmf {} "{}"'.format(
918 if not params.get('instanceGroups'):
921 _instance_group = params['instanceGroups'][_flow_index]
923 # Traffic Mixer parameters handling
924 for _key in ['mixType', 'rate']:
925 if _key in _instance_group:
927 'ls::config $p_.Dmf.InstanceGroup({}) -{} {}'.format(
928 _flow_index, _key, _instance_group[_key]))
930 # Assignments parameters handling
931 for _row_id, _row in enumerate(_instance_group.get('rows', [])):
933 'ls::config $p_.Dmf.InstanceGroup({}).Row({}) -Node {} '
934 '-OverridePort {} -ClientPort {} -Context {} -Role {} '
935 '-PreferredTransport {} -RatingGroup {} '
936 '-ServiceID {}'.format(
937 _flow_index, _row_id, _row['node'],
938 _row['overridePort'], _row['clientPort'],
939 _row['context'], _row['role'], _row['transport'],
940 _row['ratingGroup'], _row['serviceId']))
942 def _configure_reservation(self, reservation):
943 _ts_id = self.resolve_test_server_name(reservation['tsId'])
945 'set reservation_ [ls::create Reservation -under $test_]')
947 'ls::config $reservation_ -TsIndex {} -TsId {} '
948 '-TsName "{}"'.format(reservation['tsIndex'],
950 reservation['tsName']))
951 for _subnet in reservation['phySubnets']:
953 'set physubnet_ [ls::create PhySubnet -under $reservation_]')
955 'ls::config $physubnet_ -Name "{}" -Base "{}" -Mask "{}" '
956 '-NumIps {}'.format(_subnet['name'], _subnet['base'],
957 _subnet['mask'], _subnet['numIps']))
959 def _configure_preresolved_arp(self, pre_resolved_arp):
960 if not pre_resolved_arp: # Pre-resolved ARP configuration not found
962 for _entry in pre_resolved_arp:
963 # TsGroup handle name should correspond in _configure_ts_group()
965 'ls::create PreResolvedArpAddress -under $tss_ '
966 '-StartingAddress "{StartingAddress}" '
967 '-NumNodes {NumNodes}'.format(**_entry))
969 def delete_test_session(self, test_session):
970 raise NotImplementedError
972 def _save_test_session(self):
973 # Call 'Validate' to set default values for missing parameters
974 res = self._tcl.execute('ls::perform Validate -TestSession $test_')
976 res = self._tcl.execute('ls::get $test_ -ErrorsAndWarnings')
977 raise exceptions.LandslideTclException(
978 "Test session validation failed. Server response: {}".format(
981 self._tcl.execute('ls::save $test_ -overwrite')
982 LOG.debug("Test session saved successfully.")
984 def _get_library_id(self, library):
985 _library_id = self._tcl.execute(
986 "ls::get [ls::query LibraryInfo -systemLibraryName {}] -Id".format(
994 _library_id = self._tcl.execute(
995 "ls::get [ls::query LibraryInfo -userLibraryName {}] -Id".format(
1000 LOG.error("_get_library_id: library='%s' not found.", library)
1001 raise exceptions.LandslideTclException(
1002 "_get_library_id: library='{}' not found.".format(
1007 def resolve_test_server_name(self, ts_name):
1008 return self._tcl.execute("ls::query TsId {}".format(ts_name))
1011 class LsTclHandler(object):
1012 """Landslide TCL Handler class"""
1015 JRE_PATH = net_serv_utils.get_nsb_option('jre_path_i386')
1019 self._ls = LsApi(jre_path=self.JRE_PATH)
1021 "ls::config ApiOptions -NoReturnSuccessResponseString '{}'".format(
1024 def execute(self, command):
1025 res = self._ls.tcl(command)
1026 self.tcl_cmds[command] = res