Merge "Correction in pom."
[integration.git] / test / vcpe / vcpecommon.py
1 import json
2 import logging
3 import os
4 import pickle
5 import re
6 import sys
7
8 import ipaddress
9 import mysql.connector
10 import requests
11 import commands
12 import time
13
14
15 class VcpeCommon:
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
22     cloud = {
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'
32     }
33
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'
39     }
40     #     End: configurations that you must change for a new ONAP installation
41     #############################################################################################
42
43     template_variable_symbol = '${'
44     #############################################################################################
45     # preloading network config
46     #  key=network role
47     #  value = [subnet_start_ip, subnet_gateway_ip]
48     preload_network_config = {
49         'cpe_public': ['10.2.0.2', '10.2.0.1'],
50         'cpe_signal': ['10.4.0.2', '10.4.0.1'],
51         'brg_bng': ['10.3.0.2', '10.3.0.1'],
52         'bng_mux': ['10.1.0.10', '10.1.0.1'],
53         'mux_gw': ['10.5.0.10', '10.5.0.1']
54     }
55
56     dcae_ves_collector_name = 'dcae-bootstrap'
57     global_subscriber_id = 'SDN-ETHERNET-INTERNET'
58     project_name = 'Project-Demonstration'
59     owning_entity_id = '520cc603-a3c4-4ec2-9ef4-ca70facd79c0'
60     owning_entity_name = 'OE-Demonstration'
61
62     def __init__(self, extra_host_names=None):
63         self.logger = logging.getLogger(__name__)
64         self.logger.info('Initializing configuration')
65
66         self.host_names = ['so', 'sdnc', 'robot', 'aai-inst1', self.dcae_ves_collector_name]
67         if extra_host_names:
68             self.host_names.extend(extra_host_names)
69         # get IP addresses
70         self.hosts = self.get_vm_ip(self.host_names, self.external_net_addr, self.external_net_prefix_len)
71         # this is the keyword used to name vgw stack, must not be used in other stacks
72         self.vgw_name_keyword = 'base_vcpe_vgw'
73         self.svc_instance_uuid_file = '__var/svc_instance_uuid'
74         self.preload_dict_file = '__var/preload_dict'
75         self.vgmux_vnf_name_file = '__var/vgmux_vnf_name'
76         self.product_family_id = 'f9457e8c-4afd-45da-9389-46acd9bf5116'
77         self.custom_product_family_id = 'a9a77d5a-123e-4ca2-9eb9-0b015d2ee0fb'
78         self.instance_name_prefix = {
79             'service': 'vcpe_svc',
80             'network': 'vcpe_net',
81             'vnf': 'vcpe_vnf',
82             'vfmodule': 'vcpe_vfmodule'
83         }
84         self.aai_userpass = 'AAI', 'AAI'
85         self.pub_key = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDKXDgoo3+WOqcUG8/5uUbk81+yczgwC4Y8ywTmuQqbNxlY1oQ0YxdMUqUnhitSXs5S/yRuAVOYHwGg2mCs20oAINrP+mxBI544AMIb9itPjCtgqtE2EWo6MmnFGbHB4Sx3XioE7F4VPsh7japsIwzOjbrQe+Mua1TGQ5d4nfEOQaaglXLLPFfuc7WbhbJbK6Q7rHqZfRcOwAMXgDoBqlyqKeiKwnumddo2RyNT8ljYmvB6buz7KnMinzo7qB0uktVT05FH9Rg0CTWH5norlG5qXgP2aukL0gk1ph8iAt7uYLf1ktp+LJI2gaF6L0/qli9EmVCSLr1uJ38Q8CBflhkh'
86         self.os_tenant_id = self.cloud['--os-tenant-id']
87         self.os_region_name = self.cloud['--os-region-name']
88         self.common_preload_config['pub_key'] = self.pub_key
89         self.sniro_url = 'http://' + self.hosts['robot'] + ':8080/__admin/mappings'
90         self.sniro_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
91         self.homing_solution = 'sniro'  # value is either 'sniro' or 'oof'
92 #        self.homing_solution = 'oof'
93         self.customer_location_used_by_oof = {
94             "customerLatitude": "32.897480",
95             "customerLongitude": "-97.040443",
96             "customerName": "some_company"
97         }
98
99         #############################################################################################
100         # SDNC urls
101         self.sdnc_userpass = 'admin', 'Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U'
102         self.sdnc_db_name = 'sdnctl'
103         self.sdnc_db_user = 'sdnctl'
104         self.sdnc_db_pass = 'gamma'
105         self.sdnc_db_port = '32774'
106         self.sdnc_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
107         self.sdnc_preload_network_url = 'http://' + self.hosts['sdnc'] + \
108                                         ':8282/restconf/operations/VNF-API:preload-network-topology-operation'
109         self.sdnc_preload_vnf_url = 'http://' + self.hosts['sdnc'] + \
110                                     ':8282/restconf/operations/VNF-API:preload-vnf-topology-operation'
111         self.sdnc_ar_cleanup_url = 'http://' + self.hosts['sdnc'] + ':8282/restconf/config/GENERIC-RESOURCE-API:'
112
113         #############################################################################################
114         # SO urls, note: do NOT add a '/' at the end of the url
115         self.so_req_api_url = {'v4': 'http://' + self.hosts['so'] + ':8080/ecomp/mso/infra/serviceInstances/v4',
116                            'v5': 'http://' + self.hosts['so'] + ':8080/ecomp/mso/infra/serviceInstances/v5'}
117         self.so_check_progress_api_url = 'http://' + self.hosts['so'] + ':8080/ecomp/mso/infra/orchestrationRequests/v5'
118         self.so_userpass = 'InfraPortalClient', 'password1$'
119         self.so_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
120         self.so_db_name = 'mso_catalog'
121         self.so_db_user = 'root'
122         self.so_db_pass = 'password'
123         self.so_db_port = '32768'
124
125         self.vpp_inf_url = 'http://{0}:8183/restconf/config/ietf-interfaces:interfaces'
126         self.vpp_api_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
127         self.vpp_api_userpass = ('admin', 'admin')
128         self.vpp_ves_url= 'http://{0}:8183/restconf/config/vesagent:vesagent'
129
130     def headbridge(self, openstack_stack_name, svc_instance_uuid):
131         """
132         Add vserver information to AAI
133         """
134         self.logger.info('Adding vServer information to AAI for {0}'.format(openstack_stack_name))
135         cmd = '/opt/demo.sh heatbridge {0} {1} vCPE'.format(openstack_stack_name, svc_instance_uuid)
136         ret = commands.getstatusoutput("ssh -i onap_dev root@{0} '{1}'".format(self.hosts['robot'], cmd))
137         self.logger.debug('%s', ret)
138
139     def get_brg_mac_from_sdnc(self):
140         """
141         :return:  BRG MAC address. Currently we only support one BRG instance.
142         """
143         cnx = mysql.connector.connect(user=self.sdnc_db_user, password=self.sdnc_db_pass, database=self.sdnc_db_name,
144                                       host=self.hosts['sdnc'], port=self.sdnc_db_port)
145         cursor = cnx.cursor()
146         query = "SELECT * from DHCP_MAP"
147         cursor.execute(query)
148
149         self.logger.debug('DHCP_MAP table in SDNC')
150         counter = 0
151         mac = None
152         for mac, ip in cursor:
153             counter += 1
154             self.logger.debug(mac + ':' + ip)
155
156         cnx.close()
157
158         if counter != 1:
159             self.logger.error('Found %s MAC addresses in DHCP_MAP', counter)
160             sys.exit()
161         else:
162             self.logger.debug('Found MAC addresses in DHCP_MAP: %s', mac)
163             return mac
164
165     def insert_into_sdnc_db(self, cmds):
166         cnx = mysql.connector.connect(user=self.sdnc_db_user, password=self.sdnc_db_pass, database=self.sdnc_db_name,
167                                       host=self.hosts['sdnc'], port=self.sdnc_db_port)
168         cursor = cnx.cursor()
169         for cmd in cmds:
170             self.logger.debug(cmd)
171             cursor.execute(cmd)
172             self.logger.debug('%s', cursor)
173         cnx.commit()
174         cursor.close()
175         cnx.close()
176
177     def insert_into_so_db(self, cmds):
178         cnx = mysql.connector.connect(user=self.so_db_user, password=self.so_db_pass, database=self.so_db_name,
179                                       host=self.hosts['so'], port=self.so_db_port)
180         cursor = cnx.cursor()
181         for cmd in cmds:
182             self.logger.debug(cmd)
183             cursor.execute(cmd)
184             self.logger.debug('%s', cursor)
185         cnx.commit()
186         cursor.close()
187         cnx.close()
188
189     def find_file(self, file_name_keyword, file_ext, search_dir):
190         """
191         :param file_name_keyword:  keyword used to look for the csar file, case insensitive matching, e.g, infra
192         :param file_ext: e.g., csar, json
193         :param search_dir path to search
194         :return: path name of the file
195         """
196         file_name_keyword = file_name_keyword.lower()
197         file_ext = file_ext.lower()
198         if not file_ext.startswith('.'):
199             file_ext = '.' + file_ext
200
201         filenamepath = None
202         for file_name in os.listdir(search_dir):
203             file_name_lower = file_name.lower()
204             if file_name_keyword in file_name_lower and file_name_lower.endswith(file_ext):
205                 if filenamepath:
206                     self.logger.error('Multiple files found for *{0}*.{1} in '
207                                       'directory {2}'.format(file_name_keyword, file_ext, search_dir))
208                     sys.exit()
209                 filenamepath = os.path.abspath(os.path.join(search_dir, file_name))
210
211         if filenamepath:
212             return filenamepath
213         else:
214             self.logger.error("Cannot find *{0}*{1} in directory {2}".format(file_name_keyword, file_ext, search_dir))
215             sys.exit()
216
217     @staticmethod
218     def network_name_to_subnet_name(network_name):
219         """
220         :param network_name: example: vcpe_net_cpe_signal_201711281221
221         :return: vcpe_net_cpe_signal_subnet_201711281221
222         """
223         fields = network_name.split('_')
224         fields.insert(-1, 'subnet')
225         return '_'.join(fields)
226
227     def set_network_name(self, network_name):
228         param = ' '.join([k + ' ' + v for k, v in self.cloud.items()])
229         openstackcmd = 'openstack ' + param
230         cmd = ' '.join([openstackcmd, 'network set --name', network_name, 'ONAP-NW1'])
231         os.popen(cmd)
232
233     def set_subnet_name(self, network_name):
234         """
235         Example: network_name =  vcpe_net_cpe_signal_201711281221
236         set subnet name to vcpe_net_cpe_signal_subnet_201711281221
237         :return:
238         """
239         param = ' '.join([k + ' ' + v for k, v in self.cloud.items()])
240         openstackcmd = 'openstack ' + param
241
242         # expected results: | subnets | subnet_id |
243         subnet_info = os.popen(openstackcmd + ' network show ' + network_name + ' |grep subnets').read().split('|')
244         if len(subnet_info) > 2 and subnet_info[1].strip() == 'subnets':
245             subnet_id = subnet_info[2].strip()
246             subnet_name = self.network_name_to_subnet_name(network_name)
247             cmd = ' '.join([openstackcmd, 'subnet set --name', subnet_name, subnet_id])
248             os.popen(cmd)
249             self.logger.info("Subnet name set to: " + subnet_name)
250             return True
251         else:
252             self.logger.error("Can't get subnet info from network name: " + network_name)
253             return False
254
255     def is_node_in_aai(self, node_type, node_uuid):
256         key = None
257         search_node_type = None
258         if node_type == 'service':
259             search_node_type = 'service-instance'
260             key = 'service-instance-id'
261         elif node_type == 'vnf':
262             search_node_type = 'generic-vnf'
263             key = 'vnf-id'
264         else:
265             logging.error('Invalid node_type: ' + node_type)
266             sys.exit()
267
268         url = 'https://{0}:8443/aai/v11/search/nodes-query?search-node-type={1}&filter={2}:EQUALS:{3}'.format(
269             self.hosts['aai-inst1'], search_node_type, key, node_uuid)
270
271         headers = {'Content-Type': 'application/json', 'Accept': 'application/json', 'X-FromAppID': 'vCPE-Robot', 'X-TransactionId': 'get_aai_subscr'}
272         requests.packages.urllib3.disable_warnings()
273         r = requests.get(url, headers=headers, auth=self.aai_userpass, verify=False)
274         response = r.json()
275         self.logger.debug('aai query: ' + url)
276         self.logger.debug('aai response:\n' + json.dumps(response, indent=4, sort_keys=True))
277         return 'result-data' in response
278
279     @staticmethod
280     def extract_ip_from_str(net_addr, net_addr_len, sz):
281         """
282         :param net_addr:  e.g. 10.5.12.0
283         :param net_addr_len: e.g. 24
284         :param sz: a string
285         :return: the first IP address matching the network, e.g. 10.5.12.3
286         """
287         network = ipaddress.ip_network(unicode('{0}/{1}'.format(net_addr, net_addr_len)), strict=False)
288         ip_list = re.findall(r'[0-9]+(?:\.[0-9]+){3}', sz)
289         for ip in ip_list:
290             this_net = ipaddress.ip_network(unicode('{0}/{1}'.format(ip, net_addr_len)), strict=False)
291             if this_net == network:
292                 return str(ip)
293         return None
294
295     def get_vm_ip(self, keywords, net_addr=None, net_addr_len=None):
296         """
297         :param keywords: list of keywords to search for vm, e.g. ['bng', 'gmux', 'brg']
298         :param net_addr: e.g. 10.12.5.0
299         :param net_addr_len: e.g. 24
300         :return: dictionary {keyword: ip}
301         """
302         if not net_addr:
303             net_addr = self.external_net_addr
304
305         if not net_addr_len:
306             net_addr_len = self.external_net_prefix_len
307
308         param = ' '.join([k + ' ' + v for k, v in self.cloud.items() if 'identity' not in k])
309         openstackcmd = 'nova ' + param + ' list'
310         self.logger.debug(openstackcmd)
311
312         ip_dict = {}
313         results = os.popen(openstackcmd).read()
314         for line in results.split('\n'):
315             fields = line.split('|')
316             if len(fields) == 8:
317                 vm_name = fields[2]
318                 ip_info = fields[-2]
319                 for keyword in keywords:
320                     if keyword in vm_name:
321                         ip = self.extract_ip_from_str(net_addr, net_addr_len, ip_info)
322                         if ip:
323                             ip_dict[keyword] = ip
324         if len(ip_dict) != len(keywords):
325             self.logger.error('Cannot find all desired IP addresses for %s.', keywords)
326             self.logger.error(json.dumps(ip_dict, indent=4, sort_keys=True))
327             self.logger.error('Temporarily continue.. remember to check back vcpecommon.py line: 316')
328 #            sys.exit()
329         return ip_dict
330
331     def del_vgmux_ves_mode(self):
332         url = self.vpp_ves_url.format(self.hosts['mux']) + '/mode'
333         r = requests.delete(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
334         self.logger.debug('%s', r)
335
336     def del_vgmux_ves_collector(self):
337         url = self.vpp_ves_url.format(self.hosts['mux']) + '/config'
338         r = requests.delete(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
339         self.logger.debug('%s', r)
340
341     def set_vgmux_ves_collector(self ):
342         url = self.vpp_ves_url.format(self.hosts['mux'])
343         data = {'config':
344                     {'server-addr': self.hosts[self.dcae_ves_collector_name],
345                      'server-port': '8081',
346                      'read-interval': '10',
347                      'is-add':'1'
348                      }
349                 }
350         r = requests.post(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass, json=data)
351         self.logger.debug('%s', r)
352
353     def set_vgmux_packet_loss_rate(self, lossrate, vg_vnf_instance_name):
354         url = self.vpp_ves_url.format(self.hosts['mux'])
355         data = {"mode":
356                     {"working-mode": "demo",
357                      "base-packet-loss": str(lossrate),
358                      "source-name": vg_vnf_instance_name
359                      }
360                 }
361         r = requests.post(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass, json=data)
362         self.logger.debug('%s', r)
363
364         # return all the VxLAN interface names of BRG or vGMUX based on the IP address
365     def get_vxlan_interfaces(self, ip, print_info=False):
366         url = self.vpp_inf_url.format(ip)
367         self.logger.debug('url is this: %s', url)
368         r = requests.get(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
369         data = r.json()['interfaces']['interface']
370         if print_info:
371             for inf in data:
372                 if 'name' in inf and 'type' in inf and inf['type'] == 'v3po:vxlan-tunnel':
373                     print(json.dumps(inf, indent=4, sort_keys=True))
374
375         return [inf['name'] for inf in data if 'name' in inf and 'type' in inf and inf['type'] == 'v3po:vxlan-tunnel']
376
377     # delete all VxLAN interfaces of each hosts
378     def delete_vxlan_interfaces(self, host_dic):
379         for host, ip in host_dic.items():
380             deleted = False
381             self.logger.info('{0}: Getting VxLAN interfaces'.format(host))
382             inf_list = self.get_vxlan_interfaces(ip)
383             for inf in inf_list:
384                 deleted = True
385                 time.sleep(2)
386                 self.logger.info("{0}: Deleting VxLAN crossconnect {1}".format(host, inf))
387                 url = self.vpp_inf_url.format(ip) + '/interface/' + inf + '/v3po:l2'
388                 requests.delete(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
389
390             for inf in inf_list:
391                 deleted = True
392                 time.sleep(2)
393                 self.logger.info("{0}: Deleting VxLAN interface {1}".format(host, inf))
394                 url = self.vpp_inf_url.format(ip) + '/interface/' + inf
395                 requests.delete(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
396
397             if len(self.get_vxlan_interfaces(ip)) > 0:
398                 self.logger.error("Error deleting VxLAN from {0}, try to restart the VM, IP is {1}.".format(host, ip))
399                 return False
400
401             if not deleted:
402                 self.logger.info("{0}: no VxLAN interface found, nothing to delete".format(host))
403         return True
404
405     @staticmethod
406     def save_object(obj, filepathname):
407         with open(filepathname, 'wb') as fout:
408             pickle.dump(obj, fout)
409
410     @staticmethod
411     def load_object(filepathname):
412         with open(filepathname, 'rb') as fin:
413             return pickle.load(fin)
414
415     def save_preload_data(self, preload_data):
416         self.save_object(preload_data, self.preload_dict_file)
417
418     def load_preload_data(self):
419         return self.load_object(self.preload_dict_file)
420
421     def save_vgmux_vnf_name(self, vgmux_vnf_name):
422         self.save_object(vgmux_vnf_name, self.vgmux_vnf_name_file)
423
424     def load_vgmux_vnf_name(self):
425         return self.load_object(self.vgmux_vnf_name_file)
426