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
23 '--os-auth-url': 'http://10.12.25.2:5000',
24 '--os-username': 'kxi',
25 '--os-user-domain-id': 'default',
26 '--os-project-domain-id': 'default',
27 '--os-tenant-id': '1e097c6713e74fd7ac8e4295e605ee1e',
28 '--os-region-name': 'RegionOne',
29 '--os-password': 'n3JhGMGuDzD8',
30 '--os-project-domain-name': 'Integration-SB-07',
31 '--os-identity-api-version': '3'
34 common_preload_config = {
35 'oam_onap_net': 'oam_onap_lAky',
36 'oam_onap_subnet': 'oam_onap_lAky',
37 'public_net': 'external',
38 'public_net_id': '971040b2-7059-49dc-b220-4fab50cb2ad4'
40 # End: configurations that you must change for a new ONAP installation
41 #############################################################################################
43 template_variable_symbol = '${'
44 cpe_vm_prefix = 'zdcpe'
45 #############################################################################################
46 # preloading network config
48 # value = [subnet_start_ip, subnet_gateway_ip]
49 preload_network_config = {
50 'cpe_public': ['10.2.0.2', '10.2.0.1'],
51 'cpe_signal': ['10.4.0.2', '10.4.0.1'],
52 'brg_bng': ['10.3.0.2', '10.3.0.1'],
53 'bng_mux': ['10.1.0.10', '10.1.0.1'],
54 'mux_gw': ['10.5.0.10', '10.5.0.1']
57 dcae_ves_collector_name = 'dcae-bootstrap'
58 global_subscriber_id = 'SDN-ETHERNET-INTERNET'
59 project_name = 'Project-Demonstration'
60 owning_entity_id = '520cc603-a3c4-4ec2-9ef4-ca70facd79c0'
61 owning_entity_name = 'OE-Demonstration'
63 def __init__(self, extra_host_names=None):
64 self.logger = logging.getLogger(__name__)
65 self.logger.info('Initializing configuration')
67 self.host_names = ['so', 'sdnc', 'robot', 'aai-inst1', self.dcae_ves_collector_name]
69 self.host_names.extend(extra_host_names)
71 self.hosts = self.get_vm_ip(self.host_names, self.external_net_addr, self.external_net_prefix_len)
72 # this is the keyword used to name vgw stack, must not be used in other stacks
73 self.vgw_name_keyword = 'base_vcpe_vgw'
74 self.svc_instance_uuid_file = '__var/svc_instance_uuid'
75 self.preload_dict_file = '__var/preload_dict'
76 self.vgmux_vnf_name_file = '__var/vgmux_vnf_name'
77 self.product_family_id = 'f9457e8c-4afd-45da-9389-46acd9bf5116'
78 self.custom_product_family_id = 'a9a77d5a-123e-4ca2-9eb9-0b015d2ee0fb'
79 self.instance_name_prefix = {
80 'service': 'vcpe_svc',
81 'network': 'vcpe_net',
83 'vfmodule': 'vcpe_vfmodule'
85 self.aai_userpass = 'AAI', 'AAI'
86 self.pub_key = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDKXDgoo3+WOqcUG8/5uUbk81+yczgwC4Y8ywTmuQqbNxlY1oQ0YxdMUqUnhitSXs5S/yRuAVOYHwGg2mCs20oAINrP+mxBI544AMIb9itPjCtgqtE2EWo6MmnFGbHB4Sx3XioE7F4VPsh7japsIwzOjbrQe+Mua1TGQ5d4nfEOQaaglXLLPFfuc7WbhbJbK6Q7rHqZfRcOwAMXgDoBqlyqKeiKwnumddo2RyNT8ljYmvB6buz7KnMinzo7qB0uktVT05FH9Rg0CTWH5norlG5qXgP2aukL0gk1ph8iAt7uYLf1ktp+LJI2gaF6L0/qli9EmVCSLr1uJ38Q8CBflhkh'
87 self.os_tenant_id = self.cloud['--os-tenant-id']
88 self.os_region_name = self.cloud['--os-region-name']
89 self.common_preload_config['pub_key'] = self.pub_key
90 self.sniro_url = 'http://' + self.hosts['robot'] + ':8080/__admin/mappings'
91 self.sniro_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
92 self.homing_solution = 'sniro' # value is either 'sniro' or 'oof'
93 # self.homing_solution = 'oof'
94 self.customer_location_used_by_oof = {
95 "customerLatitude": "32.897480",
96 "customerLongitude": "-97.040443",
97 "customerName": "some_company"
100 #############################################################################################
102 self.sdnc_userpass = 'admin', 'Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U'
103 self.sdnc_db_name = 'sdnctl'
104 self.sdnc_db_user = 'sdnctl'
105 self.sdnc_db_pass = 'gamma'
106 self.sdnc_db_port = '32774'
107 self.sdnc_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
108 self.sdnc_preload_network_url = 'http://' + self.hosts['sdnc'] + \
109 ':8282/restconf/operations/VNF-API:preload-network-topology-operation'
110 self.sdnc_preload_vnf_url = 'http://' + self.hosts['sdnc'] + \
111 ':8282/restconf/operations/VNF-API:preload-vnf-topology-operation'
112 self.sdnc_ar_cleanup_url = 'http://' + self.hosts['sdnc'] + ':8282/restconf/config/GENERIC-RESOURCE-API:'
114 #############################################################################################
115 # SO urls, note: do NOT add a '/' at the end of the url
116 self.so_req_api_url = {'v4': 'http://' + self.hosts['so'] + ':8080/ecomp/mso/infra/serviceInstances/v4',
117 'v5': 'http://' + self.hosts['so'] + ':8080/ecomp/mso/infra/serviceInstances/v5'}
118 self.so_check_progress_api_url = 'http://' + self.hosts['so'] + ':8080/ecomp/mso/infra/orchestrationRequests/v5'
119 self.so_userpass = 'InfraPortalClient', 'password1$'
120 self.so_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
121 self.so_db_name = 'mso_catalog'
122 self.so_db_user = 'root'
123 self.so_db_pass = 'password'
124 self.so_db_port = '32769'
126 self.vpp_inf_url = 'http://{0}:8183/restconf/config/ietf-interfaces:interfaces'
127 self.vpp_api_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
128 self.vpp_api_userpass = ('admin', 'admin')
129 self.vpp_ves_url= 'http://{0}:8183/restconf/config/vesagent:vesagent'
131 def headbridge(self, openstack_stack_name, svc_instance_uuid):
133 Add vserver information to AAI
135 self.logger.info('Adding vServer information to AAI for {0}'.format(openstack_stack_name))
136 cmd = '/opt/demo.sh heatbridge {0} {1} vCPE'.format(openstack_stack_name, svc_instance_uuid)
137 ret = commands.getstatusoutput("ssh -i onap_dev root@{0} '{1}'".format(self.hosts['robot'], cmd))
138 self.logger.debug('%s', ret)
140 def get_brg_mac_from_sdnc(self):
142 Check table DHCP_MAP in the SDNC DB. Find the newly instantiated BRG MAC address.
143 Note that there might be multiple BRGs, the most recently instantiated BRG always has the largest IP address.
145 cnx = mysql.connector.connect(user=self.sdnc_db_user, password=self.sdnc_db_pass, database=self.sdnc_db_name,
146 host=self.hosts['sdnc'], port=self.sdnc_db_port)
147 cursor = cnx.cursor()
148 query = "SELECT * from DHCP_MAP"
149 cursor.execute(query)
151 self.logger.debug('DHCP_MAP table in SDNC')
154 for mac, ip in cursor:
155 self.logger.debug(mac + ':' + ip)
156 this_host = int(ip.split('.')[-1])
166 def execute_cmds_sdnc_db(self, cmds):
167 self.execute_cmds_db(cmds, self.sdnc_db_user, self.sdnc_db_pass, self.sdnc_db_name,
168 self.hosts['sdnc'], self.sdnc_db_port)
170 def execute_cmds_so_db(self, cmds):
171 self.execute_cmds_db(cmds, self.so_db_user, self.so_db_pass, self.so_db_name,
172 self.hosts['so'], self.so_db_port)
174 def execute_cmds_db(self, cmds, dbuser, dbpass, dbname, host, port):
175 cnx = mysql.connector.connect(user=dbuser, password=dbpass, database=dbname, host=host, port=port)
176 cursor = cnx.cursor()
178 self.logger.debug(cmd)
180 self.logger.debug('%s', cursor)
185 def find_file(self, file_name_keyword, file_ext, search_dir):
187 :param file_name_keyword: keyword used to look for the csar file, case insensitive matching, e.g, infra
188 :param file_ext: e.g., csar, json
189 :param search_dir path to search
190 :return: path name of the file
192 file_name_keyword = file_name_keyword.lower()
193 file_ext = file_ext.lower()
194 if not file_ext.startswith('.'):
195 file_ext = '.' + file_ext
198 for file_name in os.listdir(search_dir):
199 file_name_lower = file_name.lower()
200 if file_name_keyword in file_name_lower and file_name_lower.endswith(file_ext):
202 self.logger.error('Multiple files found for *{0}*.{1} in '
203 'directory {2}'.format(file_name_keyword, file_ext, search_dir))
205 filenamepath = os.path.abspath(os.path.join(search_dir, file_name))
210 self.logger.error("Cannot find *{0}*{1} in directory {2}".format(file_name_keyword, file_ext, search_dir))
214 def network_name_to_subnet_name(network_name):
216 :param network_name: example: vcpe_net_cpe_signal_201711281221
217 :return: vcpe_net_cpe_signal_subnet_201711281221
219 fields = network_name.split('_')
220 fields.insert(-1, 'subnet')
221 return '_'.join(fields)
223 def set_network_name(self, network_name):
224 param = ' '.join([k + ' ' + v for k, v in self.cloud.items()])
225 openstackcmd = 'openstack ' + param
226 cmd = ' '.join([openstackcmd, 'network set --name', network_name, 'ONAP-NW1'])
229 def set_subnet_name(self, network_name):
231 Example: network_name = vcpe_net_cpe_signal_201711281221
232 set subnet name to vcpe_net_cpe_signal_subnet_201711281221
235 param = ' '.join([k + ' ' + v for k, v in self.cloud.items()])
236 openstackcmd = 'openstack ' + param
238 # expected results: | subnets | subnet_id |
239 subnet_info = os.popen(openstackcmd + ' network show ' + network_name + ' |grep subnets').read().split('|')
240 if len(subnet_info) > 2 and subnet_info[1].strip() == 'subnets':
241 subnet_id = subnet_info[2].strip()
242 subnet_name = self.network_name_to_subnet_name(network_name)
243 cmd = ' '.join([openstackcmd, 'subnet set --name', subnet_name, subnet_id])
245 self.logger.info("Subnet name set to: " + subnet_name)
248 self.logger.error("Can't get subnet info from network name: " + network_name)
251 def is_node_in_aai(self, node_type, node_uuid):
253 search_node_type = None
254 if node_type == 'service':
255 search_node_type = 'service-instance'
256 key = 'service-instance-id'
257 elif node_type == 'vnf':
258 search_node_type = 'generic-vnf'
261 logging.error('Invalid node_type: ' + node_type)
264 url = 'https://{0}:8443/aai/v11/search/nodes-query?search-node-type={1}&filter={2}:EQUALS:{3}'.format(
265 self.hosts['aai-inst1'], search_node_type, key, node_uuid)
267 headers = {'Content-Type': 'application/json', 'Accept': 'application/json', 'X-FromAppID': 'vCPE-Robot', 'X-TransactionId': 'get_aai_subscr'}
268 requests.packages.urllib3.disable_warnings()
269 r = requests.get(url, headers=headers, auth=self.aai_userpass, verify=False)
271 self.logger.debug('aai query: ' + url)
272 self.logger.debug('aai response:\n' + json.dumps(response, indent=4, sort_keys=True))
273 return 'result-data' in response
276 def extract_ip_from_str(net_addr, net_addr_len, sz):
278 :param net_addr: e.g. 10.5.12.0
279 :param net_addr_len: e.g. 24
281 :return: the first IP address matching the network, e.g. 10.5.12.3
283 network = ipaddress.ip_network(unicode('{0}/{1}'.format(net_addr, net_addr_len)), strict=False)
284 ip_list = re.findall(r'[0-9]+(?:\.[0-9]+){3}', sz)
286 this_net = ipaddress.ip_network(unicode('{0}/{1}'.format(ip, net_addr_len)), strict=False)
287 if this_net == network:
291 def get_vm_ip(self, keywords, net_addr=None, net_addr_len=None):
293 :param keywords: list of keywords to search for vm, e.g. ['bng', 'gmux', 'brg']
294 :param net_addr: e.g. 10.12.5.0
295 :param net_addr_len: e.g. 24
296 :return: dictionary {keyword: ip}
299 net_addr = self.external_net_addr
302 net_addr_len = self.external_net_prefix_len
304 param = ' '.join([k + ' ' + v for k, v in self.cloud.items() if 'identity' not in k])
305 openstackcmd = 'nova ' + param + ' list'
306 self.logger.debug(openstackcmd)
308 results = os.popen(openstackcmd).read()
309 all_vm_ip_dict = self.extract_vm_ip_as_dict(results, net_addr, net_addr_len)
310 latest_vm_list = self.remove_old_vms(all_vm_ip_dict.keys(), self.cpe_vm_prefix)
311 latest_vm_ip_dict = {vm: all_vm_ip_dict[vm] for vm in latest_vm_list}
312 ip_dict = self.select_subset_vm_ip(latest_vm_ip_dict, keywords)
314 if len(ip_dict) != len(keywords):
315 self.logger.error('Cannot find all desired IP addresses for %s.', keywords)
316 self.logger.error(json.dumps(ip_dict, indent=4, sort_keys=True))
317 self.logger.error('Temporarily continue.. remember to check back vcpecommon.py line: 316')
321 def extract_vm_ip_as_dict(self, novalist_results, net_addr, net_addr_len):
323 for line in novalist_results.split('\n'):
324 fields = line.split('|')
328 ip = self.extract_ip_from_str(net_addr, net_addr_len, ip_info)
329 vm_ip_dict[vm_name] = ip
333 def remove_old_vms(self, vm_list, prefix):
335 For vms with format name_timestamp, only keep the one with the latest timestamp.
337 zdcpe1cpe01brgemu01_201805222148 (drop this)
338 zdcpe1cpe01brgemu01_201805222229 (keep this)
339 zdcpe1cpe01gw01_201805162201
342 same_type_vm_dict = {}
344 fields = vm.split('_')
345 if vm.startswith(prefix) and len(fields) == 2 and len(fields[-1]) == len('201805222148') and fields[-1].isdigit():
346 if vm > same_type_vm_dict.get(fields[0], '0'):
347 same_type_vm_dict[fields[0]] = vm
349 new_vm_list.append(vm)
351 new_vm_list.extend(same_type_vm_dict.values())
354 def select_subset_vm_ip(self, all_vm_ip_dict, vm_name_keyword_list):
356 for keyword in vm_name_keyword_list:
357 for vm, ip in all_vm_ip_dict.items():
359 vm_ip_dict[keyword] = ip
363 def del_vgmux_ves_mode(self):
364 url = self.vpp_ves_url.format(self.hosts['mux']) + '/mode'
365 r = requests.delete(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
366 self.logger.debug('%s', r)
368 def del_vgmux_ves_collector(self):
369 url = self.vpp_ves_url.format(self.hosts['mux']) + '/config'
370 r = requests.delete(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
371 self.logger.debug('%s', r)
373 def set_vgmux_ves_collector(self ):
374 url = self.vpp_ves_url.format(self.hosts['mux'])
376 {'server-addr': self.hosts[self.dcae_ves_collector_name],
377 'server-port': '8081',
378 'read-interval': '10',
382 r = requests.post(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass, json=data)
383 self.logger.debug('%s', r)
385 def set_vgmux_packet_loss_rate(self, lossrate, vg_vnf_instance_name):
386 url = self.vpp_ves_url.format(self.hosts['mux'])
388 {"working-mode": "demo",
389 "base-packet-loss": str(lossrate),
390 "source-name": vg_vnf_instance_name
393 r = requests.post(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass, json=data)
394 self.logger.debug('%s', r)
396 # return all the VxLAN interface names of BRG or vGMUX based on the IP address
397 def get_vxlan_interfaces(self, ip, print_info=False):
398 url = self.vpp_inf_url.format(ip)
399 self.logger.debug('url is this: %s', url)
400 r = requests.get(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
401 data = r.json()['interfaces']['interface']
404 if 'name' in inf and 'type' in inf and inf['type'] == 'v3po:vxlan-tunnel':
405 print(json.dumps(inf, indent=4, sort_keys=True))
407 return [inf['name'] for inf in data if 'name' in inf and 'type' in inf and inf['type'] == 'v3po:vxlan-tunnel']
409 # delete all VxLAN interfaces of each hosts
410 def delete_vxlan_interfaces(self, host_dic):
411 for host, ip in host_dic.items():
413 self.logger.info('{0}: Getting VxLAN interfaces'.format(host))
414 inf_list = self.get_vxlan_interfaces(ip)
418 self.logger.info("{0}: Deleting VxLAN crossconnect {1}".format(host, inf))
419 url = self.vpp_inf_url.format(ip) + '/interface/' + inf + '/v3po:l2'
420 requests.delete(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
425 self.logger.info("{0}: Deleting VxLAN interface {1}".format(host, inf))
426 url = self.vpp_inf_url.format(ip) + '/interface/' + inf
427 requests.delete(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
429 if len(self.get_vxlan_interfaces(ip)) > 0:
430 self.logger.error("Error deleting VxLAN from {0}, try to restart the VM, IP is {1}.".format(host, ip))
434 self.logger.info("{0}: no VxLAN interface found, nothing to delete".format(host))
438 def save_object(obj, filepathname):
439 with open(filepathname, 'wb') as fout:
440 pickle.dump(obj, fout)
443 def load_object(filepathname):
444 with open(filepathname, 'rb') as fin:
445 return pickle.load(fin)
448 def increase_ip_address_or_vni_in_template(vnf_template_file, vnf_parameter_name_list):
449 with open(vnf_template_file) as json_input:
450 json_data = json.load(json_input)
451 param_list = json_data['VNF-API:input']['VNF-API:vnf-topology-information']['VNF-API:vnf-parameters']
452 for param in param_list:
453 if param['vnf-parameter-name'] in vnf_parameter_name_list:
454 ipaddr_or_vni = param['vnf-parameter-value'].split('.')
455 number = int(ipaddr_or_vni[-1])
460 ipaddr_or_vni[-1] = str(number)
461 param['vnf-parameter-value'] = '.'.join(ipaddr_or_vni)
463 assert json_data is not None
464 with open(vnf_template_file, 'w') as json_output:
465 json.dump(json_data, json_output, indent=4, sort_keys=True)
467 def save_preload_data(self, preload_data):
468 self.save_object(preload_data, self.preload_dict_file)
470 def load_preload_data(self):
471 return self.load_object(self.preload_dict_file)
473 def save_vgmux_vnf_name(self, vgmux_vnf_name):
474 self.save_object(vgmux_vnf_name, self.vgmux_vnf_name_file)
476 def load_vgmux_vnf_name(self):
477 return self.load_object(self.vgmux_vnf_name_file)