summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorStephen Finucane <stephenfin@redhat.com>2020-08-31 15:01:53 +0100
committerStephen Finucane <stephenfin@redhat.com>2020-08-31 15:53:35 +0100
commit45c0ea4a3e6603e5be30b3ff8e90e4c4d0cb4136 (patch)
tree1c7efda545557b984be26988a776c9d5e272cee9 /tools
parent58f7582c6326f5a59623b1786f4bcce959b1fb34 (diff)
downloadnova-45c0ea4a3e6603e5be30b3ff8e90e4c4d0cb4136.tar.gz
tools: Remove xenserver tooling
These will not be used in a world without the XenAPI driver. Change-Id: I5bc3c7855b817c4ce2b8919c76be80cceabeec9e Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
Diffstat (limited to 'tools')
-rw-r--r--tools/xenserver/destroy_cached_images.py85
-rw-r--r--tools/xenserver/populate_other_config.py103
-rwxr-xr-xtools/xenserver/rotate_xen_guest_logs.sh69
-rw-r--r--tools/xenserver/stress_test.py181
-rw-r--r--tools/xenserver/vdi_chain_cleanup.py128
-rwxr-xr-xtools/xenserver/vm_vdi_cleaner.py316
6 files changed, 0 insertions, 882 deletions
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-<UUID>-[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 "<ExecutionFailed returncode=%s out='%s' stderr='%s'>" % (
- 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 <SR PATH> <print|delete|move>" % 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()