/tools/xenserver/vm_vdi_cleaner.py
Python | 385 lines | 338 code | 19 blank | 28 comment | 2 complexity | 0c9ae4e0646b9a192dfe2ae1d592c874 MD5 | raw file
- #!/usr/bin/env python
- # Copyright 2011 OpenStack, LLC
- #
- # 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.
- """vm_vdi_cleaner.py - List or clean orphaned VDIs/instances on XenServer."""
- import doctest
- import optparse
- import sys
- import XenAPI
- from nova import context
- from nova import db
- from nova import exception
- from nova import flags
- from nova import utils
- from nova.virt import xenapi_conn
- flags.DECLARE("resize_confirm_window", "nova.compute.manager")
- flags.DECLARE("xenapi_connection_url", "nova.virt.xenapi_conn")
- flags.DECLARE("xenapi_connection_username", "nova.virt.xenapi_conn")
- flags.DECLARE("xenapi_connection_password", "nova.virt.xenapi_conn")
- FLAGS = flags.FLAGS
- # NOTE(sirp): Nova futzs with the sys.argv in order to provide default
- # flagfile. To isolate this awful practice, we're supplying a dummy
- # argument list.
- dummy = ["fakearg"]
- utils.default_flagfile(args=dummy)
- FLAGS(dummy)
- class UnrecognizedNameLabel(Exception):
- pass
- def parse_options():
- """Generate command line options."""
- ALLOWED_COMMANDS = ["list-vdis", "clean-vdis", "list-instances",
- "clean-instances", "test"]
- arg_str = "|".join(ALLOWED_COMMANDS)
- parser = optparse.OptionParser("%prog [options] [" + arg_str + "]")
- parser.add_option("--verbose", action="store_true")
- options, args = parser.parse_args()
- if not args:
- parser.print_usage()
- sys.exit(1)
- return options, args
- def get_instance_id_from_name_label(name_label, template):
- """In order to derive the instance_id from the name label on the VM, we
- take the following steps:
- 1. We substitute a dummy value in to the instance_name_template so we
- can figure out the prefix and the suffix of the template (the
- instance_id is between the two)
- 2. We delete the prefix and suffix from the name_label.
- 3. What's left *should* be the instance_id which we cast to an int
- and return.
- >>> get_instance_id_from_name_label("", "instance-%08x")
- Traceback (most recent call last):
- ...
- UnrecognizedNameLabel
- >>> get_instance_id_from_name_label("instance-00000001", "instance-%08x")
- 1
- >>> get_instance_id_from_name_label("instance-0000000A", "instance-%08x")
- 10
- >>> get_instance_id_from_name_label("instance-42-suffix", \
- "instance-%d-suffix")
- 42
- """
- # Interpolate template to figure out where to extract the instance_id from.
- # The instance_id may be in hex "%x" or decimal "%d", so try decimal first
- # then fall back to hex.
- fake_instance_id = 123456789
- result = template % fake_instance_id
- in_hex = False
- base_10 = "%d" % fake_instance_id
- try:
- prefix, suffix = result.split(base_10)
- except ValueError:
- base_16 = "%x" % fake_instance_id
- prefix, suffix = result.split(base_16)
- in_hex = True
- if prefix:
- name_label = name_label.replace(prefix, '')
- if suffix:
- name_label = name_label.replace(suffix, '')
- try:
- if in_hex:
- instance_id = int(name_label, 16)
- else:
- instance_id = int(name_label)
- except ValueError:
- raise UnrecognizedNameLabel(name_label)
- return instance_id
- def find_orphaned_instances(session, verbose=False):
- """Find and return a list of orphaned instances."""
- ctxt = context.get_admin_context()
- ctxt.read_deleted = True
- orphaned_instances = []
- for vm_rec in _get_applicable_vm_recs(session):
- try:
- instance_id = get_instance_id_from_name_label(
- vm_rec["name_label"], FLAGS.instance_name_template)
- except UnrecognizedNameLabel, exc:
- print_xen_object("WARNING: Unrecognized VM", vm_rec,
- indent_level=0, verbose=verbose)
- continue
- try:
- instance = db.api.instance_get(ctxt, instance_id)
- except exception.InstanceNotFound:
- # NOTE(jk0): Err on the side of caution here. If we don't know
- # anything about the particular instance, ignore it.
- print_xen_object("INFO: Ignoring VM", vm_rec, indent_level=0,
- verbose=verbose)
- continue
- # NOTE(jk0): This would be triggered if a VM was deleted but the
- # actual deletion process failed somewhere along the line.
- is_active_and_deleting = (instance.vm_state == "active" and
- instance.task_state == "deleting")
- # NOTE(jk0): A zombie VM is an instance that is not active and hasn't
- # been updated in over the specified period.
- is_zombie_vm = (instance.vm_state != "active"
- and utils.is_older_than(instance.updated_at,
- FLAGS.zombie_instance_updated_at_window))
- if is_active_and_deleting or is_zombie_vm:
- orphaned_instances.append(instance)
- return orphaned_instances
- def cleanup_instance(session, instance):
- """Delete orphaned instances."""
- network_info = None
- connection = xenapi_conn.get_connection(_)
- connection.destroy(instance, network_info)
- def _get_applicable_vm_recs(session):
- """An 'applicable' VM is one that is not a template and not the control
- domain.
- """
- for vm_ref in session.xenapi.VM.get_all():
- vm_rec = session.xenapi.VM.get_record(vm_ref)
- if vm_rec["is_a_template"] or vm_rec["is_control_domain"]:
- continue
- yield vm_rec
- def print_xen_object(obj_type, obj, indent_level=0, spaces_per_indent=4,
- verbose=False):
- """Pretty-print a Xen object.
- Looks like:
- VM (abcd-abcd-abcd): 'name label here'
- """
- if not verbose:
- return
- uuid = obj["uuid"]
- try:
- name_label = obj["name_label"]
- except KeyError:
- name_label = ""
- msg = "%(obj_type)s (%(uuid)s) '%(name_label)s'" % locals()
- indent = " " * spaces_per_indent * indent_level
- print "".join([indent, msg])
- def _find_vdis_connected_to_vm(session, connected_vdi_uuids, verbose=False):
- """Find VDIs which are connected to VBDs which are connected to VMs."""
- def _is_null_ref(ref):
- return ref == "OpaqueRef:NULL"
- def _add_vdi_and_parents_to_connected(vdi_rec, indent_level):
- indent_level += 1
- vdi_and_parent_uuids = []
- cur_vdi_rec = vdi_rec
- while True:
- cur_vdi_uuid = cur_vdi_rec["uuid"]
- print_xen_object("VDI", vdi_rec, indent_level=indent_level,
- verbose=verbose)
- connected_vdi_uuids.add(cur_vdi_uuid)
- vdi_and_parent_uuids.append(cur_vdi_uuid)
- try:
- parent_vdi_uuid = vdi_rec["sm_config"]["vhd-parent"]
- except KeyError:
- parent_vdi_uuid = None
- # NOTE(sirp): VDI's can have themselves as a parent?!
- if parent_vdi_uuid and parent_vdi_uuid != cur_vdi_uuid:
- indent_level += 1
- cur_vdi_ref = session.xenapi.VDI.get_by_uuid(
- parent_vdi_uuid)
- cur_vdi_rec = session.xenapi.VDI.get_record(
- cur_vdi_ref)
- else:
- break
- for vm_rec in _get_applicable_vm_recs(session):
- indent_level = 0
- print_xen_object("VM", vm_rec, indent_level=indent_level,
- verbose=verbose)
- vbd_refs = vm_rec["VBDs"]
- for vbd_ref in vbd_refs:
- vbd_rec = session.xenapi.VBD.get_record(vbd_ref)
- indent_level = 1
- print_xen_object("VBD", vbd_rec, indent_level=indent_level,
- verbose=verbose)
- vbd_vdi_ref = vbd_rec["VDI"]
- if _is_null_ref(vbd_vdi_ref):
- continue
- vdi_rec = session.xenapi.VDI.get_record(vbd_vdi_ref)
- _add_vdi_and_parents_to_connected(vdi_rec, indent_level)
- def _find_all_vdis_and_system_vdis(session, all_vdi_uuids, connected_vdi_uuids,
- verbose=False):
- """Collects all VDIs and adds system VDIs to the connected set."""
- def _system_owned(vdi_rec):
- vdi_name = vdi_rec["name_label"]
- return (vdi_name.startswith("USB") or
- vdi_name.endswith(".iso") or
- vdi_rec["type"] == "system")
- for vdi_ref in session.xenapi.VDI.get_all():
- vdi_rec = session.xenapi.VDI.get_record(vdi_ref)
- vdi_uuid = vdi_rec["uuid"]
- all_vdi_uuids.add(vdi_uuid)
- # System owned and non-managed VDIs should be considered 'connected'
- # for our purposes.
- if _system_owned(vdi_rec):
- print_xen_object("SYSTEM VDI", vdi_rec, indent_level=0,
- verbose=verbose)
- connected_vdi_uuids.add(vdi_uuid)
- elif not vdi_rec["managed"]:
- print_xen_object("UNMANAGED VDI", vdi_rec, indent_level=0,
- verbose=verbose)
- connected_vdi_uuids.add(vdi_uuid)
- def find_orphaned_vdi_uuids(session, verbose=False):
- """Walk VM -> VBD -> VDI change and accumulate connected VDIs."""
- connected_vdi_uuids = set()
- _find_vdis_connected_to_vm(session, connected_vdi_uuids, verbose=verbose)
- all_vdi_uuids = set()
- _find_all_vdis_and_system_vdis(session, all_vdi_uuids, connected_vdi_uuids,
- verbose=verbose)
- orphaned_vdi_uuids = all_vdi_uuids - connected_vdi_uuids
- return orphaned_vdi_uuids
- def list_orphaned_vdis(vdi_uuids, verbose=False):
- """List orphaned VDIs."""
- for vdi_uuid in vdi_uuids:
- if verbose:
- print "ORPHANED VDI (%s)" % vdi_uuid
- else:
- print vdi_uuid
- def clean_orphaned_vdis(session, vdi_uuids, verbose=False):
- """Clean orphaned VDIs."""
- for vdi_uuid in vdi_uuids:
- if verbose:
- print "CLEANING VDI (%s)" % vdi_uuid
- vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid)
- try:
- session.xenapi.VDI.destroy(vdi_ref)
- except XenAPI.Failure, exc:
- print >> sys.stderr, "Skipping %s: %s" % (vdi_uuid, exc)
- def list_orphaned_instances(orphaned_instances, verbose=False):
- """List orphaned instances."""
- for orphaned_instance in orphaned_instances:
- if verbose:
- print "ORPHANED INSTANCE (%s)" % orphaned_instance.name
- else:
- print orphaned_instance.name
- def clean_orphaned_instances(session, orphaned_instances, verbose=False):
- """Clean orphaned instances."""
- for instance in orphaned_instances:
- if verbose:
- print "CLEANING INSTANCE (%s)" % instance.name
- cleanup_instance(session, instance)
- def main():
- """Main loop."""
- options, args = parse_options()
- verbose = options.verbose
- command = args[0]
- if FLAGS.zombie_instance_updated_at_window < FLAGS.resize_confirm_window:
- raise Exception("`zombie_instance_updated_at_window` has to be longer"
- " than `resize_confirm_window`.")
- session = XenAPI.Session(FLAGS.xenapi_connection_url)
- session.xenapi.login_with_password(FLAGS.xenapi_connection_username,
- FLAGS.xenapi_connection_password)
- if command == "list-vdis":
- if verbose:
- print "Connected VDIs:\n"
- orphaned_vdi_uuids = find_orphaned_vdi_uuids(session, verbose=verbose)
- if verbose:
- print "\nOprhaned VDIs:\n"
- list_orphaned_vdis(orphaned_vdi_uuids, verbose=verbose)
- elif command == "clean-vdis":
- orphaned_vdi_uuids = find_orphaned_vdi_uuids(session, verbose=verbose)
- clean_orphaned_vdis(session, orphaned_vdi_uuids, verbose=verbose)
- elif command == "list-instances":
- orphaned_instances = find_orphaned_instances(session, verbose=verbose)
- list_orphaned_instances(orphaned_instances, verbose=verbose)
- elif command == "clean-instances":
- orphaned_instances = find_orphaned_instances(session, verbose=verbose)
- clean_orphaned_instances(session, orphaned_instances,
- verbose=verbose)
- elif command == "test":
- doctest.testmod()
- else:
- print "Unknown command '%s'" % command
- sys.exit(1)
- if __name__ == "__main__":
- main()