16 #############################################################################################
17 # Start: configurations that you must change for a new ONAP installation
18 external_net_addr = '10.12.0.0'
19 external_net_prefix_len = 16
20 #############################################################################################
21 # set the openstack cloud access credentials here
25 '--os-auth-url': 'http://10.12.25.2:5000',
26 '--os-username': 'kxi',
27 '--os-user-domain-id': 'default',
28 '--os-project-domain-id': 'default',
29 '--os-tenant-id': '41d6d38489bd40b09ea8a6b6b852dcbd' if oom_mode else '1e097c6713e74fd7ac8e4295e605ee1e',
30 '--os-region-name': 'RegionOne',
31 '--os-password': 'n3JhGMGuDzD8',
32 '--os-project-domain-name': 'Integration-SB-00' if oom_mode else 'Integration-SB-07',
33 '--os-identity-api-version': '3'
36 common_preload_config = {
37 'oam_onap_net': 'oam_network_0qV7' if oom_mode else 'oam_onap_lAky',
38 'oam_onap_subnet': 'oam_network_0qV7' if oom_mode else 'oam_onap_lAky',
39 'public_net': 'external',
40 'public_net_id': '971040b2-7059-49dc-b220-4fab50cb2ad4'
45 #'oam_onap_net': 'oam_network_0qV7',
46 #'oam_onap_subnet': 'oam_network_0qV7',
47 # End: configurations that you must change for a new ONAP installation
48 #############################################################################################
50 template_variable_symbol = '${'
51 cpe_vm_prefix = 'zdcpe'
52 #############################################################################################
53 # preloading network config
55 # value = [subnet_start_ip, subnet_gateway_ip]
56 preload_network_config = {
57 'cpe_public': ['10.2.0.2', '10.2.0.1'],
58 'cpe_signal': ['10.4.0.2', '10.4.0.1'],
59 'brg_bng': ['10.3.0.2', '10.3.0.1'],
60 'bng_mux': ['10.1.0.10', '10.1.0.1'],
61 'mux_gw': ['10.5.0.10', '10.5.0.1']
64 dcae_ves_collector_name = 'dcae-bootstrap'
65 global_subscriber_id = 'SDN-ETHERNET-INTERNET'
66 project_name = 'Project-Demonstration'
67 owning_entity_id = '520cc603-a3c4-4ec2-9ef4-ca70facd79c0'
68 owning_entity_name = 'OE-Demonstration'
70 def __init__(self, extra_host_names=None):
71 self.logger = logging.getLogger(__name__)
72 self.logger.info('Initializing configuration')
74 # OOM: this is the address that the brg and bng will nat for config of brg - 10.0.0.x address of k8 host for sdnc
75 self.sdnc_brg_bng_ip = '10.0.0.17'
76 # OOM: this is a k8 host external IP
77 self.oom_so_sdnc_aai_ip = '10.12.5.18'
78 # OOM: this is a k8 host external IP can be same as oom_so_sdnc_aai_ip
79 self.oom_dcae_ves_collector = '10.12.5.18'
80 # OOM: this is a k8 host external IP can be same as oom_so_sdnc_aai_ip
81 self.mr_ip_addr = '10.12.5.18'
82 self.mr_ip_port = '30227'
83 self.so_nbi_port = '30277' if self.oom_mode else '8080'
84 self.sdnc_preloading_port = '30202' if self.oom_mode else '8282'
85 self.aai_query_port = '30233' if self.oom_mode else '8443'
86 self.sniro_port = '30288' if self.oom_mode else '8080'
88 self.host_names = ['so', 'sdnc', 'robot', 'aai-inst1', self.dcae_ves_collector_name]
90 self.host_names.extend(extra_host_names)
92 self.hosts = self.get_vm_ip(self.host_names, self.external_net_addr, self.external_net_prefix_len)
93 # this is the keyword used to name vgw stack, must not be used in other stacks
94 self.vgw_name_keyword = 'base_vcpe_vgw'
95 # this is the file that will keep the index of last assigned SO name
96 self.vgw_vfmod_name_index_file= '__var/vgw_vfmod_name_index'
97 self.svc_instance_uuid_file = '__var/svc_instance_uuid'
98 self.preload_dict_file = '__var/preload_dict'
99 self.vgmux_vnf_name_file = '__var/vgmux_vnf_name'
100 self.product_family_id = 'f9457e8c-4afd-45da-9389-46acd9bf5116'
101 self.custom_product_family_id = 'a9a77d5a-123e-4ca2-9eb9-0b015d2ee0fb'
102 self.instance_name_prefix = {
103 'service': 'vcpe_svc',
104 'network': 'vcpe_net',
106 'vfmodule': 'vcpe_vfmodule'
108 self.aai_userpass = 'AAI', 'AAI'
109 self.pub_key = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDKXDgoo3+WOqcUG8/5uUbk81+yczgwC4Y8ywTmuQqbNxlY1oQ0YxdMUqUnhitSXs5S/yRuAVOYHwGg2mCs20oAINrP+mxBI544AMIb9itPjCtgqtE2EWo6MmnFGbHB4Sx3XioE7F4VPsh7japsIwzOjbrQe+Mua1TGQ5d4nfEOQaaglXLLPFfuc7WbhbJbK6Q7rHqZfRcOwAMXgDoBqlyqKeiKwnumddo2RyNT8ljYmvB6buz7KnMinzo7qB0uktVT05FH9Rg0CTWH5norlG5qXgP2aukL0gk1ph8iAt7uYLf1ktp+LJI2gaF6L0/qli9EmVCSLr1uJ38Q8CBflhkh'
110 self.os_tenant_id = self.cloud['--os-tenant-id']
111 self.os_region_name = self.cloud['--os-region-name']
112 self.common_preload_config['pub_key'] = self.pub_key
113 self.sniro_url = 'http://' + self.hosts['robot'] + ':' + self.sniro_port + '/__admin/mappings'
114 self.sniro_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
115 self.homing_solution = 'sniro' # value is either 'sniro' or 'oof'
116 # self.homing_solution = 'oof'
117 self.customer_location_used_by_oof = {
118 "customerLatitude": "32.897480",
119 "customerLongitude": "-97.040443",
120 "customerName": "some_company"
123 #############################################################################################
125 self.sdnc_userpass = 'admin', 'Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U'
126 self.sdnc_db_name = 'sdnctl'
127 self.sdnc_db_user = 'sdnctl'
128 self.sdnc_db_pass = 'gamma'
129 self.sdnc_db_port = '32774'
130 self.sdnc_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
131 self.sdnc_preload_network_url = 'http://' + self.hosts['sdnc'] + \
132 ':' + self.sdnc_preloading_port + '/restconf/operations/VNF-API:preload-network-topology-operation'
133 self.sdnc_preload_vnf_url = 'http://' + self.hosts['sdnc'] + \
134 ':' + self.sdnc_preloading_port + '/restconf/operations/VNF-API:preload-vnf-topology-operation'
135 self.sdnc_preload_gra_url = 'http://' + self.hosts['sdnc'] + \
136 ':' + self.sdnc_preloading_port + '/restconf/operations/GENERIC-RESOURCE-API:preload-vf-module-topology-operation'
137 self.sdnc_ar_cleanup_url = 'http://' + self.hosts['sdnc'] + ':' + self.sdnc_preloading_port + \
138 '/restconf/config/GENERIC-RESOURCE-API:'
140 #############################################################################################
141 # SO urls, note: do NOT add a '/' at the end of the url
142 self.so_req_api_url = {'v4': 'http://' + self.hosts['so'] + ':' + self.so_nbi_port + '/onap/so/infra/serviceInstantiation/v7/serviceInstances',
143 'v5': 'http://' + self.hosts['so'] + ':' + self.so_nbi_port + '/onap/so/infraserviceInstantiation/v7/serviceInstances'}
144 self.so_check_progress_api_url = 'http://' + self.hosts['so'] + ':' + self.so_nbi_port + '/onap/so/infra/orchestrationRequests/v6'
145 self.so_userpass = 'InfraPortalClient', 'password1$'
146 self.so_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
147 self.so_db_name = 'catalogdb'
148 self.so_db_user = 'root'
149 self.so_db_pass = 'password'
150 self.so_db_port = '30252' if self.oom_mode else '32769'
152 self.vpp_inf_url = 'http://{0}:8183/restconf/config/ietf-interfaces:interfaces'
153 self.vpp_api_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
154 self.vpp_api_userpass = ('admin', 'admin')
155 self.vpp_ves_url= 'http://{0}:8183/restconf/config/vesagent:vesagent'
157 def headbridge(self, openstack_stack_name, svc_instance_uuid):
159 Add vserver information to AAI
161 self.logger.info('Adding vServer information to AAI for {0}'.format(openstack_stack_name))
162 if not self.oom_mode:
163 cmd = '/opt/demo.sh heatbridge {0} {1} vCPE'.format(openstack_stack_name, svc_instance_uuid)
164 ret = commands.getstatusoutput("ssh -i onap_dev root@{0} '{1}'".format(self.hosts['robot'], cmd))
165 self.logger.debug('%s', ret)
167 print('To add vGMUX vserver info to AAI, do the following:')
168 print('- ssh to rancher')
170 print('- cd /root/oom/kubernetes/robot')
171 print('- ./demo-k8s.sh onap heatbridge {0} {1} vCPE'.format(openstack_stack_name, svc_instance_uuid))
173 def get_brg_mac_from_sdnc(self):
175 Check table DHCP_MAP in the SDNC DB. Find the newly instantiated BRG MAC address.
176 Note that there might be multiple BRGs, the most recently instantiated BRG always has the largest IP address.
178 cnx = mysql.connector.connect(user=self.sdnc_db_user, password=self.sdnc_db_pass, database=self.sdnc_db_name,
179 host=self.hosts['sdnc'], port=self.sdnc_db_port)
180 cursor = cnx.cursor()
181 query = "SELECT * from DHCP_MAP"
182 cursor.execute(query)
184 self.logger.debug('DHCP_MAP table in SDNC')
187 for mac, ip in cursor:
188 self.logger.debug(mac + ':' + ip)
189 this_host = int(ip.split('.')[-1])
199 def execute_cmds_sdnc_db(self, cmds):
200 self.execute_cmds_db(cmds, self.sdnc_db_user, self.sdnc_db_pass, self.sdnc_db_name,
201 self.hosts['sdnc'], self.sdnc_db_port)
203 def execute_cmds_so_db(self, cmds):
204 self.execute_cmds_db(cmds, self.so_db_user, self.so_db_pass, self.so_db_name,
205 self.hosts['so'], self.so_db_port)
207 def execute_cmds_db(self, cmds, dbuser, dbpass, dbname, host, port):
208 cnx = mysql.connector.connect(user=dbuser, password=dbpass, database=dbname, host=host, port=port)
209 cursor = cnx.cursor()
211 self.logger.debug(cmd)
213 self.logger.debug('%s', cursor)
218 def find_file(self, file_name_keyword, file_ext, search_dir):
220 :param file_name_keyword: keyword used to look for the csar file, case insensitive matching, e.g, infra
221 :param file_ext: e.g., csar, json
222 :param search_dir path to search
223 :return: path name of the file
225 file_name_keyword = file_name_keyword.lower()
226 file_ext = file_ext.lower()
227 if not file_ext.startswith('.'):
228 file_ext = '.' + file_ext
231 for file_name in os.listdir(search_dir):
232 file_name_lower = file_name.lower()
233 if file_name_keyword in file_name_lower and file_name_lower.endswith(file_ext):
235 self.logger.error('Multiple files found for *{0}*.{1} in '
236 'directory {2}'.format(file_name_keyword, file_ext, search_dir))
238 filenamepath = os.path.abspath(os.path.join(search_dir, file_name))
243 self.logger.error("Cannot find *{0}*{1} in directory {2}".format(file_name_keyword, file_ext, search_dir))
247 def network_name_to_subnet_name(network_name):
249 :param network_name: example: vcpe_net_cpe_signal_201711281221
250 :return: vcpe_net_cpe_signal_subnet_201711281221
252 fields = network_name.split('_')
253 fields.insert(-1, 'subnet')
254 return '_'.join(fields)
256 def set_network_name(self, network_name):
257 param = ' '.join([k + ' ' + v for k, v in self.cloud.items()])
258 openstackcmd = 'openstack ' + param
259 cmd = ' '.join([openstackcmd, 'network set --name', network_name, 'ONAP-NW1'])
262 def set_subnet_name(self, network_name):
264 Example: network_name = vcpe_net_cpe_signal_201711281221
265 set subnet name to vcpe_net_cpe_signal_subnet_201711281221
268 param = ' '.join([k + ' ' + v for k, v in self.cloud.items()])
269 openstackcmd = 'openstack ' + param
271 # expected results: | subnets | subnet_id |
272 subnet_info = os.popen(openstackcmd + ' network show ' + network_name + ' |grep subnets').read().split('|')
273 if len(subnet_info) > 2 and subnet_info[1].strip() == 'subnets':
274 subnet_id = subnet_info[2].strip()
275 subnet_name = self.network_name_to_subnet_name(network_name)
276 cmd = ' '.join([openstackcmd, 'subnet set --name', subnet_name, subnet_id])
278 self.logger.info("Subnet name set to: " + subnet_name)
281 self.logger.error("Can't get subnet info from network name: " + network_name)
284 def is_node_in_aai(self, node_type, node_uuid):
286 search_node_type = None
287 if node_type == 'service':
288 search_node_type = 'service-instance'
289 key = 'service-instance-id'
290 elif node_type == 'vnf':
291 search_node_type = 'generic-vnf'
294 logging.error('Invalid node_type: ' + node_type)
297 url = 'https://{0}:{1}/aai/v11/search/nodes-query?search-node-type={2}&filter={3}:EQUALS:{4}'.format(
298 self.hosts['aai-inst1'], self.aai_query_port, search_node_type, key, node_uuid)
300 headers = {'Content-Type': 'application/json', 'Accept': 'application/json', 'X-FromAppID': 'vCPE-Robot', 'X-TransactionId': 'get_aai_subscr'}
301 requests.packages.urllib3.disable_warnings()
302 r = requests.get(url, headers=headers, auth=self.aai_userpass, verify=False)
304 self.logger.debug('aai query: ' + url)
305 self.logger.debug('aai response:\n' + json.dumps(response, indent=4, sort_keys=True))
306 return 'result-data' in response
309 def extract_ip_from_str(net_addr, net_addr_len, sz):
311 :param net_addr: e.g. 10.5.12.0
312 :param net_addr_len: e.g. 24
314 :return: the first IP address matching the network, e.g. 10.5.12.3
316 network = ipaddress.ip_network(unicode('{0}/{1}'.format(net_addr, net_addr_len)), strict=False)
317 ip_list = re.findall(r'[0-9]+(?:\.[0-9]+){3}', sz)
319 this_net = ipaddress.ip_network(unicode('{0}/{1}'.format(ip, net_addr_len)), strict=False)
320 if this_net == network:
324 def get_vm_ip(self, keywords, net_addr=None, net_addr_len=None):
326 :param keywords: list of keywords to search for vm, e.g. ['bng', 'gmux', 'brg']
327 :param net_addr: e.g. 10.12.5.0
328 :param net_addr_len: e.g. 24
329 :return: dictionary {keyword: ip}
332 net_addr = self.external_net_addr
335 net_addr_len = self.external_net_prefix_len
337 param = ' '.join([k + ' ' + v for k, v in self.cloud.items() if 'identity' not in k])
338 openstackcmd = 'nova ' + param + ' list'
339 self.logger.debug(openstackcmd)
341 results = os.popen(openstackcmd).read()
342 all_vm_ip_dict = self.extract_vm_ip_as_dict(results, net_addr, net_addr_len)
343 latest_vm_list = self.remove_old_vms(all_vm_ip_dict.keys(), self.cpe_vm_prefix)
344 latest_vm_ip_dict = {vm: all_vm_ip_dict[vm] for vm in latest_vm_list}
345 ip_dict = self.select_subset_vm_ip(latest_vm_ip_dict, keywords)
347 ip_dict.update(self.get_oom_onap_vm_ip(keywords))
349 if len(ip_dict) != len(keywords):
350 self.logger.error('Cannot find all desired IP addresses for %s.', keywords)
351 self.logger.error(json.dumps(ip_dict, indent=4, sort_keys=True))
352 self.logger.error('Temporarily continue.. remember to check back vcpecommon.py line: 316')
356 def get_oom_onap_vm_ip(self, keywords):
358 onap_vm_list = set(['so', 'sdnc', 'aai-inst1', 'robot', self.dcae_ves_collector_name])
360 if vm in onap_vm_list:
361 vm_ip[vm] = self.oom_so_sdnc_aai_ip
364 def extract_vm_ip_as_dict(self, novalist_results, net_addr, net_addr_len):
366 for line in novalist_results.split('\n'):
367 fields = line.split('|')
371 ip = self.extract_ip_from_str(net_addr, net_addr_len, ip_info)
372 vm_ip_dict[vm_name] = ip
376 def remove_old_vms(self, vm_list, prefix):
378 For vms with format name_timestamp, only keep the one with the latest timestamp.
380 zdcpe1cpe01brgemu01_201805222148 (drop this)
381 zdcpe1cpe01brgemu01_201805222229 (keep this)
382 zdcpe1cpe01gw01_201805162201
385 same_type_vm_dict = {}
387 fields = vm.split('_')
388 if vm.startswith(prefix) and len(fields) == 2 and len(fields[-1]) == len('201805222148') and fields[-1].isdigit():
389 if vm > same_type_vm_dict.get(fields[0], '0'):
390 same_type_vm_dict[fields[0]] = vm
392 new_vm_list.append(vm)
394 new_vm_list.extend(same_type_vm_dict.values())
397 def select_subset_vm_ip(self, all_vm_ip_dict, vm_name_keyword_list):
399 for keyword in vm_name_keyword_list:
400 for vm, ip in all_vm_ip_dict.items():
402 vm_ip_dict[keyword] = ip
406 def del_vgmux_ves_mode(self):
407 url = self.vpp_ves_url.format(self.hosts['mux']) + '/mode'
408 r = requests.delete(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
409 self.logger.debug('%s', r)
411 def del_vgmux_ves_collector(self):
412 url = self.vpp_ves_url.format(self.hosts['mux']) + '/config'
413 r = requests.delete(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
414 self.logger.debug('%s', r)
416 def set_vgmux_ves_collector(self ):
417 url = self.vpp_ves_url.format(self.hosts['mux'])
419 {'server-addr': self.hosts[self.dcae_ves_collector_name],
420 'server-port': '30235' if self.oom_mode else '8081',
421 'read-interval': '10',
425 r = requests.post(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass, json=data)
426 self.logger.debug('%s', r)
428 def set_vgmux_packet_loss_rate(self, lossrate, vg_vnf_instance_name):
429 url = self.vpp_ves_url.format(self.hosts['mux'])
431 {"working-mode": "demo",
432 "base-packet-loss": str(lossrate),
433 "source-name": vg_vnf_instance_name
436 r = requests.post(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass, json=data)
437 self.logger.debug('%s', r)
439 # return all the VxLAN interface names of BRG or vGMUX based on the IP address
440 def get_vxlan_interfaces(self, ip, print_info=False):
441 url = self.vpp_inf_url.format(ip)
442 self.logger.debug('url is this: %s', url)
443 r = requests.get(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
444 data = r.json()['interfaces']['interface']
447 if 'name' in inf and 'type' in inf and inf['type'] == 'v3po:vxlan-tunnel':
448 print(json.dumps(inf, indent=4, sort_keys=True))
450 return [inf['name'] for inf in data if 'name' in inf and 'type' in inf and inf['type'] == 'v3po:vxlan-tunnel']
452 # delete all VxLAN interfaces of each hosts
453 def delete_vxlan_interfaces(self, host_dic):
454 for host, ip in host_dic.items():
456 self.logger.info('{0}: Getting VxLAN interfaces'.format(host))
457 inf_list = self.get_vxlan_interfaces(ip)
461 self.logger.info("{0}: Deleting VxLAN crossconnect {1}".format(host, inf))
462 url = self.vpp_inf_url.format(ip) + '/interface/' + inf + '/v3po:l2'
463 requests.delete(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
468 self.logger.info("{0}: Deleting VxLAN interface {1}".format(host, inf))
469 url = self.vpp_inf_url.format(ip) + '/interface/' + inf
470 requests.delete(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
472 if len(self.get_vxlan_interfaces(ip)) > 0:
473 self.logger.error("Error deleting VxLAN from {0}, try to restart the VM, IP is {1}.".format(host, ip))
477 self.logger.info("{0}: no VxLAN interface found, nothing to delete".format(host))
481 def save_object(obj, filepathname):
482 with open(filepathname, 'wb') as fout:
483 pickle.dump(obj, fout)
486 def load_object(filepathname):
487 with open(filepathname, 'rb') as fin:
488 return pickle.load(fin)
491 def increase_ip_address_or_vni_in_template(vnf_template_file, vnf_parameter_name_list):
492 with open(vnf_template_file) as json_input:
493 json_data = json.load(json_input)
494 param_list = json_data['VNF-API:input']['VNF-API:vnf-topology-information']['VNF-API:vnf-parameters']
495 for param in param_list:
496 if param['vnf-parameter-name'] in vnf_parameter_name_list:
497 ipaddr_or_vni = param['vnf-parameter-value'].split('.')
498 number = int(ipaddr_or_vni[-1])
503 ipaddr_or_vni[-1] = str(number)
504 param['vnf-parameter-value'] = '.'.join(ipaddr_or_vni)
506 assert json_data is not None
507 with open(vnf_template_file, 'w') as json_output:
508 json.dump(json_data, json_output, indent=4, sort_keys=True)
510 def save_preload_data(self, preload_data):
511 self.save_object(preload_data, self.preload_dict_file)
513 def load_preload_data(self):
514 return self.load_object(self.preload_dict_file)
516 def save_vgmux_vnf_name(self, vgmux_vnf_name):
517 self.save_object(vgmux_vnf_name, self.vgmux_vnf_name_file)
519 def load_vgmux_vnf_name(self):
520 return self.load_object(self.vgmux_vnf_name_file)