blob: 9f7f57fb19ee02416d27959dec50db9cc9d82b5c [file] [log] [blame]
Bartek Grzybowskifbbdbec2019-09-25 16:37:05 +02001#!/usr/bin/env python
2
Kang Xi11d278c2018-04-06 16:56:04 -04003import json
4import logging
5import os
6import pickle
7import re
8import sys
9
10import ipaddress
11import mysql.connector
12import requests
13import commands
14import time
Bartek Grzybowski3f047b82019-12-10 09:50:09 +010015import yaml
Yang Xu65b84a42018-12-31 17:48:02 +000016from novaclient import client as openstackclient
Yang Xu64339a82018-12-30 05:32:21 +000017from kubernetes import client, config
Yang Xu65b84a42018-12-31 17:48:02 +000018from netaddr import IPAddress, IPNetwork
Kang Xi11d278c2018-04-06 16:56:04 -040019
20class VcpeCommon:
Kang Xi11d278c2018-04-06 16:56:04 -040021
22 def __init__(self, extra_host_names=None):
23 self.logger = logging.getLogger(__name__)
Yang Xu65b84a42018-12-31 17:48:02 +000024 self.logger.setLevel(logging.DEBUG)
Kang Xi11d278c2018-04-06 16:56:04 -040025 self.logger.info('Initializing configuration')
26
Bartek Grzybowski3f047b82019-12-10 09:50:09 +010027 # Read configuration from config file
28 self._load_config()
29
Bartek Grzybowski156cf4c2019-12-10 13:56:06 +010030 self.sdnc_controller_pod = '-'.join([self.onap_environment, 'sdnc-sdnc-0'])
Michal Ptacek0b06f782019-09-12 12:27:47 +000031 # 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
Bartek Grzybowski863af9a2019-09-13 08:54:14 +020032 self.sdnc_oam_ip = self.get_pod_node_oam_ip(self.sdnc_controller_pod)
33 # OOM: this is a k8s host external IP, e.g. oom-k8s-01 IP
34 self.oom_so_sdnc_aai_ip = self.get_pod_node_public_ip(self.sdnc_controller_pod)
Michal Ptacek0b06f782019-09-12 12:27:47 +000035 # OOM: this is a k8s host external IP, e.g. oom-k8s-01 IP
Yang Xu65b84a42018-12-31 17:48:02 +000036 self.oom_dcae_ves_collector = self.oom_so_sdnc_aai_ip
Michal Ptacek0b06f782019-09-12 12:27:47 +000037 # OOM: this is a k8s host external IP, e.g. oom-k8s-01 IP
Yang Xu65b84a42018-12-31 17:48:02 +000038 self.mr_ip_addr = self.oom_so_sdnc_aai_ip
Brian Freeman8076a872018-11-13 11:34:48 -050039 self.mr_ip_port = '30227'
Brian Freemana605bc72018-11-12 10:50:30 -050040 self.so_nbi_port = '30277' if self.oom_mode else '8080'
Brian Freemane8aa3f02019-09-20 08:29:22 -050041 self.sdnc_preloading_port = '30267' if self.oom_mode else '8282'
Kang Xi0e0a1d62018-07-23 16:53:54 -040042 self.aai_query_port = '30233' if self.oom_mode else '8443'
43 self.sniro_port = '30288' if self.oom_mode else '8080'
44
Bartek Grzybowski19199da2019-11-07 12:44:46 +010045 self.host_names = ['sdc', 'so', 'sdnc', 'robot', 'aai-inst1', self.dcae_ves_collector_name, 'mariadb-galera']
Kang Xi11d278c2018-04-06 16:56:04 -040046 if extra_host_names:
47 self.host_names.extend(extra_host_names)
48 # get IP addresses
49 self.hosts = self.get_vm_ip(self.host_names, self.external_net_addr, self.external_net_prefix_len)
50 # this is the keyword used to name vgw stack, must not be used in other stacks
51 self.vgw_name_keyword = 'base_vcpe_vgw'
Brian Freeman81f6e9e2018-11-11 22:36:20 -050052 # this is the file that will keep the index of last assigned SO name
53 self.vgw_vfmod_name_index_file= '__var/vgw_vfmod_name_index'
Kang Xi11d278c2018-04-06 16:56:04 -040054 self.svc_instance_uuid_file = '__var/svc_instance_uuid'
55 self.preload_dict_file = '__var/preload_dict'
56 self.vgmux_vnf_name_file = '__var/vgmux_vnf_name'
57 self.product_family_id = 'f9457e8c-4afd-45da-9389-46acd9bf5116'
58 self.custom_product_family_id = 'a9a77d5a-123e-4ca2-9eb9-0b015d2ee0fb'
59 self.instance_name_prefix = {
60 'service': 'vcpe_svc',
61 'network': 'vcpe_net',
62 'vnf': 'vcpe_vnf',
63 'vfmodule': 'vcpe_vfmodule'
64 }
65 self.aai_userpass = 'AAI', 'AAI'
Kang Xi11d278c2018-04-06 16:56:04 -040066 self.os_tenant_id = self.cloud['--os-tenant-id']
67 self.os_region_name = self.cloud['--os-region-name']
68 self.common_preload_config['pub_key'] = self.pub_key
Kang Xi0e0a1d62018-07-23 16:53:54 -040069 self.sniro_url = 'http://' + self.hosts['robot'] + ':' + self.sniro_port + '/__admin/mappings'
Kang Xi11d278c2018-04-06 16:56:04 -040070 self.sniro_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
Kang Xi268b74d2018-05-23 15:53:42 -040071 self.homing_solution = 'sniro' # value is either 'sniro' or 'oof'
72# self.homing_solution = 'oof'
73 self.customer_location_used_by_oof = {
74 "customerLatitude": "32.897480",
75 "customerLongitude": "-97.040443",
76 "customerName": "some_company"
77 }
Kang Xi11d278c2018-04-06 16:56:04 -040078
79 #############################################################################################
Yang Xuc52ed6e2019-04-29 00:20:52 -040080 # SDC urls
Brian Freeman0c724152019-09-18 09:30:05 -050081 self.sdc_be_port = '30204'
Yang Xu0e319ef2019-04-30 14:28:07 -040082 self.sdc_be_request_userpass = 'vid', 'Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U'
83 self.sdc_be_request_headers = {'X-ECOMP-InstanceID': 'VID'}
Brian Freeman0c724152019-09-18 09:30:05 -050084 self.sdc_be_url_prefix = 'https://' + self.hosts['sdc'] + ':' + self.sdc_be_port
Yang Xu0e319ef2019-04-30 14:28:07 -040085 self.sdc_service_list_url = self.sdc_be_url_prefix + '/sdc/v1/catalog/services'
86
Brian Freeman0c724152019-09-18 09:30:05 -050087 self.sdc_fe_port = '30207'
Yang Xu0e319ef2019-04-30 14:28:07 -040088 self.sdc_fe_request_userpass = 'beep', 'boop'
89 self.sdc_fe_request_headers = {'USER_ID': 'demo', 'Content-Type': 'application/json'}
Brian Freeman0c724152019-09-18 09:30:05 -050090 self.sdc_fe_url_prefix = 'https://' + self.hosts['sdc'] + ':' + self.sdc_fe_port
Yang Xu0e319ef2019-04-30 14:28:07 -040091 self.sdc_get_category_list_url = self.sdc_fe_url_prefix + '/sdc1/feProxy/rest/v1/categories'
92 self.sdc_create_allotted_resource_subcategory_url = self.sdc_fe_url_prefix + '/sdc1/feProxy/rest/v1/category/resources/resourceNewCategory.allotted%20resource/subCategory'
Yang Xuc52ed6e2019-04-29 00:20:52 -040093
94 #############################################################################################
Kang Xi11d278c2018-04-06 16:56:04 -040095 # SDNC urls
96 self.sdnc_userpass = 'admin', 'Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U'
97 self.sdnc_db_name = 'sdnctl'
98 self.sdnc_db_user = 'sdnctl'
99 self.sdnc_db_pass = 'gamma'
Bartek Grzybowski19199da2019-11-07 12:44:46 +0100100 self.sdnc_db_port = self.get_k8s_service_endpoint_info('mariadb-galera','port') if self.oom_mode else '3306'
Kang Xi11d278c2018-04-06 16:56:04 -0400101 self.sdnc_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
Brian Freemane8aa3f02019-09-20 08:29:22 -0500102 self.sdnc_preload_network_url = 'https://' + self.hosts['sdnc'] + \
Kang Xi0e0a1d62018-07-23 16:53:54 -0400103 ':' + self.sdnc_preloading_port + '/restconf/operations/VNF-API:preload-network-topology-operation'
Brian Freeman9b3d6ca2019-11-06 13:22:53 -0500104 self.sdnc_preload_network_gra_url = 'https://' + self.hosts['sdnc'] + \
105 ':' + self.sdnc_preloading_port + '/restconf/operations/GENERIC-RESOURCE-API:preload-network-topology-operation'
Brian Freemane8aa3f02019-09-20 08:29:22 -0500106 self.sdnc_preload_vnf_url = 'https://' + self.hosts['sdnc'] + \
Kang Xi0e0a1d62018-07-23 16:53:54 -0400107 ':' + self.sdnc_preloading_port + '/restconf/operations/VNF-API:preload-vnf-topology-operation'
Brian Freemane8aa3f02019-09-20 08:29:22 -0500108 self.sdnc_preload_gra_url = 'https://' + self.hosts['sdnc'] + \
Brian Freeman81f6e9e2018-11-11 22:36:20 -0500109 ':' + self.sdnc_preloading_port + '/restconf/operations/GENERIC-RESOURCE-API:preload-vf-module-topology-operation'
Brian Freemane8aa3f02019-09-20 08:29:22 -0500110 self.sdnc_ar_cleanup_url = 'https://' + self.hosts['sdnc'] + ':' + self.sdnc_preloading_port + \
Kang Xi0e0a1d62018-07-23 16:53:54 -0400111 '/restconf/config/GENERIC-RESOURCE-API:'
Kang Xi11d278c2018-04-06 16:56:04 -0400112
113 #############################################################################################
Bartek Grzybowskiba8a72f2019-11-22 15:02:21 +0100114 # MARIADB-GALERA settings
115 self.mariadb_galera_endpoint_ip = self.get_k8s_service_endpoint_info('mariadb-galera','ip')
116 self.mariadb_galera_endpoint_port = self.get_k8s_service_endpoint_info('mariadb-galera','port')
117
118 #############################################################################################
Kang Xi11d278c2018-04-06 16:56:04 -0400119 # SO urls, note: do NOT add a '/' at the end of the url
Brian Freemana605bc72018-11-12 10:50:30 -0500120 self.so_req_api_url = {'v4': 'http://' + self.hosts['so'] + ':' + self.so_nbi_port + '/onap/so/infra/serviceInstantiation/v7/serviceInstances',
Yang Xu63a0afd2018-11-20 16:01:01 -0500121 'v5': 'http://' + self.hosts['so'] + ':' + self.so_nbi_port + '/onap/so/infra/serviceInstantiation/v7/serviceInstances'}
Brian Freemana605bc72018-11-12 10:50:30 -0500122 self.so_check_progress_api_url = 'http://' + self.hosts['so'] + ':' + self.so_nbi_port + '/onap/so/infra/orchestrationRequests/v6'
Kang Xi11d278c2018-04-06 16:56:04 -0400123 self.so_userpass = 'InfraPortalClient', 'password1$'
124 self.so_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
Brian Freemana605bc72018-11-12 10:50:30 -0500125 self.so_db_name = 'catalogdb'
Kang Xi11d278c2018-04-06 16:56:04 -0400126 self.so_db_user = 'root'
Yang Xu21b09c92019-06-13 13:19:20 -0400127 self.so_db_pass = 'secretpassword'
Bartek Grzybowskiba8a72f2019-11-22 15:02:21 +0100128 self.so_db_host = self.mariadb_galera_endpoint_ip if self.oom_mode else self.hosts['so']
129 self.so_db_port = self.mariadb_galera_endpoint_port if self.oom_mode else '3306'
Kang Xi11d278c2018-04-06 16:56:04 -0400130
131 self.vpp_inf_url = 'http://{0}:8183/restconf/config/ietf-interfaces:interfaces'
132 self.vpp_api_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
133 self.vpp_api_userpass = ('admin', 'admin')
134 self.vpp_ves_url= 'http://{0}:8183/restconf/config/vesagent:vesagent'
135
Bartek Grzybowskidbb3ba12019-09-25 11:21:42 +0200136 #############################################################################################
137 # POLICY urls
138 self.policy_userpass = ('healthcheck', 'zb!XztG34')
139 self.policy_headers = {'Accept': 'application/json', 'Content-Type': 'application/json'}
140 self.policy_api_url = 'https://{0}:6969/policy/api/v1/policytypes/onap.policies.controlloop.Operational/versions/1.0.0/policies'
141 self.policy_pap_get_url = 'https://{0}:6969/policy/pap/v1/pdps'
142 self.policy_pap_json = {'policies': [{'policy-id': 'operational.vcpe'}]}
143 self.policy_pap_post_url = self.policy_pap_get_url + '/policies'
144 self.policy_api_service_name = 'policy-api'
145 self.policy_pap_service_name = 'policy-pap'
146
Bartek Grzybowski9018a842019-10-16 15:28:23 +0200147 #############################################################################################
Bartek Grzybowski6358aa32019-10-30 13:46:43 +0100148 # AAI urls
149 self.aai_region_query_url = 'https://' + self.oom_so_sdnc_aai_ip + ':' +\
150 self.aai_query_port +\
151 '/aai/v14/cloud-infrastructure/cloud-regions/cloud-region/CloudOwner/' +\
152 self.cloud['--os-region-name']
153 self.aai_headers = {'Accept': 'application/json',
154 'Content-Type': 'application/json',
155 'X-FromAppId': 'postman', 'X-TransactionId': '9999'}
156
Bartek Grzybowski3f047b82019-12-10 09:50:09 +0100157 def _load_config(self, cfg_file='vcpeconfig.yaml'):
158 """
159 Reads vcpe config file and injects settings as object's attributes
160 :param cfg_file: Configuration file path
161 """
162
163 try:
164 with open(cfg_file, 'r') as cfg:
165 cfg_yml = yaml.full_load(cfg)
166 except Exception as e:
167 self.logger.error('Error loading configuration: ' + str(e))
168 sys.exit(1)
169
170 self.logger.debug('\n' + yaml.dump(cfg_yml))
171
172 # Use setattr to load config file keys as VcpeCommon class' object
173 # attributes
174 try:
175 # Check config isn't empty
176 if cfg_yml is not None:
177 for cfg_key in cfg_yml:
178 setattr(self, cfg_key, cfg_yml[cfg_key])
179 except TypeError as e:
180 self.logger.error('Unable to parse config file: ' + str(e))
181 sys.exit(1)
182
Bartek Grzybowskibfeeb242019-09-13 08:35:48 +0200183 def heatbridge(self, openstack_stack_name, svc_instance_uuid):
Kang Xi11d278c2018-04-06 16:56:04 -0400184 """
185 Add vserver information to AAI
186 """
187 self.logger.info('Adding vServer information to AAI for {0}'.format(openstack_stack_name))
Kang Xi0e0a1d62018-07-23 16:53:54 -0400188 if not self.oom_mode:
189 cmd = '/opt/demo.sh heatbridge {0} {1} vCPE'.format(openstack_stack_name, svc_instance_uuid)
190 ret = commands.getstatusoutput("ssh -i onap_dev root@{0} '{1}'".format(self.hosts['robot'], cmd))
191 self.logger.debug('%s', ret)
192 else:
193 print('To add vGMUX vserver info to AAI, do the following:')
194 print('- ssh to rancher')
195 print('- sudo su -')
196 print('- cd /root/oom/kubernetes/robot')
197 print('- ./demo-k8s.sh onap heatbridge {0} {1} vCPE'.format(openstack_stack_name, svc_instance_uuid))
Kang Xi11d278c2018-04-06 16:56:04 -0400198
199 def get_brg_mac_from_sdnc(self):
200 """
Kang Xi6c762392018-05-30 09:27:34 -0400201 Check table DHCP_MAP in the SDNC DB. Find the newly instantiated BRG MAC address.
202 Note that there might be multiple BRGs, the most recently instantiated BRG always has the largest IP address.
Kang Xi11d278c2018-04-06 16:56:04 -0400203 """
Bartek Grzybowski19199da2019-11-07 12:44:46 +0100204 if self.oom_mode:
205 db_host=self.mariadb_galera_endpoint_ip
206 else:
207 db_host=self.hosts['mariadb-galera']
208
209 cnx = mysql.connector.connect(user=self.sdnc_db_user,
210 password=self.sdnc_db_pass,
211 database=self.sdnc_db_name,
212 host=db_host,
213 port=self.sdnc_db_port)
Kang Xi11d278c2018-04-06 16:56:04 -0400214 cursor = cnx.cursor()
215 query = "SELECT * from DHCP_MAP"
216 cursor.execute(query)
217
218 self.logger.debug('DHCP_MAP table in SDNC')
Kang Xi6c762392018-05-30 09:27:34 -0400219 mac_recent = None
220 host = -1
Kang Xi11d278c2018-04-06 16:56:04 -0400221 for mac, ip in cursor:
Bartek Grzybowski19199da2019-11-07 12:44:46 +0100222 self.logger.debug(mac + ' - ' + ip)
Kang Xi6c762392018-05-30 09:27:34 -0400223 this_host = int(ip.split('.')[-1])
224 if host < this_host:
225 host = this_host
226 mac_recent = mac
Kang Xi11d278c2018-04-06 16:56:04 -0400227
228 cnx.close()
229
Bartek Grzybowski19199da2019-11-07 12:44:46 +0100230 try:
231 assert mac_recent
232 except AssertionError:
233 self.logger.error('Failed to obtain BRG MAC address from database')
234 sys.exit(1)
235
Kang Xi6c762392018-05-30 09:27:34 -0400236 return mac_recent
Kang Xi11d278c2018-04-06 16:56:04 -0400237
Bartek Grzybowski9018a842019-10-16 15:28:23 +0200238 def execute_cmds_mariadb(self, cmds):
239 self.execute_cmds_db(cmds, self.sdnc_db_user, self.sdnc_db_pass,
240 self.sdnc_db_name, self.mariadb_galera_endpoint_ip,
241 self.mariadb_galera_endpoint_port)
242
Kang Xi6c762392018-05-30 09:27:34 -0400243 def execute_cmds_sdnc_db(self, cmds):
244 self.execute_cmds_db(cmds, self.sdnc_db_user, self.sdnc_db_pass, self.sdnc_db_name,
245 self.hosts['sdnc'], self.sdnc_db_port)
Kang Xi11d278c2018-04-06 16:56:04 -0400246
Kang Xi6c762392018-05-30 09:27:34 -0400247 def execute_cmds_so_db(self, cmds):
248 self.execute_cmds_db(cmds, self.so_db_user, self.so_db_pass, self.so_db_name,
Bartek Grzybowskiba8a72f2019-11-22 15:02:21 +0100249 self.so_db_host, self.so_db_port)
Kang Xi6c762392018-05-30 09:27:34 -0400250
251 def execute_cmds_db(self, cmds, dbuser, dbpass, dbname, host, port):
252 cnx = mysql.connector.connect(user=dbuser, password=dbpass, database=dbname, host=host, port=port)
Kang Xi11d278c2018-04-06 16:56:04 -0400253 cursor = cnx.cursor()
254 for cmd in cmds:
255 self.logger.debug(cmd)
256 cursor.execute(cmd)
257 self.logger.debug('%s', cursor)
258 cnx.commit()
259 cursor.close()
260 cnx.close()
261
262 def find_file(self, file_name_keyword, file_ext, search_dir):
263 """
264 :param file_name_keyword: keyword used to look for the csar file, case insensitive matching, e.g, infra
265 :param file_ext: e.g., csar, json
266 :param search_dir path to search
267 :return: path name of the file
268 """
269 file_name_keyword = file_name_keyword.lower()
270 file_ext = file_ext.lower()
271 if not file_ext.startswith('.'):
272 file_ext = '.' + file_ext
273
274 filenamepath = None
275 for file_name in os.listdir(search_dir):
276 file_name_lower = file_name.lower()
277 if file_name_keyword in file_name_lower and file_name_lower.endswith(file_ext):
278 if filenamepath:
279 self.logger.error('Multiple files found for *{0}*.{1} in '
280 'directory {2}'.format(file_name_keyword, file_ext, search_dir))
Bartek Grzybowski68d93c22019-10-30 10:55:55 +0100281 sys.exit(1)
Kang Xi11d278c2018-04-06 16:56:04 -0400282 filenamepath = os.path.abspath(os.path.join(search_dir, file_name))
283
284 if filenamepath:
285 return filenamepath
286 else:
287 self.logger.error("Cannot find *{0}*{1} in directory {2}".format(file_name_keyword, file_ext, search_dir))
Bartek Grzybowski68d93c22019-10-30 10:55:55 +0100288 sys.exit(1)
Kang Xi11d278c2018-04-06 16:56:04 -0400289
290 @staticmethod
291 def network_name_to_subnet_name(network_name):
292 """
293 :param network_name: example: vcpe_net_cpe_signal_201711281221
294 :return: vcpe_net_cpe_signal_subnet_201711281221
295 """
296 fields = network_name.split('_')
297 fields.insert(-1, 'subnet')
298 return '_'.join(fields)
299
300 def set_network_name(self, network_name):
301 param = ' '.join([k + ' ' + v for k, v in self.cloud.items()])
302 openstackcmd = 'openstack ' + param
303 cmd = ' '.join([openstackcmd, 'network set --name', network_name, 'ONAP-NW1'])
304 os.popen(cmd)
305
306 def set_subnet_name(self, network_name):
307 """
308 Example: network_name = vcpe_net_cpe_signal_201711281221
309 set subnet name to vcpe_net_cpe_signal_subnet_201711281221
310 :return:
311 """
312 param = ' '.join([k + ' ' + v for k, v in self.cloud.items()])
313 openstackcmd = 'openstack ' + param
314
315 # expected results: | subnets | subnet_id |
316 subnet_info = os.popen(openstackcmd + ' network show ' + network_name + ' |grep subnets').read().split('|')
317 if len(subnet_info) > 2 and subnet_info[1].strip() == 'subnets':
318 subnet_id = subnet_info[2].strip()
319 subnet_name = self.network_name_to_subnet_name(network_name)
320 cmd = ' '.join([openstackcmd, 'subnet set --name', subnet_name, subnet_id])
321 os.popen(cmd)
322 self.logger.info("Subnet name set to: " + subnet_name)
323 return True
324 else:
325 self.logger.error("Can't get subnet info from network name: " + network_name)
326 return False
327
Bartek Grzybowskidbb3ba12019-09-25 11:21:42 +0200328 def set_closed_loop_policy(self, policy_template_file):
329 # Gather policy services cluster ips
330 p_api_cluster_ip = self.get_k8s_service_cluster_ip(self.policy_api_service_name)
331 p_pap_cluster_ip = self.get_k8s_service_cluster_ip(self.policy_pap_service_name)
332
333 # Read policy json from file
334 with open(policy_template_file) as f:
335 try:
336 policy_json = json.load(f)
337 except ValueError:
338 self.logger.error(policy_template_file + " doesn't seem to contain valid JSON data")
Bartek Grzybowski68d93c22019-10-30 10:55:55 +0100339 sys.exit(1)
Bartek Grzybowskidbb3ba12019-09-25 11:21:42 +0200340
341 # Check policy already applied
Bartek Grzybowskidbb3ba12019-09-25 11:21:42 +0200342 policy_exists_req = requests.get(self.policy_pap_get_url.format(
343 p_pap_cluster_ip), auth=self.policy_userpass,
344 verify=False, headers=self.policy_headers)
345 if policy_exists_req.status_code != 200:
346 self.logger.error('Failure in checking CL policy existence. '
347 'Policy-pap responded with HTTP code {0}'.format(
348 policy_exists_req.status_code))
Bartek Grzybowski68d93c22019-10-30 10:55:55 +0100349 sys.exit(1)
Bartek Grzybowskidbb3ba12019-09-25 11:21:42 +0200350
351 try:
352 policy_exists_json = policy_exists_req.json()
353 except ValueError as e:
354 self.logger.error('Policy-pap request failed: ' + e.message)
Bartek Grzybowski68d93c22019-10-30 10:55:55 +0100355 sys.exit(1)
Bartek Grzybowskidbb3ba12019-09-25 11:21:42 +0200356
357 try:
358 assert policy_exists_json['groups'][0]['pdpSubgroups'] \
359 [1]['policies'][0]['name'] != 'operational.vcpe'
360 except AssertionError:
361 self.logger.info('vCPE closed loop policy already exists, not applying')
362 return
363 except IndexError:
364 pass # policy doesn't exist
365
366 # Create policy
367 policy_create_req = requests.post(self.policy_api_url.format(
368 p_api_cluster_ip), auth=self.policy_userpass,
369 json=policy_json, verify=False,
370 headers=self.policy_headers)
371 # Get the policy id from policy-api response
372 if policy_create_req.status_code != 200:
373 self.logger.error('Failed creating policy. Policy-api responded'
374 ' with HTTP code {0}'.format(policy_create_req.status_code))
Bartek Grzybowski68d93c22019-10-30 10:55:55 +0100375 sys.exit(1)
Bartek Grzybowskidbb3ba12019-09-25 11:21:42 +0200376
377 try:
378 policy_version = json.loads(policy_create_req.text)['policy-version']
379 except (KeyError, ValueError):
380 self.logger.error('Policy API response not understood:')
381 self.logger.debug('\n' + str(policy_create_req.text))
382
383 # Inject the policy into Policy PAP
384 self.policy_pap_json['policies'].append({'policy-version': policy_version})
385 policy_insert_req = requests.post(self.policy_pap_post_url.format(
386 p_pap_cluster_ip), auth=self.policy_userpass,
387 json=self.policy_pap_json, verify=False,
388 headers=self.policy_headers)
389 if policy_insert_req.status_code != 200:
390 self.logger.error('Policy PAP request failed with HTTP code'
391 '{0}'.format(policy_insert_req.status_code))
Bartek Grzybowski68d93c22019-10-30 10:55:55 +0100392 sys.exit(1)
Bartek Grzybowskidbb3ba12019-09-25 11:21:42 +0200393 self.logger.info('Successully pushed closed loop Policy')
394
Kang Xi11d278c2018-04-06 16:56:04 -0400395 def is_node_in_aai(self, node_type, node_uuid):
396 key = None
397 search_node_type = None
398 if node_type == 'service':
399 search_node_type = 'service-instance'
400 key = 'service-instance-id'
401 elif node_type == 'vnf':
402 search_node_type = 'generic-vnf'
403 key = 'vnf-id'
404 else:
405 logging.error('Invalid node_type: ' + node_type)
Bartek Grzybowski68d93c22019-10-30 10:55:55 +0100406 sys.exit(1)
Kang Xi11d278c2018-04-06 16:56:04 -0400407
Kang Xi0e0a1d62018-07-23 16:53:54 -0400408 url = 'https://{0}:{1}/aai/v11/search/nodes-query?search-node-type={2}&filter={3}:EQUALS:{4}'.format(
409 self.hosts['aai-inst1'], self.aai_query_port, search_node_type, key, node_uuid)
Kang Xi11d278c2018-04-06 16:56:04 -0400410
Kang Xi268b74d2018-05-23 15:53:42 -0400411 headers = {'Content-Type': 'application/json', 'Accept': 'application/json', 'X-FromAppID': 'vCPE-Robot', 'X-TransactionId': 'get_aai_subscr'}
Kang Xi11d278c2018-04-06 16:56:04 -0400412 r = requests.get(url, headers=headers, auth=self.aai_userpass, verify=False)
413 response = r.json()
414 self.logger.debug('aai query: ' + url)
415 self.logger.debug('aai response:\n' + json.dumps(response, indent=4, sort_keys=True))
416 return 'result-data' in response
417
418 @staticmethod
419 def extract_ip_from_str(net_addr, net_addr_len, sz):
420 """
421 :param net_addr: e.g. 10.5.12.0
422 :param net_addr_len: e.g. 24
423 :param sz: a string
424 :return: the first IP address matching the network, e.g. 10.5.12.3
425 """
426 network = ipaddress.ip_network(unicode('{0}/{1}'.format(net_addr, net_addr_len)), strict=False)
427 ip_list = re.findall(r'[0-9]+(?:\.[0-9]+){3}', sz)
428 for ip in ip_list:
429 this_net = ipaddress.ip_network(unicode('{0}/{1}'.format(ip, net_addr_len)), strict=False)
430 if this_net == network:
431 return str(ip)
432 return None
433
Yang Xu75b0f5e2018-11-14 14:04:20 -0500434 def get_pod_node_oam_ip(self, pod):
435 """
Yang Xu64339a82018-12-30 05:32:21 +0000436 :Assuming kubectl is available and configured by default config (~/.kube/config)
437 :param pod: pod name substring, e.g. 'sdnc-sdnc-0'
438 :return pod's cluster node oam ip (10.0.0.0/16)
Yang Xu75b0f5e2018-11-14 14:04:20 -0500439 """
Yang Xu64339a82018-12-30 05:32:21 +0000440 ret = None
441 config.load_kube_config()
442 api = client.CoreV1Api()
443 kslogger = logging.getLogger('kubernetes')
444 kslogger.setLevel(logging.INFO)
445 res = api.list_pod_for_all_namespaces()
446 for i in res.items:
447 if pod in i.metadata.name:
Yang Xu65b84a42018-12-31 17:48:02 +0000448 self.logger.debug("found {0}\t{1}\t{2}".format(i.metadata.name, i.status.host_ip, i.spec.node_name))
Yang Xu64339a82018-12-30 05:32:21 +0000449 ret = i.status.host_ip
450 break
451
452 if ret is None:
Bartek Grzybowski863af9a2019-09-13 08:54:14 +0200453 ret = raw_input("Enter " + self.sdnc_controller_pod + " pod cluster node OAM IP address(10.0.0.0/16): ")
Yang Xu64339a82018-12-30 05:32:21 +0000454 return ret
455
456 def get_pod_node_public_ip(self, pod):
457 """
458 :Assuming kubectl is available and configured by default config (~/.kube/config)
459 :param pod: pod name substring, e.g. 'sdnc-sdnc-0'
460 :return pod's cluster node public ip (i.e. 10.12.0.0/16)
461 """
462 ret = None
463 config.load_kube_config()
464 api = client.CoreV1Api()
465 kslogger = logging.getLogger('kubernetes')
466 kslogger.setLevel(logging.INFO)
467 res = api.list_pod_for_all_namespaces()
468 for i in res.items:
469 if pod in i.metadata.name:
Yang Xu65b84a42018-12-31 17:48:02 +0000470 ret = self.get_vm_public_ip_by_nova(i.spec.node_name)
471 self.logger.debug("found node {0} public ip: {1}".format(i.spec.node_name, ret))
Yang Xu64339a82018-12-30 05:32:21 +0000472 break
473
474 if ret is None:
Bartek Grzybowski863af9a2019-09-13 08:54:14 +0200475 ret = raw_input("Enter " + self.sdnc_controller_pod + " pod cluster node public IP address(i.e. " + self.external_net_addr + "): ")
Yang Xu75b0f5e2018-11-14 14:04:20 -0500476 return ret
477
Yang Xu65b84a42018-12-31 17:48:02 +0000478 def get_vm_public_ip_by_nova(self, vm):
479 """
480 This method uses openstack nova api to retrieve vm public ip
481 :param vm: vm name
482 :return vm public ip
483 """
484 subnet = IPNetwork('{0}/{1}'.format(self.external_net_addr, self.external_net_prefix_len))
485 nova = openstackclient.Client(2, self.cloud['--os-username'], self.cloud['--os-password'], self.cloud['--os-tenant-id'], self.cloud['--os-auth-url'])
486 for i in nova.servers.list():
487 if i.name == vm:
488 for k, v in i.networks.items():
489 for ip in v:
490 if IPAddress(ip) in subnet:
491 return ip
492 return None
493
Kang Xi11d278c2018-04-06 16:56:04 -0400494 def get_vm_ip(self, keywords, net_addr=None, net_addr_len=None):
495 """
496 :param keywords: list of keywords to search for vm, e.g. ['bng', 'gmux', 'brg']
497 :param net_addr: e.g. 10.12.5.0
498 :param net_addr_len: e.g. 24
499 :return: dictionary {keyword: ip}
500 """
501 if not net_addr:
502 net_addr = self.external_net_addr
503
504 if not net_addr_len:
505 net_addr_len = self.external_net_prefix_len
506
507 param = ' '.join([k + ' ' + v for k, v in self.cloud.items() if 'identity' not in k])
508 openstackcmd = 'nova ' + param + ' list'
509 self.logger.debug(openstackcmd)
510
Kang Xi11d278c2018-04-06 16:56:04 -0400511 results = os.popen(openstackcmd).read()
Kang Xi6c762392018-05-30 09:27:34 -0400512 all_vm_ip_dict = self.extract_vm_ip_as_dict(results, net_addr, net_addr_len)
513 latest_vm_list = self.remove_old_vms(all_vm_ip_dict.keys(), self.cpe_vm_prefix)
514 latest_vm_ip_dict = {vm: all_vm_ip_dict[vm] for vm in latest_vm_list}
515 ip_dict = self.select_subset_vm_ip(latest_vm_ip_dict, keywords)
Kang Xi0e0a1d62018-07-23 16:53:54 -0400516 if self.oom_mode:
517 ip_dict.update(self.get_oom_onap_vm_ip(keywords))
Kang Xi6c762392018-05-30 09:27:34 -0400518
Kang Xi11d278c2018-04-06 16:56:04 -0400519 if len(ip_dict) != len(keywords):
520 self.logger.error('Cannot find all desired IP addresses for %s.', keywords)
521 self.logger.error(json.dumps(ip_dict, indent=4, sort_keys=True))
Yang Xu64339a82018-12-30 05:32:21 +0000522 self.logger.error('Temporarily continue.. remember to check back vcpecommon.py line: 396')
Bartek Grzybowski68d93c22019-10-30 10:55:55 +0100523# sys.exit(1)
Kang Xi11d278c2018-04-06 16:56:04 -0400524 return ip_dict
525
Kang Xi0e0a1d62018-07-23 16:53:54 -0400526 def get_oom_onap_vm_ip(self, keywords):
527 vm_ip = {}
Kang Xi0e0a1d62018-07-23 16:53:54 -0400528 for vm in keywords:
Bartek Grzybowski8b409a12019-10-24 13:26:21 +0200529 if vm in self.host_names:
Kang Xi0e0a1d62018-07-23 16:53:54 -0400530 vm_ip[vm] = self.oom_so_sdnc_aai_ip
531 return vm_ip
532
Bartek Grzybowskidbb3ba12019-09-25 11:21:42 +0200533 def get_k8s_service_cluster_ip(self, service):
534 """
535 Returns cluster IP for a given service
536 :param service: name of the service
537 :return: cluster ip
538 """
539 config.load_kube_config()
540 api = client.CoreV1Api()
541 kslogger = logging.getLogger('kubernetes')
542 kslogger.setLevel(logging.INFO)
543 try:
544 resp = api.read_namespaced_service(service, self.onap_namespace)
545 except client.rest.ApiException as e:
546 self.logger.error('Error while making k8s API request: ' + e.body)
Bartek Grzybowski68d93c22019-10-30 10:55:55 +0100547 sys.exit(1)
Bartek Grzybowskidbb3ba12019-09-25 11:21:42 +0200548
549 return resp.spec.cluster_ip
550
Bartek Grzybowski9018a842019-10-16 15:28:23 +0200551 def get_k8s_service_endpoint_info(self, service, subset):
552 """
553 Returns endpoint data for a given service and subset. If there
554 is more than one endpoint returns data for the first one from
555 the list that API returned.
556 :param service: name of the service
557 :param subset: subset name, one of "ip","port"
558 :return: endpoint ip
559 """
560 config.load_kube_config()
561 api = client.CoreV1Api()
562 kslogger = logging.getLogger('kubernetes')
563 kslogger.setLevel(logging.INFO)
564 try:
565 resp = api.read_namespaced_endpoints(service, self.onap_namespace)
566 except client.rest.ApiException as e:
567 self.logger.error('Error while making k8s API request: ' + e.body)
Bartek Grzybowski68d93c22019-10-30 10:55:55 +0100568 sys.exit(1)
Bartek Grzybowski9018a842019-10-16 15:28:23 +0200569
570 if subset == "ip":
571 return resp.subsets[0].addresses[0].ip
572 elif subset == "port":
573 return resp.subsets[0].ports[0].port
574 else:
575 self.logger.error("Unsupported subset type")
576
Kang Xi6c762392018-05-30 09:27:34 -0400577 def extract_vm_ip_as_dict(self, novalist_results, net_addr, net_addr_len):
578 vm_ip_dict = {}
579 for line in novalist_results.split('\n'):
580 fields = line.split('|')
581 if len(fields) == 8:
582 vm_name = fields[2]
583 ip_info = fields[-2]
584 ip = self.extract_ip_from_str(net_addr, net_addr_len, ip_info)
585 vm_ip_dict[vm_name] = ip
586
587 return vm_ip_dict
588
589 def remove_old_vms(self, vm_list, prefix):
590 """
591 For vms with format name_timestamp, only keep the one with the latest timestamp.
592 E.g.,
593 zdcpe1cpe01brgemu01_201805222148 (drop this)
594 zdcpe1cpe01brgemu01_201805222229 (keep this)
595 zdcpe1cpe01gw01_201805162201
596 """
597 new_vm_list = []
598 same_type_vm_dict = {}
599 for vm in vm_list:
600 fields = vm.split('_')
601 if vm.startswith(prefix) and len(fields) == 2 and len(fields[-1]) == len('201805222148') and fields[-1].isdigit():
602 if vm > same_type_vm_dict.get(fields[0], '0'):
603 same_type_vm_dict[fields[0]] = vm
604 else:
605 new_vm_list.append(vm)
606
607 new_vm_list.extend(same_type_vm_dict.values())
608 return new_vm_list
609
610 def select_subset_vm_ip(self, all_vm_ip_dict, vm_name_keyword_list):
611 vm_ip_dict = {}
612 for keyword in vm_name_keyword_list:
613 for vm, ip in all_vm_ip_dict.items():
614 if keyword in vm:
615 vm_ip_dict[keyword] = ip
616 break
617 return vm_ip_dict
618
Kang Xi11d278c2018-04-06 16:56:04 -0400619 def del_vgmux_ves_mode(self):
620 url = self.vpp_ves_url.format(self.hosts['mux']) + '/mode'
621 r = requests.delete(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
622 self.logger.debug('%s', r)
623
624 def del_vgmux_ves_collector(self):
625 url = self.vpp_ves_url.format(self.hosts['mux']) + '/config'
626 r = requests.delete(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
627 self.logger.debug('%s', r)
628
629 def set_vgmux_ves_collector(self ):
630 url = self.vpp_ves_url.format(self.hosts['mux'])
631 data = {'config':
Kang Xi268b74d2018-05-23 15:53:42 -0400632 {'server-addr': self.hosts[self.dcae_ves_collector_name],
Kang Xi0e0a1d62018-07-23 16:53:54 -0400633 'server-port': '30235' if self.oom_mode else '8081',
Kang Xi11d278c2018-04-06 16:56:04 -0400634 'read-interval': '10',
635 'is-add':'1'
636 }
637 }
638 r = requests.post(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass, json=data)
639 self.logger.debug('%s', r)
640
641 def set_vgmux_packet_loss_rate(self, lossrate, vg_vnf_instance_name):
642 url = self.vpp_ves_url.format(self.hosts['mux'])
643 data = {"mode":
644 {"working-mode": "demo",
645 "base-packet-loss": str(lossrate),
646 "source-name": vg_vnf_instance_name
647 }
648 }
649 r = requests.post(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass, json=data)
650 self.logger.debug('%s', r)
651
652 # return all the VxLAN interface names of BRG or vGMUX based on the IP address
653 def get_vxlan_interfaces(self, ip, print_info=False):
654 url = self.vpp_inf_url.format(ip)
655 self.logger.debug('url is this: %s', url)
656 r = requests.get(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
657 data = r.json()['interfaces']['interface']
658 if print_info:
659 for inf in data:
660 if 'name' in inf and 'type' in inf and inf['type'] == 'v3po:vxlan-tunnel':
661 print(json.dumps(inf, indent=4, sort_keys=True))
662
663 return [inf['name'] for inf in data if 'name' in inf and 'type' in inf and inf['type'] == 'v3po:vxlan-tunnel']
664
665 # delete all VxLAN interfaces of each hosts
666 def delete_vxlan_interfaces(self, host_dic):
667 for host, ip in host_dic.items():
668 deleted = False
669 self.logger.info('{0}: Getting VxLAN interfaces'.format(host))
670 inf_list = self.get_vxlan_interfaces(ip)
671 for inf in inf_list:
672 deleted = True
673 time.sleep(2)
674 self.logger.info("{0}: Deleting VxLAN crossconnect {1}".format(host, inf))
675 url = self.vpp_inf_url.format(ip) + '/interface/' + inf + '/v3po:l2'
676 requests.delete(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
677
678 for inf in inf_list:
679 deleted = True
680 time.sleep(2)
681 self.logger.info("{0}: Deleting VxLAN interface {1}".format(host, inf))
682 url = self.vpp_inf_url.format(ip) + '/interface/' + inf
683 requests.delete(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
684
685 if len(self.get_vxlan_interfaces(ip)) > 0:
686 self.logger.error("Error deleting VxLAN from {0}, try to restart the VM, IP is {1}.".format(host, ip))
687 return False
688
689 if not deleted:
690 self.logger.info("{0}: no VxLAN interface found, nothing to delete".format(host))
691 return True
692
693 @staticmethod
694 def save_object(obj, filepathname):
695 with open(filepathname, 'wb') as fout:
696 pickle.dump(obj, fout)
697
698 @staticmethod
699 def load_object(filepathname):
700 with open(filepathname, 'rb') as fin:
701 return pickle.load(fin)
702
Kang Xi6c762392018-05-30 09:27:34 -0400703 @staticmethod
704 def increase_ip_address_or_vni_in_template(vnf_template_file, vnf_parameter_name_list):
705 with open(vnf_template_file) as json_input:
706 json_data = json.load(json_input)
707 param_list = json_data['VNF-API:input']['VNF-API:vnf-topology-information']['VNF-API:vnf-parameters']
708 for param in param_list:
709 if param['vnf-parameter-name'] in vnf_parameter_name_list:
710 ipaddr_or_vni = param['vnf-parameter-value'].split('.')
711 number = int(ipaddr_or_vni[-1])
712 if 254 == number:
713 number = 10
714 else:
715 number = number + 1
716 ipaddr_or_vni[-1] = str(number)
717 param['vnf-parameter-value'] = '.'.join(ipaddr_or_vni)
718
719 assert json_data is not None
720 with open(vnf_template_file, 'w') as json_output:
721 json.dump(json_data, json_output, indent=4, sort_keys=True)
722
Kang Xi11d278c2018-04-06 16:56:04 -0400723 def save_preload_data(self, preload_data):
724 self.save_object(preload_data, self.preload_dict_file)
725
726 def load_preload_data(self):
727 return self.load_object(self.preload_dict_file)
728
729 def save_vgmux_vnf_name(self, vgmux_vnf_name):
730 self.save_object(vgmux_vnf_name, self.vgmux_vnf_name_file)
731
732 def load_vgmux_vnf_name(self):
733 return self.load_object(self.vgmux_vnf_name_file)
734