blob: 58f91a777b234de49c99438c0769a339c9616828 [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
Yang Xu65b84a42018-12-31 17:48:02 +000015from novaclient import client as openstackclient
Yang Xu64339a82018-12-30 05:32:21 +000016from kubernetes import client, config
Yang Xu65b84a42018-12-31 17:48:02 +000017from netaddr import IPAddress, IPNetwork
Kang Xi11d278c2018-04-06 16:56:04 -040018
Michal Ptacek0b06f782019-09-12 12:27:47 +000019######################################################################
20# Parts which must be updated / cross-checked during each deployment #
21# are marked as CHANGEME #
22######################################################################
23
Kang Xi11d278c2018-04-06 16:56:04 -040024class VcpeCommon:
25 #############################################################################################
Michal Ptacek0b06f782019-09-12 12:27:47 +000026 # Set network prefix of k8s host external address; it's used for pod public IP autodetection
27 # but can be overriden from user in case of autodetection failure
Kang Xi11d278c2018-04-06 16:56:04 -040028 external_net_addr = '10.12.0.0'
29 external_net_prefix_len = 16
Bartek Grzybowski73b02ed2019-09-12 14:04:59 +020030
Kang Xi11d278c2018-04-06 16:56:04 -040031 #############################################################################################
32 # set the openstack cloud access credentials here
Bartek Grzybowskib969daf2019-10-18 13:54:21 +020033 oom_mode = False
Kang Xi0e0a1d62018-07-23 16:53:54 -040034
Michal Ptacek0b06f782019-09-12 12:27:47 +000035 ###########################
36 # set Openstack credentials
37 # CHANGEME part
Kang Xi11d278c2018-04-06 16:56:04 -040038 cloud = {
39 '--os-auth-url': 'http://10.12.25.2:5000',
Kang Xi268b74d2018-05-23 15:53:42 -040040 '--os-username': 'kxi',
Kang Xi11d278c2018-04-06 16:56:04 -040041 '--os-user-domain-id': 'default',
42 '--os-project-domain-id': 'default',
Yang Xu27e16242018-12-28 01:00:50 -050043 '--os-tenant-id': 'bc43d50ffcb84750bac0c1707a9a765b' if oom_mode else '1e097c6713e74fd7ac8e4295e605ee1e',
Kang Xi11d278c2018-04-06 16:56:04 -040044 '--os-region-name': 'RegionOne',
Kang Xi268b74d2018-05-23 15:53:42 -040045 '--os-password': 'n3JhGMGuDzD8',
Yang Xu27e16242018-12-28 01:00:50 -050046 '--os-project-domain-name': 'Integration-SB-03' if oom_mode else 'Integration-SB-07',
Kang Xi11d278c2018-04-06 16:56:04 -040047 '--os-identity-api-version': '3'
48 }
49
Michal Ptacek0b06f782019-09-12 12:27:47 +000050 ############################################################################
51 # set oam and public network which must exist in openstack before deployment
52 # CHANGEME part
Kang Xi11d278c2018-04-06 16:56:04 -040053 common_preload_config = {
Yang Xu27e16242018-12-28 01:00:50 -050054 'oam_onap_net': 'oam_network_2No2' if oom_mode else 'oam_onap_lAky',
55 'oam_onap_subnet': 'oam_network_2No2' if oom_mode else 'oam_onap_lAky',
Kang Xi11d278c2018-04-06 16:56:04 -040056 'public_net': 'external',
57 'public_net_id': '971040b2-7059-49dc-b220-4fab50cb2ad4'
58 }
Yang Xu75b0f5e2018-11-14 14:04:20 -050059
Michal Ptacek0b06f782019-09-12 12:27:47 +000060 #############################################################################
Bartek Grzybowskidbb3ba12019-09-25 11:21:42 +020061 # Set name of Onap's k8s namespace and sdnc controller pod
Michal Ptacek0b06f782019-09-12 12:27:47 +000062 # CHANGEME part
Bartek Grzybowski1ee85df2019-10-10 14:57:13 +020063 onap_namespace = 'onap'
64 onap_environment = 'dev'
65 sdnc_controller_pod = '-'.join([onap_environment, 'sdnc-sdnc-0'])
Kang Xi11d278c2018-04-06 16:56:04 -040066
67 template_variable_symbol = '${'
Kang Xi6c762392018-05-30 09:27:34 -040068 cpe_vm_prefix = 'zdcpe'
Michal Ptacek0b06f782019-09-12 12:27:47 +000069
Kang Xi11d278c2018-04-06 16:56:04 -040070 #############################################################################################
71 # preloading network config
72 # key=network role
73 # value = [subnet_start_ip, subnet_gateway_ip]
74 preload_network_config = {
75 'cpe_public': ['10.2.0.2', '10.2.0.1'],
76 'cpe_signal': ['10.4.0.2', '10.4.0.1'],
77 'brg_bng': ['10.3.0.2', '10.3.0.1'],
78 'bng_mux': ['10.1.0.10', '10.1.0.1'],
79 'mux_gw': ['10.5.0.10', '10.5.0.1']
80 }
81
Kang Xi268b74d2018-05-23 15:53:42 -040082 dcae_ves_collector_name = 'dcae-bootstrap'
Kang Xi11d278c2018-04-06 16:56:04 -040083 global_subscriber_id = 'SDN-ETHERNET-INTERNET'
Kang Xi268b74d2018-05-23 15:53:42 -040084 project_name = 'Project-Demonstration'
85 owning_entity_id = '520cc603-a3c4-4ec2-9ef4-ca70facd79c0'
Yang Xu86c0e4a2018-12-02 13:10:41 -050086 owning_entity_name = 'OE-Demonstration1'
Kang Xi11d278c2018-04-06 16:56:04 -040087
88 def __init__(self, extra_host_names=None):
89 self.logger = logging.getLogger(__name__)
Yang Xu65b84a42018-12-31 17:48:02 +000090 self.logger.setLevel(logging.DEBUG)
Kang Xi11d278c2018-04-06 16:56:04 -040091 self.logger.info('Initializing configuration')
92
Michal Ptacek0b06f782019-09-12 12:27:47 +000093 ##################################################################################################################################
94 # following param must be updated e.g. from csar file (grep for VfModuleModelInvariantUuid string) before vcpe.py customer call !!
95 # vgw_VfModuleModelInvariantUuid is in rescust service csar,
96 # look in service-VcpesvcRescust1118-template.yml for groups vgw module metadata. TODO: read this value automatically
97 # CHANGEME part
Yang Xu27e16242018-12-28 01:00:50 -050098 self.vgw_VfModuleModelInvariantUuid = '26d6a718-17b2-4ba8-8691-c44343b2ecd2'
Michal Ptacek0b06f782019-09-12 12:27:47 +000099
100 # 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 +0200101 self.sdnc_oam_ip = self.get_pod_node_oam_ip(self.sdnc_controller_pod)
102 # OOM: this is a k8s host external IP, e.g. oom-k8s-01 IP
103 self.oom_so_sdnc_aai_ip = self.get_pod_node_public_ip(self.sdnc_controller_pod)
Michal Ptacek0b06f782019-09-12 12:27:47 +0000104 # OOM: this is a k8s host external IP, e.g. oom-k8s-01 IP
Yang Xu65b84a42018-12-31 17:48:02 +0000105 self.oom_dcae_ves_collector = self.oom_so_sdnc_aai_ip
Michal Ptacek0b06f782019-09-12 12:27:47 +0000106 # OOM: this is a k8s host external IP, e.g. oom-k8s-01 IP
Yang Xu65b84a42018-12-31 17:48:02 +0000107 self.mr_ip_addr = self.oom_so_sdnc_aai_ip
Brian Freeman8076a872018-11-13 11:34:48 -0500108 self.mr_ip_port = '30227'
Brian Freemana605bc72018-11-12 10:50:30 -0500109 self.so_nbi_port = '30277' if self.oom_mode else '8080'
Brian Freemane8aa3f02019-09-20 08:29:22 -0500110 self.sdnc_preloading_port = '30267' if self.oom_mode else '8282'
Kang Xi0e0a1d62018-07-23 16:53:54 -0400111 self.aai_query_port = '30233' if self.oom_mode else '8443'
112 self.sniro_port = '30288' if self.oom_mode else '8080'
113
Yang Xuc52ed6e2019-04-29 00:20:52 -0400114 self.host_names = ['sdc', 'so', 'sdnc', 'robot', 'aai-inst1', self.dcae_ves_collector_name]
Kang Xi11d278c2018-04-06 16:56:04 -0400115 if extra_host_names:
116 self.host_names.extend(extra_host_names)
117 # get IP addresses
118 self.hosts = self.get_vm_ip(self.host_names, self.external_net_addr, self.external_net_prefix_len)
119 # this is the keyword used to name vgw stack, must not be used in other stacks
120 self.vgw_name_keyword = 'base_vcpe_vgw'
Brian Freeman81f6e9e2018-11-11 22:36:20 -0500121 # this is the file that will keep the index of last assigned SO name
122 self.vgw_vfmod_name_index_file= '__var/vgw_vfmod_name_index'
Kang Xi11d278c2018-04-06 16:56:04 -0400123 self.svc_instance_uuid_file = '__var/svc_instance_uuid'
124 self.preload_dict_file = '__var/preload_dict'
125 self.vgmux_vnf_name_file = '__var/vgmux_vnf_name'
126 self.product_family_id = 'f9457e8c-4afd-45da-9389-46acd9bf5116'
127 self.custom_product_family_id = 'a9a77d5a-123e-4ca2-9eb9-0b015d2ee0fb'
128 self.instance_name_prefix = {
129 'service': 'vcpe_svc',
130 'network': 'vcpe_net',
131 'vnf': 'vcpe_vnf',
132 'vfmodule': 'vcpe_vfmodule'
133 }
134 self.aai_userpass = 'AAI', 'AAI'
Michal Ptacek0b06f782019-09-12 12:27:47 +0000135
136 ############################################################################################################
137 # following key is overriding public key from vCPE heat templates, it's important to use correct one in here
138 # CHANGEME part
Kang Xi11d278c2018-04-06 16:56:04 -0400139 self.pub_key = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDKXDgoo3+WOqcUG8/5uUbk81+yczgwC4Y8ywTmuQqbNxlY1oQ0YxdMUqUnhitSXs5S/yRuAVOYHwGg2mCs20oAINrP+mxBI544AMIb9itPjCtgqtE2EWo6MmnFGbHB4Sx3XioE7F4VPsh7japsIwzOjbrQe+Mua1TGQ5d4nfEOQaaglXLLPFfuc7WbhbJbK6Q7rHqZfRcOwAMXgDoBqlyqKeiKwnumddo2RyNT8ljYmvB6buz7KnMinzo7qB0uktVT05FH9Rg0CTWH5norlG5qXgP2aukL0gk1ph8iAt7uYLf1ktp+LJI2gaF6L0/qli9EmVCSLr1uJ38Q8CBflhkh'
Michal Ptacek0b06f782019-09-12 12:27:47 +0000140
Kang Xi11d278c2018-04-06 16:56:04 -0400141 self.os_tenant_id = self.cloud['--os-tenant-id']
142 self.os_region_name = self.cloud['--os-region-name']
143 self.common_preload_config['pub_key'] = self.pub_key
Kang Xi0e0a1d62018-07-23 16:53:54 -0400144 self.sniro_url = 'http://' + self.hosts['robot'] + ':' + self.sniro_port + '/__admin/mappings'
Kang Xi11d278c2018-04-06 16:56:04 -0400145 self.sniro_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
Kang Xi268b74d2018-05-23 15:53:42 -0400146 self.homing_solution = 'sniro' # value is either 'sniro' or 'oof'
147# self.homing_solution = 'oof'
148 self.customer_location_used_by_oof = {
149 "customerLatitude": "32.897480",
150 "customerLongitude": "-97.040443",
151 "customerName": "some_company"
152 }
Kang Xi11d278c2018-04-06 16:56:04 -0400153
154 #############################################################################################
Yang Xuc52ed6e2019-04-29 00:20:52 -0400155 # SDC urls
Brian Freeman0c724152019-09-18 09:30:05 -0500156 self.sdc_be_port = '30204'
Yang Xu0e319ef2019-04-30 14:28:07 -0400157 self.sdc_be_request_userpass = 'vid', 'Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U'
158 self.sdc_be_request_headers = {'X-ECOMP-InstanceID': 'VID'}
Brian Freeman0c724152019-09-18 09:30:05 -0500159 self.sdc_be_url_prefix = 'https://' + self.hosts['sdc'] + ':' + self.sdc_be_port
Yang Xu0e319ef2019-04-30 14:28:07 -0400160 self.sdc_service_list_url = self.sdc_be_url_prefix + '/sdc/v1/catalog/services'
161
Brian Freeman0c724152019-09-18 09:30:05 -0500162 self.sdc_fe_port = '30207'
Yang Xu0e319ef2019-04-30 14:28:07 -0400163 self.sdc_fe_request_userpass = 'beep', 'boop'
164 self.sdc_fe_request_headers = {'USER_ID': 'demo', 'Content-Type': 'application/json'}
Brian Freeman0c724152019-09-18 09:30:05 -0500165 self.sdc_fe_url_prefix = 'https://' + self.hosts['sdc'] + ':' + self.sdc_fe_port
Yang Xu0e319ef2019-04-30 14:28:07 -0400166 self.sdc_get_category_list_url = self.sdc_fe_url_prefix + '/sdc1/feProxy/rest/v1/categories'
167 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 -0400168
169 #############################################################################################
Kang Xi11d278c2018-04-06 16:56:04 -0400170 # SDNC urls
171 self.sdnc_userpass = 'admin', 'Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U'
172 self.sdnc_db_name = 'sdnctl'
173 self.sdnc_db_user = 'sdnctl'
174 self.sdnc_db_pass = 'gamma'
Kang Xi268b74d2018-05-23 15:53:42 -0400175 self.sdnc_db_port = '32774'
Kang Xi11d278c2018-04-06 16:56:04 -0400176 self.sdnc_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
Brian Freemane8aa3f02019-09-20 08:29:22 -0500177 self.sdnc_preload_network_url = 'https://' + self.hosts['sdnc'] + \
Kang Xi0e0a1d62018-07-23 16:53:54 -0400178 ':' + self.sdnc_preloading_port + '/restconf/operations/VNF-API:preload-network-topology-operation'
Brian Freemane8aa3f02019-09-20 08:29:22 -0500179 self.sdnc_preload_vnf_url = 'https://' + self.hosts['sdnc'] + \
Kang Xi0e0a1d62018-07-23 16:53:54 -0400180 ':' + self.sdnc_preloading_port + '/restconf/operations/VNF-API:preload-vnf-topology-operation'
Brian Freemane8aa3f02019-09-20 08:29:22 -0500181 self.sdnc_preload_gra_url = 'https://' + self.hosts['sdnc'] + \
Brian Freeman81f6e9e2018-11-11 22:36:20 -0500182 ':' + self.sdnc_preloading_port + '/restconf/operations/GENERIC-RESOURCE-API:preload-vf-module-topology-operation'
Brian Freemane8aa3f02019-09-20 08:29:22 -0500183 self.sdnc_ar_cleanup_url = 'https://' + self.hosts['sdnc'] + ':' + self.sdnc_preloading_port + \
Kang Xi0e0a1d62018-07-23 16:53:54 -0400184 '/restconf/config/GENERIC-RESOURCE-API:'
Kang Xi11d278c2018-04-06 16:56:04 -0400185
186 #############################################################################################
187 # SO urls, note: do NOT add a '/' at the end of the url
Brian Freemana605bc72018-11-12 10:50:30 -0500188 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 -0500189 'v5': 'http://' + self.hosts['so'] + ':' + self.so_nbi_port + '/onap/so/infra/serviceInstantiation/v7/serviceInstances'}
Brian Freemana605bc72018-11-12 10:50:30 -0500190 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 -0400191 self.so_userpass = 'InfraPortalClient', 'password1$'
192 self.so_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
Brian Freemana605bc72018-11-12 10:50:30 -0500193 self.so_db_name = 'catalogdb'
Kang Xi11d278c2018-04-06 16:56:04 -0400194 self.so_db_user = 'root'
Yang Xu21b09c92019-06-13 13:19:20 -0400195 self.so_db_pass = 'secretpassword'
Kang Xi0e0a1d62018-07-23 16:53:54 -0400196 self.so_db_port = '30252' if self.oom_mode else '32769'
Kang Xi11d278c2018-04-06 16:56:04 -0400197
198 self.vpp_inf_url = 'http://{0}:8183/restconf/config/ietf-interfaces:interfaces'
199 self.vpp_api_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
200 self.vpp_api_userpass = ('admin', 'admin')
201 self.vpp_ves_url= 'http://{0}:8183/restconf/config/vesagent:vesagent'
202
Bartek Grzybowskidbb3ba12019-09-25 11:21:42 +0200203 #############################################################################################
204 # POLICY urls
205 self.policy_userpass = ('healthcheck', 'zb!XztG34')
206 self.policy_headers = {'Accept': 'application/json', 'Content-Type': 'application/json'}
207 self.policy_api_url = 'https://{0}:6969/policy/api/v1/policytypes/onap.policies.controlloop.Operational/versions/1.0.0/policies'
208 self.policy_pap_get_url = 'https://{0}:6969/policy/pap/v1/pdps'
209 self.policy_pap_json = {'policies': [{'policy-id': 'operational.vcpe'}]}
210 self.policy_pap_post_url = self.policy_pap_get_url + '/policies'
211 self.policy_api_service_name = 'policy-api'
212 self.policy_pap_service_name = 'policy-pap'
213
Bartek Grzybowski9018a842019-10-16 15:28:23 +0200214 #############################################################################################
215 # MARIADB-GALERA settings
216 self.mariadb_galera_endpoint_ip = self.get_k8s_service_endpoint_info('mariadb-galera','ip')
217 self.mariadb_galera_endpoint_port = self.get_k8s_service_endpoint_info('mariadb-galera','port')
218
Bartek Grzybowskibfeeb242019-09-13 08:35:48 +0200219 def heatbridge(self, openstack_stack_name, svc_instance_uuid):
Kang Xi11d278c2018-04-06 16:56:04 -0400220 """
221 Add vserver information to AAI
222 """
223 self.logger.info('Adding vServer information to AAI for {0}'.format(openstack_stack_name))
Kang Xi0e0a1d62018-07-23 16:53:54 -0400224 if not self.oom_mode:
225 cmd = '/opt/demo.sh heatbridge {0} {1} vCPE'.format(openstack_stack_name, svc_instance_uuid)
226 ret = commands.getstatusoutput("ssh -i onap_dev root@{0} '{1}'".format(self.hosts['robot'], cmd))
227 self.logger.debug('%s', ret)
228 else:
229 print('To add vGMUX vserver info to AAI, do the following:')
230 print('- ssh to rancher')
231 print('- sudo su -')
232 print('- cd /root/oom/kubernetes/robot')
233 print('- ./demo-k8s.sh onap heatbridge {0} {1} vCPE'.format(openstack_stack_name, svc_instance_uuid))
Kang Xi11d278c2018-04-06 16:56:04 -0400234
235 def get_brg_mac_from_sdnc(self):
236 """
Kang Xi6c762392018-05-30 09:27:34 -0400237 Check table DHCP_MAP in the SDNC DB. Find the newly instantiated BRG MAC address.
238 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 -0400239 """
240 cnx = mysql.connector.connect(user=self.sdnc_db_user, password=self.sdnc_db_pass, database=self.sdnc_db_name,
241 host=self.hosts['sdnc'], port=self.sdnc_db_port)
242 cursor = cnx.cursor()
243 query = "SELECT * from DHCP_MAP"
244 cursor.execute(query)
245
246 self.logger.debug('DHCP_MAP table in SDNC')
Kang Xi6c762392018-05-30 09:27:34 -0400247 mac_recent = None
248 host = -1
Kang Xi11d278c2018-04-06 16:56:04 -0400249 for mac, ip in cursor:
Kang Xi11d278c2018-04-06 16:56:04 -0400250 self.logger.debug(mac + ':' + ip)
Kang Xi6c762392018-05-30 09:27:34 -0400251 this_host = int(ip.split('.')[-1])
252 if host < this_host:
253 host = this_host
254 mac_recent = mac
Kang Xi11d278c2018-04-06 16:56:04 -0400255
256 cnx.close()
257
Kang Xi6c762392018-05-30 09:27:34 -0400258 assert mac_recent
259 return mac_recent
Kang Xi11d278c2018-04-06 16:56:04 -0400260
Bartek Grzybowski9018a842019-10-16 15:28:23 +0200261 def execute_cmds_mariadb(self, cmds):
262 self.execute_cmds_db(cmds, self.sdnc_db_user, self.sdnc_db_pass,
263 self.sdnc_db_name, self.mariadb_galera_endpoint_ip,
264 self.mariadb_galera_endpoint_port)
265
Kang Xi6c762392018-05-30 09:27:34 -0400266 def execute_cmds_sdnc_db(self, cmds):
267 self.execute_cmds_db(cmds, self.sdnc_db_user, self.sdnc_db_pass, self.sdnc_db_name,
268 self.hosts['sdnc'], self.sdnc_db_port)
Kang Xi11d278c2018-04-06 16:56:04 -0400269
Kang Xi6c762392018-05-30 09:27:34 -0400270 def execute_cmds_so_db(self, cmds):
271 self.execute_cmds_db(cmds, self.so_db_user, self.so_db_pass, self.so_db_name,
272 self.hosts['so'], self.so_db_port)
273
274 def execute_cmds_db(self, cmds, dbuser, dbpass, dbname, host, port):
275 cnx = mysql.connector.connect(user=dbuser, password=dbpass, database=dbname, host=host, port=port)
Kang Xi11d278c2018-04-06 16:56:04 -0400276 cursor = cnx.cursor()
277 for cmd in cmds:
278 self.logger.debug(cmd)
279 cursor.execute(cmd)
280 self.logger.debug('%s', cursor)
281 cnx.commit()
282 cursor.close()
283 cnx.close()
284
285 def find_file(self, file_name_keyword, file_ext, search_dir):
286 """
287 :param file_name_keyword: keyword used to look for the csar file, case insensitive matching, e.g, infra
288 :param file_ext: e.g., csar, json
289 :param search_dir path to search
290 :return: path name of the file
291 """
292 file_name_keyword = file_name_keyword.lower()
293 file_ext = file_ext.lower()
294 if not file_ext.startswith('.'):
295 file_ext = '.' + file_ext
296
297 filenamepath = None
298 for file_name in os.listdir(search_dir):
299 file_name_lower = file_name.lower()
300 if file_name_keyword in file_name_lower and file_name_lower.endswith(file_ext):
301 if filenamepath:
302 self.logger.error('Multiple files found for *{0}*.{1} in '
303 'directory {2}'.format(file_name_keyword, file_ext, search_dir))
Bartek Grzybowski68d93c22019-10-30 10:55:55 +0100304 sys.exit(1)
Kang Xi11d278c2018-04-06 16:56:04 -0400305 filenamepath = os.path.abspath(os.path.join(search_dir, file_name))
306
307 if filenamepath:
308 return filenamepath
309 else:
310 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 +0100311 sys.exit(1)
Kang Xi11d278c2018-04-06 16:56:04 -0400312
313 @staticmethod
314 def network_name_to_subnet_name(network_name):
315 """
316 :param network_name: example: vcpe_net_cpe_signal_201711281221
317 :return: vcpe_net_cpe_signal_subnet_201711281221
318 """
319 fields = network_name.split('_')
320 fields.insert(-1, 'subnet')
321 return '_'.join(fields)
322
323 def set_network_name(self, network_name):
324 param = ' '.join([k + ' ' + v for k, v in self.cloud.items()])
325 openstackcmd = 'openstack ' + param
326 cmd = ' '.join([openstackcmd, 'network set --name', network_name, 'ONAP-NW1'])
327 os.popen(cmd)
328
329 def set_subnet_name(self, network_name):
330 """
331 Example: network_name = vcpe_net_cpe_signal_201711281221
332 set subnet name to vcpe_net_cpe_signal_subnet_201711281221
333 :return:
334 """
335 param = ' '.join([k + ' ' + v for k, v in self.cloud.items()])
336 openstackcmd = 'openstack ' + param
337
338 # expected results: | subnets | subnet_id |
339 subnet_info = os.popen(openstackcmd + ' network show ' + network_name + ' |grep subnets').read().split('|')
340 if len(subnet_info) > 2 and subnet_info[1].strip() == 'subnets':
341 subnet_id = subnet_info[2].strip()
342 subnet_name = self.network_name_to_subnet_name(network_name)
343 cmd = ' '.join([openstackcmd, 'subnet set --name', subnet_name, subnet_id])
344 os.popen(cmd)
345 self.logger.info("Subnet name set to: " + subnet_name)
346 return True
347 else:
348 self.logger.error("Can't get subnet info from network name: " + network_name)
349 return False
350
Bartek Grzybowskidbb3ba12019-09-25 11:21:42 +0200351 def set_closed_loop_policy(self, policy_template_file):
352 # Gather policy services cluster ips
353 p_api_cluster_ip = self.get_k8s_service_cluster_ip(self.policy_api_service_name)
354 p_pap_cluster_ip = self.get_k8s_service_cluster_ip(self.policy_pap_service_name)
355
356 # Read policy json from file
357 with open(policy_template_file) as f:
358 try:
359 policy_json = json.load(f)
360 except ValueError:
361 self.logger.error(policy_template_file + " doesn't seem to contain valid JSON data")
Bartek Grzybowski68d93c22019-10-30 10:55:55 +0100362 sys.exit(1)
Bartek Grzybowskidbb3ba12019-09-25 11:21:42 +0200363
364 # Check policy already applied
365 requests.packages.urllib3.disable_warnings()
366 policy_exists_req = requests.get(self.policy_pap_get_url.format(
367 p_pap_cluster_ip), auth=self.policy_userpass,
368 verify=False, headers=self.policy_headers)
369 if policy_exists_req.status_code != 200:
370 self.logger.error('Failure in checking CL policy existence. '
371 'Policy-pap responded with HTTP code {0}'.format(
372 policy_exists_req.status_code))
Bartek Grzybowski68d93c22019-10-30 10:55:55 +0100373 sys.exit(1)
Bartek Grzybowskidbb3ba12019-09-25 11:21:42 +0200374
375 try:
376 policy_exists_json = policy_exists_req.json()
377 except ValueError as e:
378 self.logger.error('Policy-pap request failed: ' + e.message)
Bartek Grzybowski68d93c22019-10-30 10:55:55 +0100379 sys.exit(1)
Bartek Grzybowskidbb3ba12019-09-25 11:21:42 +0200380
381 try:
382 assert policy_exists_json['groups'][0]['pdpSubgroups'] \
383 [1]['policies'][0]['name'] != 'operational.vcpe'
384 except AssertionError:
385 self.logger.info('vCPE closed loop policy already exists, not applying')
386 return
387 except IndexError:
388 pass # policy doesn't exist
389
390 # Create policy
391 policy_create_req = requests.post(self.policy_api_url.format(
392 p_api_cluster_ip), auth=self.policy_userpass,
393 json=policy_json, verify=False,
394 headers=self.policy_headers)
395 # Get the policy id from policy-api response
396 if policy_create_req.status_code != 200:
397 self.logger.error('Failed creating policy. Policy-api responded'
398 ' with HTTP code {0}'.format(policy_create_req.status_code))
Bartek Grzybowski68d93c22019-10-30 10:55:55 +0100399 sys.exit(1)
Bartek Grzybowskidbb3ba12019-09-25 11:21:42 +0200400
401 try:
402 policy_version = json.loads(policy_create_req.text)['policy-version']
403 except (KeyError, ValueError):
404 self.logger.error('Policy API response not understood:')
405 self.logger.debug('\n' + str(policy_create_req.text))
406
407 # Inject the policy into Policy PAP
408 self.policy_pap_json['policies'].append({'policy-version': policy_version})
409 policy_insert_req = requests.post(self.policy_pap_post_url.format(
410 p_pap_cluster_ip), auth=self.policy_userpass,
411 json=self.policy_pap_json, verify=False,
412 headers=self.policy_headers)
413 if policy_insert_req.status_code != 200:
414 self.logger.error('Policy PAP request failed with HTTP code'
415 '{0}'.format(policy_insert_req.status_code))
Bartek Grzybowski68d93c22019-10-30 10:55:55 +0100416 sys.exit(1)
Bartek Grzybowskidbb3ba12019-09-25 11:21:42 +0200417 self.logger.info('Successully pushed closed loop Policy')
418
Kang Xi11d278c2018-04-06 16:56:04 -0400419 def is_node_in_aai(self, node_type, node_uuid):
420 key = None
421 search_node_type = None
422 if node_type == 'service':
423 search_node_type = 'service-instance'
424 key = 'service-instance-id'
425 elif node_type == 'vnf':
426 search_node_type = 'generic-vnf'
427 key = 'vnf-id'
428 else:
429 logging.error('Invalid node_type: ' + node_type)
Bartek Grzybowski68d93c22019-10-30 10:55:55 +0100430 sys.exit(1)
Kang Xi11d278c2018-04-06 16:56:04 -0400431
Kang Xi0e0a1d62018-07-23 16:53:54 -0400432 url = 'https://{0}:{1}/aai/v11/search/nodes-query?search-node-type={2}&filter={3}:EQUALS:{4}'.format(
433 self.hosts['aai-inst1'], self.aai_query_port, search_node_type, key, node_uuid)
Kang Xi11d278c2018-04-06 16:56:04 -0400434
Kang Xi268b74d2018-05-23 15:53:42 -0400435 headers = {'Content-Type': 'application/json', 'Accept': 'application/json', 'X-FromAppID': 'vCPE-Robot', 'X-TransactionId': 'get_aai_subscr'}
Kang Xi11d278c2018-04-06 16:56:04 -0400436 requests.packages.urllib3.disable_warnings()
437 r = requests.get(url, headers=headers, auth=self.aai_userpass, verify=False)
438 response = r.json()
439 self.logger.debug('aai query: ' + url)
440 self.logger.debug('aai response:\n' + json.dumps(response, indent=4, sort_keys=True))
441 return 'result-data' in response
442
443 @staticmethod
444 def extract_ip_from_str(net_addr, net_addr_len, sz):
445 """
446 :param net_addr: e.g. 10.5.12.0
447 :param net_addr_len: e.g. 24
448 :param sz: a string
449 :return: the first IP address matching the network, e.g. 10.5.12.3
450 """
451 network = ipaddress.ip_network(unicode('{0}/{1}'.format(net_addr, net_addr_len)), strict=False)
452 ip_list = re.findall(r'[0-9]+(?:\.[0-9]+){3}', sz)
453 for ip in ip_list:
454 this_net = ipaddress.ip_network(unicode('{0}/{1}'.format(ip, net_addr_len)), strict=False)
455 if this_net == network:
456 return str(ip)
457 return None
458
Yang Xu75b0f5e2018-11-14 14:04:20 -0500459 def get_pod_node_oam_ip(self, pod):
460 """
Yang Xu64339a82018-12-30 05:32:21 +0000461 :Assuming kubectl is available and configured by default config (~/.kube/config)
462 :param pod: pod name substring, e.g. 'sdnc-sdnc-0'
463 :return pod's cluster node oam ip (10.0.0.0/16)
Yang Xu75b0f5e2018-11-14 14:04:20 -0500464 """
Yang Xu64339a82018-12-30 05:32:21 +0000465 ret = None
466 config.load_kube_config()
467 api = client.CoreV1Api()
468 kslogger = logging.getLogger('kubernetes')
469 kslogger.setLevel(logging.INFO)
470 res = api.list_pod_for_all_namespaces()
471 for i in res.items:
472 if pod in i.metadata.name:
Yang Xu65b84a42018-12-31 17:48:02 +0000473 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 +0000474 ret = i.status.host_ip
475 break
476
477 if ret is None:
Bartek Grzybowski863af9a2019-09-13 08:54:14 +0200478 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 +0000479 return ret
480
481 def get_pod_node_public_ip(self, pod):
482 """
483 :Assuming kubectl is available and configured by default config (~/.kube/config)
484 :param pod: pod name substring, e.g. 'sdnc-sdnc-0'
485 :return pod's cluster node public ip (i.e. 10.12.0.0/16)
486 """
487 ret = None
488 config.load_kube_config()
489 api = client.CoreV1Api()
490 kslogger = logging.getLogger('kubernetes')
491 kslogger.setLevel(logging.INFO)
492 res = api.list_pod_for_all_namespaces()
493 for i in res.items:
494 if pod in i.metadata.name:
Yang Xu65b84a42018-12-31 17:48:02 +0000495 ret = self.get_vm_public_ip_by_nova(i.spec.node_name)
496 self.logger.debug("found node {0} public ip: {1}".format(i.spec.node_name, ret))
Yang Xu64339a82018-12-30 05:32:21 +0000497 break
498
499 if ret is None:
Bartek Grzybowski863af9a2019-09-13 08:54:14 +0200500 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 -0500501 return ret
502
Yang Xu65b84a42018-12-31 17:48:02 +0000503 def get_vm_public_ip_by_nova(self, vm):
504 """
505 This method uses openstack nova api to retrieve vm public ip
506 :param vm: vm name
507 :return vm public ip
508 """
509 subnet = IPNetwork('{0}/{1}'.format(self.external_net_addr, self.external_net_prefix_len))
510 nova = openstackclient.Client(2, self.cloud['--os-username'], self.cloud['--os-password'], self.cloud['--os-tenant-id'], self.cloud['--os-auth-url'])
511 for i in nova.servers.list():
512 if i.name == vm:
513 for k, v in i.networks.items():
514 for ip in v:
515 if IPAddress(ip) in subnet:
516 return ip
517 return None
518
Kang Xi11d278c2018-04-06 16:56:04 -0400519 def get_vm_ip(self, keywords, net_addr=None, net_addr_len=None):
520 """
521 :param keywords: list of keywords to search for vm, e.g. ['bng', 'gmux', 'brg']
522 :param net_addr: e.g. 10.12.5.0
523 :param net_addr_len: e.g. 24
524 :return: dictionary {keyword: ip}
525 """
526 if not net_addr:
527 net_addr = self.external_net_addr
528
529 if not net_addr_len:
530 net_addr_len = self.external_net_prefix_len
531
532 param = ' '.join([k + ' ' + v for k, v in self.cloud.items() if 'identity' not in k])
533 openstackcmd = 'nova ' + param + ' list'
534 self.logger.debug(openstackcmd)
535
Kang Xi11d278c2018-04-06 16:56:04 -0400536 results = os.popen(openstackcmd).read()
Kang Xi6c762392018-05-30 09:27:34 -0400537 all_vm_ip_dict = self.extract_vm_ip_as_dict(results, net_addr, net_addr_len)
538 latest_vm_list = self.remove_old_vms(all_vm_ip_dict.keys(), self.cpe_vm_prefix)
539 latest_vm_ip_dict = {vm: all_vm_ip_dict[vm] for vm in latest_vm_list}
540 ip_dict = self.select_subset_vm_ip(latest_vm_ip_dict, keywords)
Kang Xi0e0a1d62018-07-23 16:53:54 -0400541 if self.oom_mode:
542 ip_dict.update(self.get_oom_onap_vm_ip(keywords))
Kang Xi6c762392018-05-30 09:27:34 -0400543
Kang Xi11d278c2018-04-06 16:56:04 -0400544 if len(ip_dict) != len(keywords):
545 self.logger.error('Cannot find all desired IP addresses for %s.', keywords)
546 self.logger.error(json.dumps(ip_dict, indent=4, sort_keys=True))
Yang Xu64339a82018-12-30 05:32:21 +0000547 self.logger.error('Temporarily continue.. remember to check back vcpecommon.py line: 396')
Bartek Grzybowski68d93c22019-10-30 10:55:55 +0100548# sys.exit(1)
Kang Xi11d278c2018-04-06 16:56:04 -0400549 return ip_dict
550
Kang Xi0e0a1d62018-07-23 16:53:54 -0400551 def get_oom_onap_vm_ip(self, keywords):
552 vm_ip = {}
Kang Xi0e0a1d62018-07-23 16:53:54 -0400553 for vm in keywords:
Bartek Grzybowski8b409a12019-10-24 13:26:21 +0200554 if vm in self.host_names:
Kang Xi0e0a1d62018-07-23 16:53:54 -0400555 vm_ip[vm] = self.oom_so_sdnc_aai_ip
556 return vm_ip
557
Bartek Grzybowskidbb3ba12019-09-25 11:21:42 +0200558 def get_k8s_service_cluster_ip(self, service):
559 """
560 Returns cluster IP for a given service
561 :param service: name of the service
562 :return: cluster ip
563 """
564 config.load_kube_config()
565 api = client.CoreV1Api()
566 kslogger = logging.getLogger('kubernetes')
567 kslogger.setLevel(logging.INFO)
568 try:
569 resp = api.read_namespaced_service(service, self.onap_namespace)
570 except client.rest.ApiException as e:
571 self.logger.error('Error while making k8s API request: ' + e.body)
Bartek Grzybowski68d93c22019-10-30 10:55:55 +0100572 sys.exit(1)
Bartek Grzybowskidbb3ba12019-09-25 11:21:42 +0200573
574 return resp.spec.cluster_ip
575
Bartek Grzybowski9018a842019-10-16 15:28:23 +0200576 def get_k8s_service_endpoint_info(self, service, subset):
577 """
578 Returns endpoint data for a given service and subset. If there
579 is more than one endpoint returns data for the first one from
580 the list that API returned.
581 :param service: name of the service
582 :param subset: subset name, one of "ip","port"
583 :return: endpoint ip
584 """
585 config.load_kube_config()
586 api = client.CoreV1Api()
587 kslogger = logging.getLogger('kubernetes')
588 kslogger.setLevel(logging.INFO)
589 try:
590 resp = api.read_namespaced_endpoints(service, self.onap_namespace)
591 except client.rest.ApiException as e:
592 self.logger.error('Error while making k8s API request: ' + e.body)
Bartek Grzybowski68d93c22019-10-30 10:55:55 +0100593 sys.exit(1)
Bartek Grzybowski9018a842019-10-16 15:28:23 +0200594
595 if subset == "ip":
596 return resp.subsets[0].addresses[0].ip
597 elif subset == "port":
598 return resp.subsets[0].ports[0].port
599 else:
600 self.logger.error("Unsupported subset type")
601
Kang Xi6c762392018-05-30 09:27:34 -0400602 def extract_vm_ip_as_dict(self, novalist_results, net_addr, net_addr_len):
603 vm_ip_dict = {}
604 for line in novalist_results.split('\n'):
605 fields = line.split('|')
606 if len(fields) == 8:
607 vm_name = fields[2]
608 ip_info = fields[-2]
609 ip = self.extract_ip_from_str(net_addr, net_addr_len, ip_info)
610 vm_ip_dict[vm_name] = ip
611
612 return vm_ip_dict
613
614 def remove_old_vms(self, vm_list, prefix):
615 """
616 For vms with format name_timestamp, only keep the one with the latest timestamp.
617 E.g.,
618 zdcpe1cpe01brgemu01_201805222148 (drop this)
619 zdcpe1cpe01brgemu01_201805222229 (keep this)
620 zdcpe1cpe01gw01_201805162201
621 """
622 new_vm_list = []
623 same_type_vm_dict = {}
624 for vm in vm_list:
625 fields = vm.split('_')
626 if vm.startswith(prefix) and len(fields) == 2 and len(fields[-1]) == len('201805222148') and fields[-1].isdigit():
627 if vm > same_type_vm_dict.get(fields[0], '0'):
628 same_type_vm_dict[fields[0]] = vm
629 else:
630 new_vm_list.append(vm)
631
632 new_vm_list.extend(same_type_vm_dict.values())
633 return new_vm_list
634
635 def select_subset_vm_ip(self, all_vm_ip_dict, vm_name_keyword_list):
636 vm_ip_dict = {}
637 for keyword in vm_name_keyword_list:
638 for vm, ip in all_vm_ip_dict.items():
639 if keyword in vm:
640 vm_ip_dict[keyword] = ip
641 break
642 return vm_ip_dict
643
Kang Xi11d278c2018-04-06 16:56:04 -0400644 def del_vgmux_ves_mode(self):
645 url = self.vpp_ves_url.format(self.hosts['mux']) + '/mode'
646 r = requests.delete(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
647 self.logger.debug('%s', r)
648
649 def del_vgmux_ves_collector(self):
650 url = self.vpp_ves_url.format(self.hosts['mux']) + '/config'
651 r = requests.delete(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
652 self.logger.debug('%s', r)
653
654 def set_vgmux_ves_collector(self ):
655 url = self.vpp_ves_url.format(self.hosts['mux'])
656 data = {'config':
Kang Xi268b74d2018-05-23 15:53:42 -0400657 {'server-addr': self.hosts[self.dcae_ves_collector_name],
Kang Xi0e0a1d62018-07-23 16:53:54 -0400658 'server-port': '30235' if self.oom_mode else '8081',
Kang Xi11d278c2018-04-06 16:56:04 -0400659 'read-interval': '10',
660 'is-add':'1'
661 }
662 }
663 r = requests.post(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass, json=data)
664 self.logger.debug('%s', r)
665
666 def set_vgmux_packet_loss_rate(self, lossrate, vg_vnf_instance_name):
667 url = self.vpp_ves_url.format(self.hosts['mux'])
668 data = {"mode":
669 {"working-mode": "demo",
670 "base-packet-loss": str(lossrate),
671 "source-name": vg_vnf_instance_name
672 }
673 }
674 r = requests.post(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass, json=data)
675 self.logger.debug('%s', r)
676
677 # return all the VxLAN interface names of BRG or vGMUX based on the IP address
678 def get_vxlan_interfaces(self, ip, print_info=False):
679 url = self.vpp_inf_url.format(ip)
680 self.logger.debug('url is this: %s', url)
681 r = requests.get(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
682 data = r.json()['interfaces']['interface']
683 if print_info:
684 for inf in data:
685 if 'name' in inf and 'type' in inf and inf['type'] == 'v3po:vxlan-tunnel':
686 print(json.dumps(inf, indent=4, sort_keys=True))
687
688 return [inf['name'] for inf in data if 'name' in inf and 'type' in inf and inf['type'] == 'v3po:vxlan-tunnel']
689
690 # delete all VxLAN interfaces of each hosts
691 def delete_vxlan_interfaces(self, host_dic):
692 for host, ip in host_dic.items():
693 deleted = False
694 self.logger.info('{0}: Getting VxLAN interfaces'.format(host))
695 inf_list = self.get_vxlan_interfaces(ip)
696 for inf in inf_list:
697 deleted = True
698 time.sleep(2)
699 self.logger.info("{0}: Deleting VxLAN crossconnect {1}".format(host, inf))
700 url = self.vpp_inf_url.format(ip) + '/interface/' + inf + '/v3po:l2'
701 requests.delete(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
702
703 for inf in inf_list:
704 deleted = True
705 time.sleep(2)
706 self.logger.info("{0}: Deleting VxLAN interface {1}".format(host, inf))
707 url = self.vpp_inf_url.format(ip) + '/interface/' + inf
708 requests.delete(url, headers=self.vpp_api_headers, auth=self.vpp_api_userpass)
709
710 if len(self.get_vxlan_interfaces(ip)) > 0:
711 self.logger.error("Error deleting VxLAN from {0}, try to restart the VM, IP is {1}.".format(host, ip))
712 return False
713
714 if not deleted:
715 self.logger.info("{0}: no VxLAN interface found, nothing to delete".format(host))
716 return True
717
718 @staticmethod
719 def save_object(obj, filepathname):
720 with open(filepathname, 'wb') as fout:
721 pickle.dump(obj, fout)
722
723 @staticmethod
724 def load_object(filepathname):
725 with open(filepathname, 'rb') as fin:
726 return pickle.load(fin)
727
Kang Xi6c762392018-05-30 09:27:34 -0400728 @staticmethod
729 def increase_ip_address_or_vni_in_template(vnf_template_file, vnf_parameter_name_list):
730 with open(vnf_template_file) as json_input:
731 json_data = json.load(json_input)
732 param_list = json_data['VNF-API:input']['VNF-API:vnf-topology-information']['VNF-API:vnf-parameters']
733 for param in param_list:
734 if param['vnf-parameter-name'] in vnf_parameter_name_list:
735 ipaddr_or_vni = param['vnf-parameter-value'].split('.')
736 number = int(ipaddr_or_vni[-1])
737 if 254 == number:
738 number = 10
739 else:
740 number = number + 1
741 ipaddr_or_vni[-1] = str(number)
742 param['vnf-parameter-value'] = '.'.join(ipaddr_or_vni)
743
744 assert json_data is not None
745 with open(vnf_template_file, 'w') as json_output:
746 json.dump(json_data, json_output, indent=4, sort_keys=True)
747
Kang Xi11d278c2018-04-06 16:56:04 -0400748 def save_preload_data(self, preload_data):
749 self.save_object(preload_data, self.preload_dict_file)
750
751 def load_preload_data(self):
752 return self.load_object(self.preload_dict_file)
753
754 def save_vgmux_vnf_name(self, vgmux_vnf_name):
755 self.save_object(vgmux_vnf_name, self.vgmux_vnf_name_file)
756
757 def load_vgmux_vnf_name(self):
758 return self.load_object(self.vgmux_vnf_name_file)
759