diff --git a/devstack/plugin.sh b/devstack/plugin.sh index 3bf7369685..5786120158 100644 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -25,11 +25,16 @@ fi if [[ $Q_PLUGIN == 'vmware_nsx_v' ]]; then source $dir/lib/vmware_nsx_v + if [[ "$1" == "unstack" ]]; then + python $dir/tools/nsxv_cleanup.py --vsm-ip ${NSXV_MANAGER_URI/https:\/\/} --user $NSXV_USER --password $NSXV_PASSWORD + fi elif [[ $Q_PLUGIN == 'vmware_nsx' ]]; then source $dir/lib/vmware_nsx if [[ "$1" == "stack" && "$2" == "post-config" ]]; then init_vmware_nsx elif [[ "$1" == "stack" && "$2" == "extra" ]]; then check_vmware_nsx + elif [[ "$1" == "unstack" ]]; then + stop_vmware_nsx fi fi diff --git a/devstack/tools/nsxv_cleanup.py b/devstack/tools/nsxv_cleanup.py new file mode 100755 index 0000000000..6ecd767ee5 --- /dev/null +++ b/devstack/tools/nsxv_cleanup.py @@ -0,0 +1,334 @@ +#!/usr/bin/env python + +""" +Purpose: Sometimes NSXv backend are out of sync with OpenStack and all +the objects created by OpenStack needs to be cleaned up. + +This is a util script to cleanup NSXv objects created by OpenStack +List of objects to be cleared: + - Edge (Service Edge, DHCP Edge, VDR Edge) + - Logical Switches (Tenant Networks) + - Firewall Rules (Security Group) + +Usage: + python nsxv_cleanup.py --vsm-ip + --username + --password + --force +Note: force is optional. If it is specified, force delete security group + +You can also use it in python interactive console by import the module +>>>> import nsxv_cleanup +>>>> vsm = nsxv_cleanup.VSMClient('10.34.57.101', 'admin', 'default') +Cleanup all logical switch +>>>> vsm.cleanup_logical_switch() +Cleanup all firewall section +>>>> vsm.cleanup_firewall_section() +Cleanup all security group +>>>> vsm.cleanup_security_group() +Cleanup all edges +>>>> vsm.cleanup_edge() +Cleanup all +>>>> vsm.cleanup_all() + +If you have any comment or find a bug, please contact +Tong Liu +""" + +import sys +import json +import base64 +import requests + +requests.packages.urllib3.disable_warnings() + + +class VSMClient(object): + """ Base VSM REST client """ + API_VERSION = "2.0" + + def __init__(self, host, username, password, *args, **kwargs): + self.force = True if 'force' in kwargs else False + self.host = host + self.username = username + self.password = password + self.version = None + self.endpoint = None + self.content_type = "applicatioon/json" + self.accept_type = "application/json" + self.verify = False + self.secure = True + self.interface = "json" + self.url = None + self.headers = None + self.api_version = VSMClient.API_VERSION + + self.__set_headers() + + def __set_endpoint(self, endpoint): + self.endpoint = endpoint + + def get_endpoint(self): + return self.endpoint + + def __set_content_type(self, content_type): + self.content_type = content_type + + def get_content_type(self): + return self.content_type + + def __set_accept_type(self, accept_type): + self.accept_type = accept_type + + def get_accept_type(self): + return self.accept_type + + def __set_api_version(self, api_version): + self.api_version = api_version + + def get_api_version(self): + return self.api + + def __set_url(self, api=None, secure=None, host=None, endpoint=None): + api = self.api_version if api is None else api + secure = self.secure if secure is None else secure + host = self.host if host is None else host + endpoint = self.endpoint if endpoint is None else endpoint + http_type = 'https' if secure else 'http' + self.url = '%s://%s/api/%s%s' % (http_type, host, api, endpoint) + + def get_url(self): + return self.url + + def __set_headers(self, content=None, accept=None): + content_type = self.content_type if content is None else content + accept_type = self.accept_type if accept is None else accept + auth_cred = self.username + ":" + self.password + auth = base64.b64encode(auth_cred) + headers = {} + headers['Authorization'] = "Basic %s" % auth + headers['Content-Type'] = content_type + headers['Accept'] = accept_type + self.headers = headers + + def get(self, endpoint=None, params=None): + """ + Basic query method for json API request + """ + self.__set_url(endpoint=endpoint) + response = requests.get(self.url, headers=self.headers, + verify=self.verify, params=params) + return response + + def delete(self, endpoint=None, params=None): + """ + Basic delete API method on endpoint + """ + self.__set_url(endpoint=endpoint) + response = requests.delete(self.url, headers=self.headers, + verify=self.verify, params=params) + return response + + def post(self, endpoint=None, body=None): + """ + Basic post API method on endpoint + """ + self.__set_url(endpoint=endpoint) + self.__set_headers() + response = requests.post(self.url, headers=self.headers, + verify=self.verify, data=json.dumps(body)) + return response + + # TODO(Tong): Get exact vdnscope-id instead using of default one + def query_all_logical_switches(self): + lswitches = [] + self.__set_api_version('2.0') + self.__set_endpoint("/vdn/scopes/vdnscope-1/virtualwires") + # Query all logical switches + response = self.get() + paging_info = response.json()['dataPage']['pagingInfo'] + page_size = int(paging_info['pageSize']) + total_count = int(paging_info['totalCount']) + print "There are total %s logical switches and page size is %s" % ( + total_count, page_size) + pages = ceil(total_count, page_size) + print "Total pages: %s" % pages + for i in range(0, pages): + start_index = page_size * i + params = {'startindex': start_index} + response = self.get(params=params) + temp_lswitches = response.json()['dataPage']['data'] + lswitches += temp_lswitches + + return lswitches + + def cleanup_logical_switch(self): + print "Cleaning up logical switches on NSX manager" + lswitches = self.query_all_logical_switches() + print "There are total %s logical switches" % len(lswitches) + for ls in lswitches: + print "\nDeleting logical switch %s (%s) ..." % (ls['name'], + ls['objectId']) + endpoint = '/vdn/virtualwires/%s' % ls['objectId'] + response = self.delete(endpoint=endpoint) + if response.status_code != 200: + print "ERROR: reponse status code %s" % response.status_code + + def query_all_firewall_sections(self): + firewall_sections = [] + self.__set_api_version('4.0') + self.__set_endpoint('/firewall/globalroot-0/config') + # Query all firewall sections + response = self.get() + # Get layer3 sections related to security group + if response.status_code is 200: + l3_sections = response.json()['layer3Sections']['layer3Sections'] + firewall_sections = [s for s in l3_sections if s['name'] != + "Default Section Layer3"] + else: + print "ERROR: wrong response status code! Exiting..." + sys.exit() + + return firewall_sections + + def cleanup_firewall_section(self): + print "\n\nCleaning up firewall sections on NSX manager" + l3_sections = self.query_all_firewall_sections() + print "There are total %s firewall sections" % len(l3_sections) + for l3sec in l3_sections: + print "\nDeleting firewall section %s (%s) ..." % (l3sec['name'], + l3sec['id']) + endpoint = '/firewall/globalroot-0/config/layer3sections/%s' % \ + l3sec['id'] + response = self.delete(endpoint=endpoint) + if response.status_code != 200: + print "ERROR: reponse status code %s" % response.status_code + + def query_all_security_groups(self): + security_groups = [] + self.__set_api_version('2.0') + self.__set_endpoint("/services/securitygroup/scope/globalroot-0") + # Query all security groups + response = self.get() + if response.status_code is 200: + sg_all = response.json() + else: + print "ERROR: wrong response status code! Exiting..." + sys.exit() + # Remove Activity Monitoring Data Collection, which is not + # related to any security group created by OpenStack + security_groups = [sg for sg in sg_all if + sg['name'] != "Activity Monitoring Data Collection"] + return security_groups + + def cleanup_security_group(self): + print "\n\nCleaning up security groups on NSX manager" + security_groups = self.query_all_security_groups() + print "There are total %s security groups" % len(security_groups) + for sg in security_groups: + print "\nDeleting security group %s (%s) ..." % (sg['name'], + sg['objectId']) + endpoint = '/services/securitygroup/%s' % sg['objectId'] + params = {'force': self.force} + response = self.delete(endpoint=endpoint, params=params) + if response.status_code != 200: + print "ERROR: reponse status code %s" % response.status_code + + def query_all_spoofguard_policies(self): + self.__set_api_version('4.0') + self.__set_endpoint("/services/spoofguard/policies/") + # Query all spoofguard policies + response = self.get() + if response.status_code is not 200: + print "ERROR: Faield to get spoofguard policies" + return + sgp_all = response.json() + policies = [sgp for sgp in sgp_all['policies'] if + sgp['name'] != 'Default Policy'] + return policies + + def cleanup_spoofguard_policies(self): + print "\n\nCleaning up spoofguard policies" + policies = self.query_all_spoofguard_policies() + print "There are total %s policies" % len(policies) + for spg in policies: + print "\nDeleting spoofguard policy %s (%s) ..." % (spg['name'], + spg['policyId']) + endpoint = '/services/spoofguard/%s' % spg['policyId'] + response = self.delete(endpoint=endpoint) + print "Response code: %s" % response.status_code + + def query_all_edges(self): + edges = [] + self.__set_api_version('4.0') + self.__set_endpoint("/edges") + # Query all edges + response = self.get() + paging_info = response.json()['edgePage']['pagingInfo'] + page_size = int(paging_info['pageSize']) + total_count = int(paging_info['totalCount']) + print "There are total %s edges and page size is %s" % ( + total_count, page_size) + pages = ceil(total_count, page_size) + print "Total pages: %s" % pages + for i in range(0, pages): + start_index = page_size * i + params = {'startindex': start_index} + response = self.get(params=params) + temp_edges = response.json()['edgePage']['data'] + edges += temp_edges + + return edges + + def cleanup_edge(self): + print "\n\nCleaning up edges on NSX manager" + edges = self.query_all_edges() + for edge in edges: + print "\nDeleting edge %s (%s) ..." % (edge['name'], edge['id']) + endpoint = '/edges/%s' % edge['id'] + response = self.delete(endpoint=endpoint) + if response.status_code != 200: + print "ERROR: reponse status code %s" % response.status_code + + def cleanup_all(self): + self.cleanup_logical_switch() + self.cleanup_firewall_section() + self.cleanup_security_group() + self.cleanup_spoofguard_policies() + self.cleanup_edge() + + +def ceil(a, b): + if b == 0: + return 0 + div = a / b + mod = 0 if a % b is 0 else 1 + return div + mod + + +if __name__ == "__main__": + from optparse import OptionParser + + parser = OptionParser() + parser.add_option("--vsm-ip", dest="vsm_ip", help="NSX Manager IP address") + parser.add_option("-u", "--username", default="admin", dest="username", + help="NSX Manager username") + parser.add_option("-p", "--password", default="default", dest="password", + help="NSX Manager password") + parser.add_option("-f", "--force", dest="force", action="store_true", + help="Force cleanup option") + (options, args) = parser.parse_args() + print "vsm-ip: %s" % options.vsm_ip + print "username: %s" % options.username + print "password: %s" % options.password + print "force: %s" % options.force + + # Get VSM REST client + if options.force: + vsm_client = VSMClient(options.vsm_ip, options.username, + options.password, force=options.force) + else: + vsm_client = VSMClient(options.vsm_ip, options.username, + options.password) + # Clean all objects created by OpenStack + vsm_client.cleanup_all()