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>
This commit is contained in:
Stephen Finucane 2020-08-31 15:01:53 +01:00
parent 58f7582c63
commit 45c0ea4a3e
7 changed files with 1 additions and 883 deletions

View File

@ -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()

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -260,7 +260,7 @@ commands = bandit -r nova -x tests -n 5 -ll
# these that have to be fixed
enable-extensions = H106,H203,H904
ignore = E121,E122,E123,E124,E125,E126,E127,E128,E129,E131,E251,H405,W504,E731,H238
exclude = .venv,.git,.tox,dist,*lib/python*,*egg,build,tools/xenserver*,releasenotes
exclude = .venv,.git,.tox,dist,*lib/python*,*egg,build,releasenotes
# To get a list of functions that are more complex than 25, set max-complexity
# to 25 and run 'tox -epep8'.
# 39 is currently the most complex thing we have