#!/usr/bin/env python # # Copyright 2014 Catalyst IT Ltd # # 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 argparse import os import sys import six import traceback import prettytable from cinderclient.v1 import client as cinder_client from glanceclient import client as glance_client from oslo_utils import importutils from oslo_utils import encodeutils from heatclient import client as heat_client from keystoneclient.v2_0 import client as keystone_client from neutronclient.v2_0 import client as neutron_client try: from novaclient.v2 import client as nova_client except: from novaclient.v3 import client as nova_client from swiftclient import client as swift_client from swiftclient import exceptions as swift_exceptions DNS_NAMESERVERS = ['202.78.240.213', '202.78.240.214', '202.78.240.215'] def arg(*args, **kwargs): def _decorator(func): func.__dict__.setdefault('arguments', []).insert(0, (args, kwargs)) return func return _decorator class TenantShell(object): def get_base_parser(self): parser = argparse.ArgumentParser( prog='tenant', description='Tenant management script for Catalyst Cloud.', add_help=False, ) # Global arguments parser.add_argument('-h', '--help', action='store_true', help=argparse.SUPPRESS, ) parser.add_argument('-a', '--os-auth-url', metavar='OS_AUTH_URL', type=str, required=False, dest='OS_AUTH_URL', default=os.environ.get('OS_AUTH_URL', None), help='Keystone Authentication URL') parser.add_argument('-u', '--os-username', metavar='OS_USERNAME', type=str, required=False, dest='OS_USERNAME', default=os.environ.get('OS_USERNAME', None), help='Username for authentication') parser.add_argument('-p', '--os-password', metavar='OS_PASSWORD', type=str, required=False, dest='OS_PASSWORD', default=os.environ.get('OS_PASSWORD', None), help='Password for authentication') parser.add_argument('-t', '--os-tenant-name', metavar='OS_TENANT_NAME', type=str, required=False, dest='OS_TENANT_NAME', default=os.environ.get('OS_TENANT_NAME', None), help='Tenant name for authentication') parser.add_argument('-r', '--os-region-name', metavar='OS_REGION_NAME', type=str, required=False, dest='OS_REGION_NAME', default=os.environ.get('OS_REGION_NAME', None), help='Region for authentication') parser.add_argument('-c', '--os-cacert', metavar='OS_CACERT', dest='OS_CACERT', default=os.environ.get('OS_CACERT'), help='Path of CA TLS certificate(s) used to ' 'verify the remote server\'s certificate. ' 'Without this option glance looks for the ' 'default system CA certificates.') parser.add_argument('-k', '--insecure', default=False, action='store_true', dest='OS_INSECURE', help='Explicitly allow script to perform ' '\"insecure SSL\" (https) requests. ' 'The server\'s certificate will not be ' 'verified against any certificate authorities.' ' This option should be used with caution.') return parser def get_subcommand_parser(self): parser = self.get_base_parser() self.subcommands = {} subparsers = parser.add_subparsers(metavar='') submodule = importutils.import_module('tenant') self._find_actions(subparsers, submodule) self._find_actions(subparsers, self) return parser def _find_actions(self, subparsers, actions_module): for attr in (a for a in dir(actions_module) if a.startswith('do_')): command = attr[3:].replace('_', '-') callback = getattr(actions_module, attr) desc = callback.__doc__ or '' help = desc.strip().split('\n')[0] arguments = getattr(callback, 'arguments', []) subparser = subparsers.add_parser(command, help=help, description=desc, add_help=False, formatter_class=HelpFormatter ) subparser.add_argument('-h', '--help', action='help', help=argparse.SUPPRESS, ) self.subcommands[command] = subparser for (args, kwargs) in arguments: subparser.add_argument(*args, **kwargs) subparser.set_defaults(func=callback) @arg('command', metavar='', nargs='?', help='Display help for .') def do_help(self, args): """Display help about this program or one of its subcommands. """ if getattr(args, 'command', None): if args.command in self.subcommands: self.subcommands[args.command].print_help() else: raise Exception("'%s' is not a valid subcommand" % args.command) else: self.parser.print_help() def init_client(self, args): try: keystone = keystone_client.Client(username=args.OS_USERNAME, password=args.OS_PASSWORD, tenant_name=args.OS_TENANT_NAME, auth_url=args.OS_AUTH_URL, region_name=args.OS_REGION_NAME, cacert=args.OS_CACERT, insecure=args.OS_INSECURE) self.keystone = keystone except Exception as e: # FIXME(flwang): Improve the exception catching raise e try: neutron = neutron_client.Client(username=args.OS_USERNAME, password=args.OS_PASSWORD, tenant_name=args.OS_TENANT_NAME, auth_url=args.OS_AUTH_URL, region_name=args.OS_REGION_NAME, insecure=args.OS_INSECURE) self.neutron = neutron except Exception as e: raise e try: nova = nova_client.Client(username=args.OS_USERNAME, password=args.OS_PASSWORD, project_id=args.OS_TENANT_NAME, auth_url=args.OS_AUTH_URL, region_name=args.OS_REGION_NAME, insecure=args.OS_INSECURE) self.nova = nova except Exception as e: raise e try: client_kwargs = { 'token': self.keystone.auth_token, 'insecure': args.OS_INSECURE } endpoint_kwargs = { 'service_type': 'image', 'endpoint_type': 'publicURL', } if args.OS_REGION_NAME: endpoint_kwargs['attr'] = 'region' endpoint_kwargs['filter_value'] = args.OS_REGION_NAME endpoint = keystone.service_catalog.url_for(**endpoint_kwargs) glance = glance_client.Client('1', endpoint, **client_kwargs) self.glance = glance except Exception as e: raise e try: cinder = cinder_client.Client(args.OS_USERNAME, args.OS_PASSWORD, tenant_id=keystone.auth_tenant_id, auth_url=args.OS_AUTH_URL, region_name=args.OS_REGION_NAME, insecure=args.OS_INSECURE) self.cinder = cinder except Exception as e: raise e def main(self, argv): parser = self.get_base_parser() (options, args) = parser.parse_known_args(argv) subcommand_parser = self.get_subcommand_parser() self.parser = subcommand_parser if options.help or not argv: self.do_help(options) return 0 args = subcommand_parser.parse_args(argv) if args.func == self.do_help: self.do_help(args) return 0 try: self.init_client(args) args.func(self, args) except Exception as e: raise e class HelpFormatter(argparse.HelpFormatter): def start_section(self, heading): # Title-case the headings heading = '%s%s' % (heading[0].upper(), heading[1:]) super(HelpFormatter, self).start_section(heading) @arg('--tenant-name', type=str, metavar='TENANT_NAME', dest='TENANT_NAME', help='New tenant name (must be unique).') @arg('--tenant-description', type=str, default=None, metavar='TENANT_DESC', dest='TENANT_DESC', help='Description of new tenant. Default is none.') @arg('--meter-label-name', type=str, metavar='METER_LABEL_NAME', dest='METER_LABEL_NAME', help='Neutron meter label name which will be ' 'associated with the tenant') @arg('--meter-label-description', type=str, default=None, metavar='METER_LABEL_DESC', dest='METER_LABEL_DESC', help='Description of new meter label. Default is none.') @arg('--network-name', type=str, required=False, metavar='NETWORK_NAME', dest='NETWORK_NAME', help='New network name for the tenant.') @arg('--subnet-name', type=str, required=False, metavar='SUBNET_NAME', dest='SUBNET_NAME', help='New subnet name for the new network.') @arg('--subnet-cidr', type=str, required=False, metavar='SUBNET_CIDR', dest='SUBNET_CIDR', help='Subnet IP range with CIDR format.') @arg('--dns-server', type=str, required=False, metavar='DNS_SERVER', action='append', dest='DNS_SERVER', help='DNS server, following the format like: ' '--dns-server 10.0.0.1 --dns-server 10.0.0.2') @arg('--router-name', type=str, required=False, metavar='ROUTER_NAME', dest='ROUTER_NAME', help='Router name associated with the new network.') @arg('--public-network', type=str, required=False, metavar='PUBLIC_NETWORK', dest='PUBLIC_NETWORK', help='Public network id which the new router will use.') @arg('--meter-rule-direction', type=str, required=False, default='both', choices=('ingress', 'egress', 'both'), metavar='METER_RULE_DIRECTION', dest='METER_RULE_DIRECTION', help='Direction of meter label rule.') def do_init(shell, args): """Add a new tenant and initialize related resources.""" print('>>> CHECK CAPACITY') raw_input('CAUTION: Sort out capacity planning first.') print('>>> CREATE TENANT') tenant_id = create_tenant(shell.keystone, args) print('>>> ADD ADMIN TO THE NEW TENANT') add_admin_to_tenant(shell.keystone, tenant_id) print('>>> CREATE NETWORK') network_id = create_network(shell.neutron, tenant_id, args.NETWORK_NAME) print('>>> CREATE SUBNET') subnet = create_subnet(shell.neutron, tenant_id, network_id, args.SUBNET_NAME, args.SUBNET_CIDR, args.DNS_SERVER) print('>>> CREATE ROUTER') router = create_router(shell.neutron, tenant_id, args.PUBLIC_NETWORK, args.ROUTER_NAME) print('>>> CREATE INTERFACE') create_interface(shell.neutron, router, subnet['subnet']['id']) # Comment out metering label before we fixed the issue when it works with # VPNaaS. # print('>>> CREATE METER LABEL') # meter_label_id = create_meter_label(shell.neutron, args, tenant_id) # print('>>> CREATE METER LABEL RULE') # direction = args.METER_RULE_DIRECTION # if direction in ('ingress', 'egress'): # create_meter_label_rule(shell.neutron, meter_label_id, direction) # elif direction == 'both': # create_meter_label_rule(shell.neutron, meter_label_id, 'ingress') # create_meter_label_rule(shell.neutron, meter_label_id, 'egress') print('>>> COMPLETE SUCCESSFULLY') @arg('--label-name-template', type=str, metavar='LABEL_NAME_TEMPLATE', dest='LABEL_NAME_TEMPLATE', default='meter-label-{0}', help='Define a name template to add meter label for existed tenants. ' 'Such as: meter-label-{0}, {0} will be replaced by the tenant name' ' automatically.') @arg('--meter-rule-direction', type=str, required=False, default='both', choices=('ingress', 'egress', 'both'), metavar='METER_RULE_DIRECTION', dest='METER_RULE_DIRECTION', help='Direction of meter label rule.') def do_meter(shell, args): """Add meter label for all existed tenants for network traffic billing. """ print('>>> TENANT LIST MISSING METER LABEL') tenants = shell.keystone.tenants.list() meter_labels = shell.neutron.list_metering_labels() dict_tenants = {} for tenant in tenants: dict_tenants[tenant.id] = tenant for label in meter_labels['metering_labels']: if label['tenant_id'] in dict_tenants.keys(): del dict_tenants[label['tenant_id']] print_list(dict_tenants.values(), ['id', 'name', 'enabled']) if len(dict_tenants.values()) <= 0: print('>>> ALL TENANTS HAVE METER LABEL') return 0 answer = raw_input('Create meter label/rules for above tenants(Y/n)?') if answer.lower() == 'y': for tenant in dict_tenants.values(): label_name = args.LABEL_NAME_TEMPLATE.format(tenant.name) args.METER_LABEL_NAME = label_name args.METER_LABEL_DESC = 'Meter label of {0}'.format(tenant.name) meter_label_id = create_meter_label(shell.neutron, args, tenant.id) if meter_label_id: direction = args.METER_RULE_DIRECTION if direction in ('ingress', 'egress'): create_meter_label_rule(shell.neutron, meter_label_id, direction) elif direction == 'both': create_meter_label_rule(shell.neutron, meter_label_id, 'ingress') create_meter_label_rule(shell.neutron, meter_label_id, 'egress') print('>>> COMPLETE SUCCESSFULLY') @arg('--tenant-id', type=str, metavar='TENANT_ID', dest='TENANT_ID', required=True, help='ID of the tenant to be deleted.') @arg('--auto-clean', type=bool, metavar='AUTO_CLEAN', dest='AUTO_CLEAN', default=False, help='Auto clean all resources.') def do_delete(shell, args): """Delete tenant and all resources associated with it to avoid leaving any legacy stuff """ tenant = shell.keystone.tenants.get(args.TENANT_ID) shell.tenant = tenant if not prompt_yes_no('Tenant [%s] will be deleted. ' 'Please confirm to continue.' % tenant.name, default='yes'): return component_list = ['nova', 'neutron', 'cinder', 'glance', 'swift', 'heat', 'keystone'] myself = __import__('tenant') for component in component_list: callback = getattr(myself, component.lower() + '_delete') print('>>> TO DELETE ' + component.upper()) try: callback(shell, args) except Exception as e: print(e) print('\n\nTenant %s has been cleaned up based on above selections.' % args.TENANT_ID) def keystone_delete(shell, args): try: if(args.AUTO_CLEAN or prompt_yes_no('Please confirm to delete the' ' tenant from Keystone:')): shell.keystone.tenants.delete(args.TENANT_ID) except Exception as e: raise e def nova_delete(shell, args): # NOTE(flwang): Seems there is a bug for nova, it doesn't honour the # project id though based on the code it does. Will dig it later. print('>>>>>> SERVERS(VM) LIST') servers = shell.nova.servers.list(search_opts={'all_tenants': True}) servers = [s for s in servers if s.tenant_id == args.TENANT_ID] print_list(servers, ['id', 'name', 'status', 'tenant_id']) if (len(servers) and (args.AUTO_CLEAN or prompt_yes_no('Please confirm:'))): for server in servers: shell.nova.servers.delete(server.id) def glance_delete(shell, args): images = shell.glance.images.list(owner=args.TENANT_ID) # NOTE(flwang): Make sure the images are what we want to delete images = [img for img in images if img.owner == args.TENANT_ID] print_list(images, ['id', 'name', 'owner']) if (len(images) and (args.AUTO_CLEAN or prompt_yes_no('Please confirm:'))): for image in images: shell.glance.images.delete(image.id) def cinder_delete(shell, args): print('>>>>>> VOLUME SNAPSHOTS') # Delete snapshots snapshots = shell.cinder.volume_snapshots.list(search_opts={'all_tenants': True}) tenant_attr = 'os-extended-snapshot-attributes:project_id' # NOTE(flwang): If the script user is admin then it will get all the # snapshot and each snapshot will have the attribute # 'os-extended-snapshot-attributes:project_id' to indicate the tenant. # For non-admin, there is no that attribute. user_roles = shell.keystone.session.auth.auth_ref['user']['roles'] if {u'name': u'admin'} in user_roles: snapshots = [v for v in snapshots if getattr(v, tenant_attr) == args.TENANT_ID] print_list(snapshots, ['id', 'display_name', 'status', tenant_attr]) else: print_list(snapshots, ['id', 'display_name', 'status']) if (len(snapshots) and (args.AUTO_CLEAN or prompt_yes_no('Please confirm:'))): for snapshot in snapshots: shell.cinder.volume_snapshots.delete(snapshot.id) print('>>>>>> VOLUMES') # Delete volumes volumes = shell.cinder.volumes.list(search_opts={'all_tenants': True}) tenant_attr = 'os-vol-tenant-attr:tenant_id' if {u'name': u'admin'} in user_roles: volumes = [v for v in volumes if getattr(v, tenant_attr) == args.TENANT_ID] print_list(volumes, ['id', 'display_name', 'status', tenant_attr]) else: print_list(volumes, ['id', 'display_name', 'status']) if (len(volumes) and (args.AUTO_CLEAN or prompt_yes_no('Please confirm:'))): for volume in volumes: shell.cinder.volumes.delete(volume.id) def swift_delete(shell, args): endpoint = shell.keystone.service_catalog.url_for(service_type='object-store') # noqa url = endpoint.split('_')[0] + '_' + args.TENANT_ID try: # Get a specific token for swift ks = keystone_client.Client(username=args.OS_USERNAME, password=args.OS_PASSWORD, tenant_name=shell.tenant.name, auth_url=args.OS_AUTH_URL, region_name=args.OS_REGION_NAME, cacert=args.OS_CACERT, insecure=args.OS_INSECURE) account = getattr(swift_client, 'get_account')(url, ks.auth_token) print_list(account[1], ['name', 'count', 'bytes']) if (len(account[1]) and (args.AUTO_CLEAN or prompt_yes_no('Please confirm:'))): for co in account[1]: co_obj = getattr(swift_client, 'get_container')(url, ks.auth_token, co['name']) # Delete objects firstly for obj in co_obj[1]: getattr(swift_client, 'delete_object')(url, ks.auth_token, container=co['name'], name=obj['name']) # Delete container getattr(swift_client, 'delete_container')(url, ks.auth_token, co['name']) except swift_exceptions.ClientException as e: print('%s %s' % (e.http_status, e.http_reason)) def neutron_delete(shell, args): # vpn service _delete_targeted_tenant_resource(shell, args, 'ipsec_site_connection') _delete_targeted_tenant_resource(shell, args, 'ipsecpolicy') _delete_targeted_tenant_resource(shell, args, 'ikepolicy') _delete_targeted_tenant_resource(shell, args, 'vpnservice') # meter label and rules print('>>>>>> METER LABEL RULE LIST') if not hasattr(shell.neutron, 'list_metering_labels'): return metering_labels = shell.neutron.list_metering_labels()['metering_labels'] metering_label_rules = shell.neutron.list_metering_label_rules() metering_label_rules = metering_label_rules['metering_label_rules'] targeted_rules = [] for rule in metering_label_rules: if rule['metering_label_id'] in [m['id'] for m in metering_labels if m['tenant_id'] == args.TENANT_ID]: targeted_rules.append(rule) print_list(targeted_rules, ['id', 'name', 'tenant_id']) if (len(targeted_rules) and (args.AUTO_CLEAN or prompt_yes_no('Please confirm:'))): for rule in targeted_rules: shell.neutron.delete_metering_label_rule(rule['id']) print('>>>>>> METER LABEL LIST') metering_labels = [m for m in metering_labels if m['tenant_id'] == args.TENANT_ID] print_list(metering_labels, ['id', 'name', 'tenant_id']) if (len(metering_labels) and (args.AUTO_CLEAN or prompt_yes_no('Please confirm:'))): for label in metering_labels: shell.neutron.delete_metering_label(label['id']) # port print('>>>>>> PORT LIST') ports = shell.neutron.list_ports() targeted_ports = [] for port in ports['ports']: if port['tenant_id'] == args.TENANT_ID: targeted_ports.append(port) print_list(targeted_ports, ['id', 'name', 'tenant_id']) if (len(targeted_ports) and (args.AUTO_CLEAN or prompt_yes_no('Please confirm:'))): for port in targeted_ports: try: if port['device_owner'] == 'network:router_gateway': shell.neutron.remove_gateway_router(port['device_id']) elif port['device_owner'] == 'network:router_interface': for subnet in port['fixed_ips']: body = {'subnet_id': subnet['subnet_id']} shell.neutron.remove_interface_router( port['device_id'], body) else: shell.neutron.delete_port(port['id']) except Exception as e: print('Failed to delete port:{0}, see: {1}'. format(port['id'], str(e))) continue # security group _delete_targeted_tenant_resource(shell, args, 'security_group') # floating IP _delete_targeted_tenant_resource(shell, args, 'floatingip') # sub net _delete_targeted_tenant_resource(shell, args, 'subnet') # router _delete_targeted_tenant_resource(shell, args, 'router') # network _delete_targeted_tenant_resource(shell, args, 'network') def heat_delete(shell, args): print('>>>>>> STACKS LIST') try: heat_srv = shell.keystone.services.find(type='orchestration') heat_endpoint = shell.keystone.endpoints.find(service_id=heat_srv.id) heat_url = heat_endpoint.publicurl.replace('$(tenant_id)s', args.TENANT_ID) heat = heat_client.Client('1', endpoint=heat_url, token=shell.keystone.auth_token) except Exception as e: raise e stacks = heat.stacks.list() stacks = [s for s in stacks] print_list(stacks, ['id', 'stack_name', 'stack_status']) if (len(stacks) and (args.AUTO_CLEAN or prompt_yes_no('Please confirm:'))): for stack in stacks: heat.stacks.delete(stack.id) def _delete_targeted_tenant_resource(shell, args, resource): print('>>>>>> %s LIST' % resource.upper()) list_command = 'list_{0}s'.format(resource) if resource in ('ipsecpolicy', 'ikepolicy'): list_command = list_command.replace('policy', 'policie') resources = getattr(shell.neutron, list_command)() resp_key = resource + 's' if resource in ('ipsecpolicy', 'ikepolicy'): resp_key = resp_key.replace('policy', 'policie') delete_resources = [] for res in resources[resp_key]: if res['tenant_id'] == args.TENANT_ID: delete_resources.append(res) print_list(delete_resources, ['id', 'name', 'tenant_id']) if (len(delete_resources) and (args.AUTO_CLEAN or prompt_yes_no('Please confirm:'))): delete_function = getattr(shell.neutron, 'delete_{0}'.format(resource)) for res in delete_resources: delete_function(res['id']) @arg('--auto-clean', type=bool, metavar='AUTO_CLEAN', dest='AUTO_CLEAN', default=False, help='Auto clean legacy resources.') @arg('--component', type=str, metavar='COMPONENT', dest='COMPONENT', help='Specific component to audit.') def do_audit(shell, args): """Audit all existed tenants to make sure everything is OK. Tenant deletion related blueprints: [1] https://blueprints.launchpad.net/keystone/+spec/notifications [2] https://blueprints.launchpad.net/neutron/+spec/tenant-delete """ user_roles = shell.keystone.session.auth.auth_ref['user']['roles'] if {u'name': u'admin'} not in user_roles: print('Admin permission is required.') return component_list = ['nova', 'neutron', 'cinder', 'glance'] tenants = shell.keystone.tenants.list() tenant_ids = [t.id for t in tenants] myself = __import__('tenant') if args.COMPONENT: callback = getattr(myself, args.COMPONENT.lower() + '_audit') print('>>> AUDITING ' + args.COMPONENT.upper()) callback(shell, args, tenant_ids) else: for component in component_list: callback = getattr(myself, component.lower() + '_audit') print('>>> AUDITING ' + component.upper()) callback(shell, args, tenant_ids) def nova_audit(shell, args, tenant_ids): # instance print('>>>>>> ZOMBIE INSTANCE LIST') servers = shell.nova.servers.list(search_opts={'all_tenants': True}) zombie_servers = [s for s in servers if s.tenant_id not in tenant_ids] print_list(zombie_servers, ['id', 'name', 'tenant_id']) if ((len(zombie_servers) and (args.AUTO_CLEAN or prompt_yes_no('Confirm to delete:')))): for server in zombie_servers: shell.nova.servers.delete(server.id) def neutron_audit(shell, args, tenant_ids): # Clean up services, FWaaS, LBaaS, etc _clean_up_resource(shell, args, tenant_ids, 'ipsec_site_connection') _clean_up_resource(shell, args, tenant_ids, 'ipsecpolicy') _clean_up_resource(shell, args, tenant_ids, 'ikepolicy') _clean_up_resource(shell, args, tenant_ids, 'vpnservice') # metering label _clean_up_resource(shell, args, tenant_ids, 'metering_label') # meter label rule print('>>>>>> ZOMBIE METER LABEL RULE LIST') metering_labels = shell.neutron.list_metering_labels() metering_label_ids = [m['id'] for m in metering_labels['metering_labels']] metering_label_rules = shell.neutron.list_metering_label_rules() zombie_metering_label_rules = [] for metering_label_rule in metering_label_rules['metering_label_rules']: if metering_label_rule['metering_label_id'] not in metering_label_ids: zombie_metering_label_rules.append(metering_label_rule) print_list(zombie_metering_label_rules, ['id', 'name', 'tenant_id']) if ((len(zombie_metering_label_rules) > 0 and (args.AUTO_CLEAN or prompt_yes_no('Confirm to delete:')))): for metering_label_rule in zombie_metering_label_rules: shell.neutron.delete_metering_label_rule(metering_label_rule['id']) # floating ip _clean_up_resource(shell, args, tenant_ids, 'floatingip') # port print('>>>>>> ZOMBIE PORT LIST') ports = shell.neutron.list_ports() zombie_ports = [] for port in ports['ports']: if port['tenant_id'] not in tenant_ids: zombie_ports.append(port) print_list(zombie_ports, ['id', 'name', 'tenant_id']) if (len(zombie_ports) > 0 and (args.AUTO_CLEAN or prompt_yes_no('Confirm to delete:'))): for port in zombie_ports: try: if port['device_owner'] == 'network:router_gateway': shell.neutron.remove_gateway_router(port['device_id']) elif port['device_owner'] == 'network:router_interface': for subnet in port['fixed_ips']: body = {'subnet_id': subnet['subnet_id']} shell.neutron.remove_interface_router( port['device_id'], body) else: shell.neutron.delete_port(port['id']) except Exception as e: print('Failed to delete port:{0}, see: {1}'.format(port['id'], str(e))) continue # security group _clean_up_resource(shell, args, tenant_ids, 'security_group') # subnet _clean_up_resource(shell, args, tenant_ids, 'subnet') # router _clean_up_resource(shell, args, tenant_ids, 'router') # network _clean_up_resource(shell, args, tenant_ids, 'network') def _clean_up_resource(shell, args, tenant_ids, resource): print('>>>>>> ZOMBIE %s LIST' % resource.upper()) resources = getattr(shell.neutron, 'list_{0}s'.format(resource))() zombie_resources = [r for r in resources[resource + 's'] if r['tenant_id'] not in tenant_ids] print_list(zombie_resources, ['id', 'name', 'tenant_id']) if (len(zombie_resources) > 0 and (args.AUTO_CLEAN or prompt_yes_no('Confirm to delete:'))): delete_function = getattr(shell.neutron, 'delete_{0}'.format(resource)) for res in zombie_resources: delete_function(res['id']) def cinder_audit(shell, args, tenant_ids): print('>>>>>> ZOMBIE VOLUME SNAPSHOTS LIST') # snapshots snapshots = shell.cinder.volume_snapshots.list(search_opts={'all_tenants': True}) tenant_attr = 'os-extended-snapshot-attributes:project_id' zombie_snapshots = [s for s in snapshots if getattr(s, tenant_attr) not in tenant_ids] print_list(zombie_snapshots, ['id', 'display_name', 'status', tenant_attr]) if (len(zombie_snapshots) > 0 and (args.AUTO_CLEAN or prompt_yes_no('Confirm to delete:'))): for snapshot in zombie_snapshots: shell.cinder.volume_snapshots.delete(snapshot.id) # volume print('>>>>>> ZOMBIE VOLUME LIST') volumes = shell.cinder.volumes.list(search_opts={'all_tenants': True}) tenant_attr = 'os-vol-tenant-attr:tenant_id' zombie_volumes = [v for v in volumes if getattr(v, tenant_attr) not in tenant_ids] print_list(zombie_volumes, ['id', 'display_name', 'os-vol-tenant-attr:tenant_id']) if (len(zombie_volumes) > 0 and (args.AUTO_CLEAN or prompt_yes_no('Confirm to delete:'))): for volume in zombie_volumes: shell.cinder.volumes.delete(volume.id) def glance_audit(shell, args, tenant_ids): # image print('>>>>>> ZOMBIE IMAGE LIST') images = shell.glance.images.list(filters={"is_public": None}) zombie_images = [i for i in images if (i.owner not in tenant_ids) and (not i.is_public)] print_list(zombie_images, ['id', 'name', 'owner']) if (len(zombie_images) > 0 and (args.AUTO_CLEAN or prompt_yes_no('Confirm to delete:'))): for image in zombie_images: shell.glance.images.delete(image.id) def swift_audit(shell, args, tenant_ids): # TODO(flwang): Seems Swift can't get all the resources with admin # because all its resources will be associated with a tenant. That means # without tenant id, you can't list them. So it's hard to detect the # zombie resources. pass def create_tenant(keystone, args): try: tenant = keystone.tenants.create(tenant_name=args.TENANT_NAME, description=args.TENANT_DESC, enabled=True) print_dict(tenant._info) except Exception as e: raise e return tenant.id def add_admin_to_tenant(keystone, tenant_id): # Add admin user into the new tenant since it is required for monitoring try: admin_user_id, admin_role_id = get_admin_user_role(keystone) keystone.roles.add_user_role(admin_user_id, admin_role_id, tenant_id) except Exception as e: raise e def get_admin_user_role(keystone): # NOTE(flwang): Here is assuming that both the admin name and role name # using 'admin' as the name. try: admin_user_id = [r.id for r in keystone.users.list() if r.name == 'admin'] admin_role_id = [r.id for r in keystone.roles.list() if r.name == 'admin'] return (admin_user_id[0], admin_role_id[0]) except Exception as e: raise e def create_network(neutron, tenant_id, network_name): if not network_name: network_name = raw_input('Please enter the network name:') try: network_body = { "network": { "name": network_name, 'tenant_id': tenant_id, "admin_state_up": True } } network = neutron.create_network(body=network_body) print_dict(network['network']) return network['network']['id'] except Exception as e: raise e def create_subnet(neutron, tenant_id, network_id, subnet_name, subnet_cidr, dns_servers): if not subnet_cidr: subnet_cidr = raw_input('Please enter the IP range(CIDR format):') try: dns_servers = dns_servers if dns_servers else DNS_NAMESERVERS subnet_body = { "subnet": { "network_id": network_id, "ip_version": 4, 'tenant_id': tenant_id, 'dns_nameservers': dns_servers, "cidr": subnet_cidr } } subnet = neutron.create_subnet(body=subnet_body) print_dict(subnet['subnet']) return subnet except Exception as e: raise e def create_router(neutron, tenant_id, public_network, router_name): if not router_name: router_name = raw_input('Please enter the router name:') if not public_network: public_network = raw_input('Please enter the public network id:') try: router_body = { "router": { "name": router_name, "external_gateway_info": { "network_id": public_network }, 'tenant_id': tenant_id, "admin_state_up": True } } router = neutron.create_router(body=router_body) print_dict(router['router']) return router except Exception as e: raise e def create_interface(neutron, router, subnet_id): try: interface_body = { "subnet_id": subnet_id } interface = neutron.add_interface_router(router['router']['id'], body=interface_body) print_dict(interface) except Exception as e: raise e def create_meter_label(neutron, args, tenant_id): try: meter_label_name = args.METER_LABEL_NAME if not meter_label_name: meter_label_name = 'meter-label-' + args.TENANT_NAME meter_label_body = { 'metering_label': { 'name': meter_label_name, 'tenant_id': tenant_id, 'description': args.METER_LABEL_DESC, } } meter_label = neutron.create_metering_label(body=meter_label_body) print_dict(meter_label['metering_label']) return meter_label['metering_label']['id'] except Exception as e: raise e def create_meter_label_rule(neutron, meter_label_id, direction): try: meter_rule_body = {"metering_label_rule": {"remote_ip_prefix": "0.0.0.0/0", "direction": direction, "metering_label_id": meter_label_id } } meter_rule = neutron.create_metering_label_rule(body=meter_rule_body) print_dict(meter_rule['metering_label_rule']) except Exception as e: raise e def rollback(args, **parms): # TODO(flwang): Remove the created resources if there is any failure. pass def print_list(objs, fields, formatters={}): pt = prettytable.PrettyTable([f for f in fields], caching=False) pt.align = 'l' for o in objs: row = [] for field in fields: if field in formatters: row.append(formatters[field](o)) else: field_name = field.lower().replace(' ', '_') if type(o) == dict and field in o: data = o[field_name] else: data = getattr(o, field_name, None) or '' row.append(data) pt.add_row(row) print(encodeutils.safe_encode(pt.get_string())) def prompt_yes_no(question, default="no"): """Ask a yes/no question via raw_input() and return their answer. "question" is a string that is presented to the user. "default" is the presumed answer if the user just hits . It must be "yes" (the default), "no" or None (meaning an answer is required of the user). The "answer" return value is one of "yes" or "no". """ valid = {"yes": True, "y": True, "ye": True, "no": False, "n": False} if default is None: prompt = " [y/n] " elif default == "yes": prompt = " [Y/n] " elif default == "no": prompt = " [y/N] " else: raise ValueError("invalid default answer: '%s'" % default) while True: sys.stdout.write(question + prompt) choice = raw_input().lower() if default is not None and choice == '': return valid[default] elif choice in valid: return valid[choice] else: sys.stdout.write("Please respond with 'yes' or 'no' " "(or 'y' or 'n').\n") def print_dict(d, max_column_width=80): pt = prettytable.PrettyTable(['Property', 'Value'], caching=False) pt.align = 'l' pt.max_width = max_column_width [pt.add_row(list(r)) for r in six.iteritems(d)] print(encodeutils.safe_encode(pt.get_string(sortby='Property'))) if __name__ == '__main__': try: TenantShell().main(sys.argv[1:]) except KeyboardInterrupt: print("Terminating...") sys.exit(1) except Exception as e: exc_type, exc_value, exc_traceback = sys.exc_info() traceback.print_exception(exc_type, exc_value, exc_traceback, limit=2, file=sys.stdout)