From 45c0ea4a3e6603e5be30b3ff8e90e4c4d0cb4136 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Mon, 31 Aug 2020 15:01:53 +0100 Subject: tools: Remove xenserver tooling These will not be used in a world without the XenAPI driver. Change-Id: I5bc3c7855b817c4ce2b8919c76be80cceabeec9e Signed-off-by: Stephen Finucane --- tools/xenserver/destroy_cached_images.py | 85 --------- tools/xenserver/populate_other_config.py | 103 ---------- tools/xenserver/rotate_xen_guest_logs.sh | 69 ------- tools/xenserver/stress_test.py | 181 ------------------ tools/xenserver/vdi_chain_cleanup.py | 128 ------------- tools/xenserver/vm_vdi_cleaner.py | 316 ------------------------------- 6 files changed, 882 deletions(-) delete mode 100644 tools/xenserver/destroy_cached_images.py delete mode 100644 tools/xenserver/populate_other_config.py delete mode 100755 tools/xenserver/rotate_xen_guest_logs.sh delete mode 100644 tools/xenserver/stress_test.py delete mode 100644 tools/xenserver/vdi_chain_cleanup.py delete mode 100755 tools/xenserver/vm_vdi_cleaner.py (limited to 'tools') diff --git a/tools/xenserver/destroy_cached_images.py b/tools/xenserver/destroy_cached_images.py deleted file mode 100644 index 9f095a1b5c..0000000000 --- a/tools/xenserver/destroy_cached_images.py +++ /dev/null @@ -1,85 +0,0 @@ -# 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. - -""" -destroy_cached_images.py - -This script is used to clean up Glance images that are cached in the SR. By -default, this script will only cleanup unused cached images. - -Options: - - --dry_run - Don't actually destroy the VDIs - --all_cached - Destroy all cached images instead of just unused cached - images. - --keep_days - N - Only remove those cached images which were created - more than N days ago. -""" -import eventlet -eventlet.monkey_patch() - -import os -import sys - -from os_xenapi.client import session -from oslo_config import cfg - -# If ../nova/__init__.py exists, add ../ to Python search path, so that -# it will override what happens to be installed in /usr/(local/)lib/python... -POSSIBLE_TOPDIR = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), - os.pardir, - os.pardir, - os.pardir)) -if os.path.exists(os.path.join(POSSIBLE_TOPDIR, 'nova', '__init__.py')): - sys.path.insert(0, POSSIBLE_TOPDIR) - -import nova.conf -from nova import config -from nova.virt.xenapi import vm_utils - -destroy_opts = [ - cfg.BoolOpt('all_cached', - default=False, - help='Destroy all cached images instead of just unused cached' - ' images.'), - cfg.BoolOpt('dry_run', - default=False, - help='Don\'t actually delete the VDIs.'), - cfg.IntOpt('keep_days', - default=0, - help='Destroy cached images which were' - ' created over keep_days.') -] - -CONF = nova.conf.CONF -CONF.register_cli_opts(destroy_opts) - -def main(): - config.parse_args(sys.argv) - - _session = session.XenAPISession(CONF.xenserver.connection_url, - CONF.xenserver.connection_username, - CONF.xenserver.connection_password) - - sr_ref = vm_utils.safe_find_sr(_session) - destroyed = vm_utils.destroy_cached_images( - _session, sr_ref, all_cached=CONF.all_cached, - dry_run=CONF.dry_run, keep_days=CONF.keep_days) - - if '--verbose' in sys.argv: - print('\n'.join(destroyed)) - - print("Destroyed %d cached VDIs" % len(destroyed)) - - -if __name__ == "__main__": - main() diff --git a/tools/xenserver/populate_other_config.py b/tools/xenserver/populate_other_config.py deleted file mode 100644 index 8613ddfb0b..0000000000 --- a/tools/xenserver/populate_other_config.py +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2013 OpenStack Foundation -# -# 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. -""" -One-time script to populate VDI.other_config. - -We use metadata stored in VDI.other_config to associate a VDI with a given -instance so that we may safely cleanup orphaned VDIs. - -We had a bug in the code that meant that the vast majority of VDIs created -would not have the other_config populated. - -After deploying the fixed code, this script is intended to be run against all -compute-workers in a cluster so that existing VDIs can have their other_configs -populated. - -Run on compute-worker (not Dom0): - - python ./tools/xenserver/populate_other_config.py [--dry-run] -""" -import os -import sys - -possible_topdir = os.getcwd() -if os.path.exists(os.path.join(possible_topdir, "nova", "__init__.py")): - sys.path.insert(0, possible_topdir) - -from oslo_config import cfg -from oslo_utils import uuidutils - -from nova import config -from nova.virt import virtapi -from nova.virt.xenapi import driver as xenapi_driver -from nova.virt.xenapi import vm_utils - -cli_opts = [ - cfg.BoolOpt('dry-run', - default=False, - help='Whether to actually update other_config.'), -] - -CONF = cfg.CONF -CONF.register_cli_opts(cli_opts) - - -def main(): - config.parse_args(sys.argv) - - xenapi = xenapi_driver.XenAPIDriver(virtapi.VirtAPI()) - session = xenapi._session - - vdi_refs = session.call_xenapi('VDI.get_all') - for vdi_ref in vdi_refs: - vdi_rec = session.call_xenapi('VDI.get_record', vdi_ref) - - other_config = vdi_rec['other_config'] - - # Already set... - if 'nova_instance_uuid' in other_config: - continue - - name_label = vdi_rec['name_label'] - - # We only want name-labels of form instance--[optional-suffix] - if not name_label.startswith('instance-'): - continue - - # Parse out UUID - instance_uuid = name_label.replace('instance-', '')[:36] - if not uuidutils.is_uuid_like(instance_uuid): - print("error: name label '%s' wasn't UUID-like" % name_label) - continue - - vdi_type = vdi_rec['name_description'] - - # We don't need a full instance record, just the UUID - instance = {'uuid': instance_uuid} - - if not CONF.dry_run: - vm_utils._set_vdi_info(session, vdi_ref, vdi_type, name_label, - vdi_type, instance) - - print("Setting other_config for instance_uuid=%s vdi_uuid=%s" % ( - instance_uuid, vdi_rec['uuid'])) - - if CONF.dry_run: - print("Dry run completed") - - -if __name__ == "__main__": - main() diff --git a/tools/xenserver/rotate_xen_guest_logs.sh b/tools/xenserver/rotate_xen_guest_logs.sh deleted file mode 100755 index f01051c96c..0000000000 --- a/tools/xenserver/rotate_xen_guest_logs.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/bin/bash -set -eux - -# Script to rotate console logs -# -# Should be run on Dom0, with cron, every minute: -# * * * * * /root/rotate_xen_guest_logs.sh -# -# Should clear out the guest logs on every boot -# because the domain ids may get re-used for a -# different tenant after the reboot -# -# /var/log/xen/guest should be mounted into a -# small loopback device to stop any guest being -# able to fill dom0 file system - -log_dir="/var/log/xen/guest" -kb=1024 -max_size_bytes=$(($kb*$kb)) -truncated_size_bytes=$((5*$kb)) -syslog_tag='rotate_xen_guest_logs' - -log_file_base="${log_dir}/console." - -# Only delete log files older than this number of minutes -# to avoid a race where Xen creates the domain and starts -# logging before the XAPI VM start returns (and allows us -# to preserve the log file using last_dom_id) -min_logfile_age=10 - -# Ensure logging is setup correctly for all domains -xenstore-write /local/logconsole/@ "${log_file_base}%d" - -# Grab the list of logs now to prevent a race where the domain is -# started after we get the valid last_dom_ids, but before the logs are -# deleted. Add spaces to ensure we can do containment tests below -current_logs=$(find "$log_dir" -type f) - -# Ensure the last_dom_id is set + updated for all running VMs -for vm in $(xe vm-list power-state=running --minimal | tr ',' ' '); do - xe vm-param-set uuid=$vm other-config:last_dom_id=$(xe vm-param-get uuid=$vm param-name=dom-id) -done - -# Get the last_dom_id for all VMs -valid_last_dom_ids=$(xe vm-list params=other-config --minimal | tr ';,' '\n\n' | grep last_dom_id | sed -e 's/last_dom_id: //g' | xargs) -echo "Valid dom IDs: $valid_last_dom_ids" | /usr/bin/logger -t $syslog_tag - -# Remove old console files that do not correspond to valid last_dom_id's -allowed_consoles=".*console.\(${valid_last_dom_ids// /\\|}\)$" -delete_logs=`find "$log_dir" -type f -mmin +${min_logfile_age} -not -regex "$allowed_consoles"` -for log in $delete_logs; do - if echo "$current_logs" | grep -q -w "$log"; then - echo "Deleting: $log" | /usr/bin/logger -t $syslog_tag - rm $log - fi -done - -# Truncate all remaining logs -for log in `find "$log_dir" -type f -regex '.*console.*' -size +${max_size_bytes}c`; do - echo "Truncating log: $log" | /usr/bin/logger -t $syslog_tag - tmp="$log.tmp" - tail -c $truncated_size_bytes "$log" > "$tmp" - mv -f "$tmp" "$log" - - # Notify xen that it needs to reload the file - domid="${log##*.}" - xenstore-write /local/logconsole/$domid "$log" - xenstore-rm /local/logconsole/$domid -done diff --git a/tools/xenserver/stress_test.py b/tools/xenserver/stress_test.py deleted file mode 100644 index de7ad97c8a..0000000000 --- a/tools/xenserver/stress_test.py +++ /dev/null @@ -1,181 +0,0 @@ -# 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. - -""" -This script concurrently builds and migrates instances. This can be useful when -troubleshooting race-conditions in virt-layer code. - -Expects: - - novarc to be sourced in the environment - -Helper Script for Xen Dom0: - - # cat /tmp/destroy_cache_vdis - #!/bin/bash - xe vdi-list | grep "Glance Image" -C1 | grep "^uuid" | awk '{print $5}' | - xargs -n1 -I{} xe vdi-destroy uuid={} -""" -import argparse -import contextlib -import multiprocessing -import subprocess -import sys -import time - -DOM0_CLEANUP_SCRIPT = "/tmp/destroy_cache_vdis" - - -def run(cmd): - ret = subprocess.call(cmd, shell=True) - if ret != 0: - sys.stderr.write("Command exited non-zero: %s" % cmd) - - -@contextlib.contextmanager -def server_built(server_name, image_name, flavor=1, cleanup=True): - run("nova boot --image=%s --flavor=%s" - " --poll %s" % (image_name, flavor, server_name)) - try: - yield - finally: - if cleanup: - run("nova delete %s" % server_name) - - -@contextlib.contextmanager -def snapshot_taken(server_name, snapshot_name, cleanup=True): - run("nova image-create %s %s" - " --poll" % (server_name, snapshot_name)) - try: - yield - finally: - if cleanup: - run("nova image-delete %s" % snapshot_name) - - -def migrate_server(server_name): - run("nova migrate %s --poll" % server_name) - - cmd = "nova list | grep %s | awk '{print $6}'" % server_name - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) - stdout, stderr = proc.communicate() - status = stdout.strip() - if status.upper() != 'VERIFY_RESIZE': - sys.stderr.write("Server %s failed to rebuild" % server_name) - return False - - # Confirm the resize - run("nova resize-confirm %s" % server_name) - return True - - -def test_migrate(context): - count, args = context - server_name = "server%d" % count - cleanup = args.cleanup - with server_built(server_name, args.image, cleanup=cleanup): - # Migrate A -> B - result = migrate_server(server_name) - if not result: - return False - - # Migrate B -> A - return migrate_server(server_name) - - -def rebuild_server(server_name, snapshot_name): - run("nova rebuild %s %s --poll" % (server_name, snapshot_name)) - - cmd = "nova list | grep %s | awk '{print $6}'" % server_name - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) - stdout, stderr = proc.communicate() - status = stdout.strip() - if status != 'ACTIVE': - sys.stderr.write("Server %s failed to rebuild" % server_name) - return False - - return True - - -def test_rebuild(context): - count, args = context - server_name = "server%d" % count - snapshot_name = "snap%d" % count - cleanup = args.cleanup - with server_built(server_name, args.image, cleanup=cleanup): - with snapshot_taken(server_name, snapshot_name, cleanup=cleanup): - return rebuild_server(server_name, snapshot_name) - - -def _parse_args(): - parser = argparse.ArgumentParser( - description='Test Nova for Race Conditions.') - - parser.add_argument('tests', metavar='TESTS', type=str, nargs='*', - default=['rebuild', 'migrate'], - help='tests to run: [rebuilt|migrate]') - - parser.add_argument('-i', '--image', help="image to build from", - required=True) - parser.add_argument('-n', '--num-runs', type=int, help="number of runs", - default=1) - parser.add_argument('-c', '--concurrency', type=int, default=5, - help="number of concurrent processes") - parser.add_argument('--no-cleanup', action='store_false', dest="cleanup", - default=True) - parser.add_argument('-d', '--dom0-ips', - help="IP of dom0's to run cleanup script") - - return parser.parse_args() - - -def main(): - dom0_cleanup_script = DOM0_CLEANUP_SCRIPT - args = _parse_args() - - if args.dom0_ips: - dom0_ips = args.dom0_ips.split(',') - else: - dom0_ips = [] - - start_time = time.time() - batch_size = min(args.num_runs, args.concurrency) - pool = multiprocessing.Pool(processes=args.concurrency) - - results = [] - for test in args.tests: - test_func = globals().get("test_%s" % test) - if not test_func: - sys.stderr.write("test '%s' not found" % test) - sys.exit(1) - - contexts = [(x, args) for x in range(args.num_runs)] - - try: - results += pool.map(test_func, contexts) - finally: - if args.cleanup: - for dom0_ip in dom0_ips: - run("ssh root@%s %s" % (dom0_ip, dom0_cleanup_script)) - - success = all(results) - result = "SUCCESS" if success else "FAILED" - - duration = time.time() - start_time - print("%s, finished in %.2f secs" % (result, duration)) - - sys.exit(0 if success else 1) - - -if __name__ == "__main__": - main() diff --git a/tools/xenserver/vdi_chain_cleanup.py b/tools/xenserver/vdi_chain_cleanup.py deleted file mode 100644 index 8899488ad7..0000000000 --- a/tools/xenserver/vdi_chain_cleanup.py +++ /dev/null @@ -1,128 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2012 OpenStack Foundation -# -# 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. - -""" -This script is designed to cleanup any VHDs (and their descendents) which have -a bad parent pointer. - -The script needs to be run in the dom0 of the affected host. - -The available actions are: - - - print: display the filenames of the affected VHDs - - delete: remove the affected VHDs - - move: move the affected VHDs out of the SR into another directory -""" -import glob -import os -import subprocess -import sys - - -class ExecutionFailed(Exception): - def __init__(self, returncode, stdout, stderr, max_stream_length=32): - self.returncode = returncode - self.stdout = stdout[:max_stream_length] - self.stderr = stderr[:max_stream_length] - self.max_stream_length = max_stream_length - - def __repr__(self): - return "" % ( - self.returncode, self.stdout, self.stderr) - - __str__ = __repr__ - - -def execute(cmd, ok_exit_codes=None): - if ok_exit_codes is None: - ok_exit_codes = [0] - - proc = subprocess.Popen( - cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - - (stdout, stderr) = proc.communicate() - - if proc.returncode not in ok_exit_codes: - raise ExecutionFailed(proc.returncode, stdout, stderr) - - return proc.returncode, stdout, stderr - - -def usage(): - print("usage: %s " % sys.argv[0]) - sys.exit(1) - - -def main(): - if len(sys.argv) < 3: - usage() - - sr_path = sys.argv[1] - action = sys.argv[2] - - if action not in ('print', 'delete', 'move'): - usage() - - if action == 'move': - if len(sys.argv) < 4: - print("error: must specify where to move bad VHDs") - sys.exit(1) - - bad_vhd_path = sys.argv[3] - if not os.path.exists(bad_vhd_path): - os.makedirs(bad_vhd_path) - - bad_leaves = [] - descendents = {} - - for fname in glob.glob(os.path.join(sr_path, "*.vhd")): - (returncode, stdout, stderr) = execute( - ['vhd-util', 'query', '-n', fname, '-p'], ok_exit_codes=[0, 22]) - - stdout = stdout.strip() - - if stdout.endswith('.vhd'): - try: - descendents[stdout].append(fname) - except KeyError: - descendents[stdout] = [fname] - elif 'query failed' in stdout: - bad_leaves.append(fname) - - def walk_vhds(root): - yield root - if root in descendents: - for child in descendents[root]: - for vhd in walk_vhds(child): - yield vhd - - for bad_leaf in bad_leaves: - for bad_vhd in walk_vhds(bad_leaf): - print(bad_vhd) - if action == "print": - pass - elif action == "delete": - os.unlink(bad_vhd) - elif action == "move": - new_path = os.path.join(bad_vhd_path, - os.path.basename(bad_vhd)) - os.rename(bad_vhd, new_path) - else: - raise Exception("invalid action %s" % action) - - -if __name__ == '__main__': - main() diff --git a/tools/xenserver/vm_vdi_cleaner.py b/tools/xenserver/vm_vdi_cleaner.py deleted file mode 100755 index 27c12663be..0000000000 --- a/tools/xenserver/vm_vdi_cleaner.py +++ /dev/null @@ -1,316 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2011 OpenStack Foundation -# -# 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 os -import sys - -from oslo_config import cfg -from oslo_utils import timeutils -import XenAPI - -possible_topdir = os.getcwd() -if os.path.exists(os.path.join(possible_topdir, "nova", "__init__.py")): - sys.path.insert(0, possible_topdir) - -from nova import config -from nova import context -import nova.conf -from nova import db -from nova import exception -from nova.virt import virtapi -from nova.virt.xenapi import driver as xenapi_driver - - -cleaner_opts = [ - cfg.IntOpt('zombie_instance_updated_at_window', - default=172800, - help='Number of seconds zombie instances are cleaned up.'), -] - -cli_opt = cfg.StrOpt('command', - help='Cleaner command') - -CONF = nova.conf.CONF -CONF.register_opts(cleaner_opts) -CONF.register_cli_opt(cli_opt) - - -ALLOWED_COMMANDS = ["list-vdis", "clean-vdis", "list-instances", - "clean-instances", "test"] - - -def call_xenapi(xenapi, method, *args): - """Make a call to xapi.""" - return xenapi._session.call_xenapi(method, *args) - - -def find_orphaned_instances(xenapi): - """Find and return a list of orphaned instances.""" - ctxt = context.get_admin_context(read_deleted="only") - - orphaned_instances = [] - - for vm_ref, vm_rec in _get_applicable_vm_recs(xenapi): - try: - uuid = vm_rec['other_config']['nova_uuid'] - instance = db.instance_get_by_uuid(ctxt, uuid) - except (KeyError, 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) - 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 timeutils.is_older_than(instance.updated_at, - CONF.zombie_instance_updated_at_window)) - - if is_active_and_deleting or is_zombie_vm: - orphaned_instances.append((vm_ref, vm_rec, instance)) - - return orphaned_instances - - -def cleanup_instance(xenapi, instance, vm_ref, vm_rec): - """Delete orphaned instances.""" - xenapi._vmops._destroy(instance, vm_ref) - - -def _get_applicable_vm_recs(xenapi): - """An 'applicable' VM is one that is not a template and not the control - domain. - """ - for vm_ref in call_xenapi(xenapi, 'VM.get_all'): - try: - vm_rec = call_xenapi(xenapi, 'VM.get_record', vm_ref) - except XenAPI.Failure as e: - if e.details[0] != 'HANDLE_INVALID': - raise - continue - - if vm_rec["is_a_template"] or vm_rec["is_control_domain"]: - continue - yield vm_ref, vm_rec - - -def print_xen_object(obj_type, obj, indent_level=0, spaces_per_indent=4): - """Pretty-print a Xen object. - - Looks like: - - VM (abcd-abcd-abcd): 'name label here' - """ - uuid = obj["uuid"] - try: - name_label = obj["name_label"] - except KeyError: - name_label = "" - msg = "%s (%s) '%s'" % (obj_type, uuid, name_label) - indent = " " * spaces_per_indent * indent_level - print("".join([indent, msg])) - - -def _find_vdis_connected_to_vm(xenapi, connected_vdi_uuids): - """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) - 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 = call_xenapi(xenapi, 'VDI.get_by_uuid', - parent_vdi_uuid) - try: - cur_vdi_rec = call_xenapi(xenapi, 'VDI.get_record', - cur_vdi_ref) - except XenAPI.Failure as e: - if e.details[0] != 'HANDLE_INVALID': - raise - break - else: - break - - for vm_ref, vm_rec in _get_applicable_vm_recs(xenapi): - indent_level = 0 - print_xen_object("VM", vm_rec, indent_level=indent_level) - - vbd_refs = vm_rec["VBDs"] - for vbd_ref in vbd_refs: - try: - vbd_rec = call_xenapi(xenapi, 'VBD.get_record', vbd_ref) - except XenAPI.Failure as e: - if e.details[0] != 'HANDLE_INVALID': - raise - continue - - indent_level = 1 - print_xen_object("VBD", vbd_rec, indent_level=indent_level) - - vbd_vdi_ref = vbd_rec["VDI"] - - if _is_null_ref(vbd_vdi_ref): - continue - - try: - vdi_rec = call_xenapi(xenapi, 'VDI.get_record', vbd_vdi_ref) - except XenAPI.Failure as e: - if e.details[0] != 'HANDLE_INVALID': - raise - continue - - _add_vdi_and_parents_to_connected(vdi_rec, indent_level) - - -def _find_all_vdis_and_system_vdis(xenapi, all_vdi_uuids, connected_vdi_uuids): - """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 call_xenapi(xenapi, 'VDI.get_all'): - try: - vdi_rec = call_xenapi(xenapi, 'VDI.get_record', vdi_ref) - except XenAPI.Failure as e: - if e.details[0] != 'HANDLE_INVALID': - raise - continue - 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) - connected_vdi_uuids.add(vdi_uuid) - elif not vdi_rec["managed"]: - print_xen_object("UNMANAGED VDI", vdi_rec, indent_level=0) - connected_vdi_uuids.add(vdi_uuid) - - -def find_orphaned_vdi_uuids(xenapi): - """Walk VM -> VBD -> VDI change and accumulate connected VDIs.""" - connected_vdi_uuids = set() - - _find_vdis_connected_to_vm(xenapi, connected_vdi_uuids) - - all_vdi_uuids = set() - _find_all_vdis_and_system_vdis(xenapi, all_vdi_uuids, connected_vdi_uuids) - - orphaned_vdi_uuids = all_vdi_uuids - connected_vdi_uuids - return orphaned_vdi_uuids - - -def list_orphaned_vdis(vdi_uuids): - """List orphaned VDIs.""" - for vdi_uuid in vdi_uuids: - print("ORPHANED VDI (%s)" % vdi_uuid) - - -def clean_orphaned_vdis(xenapi, vdi_uuids): - """Clean orphaned VDIs.""" - for vdi_uuid in vdi_uuids: - print("CLEANING VDI (%s)" % vdi_uuid) - - vdi_ref = call_xenapi(xenapi, 'VDI.get_by_uuid', vdi_uuid) - try: - call_xenapi(xenapi, 'VDI.destroy', vdi_ref) - except XenAPI.Failure as exc: - sys.stderr.write("Skipping %s: %s" % (vdi_uuid, exc)) - - -def list_orphaned_instances(orphaned_instances): - """List orphaned instances.""" - for vm_ref, vm_rec, orphaned_instance in orphaned_instances: - print("ORPHANED INSTANCE (%s)" % orphaned_instance.name) - - -def clean_orphaned_instances(xenapi, orphaned_instances): - """Clean orphaned instances.""" - for vm_ref, vm_rec, instance in orphaned_instances: - print("CLEANING INSTANCE (%s)" % instance.name) - - cleanup_instance(xenapi, instance, vm_ref, vm_rec) - - -def main(): - """Main loop.""" - config.parse_args(sys.argv) - args = CONF(args=sys.argv[1:], usage='%(prog)s [options] --command={' + - '|'.join(ALLOWED_COMMANDS) + '}') - - command = CONF.command - if not command or command not in ALLOWED_COMMANDS: - CONF.print_usage() - sys.exit(1) - - if CONF.zombie_instance_updated_at_window < CONF.resize_confirm_window: - raise Exception("`zombie_instance_updated_at_window` has to be longer" - " than `resize_confirm_window`.") - - # NOTE(blamar) This tool does not require DB access, so passing in the - # 'abstract' VirtAPI class is acceptable - xenapi = xenapi_driver.XenAPIDriver(virtapi.VirtAPI()) - - if command == "list-vdis": - print("Connected VDIs:\n") - orphaned_vdi_uuids = find_orphaned_vdi_uuids(xenapi) - print("\nOrphaned VDIs:\n") - list_orphaned_vdis(orphaned_vdi_uuids) - elif command == "clean-vdis": - orphaned_vdi_uuids = find_orphaned_vdi_uuids(xenapi) - clean_orphaned_vdis(xenapi, orphaned_vdi_uuids) - elif command == "list-instances": - orphaned_instances = find_orphaned_instances(xenapi) - list_orphaned_instances(orphaned_instances) - elif command == "clean-instances": - orphaned_instances = find_orphaned_instances(xenapi) - clean_orphaned_instances(xenapi, orphaned_instances) - elif command == "test": - doctest.testmod() - else: - print("Unknown command '%s'" % command) - sys.exit(1) - - -if __name__ == "__main__": - main() -- cgit v1.2.1