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': 'b8ad3842ab3642f7bf3fbe4e4d3b9f86' if oom_mode else '1e097c6713e74fd7ac8e4295e605ee1e',
30 '--os-region-name': 'RegionOne',
31 '--os-password': 'n3JhGMGuDzD8',
32 '--os-project-domain-name': 'Integration-SB-05' if oom_mode else 'Integration-SB-07',
33 '--os-identity-api-version': '3'
36 common_preload_config = {
37 'oam_onap_net': 'oam_network_AiBB' if oom_mode else 'oam_onap_lAky',
38 'oam_onap_subnet': 'oam_network_AiBB' if oom_mode else 'oam_onap_lAky',
39 'public_net': 'external',
40 'public_net_id': '971040b2-7059-49dc-b220-4fab50cb2ad4'
42 sdnc_controller_pod = 'dev-sdnc-sdnc-0'
44 #############################################################################################
46 template_variable_symbol = '${'
47 cpe_vm_prefix = 'zdcpe'
48 #############################################################################################
49 # preloading network config
51 # value = [subnet_start_ip, subnet_gateway_ip]
52 preload_network_config = {
53 'cpe_public': ['10.2.0.2', '10.2.0.1'],
54 'cpe_signal': ['10.4.0.2', '10.4.0.1'],
55 'brg_bng': ['10.3.0.2', '10.3.0.1'],
56 'bng_mux': ['10.1.0.10', '10.1.0.1'],
57 'mux_gw': ['10.5.0.10', '10.5.0.1']
60 dcae_ves_collector_name = 'dcae-bootstrap'
61 global_subscriber_id = 'SDN-ETHERNET-INTERNET'
62 project_name = 'Project-Demonstration'
63 owning_entity_id = '520cc603-a3c4-4ec2-9ef4-ca70facd79c0'
64 owning_entity_name = 'OE-Demonstration'
66 def __init__(self, extra_host_names=None):
67 self.logger = logging.getLogger(__name__)
68 self.logger.info('Initializing configuration')
70 # vgw_VfModuleModelInvariantUuid is in rescust service csar, look in service-VcpesvcRescust1118-template.yml for groups vgw module metadata. TODO: read this value automcatically
71 self.vgw_VfModuleModelInvariantUuid = 'c16ad38c-2c2c-49ae-bbe8-66c7ffbcc30a'
72 # 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
73 self.sdnc_oam_ip = '10.0.0.20'
74 # OOM: this is a k8 host external IP
75 self.oom_so_sdnc_aai_ip = '10.12.5.228'
76 # OOM: this is a k8 host external IP can be same as oom_so_sdnc_aai_ip
77 self.oom_dcae_ves_collector = '10.12.5.228'
78 # OOM: this is a k8 host external IP can be same as oom_so_sdnc_aai_ip
79 self.mr_ip_addr = '10.12.5.228'
80 self.mr_ip_port = '30227'
81 self.so_nbi_port = '30277' if self.oom_mode else '8080'
82 self.sdnc_preloading_port = '30202' if self.oom_mode else '8282'
83 self.aai_query_port = '30233' if self.oom_mode else '8443'
84 self.sniro_port = '30288' if self.oom_mode else '8080'
86 self.host_names = ['so', 'sdnc', 'robot', 'aai-inst1', self.dcae_ves_collector_name]
88 self.host_names.extend(extra_host_names)
90 self.hosts = self.get_vm_ip(self.host_names, self.external_net_addr, self.external_net_prefix_len)
91 # this is the keyword used to name vgw stack, must not be used in other stacks
92 self.vgw_name_keyword = 'base_vcpe_vgw'
93 # this is the file that will keep the index of last assigned SO name
94 self.vgw_vfmod_name_index_file= '__var/vgw_vfmod_name_index'
95 self.svc_instance_uuid_file = '__var/svc_instance_uuid'
96 self.preload_dict_file = '__var/preload_dict'
97 self.vgmux_vnf_name_file = '__var/vgmux_vnf_name'
98 self.product_family_id = 'f9457e8c-4afd-45da-9389-46acd9bf5116'
99 self.custom_product_family_id = 'a9a77d5a-123e-4ca2-9eb9-0b015d2ee0fb'
100 self.instance_name_prefix = {
101 'service': 'vcpe_svc',
102 'network': 'vcpe_net',
104 'vfmodule': 'vcpe_vfmodule'
106 self.aai_userpass = 'AAI', 'AAI'
107 self.pub_key = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDKXDgoo3+WOqcUG8/5uUbk81+yczgwC4Y8ywTmuQqbNxlY1oQ0YxdMUqUnhitSXs5S/yRuAVOYHwGg2mCs20oAINrP+mxBI544AMIb9itPjCtgqtE2EWo6MmnFGbHB4Sx3XioE7F4VPsh7japsIwzOjbrQe+Mua1TGQ5d4nfEOQaaglXLLPFfuc7WbhbJbK6Q7rHqZfRcOwAMXgDoBqlyqKeiKwnumddo2RyNT8ljYmvB6buz7KnMinzo7qB0uktVT05FH9Rg0CTWH5norlG5qXgP2aukL0gk1ph8iAt7uYLf1ktp+LJI2gaF6L0/qli9EmVCSLr1uJ38Q8CBflhkh'
108 self.os_tenant_id = self.cloud['--os-tenant-id']
109 self.os_region_name = self.cloud['--os-region-name']
110 self.common_preload_config['pub_key'] = self.pub_key
111 self.sniro_url = 'http://' + self.hosts['robot'] + ':' + self.sniro_port + '/__admin/mappings'
112 self.sniro_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
113 self.homing_solution = 'sniro' # value is either 'sniro' or 'oof'
114 # self.homing_solution = 'oof'
115 self.customer_location_used_by_oof = {
116 "customerLatitude": "32.897480",
117 "customerLongitude": "-97.040443",
118 "customerName": "some_company"
121 #############################################################################################
123 self.sdnc_userpass = 'admin', 'Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U'
124 self.sdnc_db_name = 'sdnctl'
125 self.sdnc_db_user = 'sdnctl'
126 self.sdnc_db_pass = 'gamma'
127 self.sdnc_db_port = '32774'
128 self.sdnc_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
129 self.sdnc_preload_network_url = 'http://' + self.hosts['sdnc'] + \
130 ':' + self.sdnc_preloading_port + '/restconf/operations/VNF-API:preload-network-topology-operation'
131 self.sdnc_preload_vnf_url = 'http://' + self.hosts['sdnc'] + \
132 ':' + self.sdnc_preloading_port + '/restconf/operations/VNF-API:preload-vnf-topology-operation'
133 self.sdnc_preload_gra_url = 'http://' + self.hosts['sdnc'] + \
134 ':' + self.sdnc_preloading_port + '/restconf/operations/GENERIC-RESOURCE-API:preload-vf-module-topology-operation'
135 self.sdnc_ar_cleanup_url = 'http://' + self.hosts['sdnc'] + ':' + self.sdnc_preloading_port + \
136 '/restconf/config/GENERIC-RESOURCE-API:'
138 #############################################################################################
139 # SO urls, note: do NOT add a '/' at the end of the url
140 self.so_req_api_url = {'v4': 'http://' + self.hosts['so'] + ':' + self.so_nbi_port + '/onap/so/infra/serviceInstantiation/v7/serviceInstances',
141 'v5': 'http://' + self.hosts['so'] + ':' + self.so_nbi_port + '/onap/so/infra/serviceInstantiation/v7/serviceInstances'}
142 self.so_check_progress_api_url = 'http://' + self.hosts['so'] + ':' + self.so_nbi_port + '/onap/so/infra/orchestrationRequests/v6'
143 self.so_userpass = 'InfraPortalClient', 'password1$'
144 self.so_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
145 self.so_db_name = 'catalogdb'
146 self.so_db_user = 'root'
147 self.so_db_pass = 'password'
148 self.so_db_port = '30252' if self.oom_mode else '32769'
150 self.vpp_inf_url = 'http://{0}:8183/restconf/config/ietf-interfaces:interfaces'
151 self.vpp_api_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
152 self.vpp_api_userpass = ('admin', 'admin')
153 self.vpp_ves_url= 'http://{0}:8183/restconf/config/vesagent:vesagent'
155 def headbridge(self, openstack_stack_name, svc_instance_uuid):
157 Add vserver information to AAI
159 self.logger.info('Adding vServer information to AAI for {0}'.format(openstack_stack_name))
160 if not self.oom_mode:
161 cmd = '/opt/demo.sh heatbridge {0} {1} vCPE'.format(openstack_stack_name, svc_instance_uuid)
162 ret = commands.getstatusoutput("ssh -i onap_dev root@{0} '{1}'".format(self.hosts['robot'], cmd))
163 self.logger.debug('%s', ret)
165 print('To add vGMUX vserver info to AAI, do the following:')
166 print('- ssh to rancher')
168 print('- cd /root/oom/kubernetes/robot')
169 print('- ./demo-k8s.sh onap heatbridge {0} {1} vCPE'.format(openstack_stack_name, svc_instance_uuid))
171 def get_brg_mac_from_sdnc(self):
173 Check table DHCP_MAP in the SDNC DB. Find the newly instantiated BRG MAC address.
174 Note that there might be multiple BRGs, the most recently instantiated BRG always has the largest IP address.
176 cnx = mysql.connector.connect(user=self.sdnc_db_user, password=self.sdnc_db_pass, database=self.sdnc_db_name,
177 host=self.hosts['sdnc'], port=self.sdnc_db_port)
178 cursor = cnx.cursor()
179 query = "SELECT * from DHCP_MAP"
180 cursor.execute(query)
182 self.logger.debug('DHCP_MAP table in SDNC')
185 for mac, ip in cursor:
186 self.logger.debug(mac + ':' + ip)
187 this_host = int(ip.split('.')[-1])
197 def execute_cmds_sdnc_db(self, cmds):
198 self.execute_cmds_db(cmds, self.sdnc_db_user, self.sdnc_db_pass, self.sdnc_db_name,
199 self.hosts['sdnc'], self.sdnc_db_port)
201 def execute_cmds_so_db(self, cmds):
202 self.execute_cmds_db(cmds, self.so_db_user, self.so_db_pass, self.so_db_name,
203 self.hosts['so'], self.so_db_port)
205 def execute_cmds_db(self, cmds, dbuser, dbpass, dbname, host, port):
206 cnx = mysql.connector.connect(user=dbuser, password=dbpass, database=dbname, host=host, port=port)
207 cursor = cnx.cursor()
209 self.logger.debug(cmd)
211 self.logger.debug('%s', cursor)
216 def find_file(self, file_name_keyword, file_ext, search_dir):
218 :param file_name_keyword: keyword used to look for the csar file, case insensitive matching, e.g, infra
219 :param file_ext: e.g., csar, json
220 :param search_dir path to search
221 :return: path name of the file
223 file_name_keyword = file_name_keyword.lower()
224 file_ext = file_ext.lower()
225 if not file_ext.startswith('.'):
226 file_ext = '.' + file_ext
229 for file_name in os.listdir(search_dir):
230 file_name_lower = file_name.lower()
231 if file_name_keyword in file_name_lower and file_name_lower.endswith(file_ext):
233 self.logger.error('Multiple files found for *{0}*.{1} in '
234 'directory {2}'.format(file_name_keyword, file_ext, search_dir))
236 filenamepath = os.path.abspath(os.path.join(search_dir, file_name))
241 self.logger.error("Cannot find *{0}*{1} in directory {2}".format(file_name_keyword, file_ext, search_dir))
245 def network_name_to_subnet_name(network_name):
247 :param network_name: example: vcpe_net_cpe_signal_201711281221
248 :return: vcpe_net_cpe_signal_subnet_201711281221
250 fields = network_name.split('_')
251 fields.insert(-1, 'subnet')
252 return '_'.join(fields)
254 def set_network_name(self, network_name):
255 param = ' '.join([k + ' ' + v for k, v in self.cloud.items()])
256 openstackcmd = 'openstack ' + param
257 cmd = ' '.join([openstackcmd, 'network set --name', network_name, 'ONAP-NW1'])
260 def set_subnet_name(self, network_name):
262 Example: network_name = vcpe_net_cpe_signal_201711281221
263 set subnet name to vcpe_net_cpe_signal_subnet_201711281221
266 param = ' '.join([k + ' ' + v for k, v in self.cloud.items()])
267 openstackcmd = 'openstack ' + param
269 # expected results: | subnets | subnet_id |
270 subnet_info = os.popen(openstackcmd + ' network show ' + network_name + ' |grep subnets').read().split('|')
271 if len(subnet_info) > 2 and subnet_info[1].strip() == 'subnets':
272 subnet_id = subnet_info[2].strip()
273 subnet_name = self.network_name_to_subnet_name(network_name)
274 cmd = ' '.join([openstackcmd, 'subnet set --name', subnet_name, subnet_id])
276 self.logger.info("Subnet name set to: " + subnet_name)
279 self.logger.error("Can't get subnet info from network name: " + network_name)
282 def is_node_in_aai(self, node_type, node_uuid):
284 search_node_type = None
285 if node_type == 'service':
286 search_node_type = 'service-instance'
287 key = 'service-instance-id'
288 elif node_type == 'vnf':
289 search_node_type = 'generic-vnf'
292 logging.error('Invalid node_type: ' + node_type)
295 url = 'https://{0}:{1}/aai/v11/search/nodes-query?search-node-type={2}&filter={3}:EQUALS:{4}'.format(
296 self.hosts['aai-inst1'], self.aai_query_port, search_node_type, key, node_uuid)
298 headers = {'Content-Type': 'application/json', 'Accept': 'application/json', 'X-FromAppID': 'vCPE-Robot', 'X-TransactionId': 'get_aai_subscr'}
299 requests.packages.urllib3.disable_warnings()
300 r = requests.get(url, headers=headers, auth=self.aai_userpass, verify=False)
302 self.logger.debug('aai query: ' + url)
303 self.logger.debug('aai response:\n' + json.dumps(response, indent=4, sort_keys=True))
304 return 'result-data' in response
307 def extract_ip_from_str(net_addr, net_addr_len, sz):
309 :param net_addr: e.g. 10.5.12.0
310 :param net_addr_len: e.g. 24
312 :return: the first IP address matching the network, e.g. 10.5.12.3
314 network = ipaddress.ip_network(unicode('{0}/{1}'.format(net_addr, net_addr_len)), strict=False)
315 ip_list = re.findall(r'[0-9]+(?:\.[0-9]+){3}', sz)
317 this_net = ipaddress.ip_network(unicode('{0}/{1}'.format(ip, net_addr_len)), strict=False)
318 if this_net == network:
322 def get_pod_node_oam_ip(self, pod):
324 :Assuming kubectl is available
325 :param pod: pod name as a string, e.g. 'dev-sdnc-sdnc-0'
326 :return pod's node oam ip (10.0.0.0/16)
328 cmd = "kubectl -n onap describe pod {0} |grep Node:|cut -d'/' -f2".format(pod)
329 ret = commands.getstatusoutput(cmd)
330 self.logger.debug("cmd = %s, ret = %s", cmd, ret)
333 def get_vm_ip(self, keywords, net_addr=None, net_addr_len=None):
335 :param keywords: list of keywords to search for vm, e.g. ['bng', 'gmux', 'brg']
336 :param net_addr: e.g. 10.12.5.0
337 :param net_addr_len: e.g. 24
338 :return: dictionary {keyword: ip}
341 net_addr = self.external_net_addr
344 net_addr_len = self.external_net_prefix_len
346 param = ' '.join([k + ' ' + v for k, v in self.cloud.items() if 'identity' not in k])
347 openstackcmd = 'nova ' + param + ' list'
348 self.logger.debug(openstackcmd)
350 results = os.popen(openstackcmd).read()
351 all_vm_ip_dict = self.extract_vm_ip_as_dict(results, net_addr, net_addr_len)
352 latest_vm_list = self.remove_old_vms(all_vm_ip_dict.keys(), self.cpe_vm_prefix)
353 latest_vm_ip_dict = {vm: all_vm_ip_dict[vm] for vm in latest_vm_list}
354 ip_dict = self.select_subset_vm_ip(latest_vm_ip_dict, keywords)
356 ip_dict.update(self.get_oom_onap_vm_ip(keywords))
358 if len(ip_dict) != len(keywords):
359 self.logger.error('Cannot find all desired IP addresses for %s.', keywords)
360 self.logger.error(json.dumps(ip_dict, indent=4, sort_keys=True))
361 self.logger.error('Temporarily continue.. remember to check back vcpecommon.py line: 316')
365 def get_oom_onap_vm_ip(self, keywords):
367 onap_vm_list = set(['so', 'sdnc', 'aai-inst1', 'robot', self.dcae_ves_collector_name])
369 if vm in onap_vm_list:
370 vm_ip[vm] = self.oom_so_sdnc_aai_ip
373 def extract_vm_ip_as_dict(self, novalist_results, net_addr, net_addr_len):
375 for line in novalist_results.split('\n'):
376 fields = line.split('|')
380 ip = self.extract_ip_from_str(net_addr, net_addr_len, ip_info)
381 vm_ip_dict[vm_name] = ip
385 def remove_old_vms(self, vm_list, prefix):
387 For vms with format name_timestamp, only keep the one with the latest timestamp.
389 zdcpe1cpe01brgemu01_201805222148 (drop this)
390 zdcpe1cpe01brgemu01_201805222229 (keep this)
391 zdcpe1cpe01gw01_201805162201
394 same_type_vm_dict = {}
396 fields = vm.split('_')
397 if vm.startswith(prefix) and len(fields) == 2 and len(fields[-1]) == len('201805222148') and fields[-1].isdigit():
398 if vm > same_type_vm_dict.get(fields[0], '0'):
399 same_type_vm_dict[fields[0]] = vm
401 new_vm_list.append(vm)
403 new_vm_list.extend(same_type_vm_dict.values())
406 def select_subset_vm_ip(self, all_vm_ip_dict, vm_name_keyword_list):
408 for keyword in vm_name_keyword_list:
409 for vm, ip in all_vm_ip_dict.items():
411 vm_ip_dict[keyword] = ip
415 def del_vgmux_ves_mode(self):
416 url = self.vpp_ves_url.format(self.hosts['mux']) + '/mode'
417 r = requests.delete(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
418 self.logger.debug('%s', r)
420 def del_vgmux_ves_collector(self):
421 url = self.vpp_ves_url.format(self.hosts['mux']) + '/config'
422 r = requests.delete(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
423 self.logger.debug('%s', r)
425 def set_vgmux_ves_collector(self ):
426 url = self.vpp_ves_url.format(self.hosts['mux'])
428 {'server-addr': self.hosts[self.dcae_ves_collector_name],
429 'server-port': '30235' if self.oom_mode else '8081',
430 'read-interval': '10',
434 r = requests.post(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass, json=data)
435 self.logger.debug('%s', r)
437 def set_vgmux_packet_loss_rate(self, lossrate, vg_vnf_instance_name):
438 url = self.vpp_ves_url.format(self.hosts['mux'])
440 {"working-mode": "demo",
441 "base-packet-loss": str(lossrate),
442 "source-name": vg_vnf_instance_name
445 r = requests.post(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass, json=data)
446 self.logger.debug('%s', r)
448 # return all the VxLAN interface names of BRG or vGMUX based on the IP address
449 def get_vxlan_interfaces(self, ip, print_info=False):
450 url = self.vpp_inf_url.format(ip)
451 self.logger.debug('url is this: %s', url)
452 r = requests.get(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
453 data = r.json()['interfaces']['interface']
456 if 'name' in inf and 'type' in inf and inf['type'] == 'v3po:vxlan-tunnel':
457 print(json.dumps(inf, indent=4, sort_keys=True))
459 return [inf['name'] for inf in data if 'name' in inf and 'type' in inf and inf['type'] == 'v3po:vxlan-tunnel']
461 # delete all VxLAN interfaces of each hosts
462 def delete_vxlan_interfaces(self, host_dic):
463 for host, ip in host_dic.items():
465 self.logger.info('{0}: Getting VxLAN interfaces'.format(host))
466 inf_list = self.get_vxlan_interfaces(ip)
470 self.logger.info("{0}: Deleting VxLAN crossconnect {1}".format(host, inf))
471 url = self.vpp_inf_url.format(ip) + '/interface/' + inf + '/v3po:l2'
472 requests.delete(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
477 self.logger.info("{0}: Deleting VxLAN interface {1}".format(host, inf))
478 url = self.vpp_inf_url.format(ip) + '/interface/' + inf
479 requests.delete(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
481 if len(self.get_vxlan_interfaces(ip)) > 0:
482 self.logger.error("Error deleting VxLAN from {0}, try to restart the VM, IP is {1}.".format(host, ip))
486 self.logger.info("{0}: no VxLAN interface found, nothing to delete".format(host))
490 def save_object(obj, filepathname):
491 with open(filepathname, 'wb') as fout:
492 pickle.dump(obj, fout)
495 def load_object(filepathname):
496 with open(filepathname, 'rb') as fin:
497 return pickle.load(fin)
500 def increase_ip_address_or_vni_in_template(vnf_template_file, vnf_parameter_name_list):
501 with open(vnf_template_file) as json_input:
502 json_data = json.load(json_input)
503 param_list = json_data['VNF-API:input']['VNF-API:vnf-topology-information']['VNF-API:vnf-parameters']
504 for param in param_list:
505 if param['vnf-parameter-name'] in vnf_parameter_name_list:
506 ipaddr_or_vni = param['vnf-parameter-value'].split('.')
507 number = int(ipaddr_or_vni[-1])
512 ipaddr_or_vni[-1] = str(number)
513 param['vnf-parameter-value'] = '.'.join(ipaddr_or_vni)
515 assert json_data is not None
516 with open(vnf_template_file, 'w') as json_output:
517 json.dump(json_data, json_output, indent=4, sort_keys=True)
519 def save_preload_data(self, preload_data):
520 self.save_object(preload_data, self.preload_dict_file)
522 def load_preload_data(self):
523 return self.load_object(self.preload_dict_file)
525 def save_vgmux_vnf_name(self, vgmux_vnf_name):
526 self.save_object(vgmux_vnf_name, self.vgmux_vnf_name_file)
528 def load_vgmux_vnf_name(self):
529 return self.load_object(self.vgmux_vnf_name_file)