diff --git a/devstack/plugin.sh b/devstack/plugin.sh index 4d0e288bea..6372b79870 100644 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -39,6 +39,9 @@ elif [[ $Q_PLUGIN == 'vmware_nsx' ]]; then fi elif [[ $Q_PLUGIN == 'vmware_nsx_v3' ]]; then source $dir/lib/vmware_nsx_v3 + if [[ "$1" == "unstack" ]]; then + python $dir/tools/nsxt_cleanup.py --mgr-ip $NSX_MANAGER --user $NSX_USER --password $NSX_PASSWORD + fi elif [[ $Q_PLUGIN == 'vmware_dvs' ]]; then source $dir/lib/vmware_dvs fi diff --git a/devstack/tools/nsxt_cleanup.py b/devstack/tools/nsxt_cleanup.py new file mode 100755 index 0000000000..7635015b60 --- /dev/null +++ b/devstack/tools/nsxt_cleanup.py @@ -0,0 +1,402 @@ +# Copyright 2015 VMware Inc +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import sys +import json +import base64 +import requests + +requests.packages.urllib3.disable_warnings() + + +class NSXClient(object): + """ Base NSX REST client """ + API_VERSION = "v1" + + def __init__(self, host, username, password, *args, **kwargs): + self.host = host + self.username = username + self.password = password + self.version = None + self.endpoint = None + self.content_type = "application/json" + self.accept_type = "application/json" + self.verify = False + self.secure = True + self.interface = "json" + self.url = None + self.headers = None + self.api_version = NSXClient.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 put(self, endpoint=None, body=None): + """ + Basic put API method on endpoint + """ + self.__set_url(endpoint=endpoint) + response = requests.put(self.url, headers=self.headers, + verify=self.verify, data=json.dumps(body)) + 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) + response = requests.post(self.url, headers=self.headers, + verify=self.verify, data=json.dumps(body)) + return response + + def get_transport_zones(self): + """ + Retrieve all transport zones + """ + response = self.get(endpoint="/transport-zones") + return response.json()['results'] + + def get_logical_ports(self): + """ + Retrieve all logical ports on NSX backend + """ + response = self.get(endpoint="/logical-ports") + return response.json()['results'] + + def get_os_logical_ports(self): + """ + Retrieve all logical ports created from OpenStack + """ + lports = self.get_logical_ports() + return self.get_os_resources(lports) + + def update_logical_port_attachment(self, lports): + """ + In order to delete logical ports, we need to detach + the VIF attachment on the ports first. + """ + for p in lports: + p['attachment'] = None + endpoint = "/logical-ports/%s" % p['id'] + response = self.put(endpoint=endpoint, body=p) + if response.status_code != requests.codes.ok: + print("ERROR: Failed to update lport %s" % p['id']) + + def cleanup_os_logical_ports(self): + """ + Delete all logical ports created by OpenStack + """ + lports = self.get_logical_ports() + os_lports = self.get_os_resources(lports) + print("Number of OS Logical Ports to be deleted: %s" % len(os_lports)) + # logical port vif detachment + self.update_logical_port_attachment(os_lports) + for p in os_lports: + endpoint = '/logical-ports/%s' % p['id'] + response = self.delete(endpoint=endpoint) + if response.status_code == requests.codes.ok: + print("Successfully deleted logical port %s" % p['id']) + else: + print("ERROR: Failed to delete lport %s, response code %s" % + (p['id'], response.status_code)) + + def get_os_resources(self, resources): + """ + Get all logical resources created by OpenStack + """ + os_resources = [r for r in resources if 'tags' in r + for tag in r['tags'] if 'os-tid' in tag.values()] + return os_resources + + def get_logical_switches(self): + """ + Retrieve all logical switches on NSX backend + """ + response = self.get(endpoint="/logical-switches") + return response.json()['results'] + + def get_os_logical_switches(self): + """ + Retrieve all logical switches created from OpenStack + """ + lswitches = self.get_logical_switches() + return self.get_os_resources(lswitches) + + def get_lswitch_ports(self, ls_id): + """ + Return all the logical ports that belong to this lswitch + """ + lports = self.get_logical_ports() + return [p for p in lports if p['logical_switch_id'] is ls_id] + + def cleanup_os_logical_switches(self): + """ + Delete all logical switches created from OpenStack + """ + lswitches = self.get_os_logical_switches() + print("Number of OS Logical Switches to be deleted: %s" % + len(lswitches)) + for ls in lswitches: + endpoint = '/logical-switches/%s' % ls['id'] + response = self.delete(endpoint=endpoint) + if response.status_code == requests.codes.ok: + print("Successfully deleted logical switch %s-%s" % + (ls['display_name'], ls['id'])) + else: + print("Failed to delete lswitch %s-%s, and response is %s" % + (ls['display_name'], ls['id'], response.status_code)) + + def get_firewall_sections(self): + """ + Retrieve all firewall sections + """ + response = self.get(endpoint="/firewall/sections") + return response.json()['results'] + + def get_os_firewall_sections(self): + """ + Retrieve all firewall sections created from OpenStack + """ + fw_sections = self.get_firewall_sections() + return self.get_os_resources(fw_sections) + + def get_firewall_section_rules(self, fw_section): + """ + Retrieve all fw rules for a given fw section + """ + endpoint = "/firewall/sections/%s/rules" % fw_section['id'] + response = self.get(endpoint=endpoint) + return response.json()['results'] + + def cleanup_firewall_section_rules(self, fw_section): + """ + Cleanup all firewall rules for a given fw section + """ + fw_rules = self.get_firewall_section_rules(fw_section) + for rule in fw_rules: + endpoint = "/firewall/sections/%s/rules/%s" % (fw_section['id'], + rule['id']) + response = self.delete(endpoint=endpoint) + if response.status_code == requests.codes.ok: + print("Successfully deleted fw rule %s in fw section %s" % + (rule['display_name'], fw_section['display_name'])) + else: + print("Failed to delete fw rule %s in fw section %s" % + (rule['display_name'], fw_section['display_name'])) + + def cleanup_os_firewall_sections(self): + """ + Cleanup all firewall sections created from OpenStack + """ + fw_sections = self.get_os_firewall_sections() + print("Number of OS Firewall Sections to be deleted: %s" % + len(fw_sections)) + for fw in fw_sections: + self.cleanup_firewall_section_rules(fw) + endpoint = "/firewall/sections/%s" % fw['id'] + response = self.delete(endpoint=endpoint) + if response.status_code == requests.codes.ok: + print("Successfully deleted firewall section %s" % + fw['display_name']) + else: + print("Failed to delete firewall section %s" % + fw['display_name']) + + def get_ns_groups(self): + """ + Retrieve all NSGroups on NSX backend + """ + response = self.get(endpoint="/ns-groups") + return response.json()['results'] + + def get_os_ns_groups(self): + """ + Retrieve all NSGroups created from OpenStack + """ + ns_groups = self.get_ns_groups() + return self.get_os_resources(ns_groups) + + def cleanup_os_ns_groups(self): + """ + Cleanup all NSGroups created from OpenStack plugin + """ + ns_groups = self.get_os_ns_groups() + print("Number of OS NSGroups to be deleted: %s" % len(ns_groups)) + for nsg in ns_groups: + endpoint = "/ns-groups/%s" % nsg['id'] + response = self.delete(endpoint=endpoint) + if response.status_code == requests.codes.ok: + print("Successfully deleted NSGroup: %s" % nsg['display_name']) + else: + print("Failed to delete NSGroup: %s" % nsg['display_name']) + + def get_logical_routers(self, tier=None): + """ + Retrieve all the logical routers based on router type. If tier + is None, it will return all logical routers. + """ + if tier: + endpoint = "/logical-routers?router_type=%s" % tier + else: + endpoint = "/logical-routers" + response = self.get(endpoint=endpoint) + return response.json()['results'] + + def get_os_logical_routers(self): + """ + Retrive all logical routers created from Neutron NSXv3 plugin + """ + lrouters = self.get_logical_routers() + return self.get_os_resources(lrouters) + + def get_logical_router_ports(self, lrouter): + """ + Get all logical ports attached to lrouter + """ + endpoint = "/logical-router-ports?logical_router_id=%s" % lrouter['id'] + response = self.get(endpoint=endpoint) + return response.json()['results'] + + def cleanup_logical_router_ports(self, lrouter): + """ + Cleanup all logical ports on a logical router + """ + lports = self.get_logical_router_ports(lrouter) + for lp in lports: + endpoint = "/logical-router-ports/%s" % lp['id'] + response = self.delete(endpoint=endpoint) + if response.status_code == requests.codes.ok: + print("Successfully deleted logical router port %s-%s" % + (lp['display_name'], lp['id'])) + else: + print("Failed to delete lr port %s-%s, and response is %s" % + (lp['display_name'], lp['id'])) + + def cleanup_os_logical_routers(self): + """ + Delete all logical routers created from OpenStack + To delete a logical router, we need to delete all logical + ports on the router first. + """ + lrouters = self.get_os_logical_routers() + print("Number of OS Logical Routers to be deleted: %s" % + len(lrouters)) + for lr in lrouters: + self.cleanup_logical_router_ports(lr) + endpoint = "/logical-routers/%s" % lr['id'] + response = self.delete(endpoint=endpoint) + if response.status_code == requests.codes.ok: + print("Successfully deleted logical router %s-%s" % + (lr['display_name'], lr['id'])) + else: + print("Failed to delete lrouter %s-%s, and response is %s" % + (lr['display_name'], lr['id'])) + + def cleanup_all(self): + """ + Cleanup steps: + 1. Cleanup firewall sections + 2. Cleanup NSGroups + 3. Cleanup logical router ports + 4. Cleanup logical routers + 5. Cleanup logical switch ports + 6. Cleanup logical switches + """ + self.cleanup_os_firewall_sections() + self.cleanup_os_ns_groups() + self.cleanup_os_logical_routers() + self.cleanup_os_logical_ports() + self.cleanup_os_logical_switches() + + +if __name__ == "__main__": + from optparse import OptionParser + + parser = OptionParser() + parser.add_option("--mgr-ip", dest="mgr_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") + (options, args) = parser.parse_args() + + # Get NSX REST client + nsx_client = NSXClient(options.mgr_ip, options.username, + options.password) + # Clean all objects created by OpenStack + nsx_client.cleanup_all()