11 import mysql.connector
16 from novaclient import client as openstackclient
17 from openstack.config import loader
18 from kubernetes import client, config
19 from netaddr import IPAddress, IPNetwork
23 def __init__(self, extra_host_names=None, cfg_file=None):
24 self.logger = logging.getLogger(__name__)
25 self.logger.setLevel(logging.DEBUG)
26 self.logger.info('Initializing configuration')
27 self.default_config = 'vcpeconfig.yaml'
29 # Read configuration from config file
30 self._load_config(cfg_file)
31 # Load OpenStack settings
32 self._load_os_config()
34 self.sdnc_controller_pod = '-'.join([self.onap_environment, 'sdnc-sdnc-0'])
35 # OOM: this is the address that the brg and bng will nat for sdnc access - 10.0.0.x address of k8 host for sdnc-0 container
36 self.sdnc_oam_ip = self.get_pod_node_oam_ip(self.sdnc_controller_pod)
37 # OOM: this is a k8s host external IP, e.g. oom-k8s-01 IP
38 self.oom_so_sdnc_aai_ip = self.get_pod_node_public_ip(self.sdnc_controller_pod)
39 # OOM: this is a k8s host external IP, e.g. oom-k8s-01 IP
40 self.oom_dcae_ves_collector = self.oom_so_sdnc_aai_ip
41 # OOM: this is a k8s host external IP, e.g. oom-k8s-01 IP
42 self.mr_ip_addr = self.oom_so_sdnc_aai_ip
43 self.mr_ip_port = '30227'
44 self.so_nbi_port = '30277' if self.oom_mode else '8080'
45 self.sdnc_preloading_port = '30267' if self.oom_mode else '8282'
46 self.aai_query_port = '30233' if self.oom_mode else '8443'
47 self.sniro_port = '30288' if self.oom_mode else '8080'
49 self.host_names = ['sdc', 'so', 'sdnc', 'robot', 'aai-inst1', self.dcae_ves_collector_name, 'mariadb-galera']
51 self.host_names.extend(extra_host_names)
53 self.hosts = self.get_vm_ip(self.host_names, self.external_net_addr, self.external_net_prefix_len)
54 # this is the keyword used to name vgw stack, must not be used in other stacks
55 self.vgw_name_keyword = 'base_vcpe_vgw'
56 # this is the file that will keep the index of last assigned SO name
57 self.vgw_vfmod_name_index_file= '__var/vgw_vfmod_name_index'
58 self.svc_instance_uuid_file = '__var/svc_instance_uuid'
59 self.preload_dict_file = '__var/preload_dict'
60 self.vgmux_vnf_name_file = '__var/vgmux_vnf_name'
61 self.product_family_id = 'f9457e8c-4afd-45da-9389-46acd9bf5116'
62 self.custom_product_family_id = 'a9a77d5a-123e-4ca2-9eb9-0b015d2ee0fb'
63 self.instance_name_prefix = {
64 'service': 'vcpe_svc',
65 'network': 'vcpe_net',
67 'vfmodule': 'vcpe_vfmodule'
69 self.aai_userpass = 'AAI', 'AAI'
70 self.os_tenant_id = self.cloud['--os-tenant-id']
71 self.os_region_name = self.cloud['--os-region-name']
72 self.common_preload_config['pub_key'] = self.pub_key
73 self.sniro_url = 'http://' + self.hosts['robot'] + ':' + self.sniro_port + '/__admin/mappings'
74 self.sniro_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
75 self.homing_solution = 'sniro' # value is either 'sniro' or 'oof'
76 # self.homing_solution = 'oof'
77 self.customer_location_used_by_oof = {
78 "customerLatitude": "32.897480",
79 "customerLongitude": "-97.040443",
80 "customerName": "some_company"
83 #############################################################################################
85 self.sdc_be_port = '30204'
86 self.sdc_be_request_userpass = 'vid', 'Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U'
87 self.sdc_be_request_headers = {'X-ECOMP-InstanceID': 'VID'}
88 self.sdc_be_url_prefix = 'https://' + self.hosts['sdc'] + ':' + self.sdc_be_port
89 self.sdc_service_list_url = self.sdc_be_url_prefix + '/sdc/v1/catalog/services'
91 self.sdc_fe_port = '30207'
92 self.sdc_fe_request_userpass = 'beep', 'boop'
93 self.sdc_fe_request_headers = {'USER_ID': 'demo', 'Content-Type': 'application/json'}
94 self.sdc_fe_url_prefix = 'https://' + self.hosts['sdc'] + ':' + self.sdc_fe_port
95 self.sdc_get_category_list_url = self.sdc_fe_url_prefix + '/sdc1/feProxy/rest/v1/categories'
96 self.sdc_create_allotted_resource_subcategory_url = self.sdc_fe_url_prefix + '/sdc1/feProxy/rest/v1/category/resources/resourceNewCategory.allotted%20resource/subCategory'
98 #############################################################################################
100 self.sdnc_userpass = 'admin', 'Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U'
101 self.sdnc_db_name = 'sdnctl'
102 self.sdnc_db_user = 'sdnctl'
103 self.sdnc_db_pass = 'gamma'
104 self.sdnc_db_port = self.get_k8s_service_endpoint_info('mariadb-galera','port') if self.oom_mode else '3306'
105 self.sdnc_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
106 self.sdnc_preload_network_url = 'https://' + self.hosts['sdnc'] + \
107 ':' + self.sdnc_preloading_port + '/restconf/operations/VNF-API:preload-network-topology-operation'
108 self.sdnc_preload_network_gra_url = 'https://' + self.hosts['sdnc'] + \
109 ':' + self.sdnc_preloading_port + '/restconf/operations/GENERIC-RESOURCE-API:preload-network-topology-operation'
110 self.sdnc_preload_vnf_url = 'https://' + self.hosts['sdnc'] + \
111 ':' + self.sdnc_preloading_port + '/restconf/operations/VNF-API:preload-vnf-topology-operation'
112 self.sdnc_preload_gra_url = 'https://' + self.hosts['sdnc'] + \
113 ':' + self.sdnc_preloading_port + '/restconf/operations/GENERIC-RESOURCE-API:preload-vf-module-topology-operation'
114 self.sdnc_ar_cleanup_url = 'https://' + self.hosts['sdnc'] + ':' + self.sdnc_preloading_port + \
115 '/restconf/config/GENERIC-RESOURCE-API:'
117 #############################################################################################
118 # MARIADB-GALERA settings
119 self.mariadb_galera_endpoint_ip = self.get_k8s_service_endpoint_info('mariadb-galera','ip')
120 self.mariadb_galera_endpoint_port = self.get_k8s_service_endpoint_info('mariadb-galera','port')
122 #############################################################################################
123 # SO urls, note: do NOT add a '/' at the end of the url
124 self.so_req_api_url = {'v4': 'http://' + self.hosts['so'] + ':' + self.so_nbi_port + '/onap/so/infra/serviceInstantiation/v7/serviceInstances',
125 'v5': 'http://' + self.hosts['so'] + ':' + self.so_nbi_port + '/onap/so/infra/serviceInstantiation/v7/serviceInstances'}
126 self.so_check_progress_api_url = 'http://' + self.hosts['so'] + ':' + self.so_nbi_port + '/onap/so/infra/orchestrationRequests/v6'
127 self.so_userpass = 'InfraPortalClient', 'password1$'
128 self.so_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
129 self.so_db_name = 'catalogdb'
130 self.so_db_user = 'root'
131 self.so_db_pass = 'secretpassword'
132 self.so_db_host = self.mariadb_galera_endpoint_ip if self.oom_mode else self.hosts['so']
133 self.so_db_port = self.mariadb_galera_endpoint_port if self.oom_mode else '3306'
135 self.vpp_inf_url = 'http://{0}:8183/restconf/config/ietf-interfaces:interfaces'
136 self.vpp_api_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
137 self.vpp_api_userpass = ('admin', 'admin')
138 self.vpp_ves_url= 'http://{0}:8183/restconf/config/vesagent:vesagent'
140 #############################################################################################
142 self.policy_userpass = ('healthcheck', 'zb!XztG34')
143 self.policy_headers = {'Accept': 'application/json', 'Content-Type': 'application/json'}
144 self.policy_api_url = 'https://{0}:6969/policy/api/v1/policytypes/onap.policies.controlloop.Operational/versions/1.0.0/policies'
145 self.policy_pap_get_url = 'https://{0}:6969/policy/pap/v1/pdps'
146 self.policy_pap_json = {'policies': [{'policy-id': 'operational.vcpe'}]}
147 self.policy_pap_post_url = self.policy_pap_get_url + '/policies'
148 self.policy_api_service_name = 'policy-api'
149 self.policy_pap_service_name = 'policy-pap'
151 #############################################################################################
153 self.aai_region_query_url = 'https://' + self.oom_so_sdnc_aai_ip + ':' +\
154 self.aai_query_port +\
155 '/aai/v14/cloud-infrastructure/cloud-regions/cloud-region/CloudOwner/' +\
156 self.cloud['--os-region-name']
157 self.aai_headers = {'Accept': 'application/json',
158 'Content-Type': 'application/json',
159 'X-FromAppId': 'postman', 'X-TransactionId': '9999'}
161 def _load_config(self, cfg_file):
163 Reads vcpe config file and injects settings as object's attributes
164 :param cfg_file: Configuration file path
168 cfg_file = self.default_config
171 with open(cfg_file, 'r') as cfg:
172 cfg_yml = yaml.full_load(cfg)
173 except Exception as e:
174 self.logger.error('Error loading configuration: ' + str(e))
177 self.logger.debug('\n' + yaml.dump(cfg_yml))
179 # Use setattr to load config file keys as VcpeCommon class' object
182 # Check config isn't empty
183 if cfg_yml is not None:
184 for cfg_key in cfg_yml:
185 setattr(self, cfg_key, cfg_yml[cfg_key])
186 except TypeError as e:
187 self.logger.error('Unable to parse config file: ' + str(e))
190 def _load_os_config(self):
192 Reads cloud settings and sets them as object's 'cloud' attribute
194 # Create OpenStackConfig config instance
195 os_config = loader.OpenStackConfig()
196 # Try reading cloud settings for self.cloud_name
198 os_cloud = os_config.cloud_config['clouds'][self.cloud_name]
200 self.logger.error('Error fetching cloud settings for cloud "{0}"'
201 .format(self.cloud_name))
203 self.logger.debug('Cloud config:\n {0}'.format(json.dumps(
206 # Extract all OS settings keys and alter their names
207 # to conform to openstack cli client
210 if isinstance(os_cloud[k],dict):
211 for sub_k in os_cloud[k]:
212 os_setting_name = '--os-' + sub_k.replace('_','-')
213 self.cloud[os_setting_name] = os_cloud[k][sub_k]
215 os_setting_name = '--os-' + k.replace('_','-')
216 self.cloud[os_setting_name] = os_cloud[k]
218 def heatbridge(self, openstack_stack_name, svc_instance_uuid):
220 Add vserver information to AAI
222 self.logger.info('Adding vServer information to AAI for {0}'.format(openstack_stack_name))
223 if not self.oom_mode:
224 cmd = '/opt/demo.sh heatbridge {0} {1} vCPE'.format(openstack_stack_name, svc_instance_uuid)
225 ret = commands.getstatusoutput("ssh -i onap_dev root@{0} '{1}'".format(self.hosts['robot'], cmd))
226 self.logger.debug('%s', ret)
228 print('To add vGMUX vserver info to AAI, do the following:')
229 print('- ssh to rancher')
231 print('- cd /root/oom/kubernetes/robot')
232 print('- ./demo-k8s.sh onap heatbridge {0} {1} vCPE'.format(openstack_stack_name, svc_instance_uuid))
234 def get_brg_mac_from_sdnc(self):
236 Check table DHCP_MAP in the SDNC DB. Find the newly instantiated BRG MAC address.
237 Note that there might be multiple BRGs, the most recently instantiated BRG always has the largest IP address.
240 db_host=self.mariadb_galera_endpoint_ip
242 db_host=self.hosts['mariadb-galera']
244 cnx = mysql.connector.connect(user=self.sdnc_db_user,
245 password=self.sdnc_db_pass,
246 database=self.sdnc_db_name,
248 port=self.sdnc_db_port)
249 cursor = cnx.cursor()
250 query = "SELECT * from DHCP_MAP"
251 cursor.execute(query)
253 self.logger.debug('DHCP_MAP table in SDNC')
256 for mac, ip in cursor:
257 self.logger.debug(mac + ' - ' + ip)
258 this_host = int(ip.split('.')[-1])
267 except AssertionError:
268 self.logger.error('Failed to obtain BRG MAC address from database')
273 def execute_cmds_mariadb(self, cmds):
274 self.execute_cmds_db(cmds, self.sdnc_db_user, self.sdnc_db_pass,
275 self.sdnc_db_name, self.mariadb_galera_endpoint_ip,
276 self.mariadb_galera_endpoint_port)
278 def execute_cmds_sdnc_db(self, cmds):
279 self.execute_cmds_db(cmds, self.sdnc_db_user, self.sdnc_db_pass, self.sdnc_db_name,
280 self.hosts['sdnc'], self.sdnc_db_port)
282 def execute_cmds_so_db(self, cmds):
283 self.execute_cmds_db(cmds, self.so_db_user, self.so_db_pass, self.so_db_name,
284 self.so_db_host, self.so_db_port)
286 def execute_cmds_db(self, cmds, dbuser, dbpass, dbname, host, port):
287 cnx = mysql.connector.connect(user=dbuser, password=dbpass, database=dbname, host=host, port=port)
288 cursor = cnx.cursor()
290 self.logger.debug(cmd)
292 self.logger.debug('%s', cursor)
297 def find_file(self, file_name_keyword, file_ext, search_dir):
299 :param file_name_keyword: keyword used to look for the csar file, case insensitive matching, e.g, infra
300 :param file_ext: e.g., csar, json
301 :param search_dir path to search
302 :return: path name of the file
304 file_name_keyword = file_name_keyword.lower()
305 file_ext = file_ext.lower()
306 if not file_ext.startswith('.'):
307 file_ext = '.' + file_ext
310 for file_name in os.listdir(search_dir):
311 file_name_lower = file_name.lower()
312 if file_name_keyword in file_name_lower and file_name_lower.endswith(file_ext):
314 self.logger.error('Multiple files found for *{0}*.{1} in '
315 'directory {2}'.format(file_name_keyword, file_ext, search_dir))
317 filenamepath = os.path.abspath(os.path.join(search_dir, file_name))
322 self.logger.error("Cannot find *{0}*{1} in directory {2}".format(file_name_keyword, file_ext, search_dir))
326 def network_name_to_subnet_name(network_name):
328 :param network_name: example: vcpe_net_cpe_signal_201711281221
329 :return: vcpe_net_cpe_signal_subnet_201711281221
331 fields = network_name.split('_')
332 fields.insert(-1, 'subnet')
333 return '_'.join(fields)
335 def set_network_name(self, network_name):
336 param = ' '.join([k + ' ' + v for k, v in self.cloud.items()])
337 openstackcmd = 'openstack ' + param
338 cmd = ' '.join([openstackcmd, 'network set --name', network_name, 'ONAP-NW1'])
341 def set_subnet_name(self, network_name):
343 Example: network_name = vcpe_net_cpe_signal_201711281221
344 set subnet name to vcpe_net_cpe_signal_subnet_201711281221
347 param = ' '.join([k + ' ' + v for k, v in self.cloud.items()])
348 openstackcmd = 'openstack ' + param
350 # expected results: | subnets | subnet_id |
351 subnet_info = os.popen(openstackcmd + ' network show ' + network_name + ' |grep subnets').read().split('|')
352 if len(subnet_info) > 2 and subnet_info[1].strip() == 'subnets':
353 subnet_id = subnet_info[2].strip()
354 subnet_name = self.network_name_to_subnet_name(network_name)
355 cmd = ' '.join([openstackcmd, 'subnet set --name', subnet_name, subnet_id])
357 self.logger.info("Subnet name set to: " + subnet_name)
360 self.logger.error("Can't get subnet info from network name: " + network_name)
363 def set_closed_loop_policy(self, policy_template_file):
364 # Gather policy services cluster ips
365 p_api_cluster_ip = self.get_k8s_service_cluster_ip(self.policy_api_service_name)
366 p_pap_cluster_ip = self.get_k8s_service_cluster_ip(self.policy_pap_service_name)
368 # Read policy json from file
369 with open(policy_template_file) as f:
371 policy_json = json.load(f)
373 self.logger.error(policy_template_file + " doesn't seem to contain valid JSON data")
376 # Check policy already applied
377 policy_exists_req = requests.get(self.policy_pap_get_url.format(
378 p_pap_cluster_ip), auth=self.policy_userpass,
379 verify=False, headers=self.policy_headers)
380 if policy_exists_req.status_code != 200:
381 self.logger.error('Failure in checking CL policy existence. '
382 'Policy-pap responded with HTTP code {0}'.format(
383 policy_exists_req.status_code))
387 policy_exists_json = policy_exists_req.json()
388 except ValueError as e:
389 self.logger.error('Policy-pap request failed: ' + e.message)
393 assert policy_exists_json['groups'][0]['pdpSubgroups'] \
394 [1]['policies'][0]['name'] != 'operational.vcpe'
395 except AssertionError:
396 self.logger.info('vCPE closed loop policy already exists, not applying')
399 pass # policy doesn't exist
402 policy_create_req = requests.post(self.policy_api_url.format(
403 p_api_cluster_ip), auth=self.policy_userpass,
404 json=policy_json, verify=False,
405 headers=self.policy_headers)
406 # Get the policy id from policy-api response
407 if policy_create_req.status_code != 200:
408 self.logger.error('Failed creating policy. Policy-api responded'
409 ' with HTTP code {0}'.format(policy_create_req.status_code))
413 policy_version = json.loads(policy_create_req.text)['policy-version']
414 except (KeyError, ValueError):
415 self.logger.error('Policy API response not understood:')
416 self.logger.debug('\n' + str(policy_create_req.text))
418 # Inject the policy into Policy PAP
419 self.policy_pap_json['policies'].append({'policy-version': policy_version})
420 policy_insert_req = requests.post(self.policy_pap_post_url.format(
421 p_pap_cluster_ip), auth=self.policy_userpass,
422 json=self.policy_pap_json, verify=False,
423 headers=self.policy_headers)
424 if policy_insert_req.status_code != 200:
425 self.logger.error('Policy PAP request failed with HTTP code'
426 '{0}'.format(policy_insert_req.status_code))
428 self.logger.info('Successully pushed closed loop Policy')
430 def is_node_in_aai(self, node_type, node_uuid):
432 search_node_type = None
433 if node_type == 'service':
434 search_node_type = 'service-instance'
435 key = 'service-instance-id'
436 elif node_type == 'vnf':
437 search_node_type = 'generic-vnf'
440 logging.error('Invalid node_type: ' + node_type)
443 url = 'https://{0}:{1}/aai/v11/search/nodes-query?search-node-type={2}&filter={3}:EQUALS:{4}'.format(
444 self.hosts['aai-inst1'], self.aai_query_port, search_node_type, key, node_uuid)
446 headers = {'Content-Type': 'application/json', 'Accept': 'application/json', 'X-FromAppID': 'vCPE-Robot', 'X-TransactionId': 'get_aai_subscr'}
447 r = requests.get(url, headers=headers, auth=self.aai_userpass, verify=False)
449 self.logger.debug('aai query: ' + url)
450 self.logger.debug('aai response:\n' + json.dumps(response, indent=4, sort_keys=True))
451 return 'result-data' in response
454 def extract_ip_from_str(net_addr, net_addr_len, sz):
456 :param net_addr: e.g. 10.5.12.0
457 :param net_addr_len: e.g. 24
459 :return: the first IP address matching the network, e.g. 10.5.12.3
461 network = ipaddress.ip_network(unicode('{0}/{1}'.format(net_addr, net_addr_len)), strict=False) # pylint: disable=E0602
462 ip_list = re.findall(r'[0-9]+(?:\.[0-9]+){3}', sz)
464 this_net = ipaddress.ip_network(unicode('{0}/{1}'.format(ip, net_addr_len)), strict=False) # pylint: disable=E0602
465 if this_net == network:
469 def get_pod_node_oam_ip(self, pod):
471 :Assuming kubectl is available and configured by default config (~/.kube/config)
472 :param pod: pod name substring, e.g. 'sdnc-sdnc-0'
473 :return pod's cluster node oam ip (10.0.0.0/16)
476 config.load_kube_config()
477 api = client.CoreV1Api()
478 kslogger = logging.getLogger('kubernetes')
479 kslogger.setLevel(logging.INFO)
480 res = api.list_pod_for_all_namespaces()
482 if pod in i.metadata.name:
483 self.logger.debug("found {0}\t{1}\t{2}".format(i.metadata.name, i.status.host_ip, i.spec.node_name))
484 ret = i.status.host_ip
488 ret = raw_input("Enter " + self.sdnc_controller_pod + " pod cluster node OAM IP address(10.0.0.0/16): ") # pylint: disable=E0602
491 def get_pod_node_public_ip(self, pod):
493 :Assuming kubectl is available and configured by default config (~/.kube/config)
494 :param pod: pod name substring, e.g. 'sdnc-sdnc-0'
495 :return pod's cluster node public ip (i.e. 10.12.0.0/16)
498 config.load_kube_config()
499 api = client.CoreV1Api()
500 kslogger = logging.getLogger('kubernetes')
501 kslogger.setLevel(logging.INFO)
502 res = api.list_pod_for_all_namespaces()
504 if pod in i.metadata.name:
505 ret = self.get_vm_public_ip_by_nova(i.spec.node_name)
506 self.logger.debug("found node {0} public ip: {1}".format(i.spec.node_name, ret))
510 ret = raw_input("Enter " + self.sdnc_controller_pod + " pod cluster node public IP address(i.e. " + self.external_net_addr + "): ") # pylint: disable=E0602
513 def get_vm_public_ip_by_nova(self, vm):
515 This method uses openstack nova api to retrieve vm public ip
519 subnet = IPNetwork('{0}/{1}'.format(self.external_net_addr, self.external_net_prefix_len))
520 nova = openstackclient.Client(2, self.cloud['--os-username'], self.cloud['--os-password'], self.cloud['--os-tenant-id'], self.cloud['--os-auth-url'])
521 for i in nova.servers.list():
523 for k, v in i.networks.items(): # pylint: disable=W0612
525 if IPAddress(ip) in subnet:
529 def get_vm_ip(self, keywords, net_addr=None, net_addr_len=None):
531 :param keywords: list of keywords to search for vm, e.g. ['bng', 'gmux', 'brg']
532 :param net_addr: e.g. 10.12.5.0
533 :param net_addr_len: e.g. 24
534 :return: dictionary {keyword: ip}
537 net_addr = self.external_net_addr
540 net_addr_len = self.external_net_prefix_len
542 param = ' '.join([k + ' ' + v for k, v in self.cloud.items() if 'identity' not in k])
543 openstackcmd = 'nova ' + param + ' list'
544 self.logger.debug(openstackcmd)
546 results = os.popen(openstackcmd).read()
547 all_vm_ip_dict = self.extract_vm_ip_as_dict(results, net_addr, net_addr_len)
548 latest_vm_list = self.remove_old_vms(all_vm_ip_dict.keys(), self.cpe_vm_prefix)
549 latest_vm_ip_dict = {vm: all_vm_ip_dict[vm] for vm in latest_vm_list}
550 ip_dict = self.select_subset_vm_ip(latest_vm_ip_dict, keywords)
552 ip_dict.update(self.get_oom_onap_vm_ip(keywords))
554 if len(ip_dict) != len(keywords):
555 self.logger.error('Cannot find all desired IP addresses for %s.', keywords)
556 self.logger.error(json.dumps(ip_dict, indent=4, sort_keys=True))
557 self.logger.error('Temporarily continue.. remember to check back vcpecommon.py line: 396')
561 def get_oom_onap_vm_ip(self, keywords):
564 if vm in self.host_names:
565 vm_ip[vm] = self.oom_so_sdnc_aai_ip
568 def get_k8s_service_cluster_ip(self, service):
570 Returns cluster IP for a given service
571 :param service: name of the service
574 config.load_kube_config()
575 api = client.CoreV1Api()
576 kslogger = logging.getLogger('kubernetes')
577 kslogger.setLevel(logging.INFO)
579 resp = api.read_namespaced_service(service, self.onap_namespace)
580 except client.rest.ApiException as e:
581 self.logger.error('Error while making k8s API request: ' + e.body)
584 return resp.spec.cluster_ip
586 def get_k8s_service_endpoint_info(self, service, subset):
588 Returns endpoint data for a given service and subset. If there
589 is more than one endpoint returns data for the first one from
590 the list that API returned.
591 :param service: name of the service
592 :param subset: subset name, one of "ip","port"
595 config.load_kube_config()
596 api = client.CoreV1Api()
597 kslogger = logging.getLogger('kubernetes')
598 kslogger.setLevel(logging.INFO)
600 resp = api.read_namespaced_endpoints(service, self.onap_namespace)
601 except client.rest.ApiException as e:
602 self.logger.error('Error while making k8s API request: ' + e.body)
606 return resp.subsets[0].addresses[0].ip
607 elif subset == "port":
608 return resp.subsets[0].ports[0].port
610 self.logger.error("Unsupported subset type")
612 def extract_vm_ip_as_dict(self, novalist_results, net_addr, net_addr_len):
614 for line in novalist_results.split('\n'):
615 fields = line.split('|')
619 ip = self.extract_ip_from_str(net_addr, net_addr_len, ip_info)
620 vm_ip_dict[vm_name] = ip
624 def remove_old_vms(self, vm_list, prefix):
626 For vms with format name_timestamp, only keep the one with the latest timestamp.
628 zdcpe1cpe01brgemu01_201805222148 (drop this)
629 zdcpe1cpe01brgemu01_201805222229 (keep this)
630 zdcpe1cpe01gw01_201805162201
633 same_type_vm_dict = {}
635 fields = vm.split('_')
636 if vm.startswith(prefix) and len(fields) == 2 and len(fields[-1]) == len('201805222148') and fields[-1].isdigit():
637 if vm > same_type_vm_dict.get(fields[0], '0'):
638 same_type_vm_dict[fields[0]] = vm
640 new_vm_list.append(vm)
642 new_vm_list.extend(same_type_vm_dict.values())
645 def select_subset_vm_ip(self, all_vm_ip_dict, vm_name_keyword_list):
647 for keyword in vm_name_keyword_list:
648 for vm, ip in all_vm_ip_dict.items():
650 vm_ip_dict[keyword] = ip
654 def del_vgmux_ves_mode(self):
655 url = self.vpp_ves_url.format(self.hosts['mux']) + '/mode'
656 r = requests.delete(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
657 self.logger.debug('%s', r)
659 def del_vgmux_ves_collector(self):
660 url = self.vpp_ves_url.format(self.hosts['mux']) + '/config'
661 r = requests.delete(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
662 self.logger.debug('%s', r)
664 def set_vgmux_ves_collector(self ):
665 url = self.vpp_ves_url.format(self.hosts['mux'])
667 {'server-addr': self.hosts[self.dcae_ves_collector_name],
668 'server-port': '30235' if self.oom_mode else '8081',
669 'read-interval': '10',
673 r = requests.post(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass, json=data)
674 self.logger.debug('%s', r)
676 def set_vgmux_packet_loss_rate(self, lossrate, vg_vnf_instance_name):
677 url = self.vpp_ves_url.format(self.hosts['mux'])
679 {"working-mode": "demo",
680 "base-packet-loss": str(lossrate),
681 "source-name": vg_vnf_instance_name
684 r = requests.post(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass, json=data)
685 self.logger.debug('%s', r)
687 # return all the VxLAN interface names of BRG or vGMUX based on the IP address
688 def get_vxlan_interfaces(self, ip, print_info=False):
689 url = self.vpp_inf_url.format(ip)
690 self.logger.debug('url is this: %s', url)
691 r = requests.get(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
692 data = r.json()['interfaces']['interface']
695 if 'name' in inf and 'type' in inf and inf['type'] == 'v3po:vxlan-tunnel':
696 print(json.dumps(inf, indent=4, sort_keys=True))
698 return [inf['name'] for inf in data if 'name' in inf and 'type' in inf and inf['type'] == 'v3po:vxlan-tunnel']
700 # delete all VxLAN interfaces of each hosts
701 def delete_vxlan_interfaces(self, host_dic):
702 for host, ip in host_dic.items():
704 self.logger.info('{0}: Getting VxLAN interfaces'.format(host))
705 inf_list = self.get_vxlan_interfaces(ip)
709 self.logger.info("{0}: Deleting VxLAN crossconnect {1}".format(host, inf))
710 url = self.vpp_inf_url.format(ip) + '/interface/' + inf + '/v3po:l2'
711 requests.delete(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
716 self.logger.info("{0}: Deleting VxLAN interface {1}".format(host, inf))
717 url = self.vpp_inf_url.format(ip) + '/interface/' + inf
718 requests.delete(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
720 if self.get_vxlan_interfaces(ip):
721 self.logger.error("Error deleting VxLAN from {0}, try to restart the VM, IP is {1}.".format(host, ip))
725 self.logger.info("{0}: no VxLAN interface found, nothing to delete".format(host))
729 def save_object(obj, filepathname):
730 with open(filepathname, 'wb') as fout:
731 pickle.dump(obj, fout)
734 def load_object(filepathname):
735 with open(filepathname, 'rb') as fin:
736 return pickle.load(fin)
739 def increase_ip_address_or_vni_in_template(vnf_template_file, vnf_parameter_name_list):
740 with open(vnf_template_file) as json_input:
741 json_data = json.load(json_input)
742 param_list = json_data['VNF-API:input']['VNF-API:vnf-topology-information']['VNF-API:vnf-parameters']
743 for param in param_list:
744 if param['vnf-parameter-name'] in vnf_parameter_name_list:
745 ipaddr_or_vni = param['vnf-parameter-value'].split('.')
746 number = int(ipaddr_or_vni[-1])
751 ipaddr_or_vni[-1] = str(number)
752 param['vnf-parameter-value'] = '.'.join(ipaddr_or_vni)
754 assert json_data is not None
755 with open(vnf_template_file, 'w') as json_output:
756 json.dump(json_data, json_output, indent=4, sort_keys=True)
758 def save_preload_data(self, preload_data):
759 self.save_object(preload_data, self.preload_dict_file)
761 def load_preload_data(self):
762 return self.load_object(self.preload_dict_file)
764 def save_vgmux_vnf_name(self, vgmux_vnf_name):
765 self.save_object(vgmux_vnf_name, self.vgmux_vnf_name_file)
767 def load_vgmux_vnf_name(self):
768 return self.load_object(self.vgmux_vnf_name_file)