diff --git a/checkvsphere/vcmd/media.py b/checkvsphere/vcmd/media.py index 75ca469..8a5b13b 100644 --- a/checkvsphere/vcmd/media.py +++ b/checkvsphere/vcmd/media.py @@ -42,12 +42,15 @@ def run(): check = Check() if args.vihost: - host_view = si.content.viewManager.CreateContainerView( - si.content.rootFolder, [vim.HostSystem], True) - try: - parentView = next(x for x in host_view.view if x.name.lower() == args.vihost.lower()) - except: + host = find_entity_views( + si, + vim.HostSystem, + begin_entity=si.content.rootFolder, + sieve={'name': args.vihost}, + ) + if not host: raise CheckVsphereException(f"host {args.vihost} not found") + parentView = host[0]['obj'].obj else: parentView = si.content.rootFolder diff --git a/checkvsphere/vcmd/snapshots.py b/checkvsphere/vcmd/snapshots.py index c7e7414..a0ca53c 100644 --- a/checkvsphere/vcmd/snapshots.py +++ b/checkvsphere/vcmd/snapshots.py @@ -25,6 +25,7 @@ import logging from pyVmomi import vim from monplugin import Check, Status +from .. import CheckVsphereException from ..tools import cli, service_instance from datetime import datetime, timedelta, timezone from ..tools.helper import ( @@ -107,6 +108,8 @@ def run(): global check global args parser = get_argparser() + parser.add_optional_arguments(cli.Argument.VIHOST) + args = parser.get_args() if not (args.warning or args.critical): @@ -117,10 +120,23 @@ def run(): args._si = service_instance.connect(args) + if args.vihost: + host = find_entity_views( + args._si, + vim.HostSystem, + begin_entity=args._si.content.rootFolder, + sieve={'name': args.vihost}, + ) + if not host: + raise CheckVsphereException(f"host {args.vihost} not found") + parentView = host[0]['obj'].obj + else: + parentView = args._si.content.rootFolder + vms = find_entity_views( args._si, vim.VirtualMachine, - begin_entity=args._si.content.rootFolder, + begin_entity=parentView, properties=['name', 'snapshot', 'resourcePool', 'config.template'] ) diff --git a/checkvsphere/vcmd/vmtools.py b/checkvsphere/vcmd/vmtools.py index 0a32501..6c969a2 100644 --- a/checkvsphere/vcmd/vmtools.py +++ b/checkvsphere/vcmd/vmtools.py @@ -80,12 +80,15 @@ def run(): args._si = service_instance.connect(args) if args.vihost: - host_view = args._si.content.viewManager.CreateContainerView( - args._si.content.rootFolder, [vim.HostSystem], True) - try: - parentView = next(x for x in host_view.view if x.name.lower() == args.vihost.lower()) - except: + host = find_entity_views( + args._si, + vim.HostSystem, + begin_entity=args._si.content.rootFolder, + sieve={'name': args.vihost}, + ) + if not host: raise CheckVsphereException(f"host {args.vihost} not found") + parentView = host[0]['obj'].obj else: parentView = args._si.content.rootFolder diff --git a/checkvsphere/vcmd/vsan.py b/checkvsphere/vcmd/vsan.py index 55f3fe8..fa8c51d 100644 --- a/checkvsphere/vcmd/vsan.py +++ b/checkvsphere/vcmd/vsan.py @@ -134,7 +134,7 @@ def check_healthtest(check, clusters): continue check.add_message( health2state(test.testHealth), - f"Cluster: {cluster['moref'].name} Group: { group.groupName } Status: { test.testHealth } Test: { test.testName }" + f"Cluster: {cluster['name']} Group: { group.groupName } Status: { test.testHealth } Test: { test.testName }" ) opts = {} diff --git a/tests/integration_vcsim/test_cli_vcsim.py b/tests/integration_vcsim/test_cli_vcsim.py index 9cb155e..4aa15af 100644 --- a/tests/integration_vcsim/test_cli_vcsim.py +++ b/tests/integration_vcsim/test_cli_vcsim.py @@ -121,6 +121,18 @@ def test_vm_tools_not_installed_flag_can_escalate(run_cli, cli_connection_args): assert "tools not installed" in result.stdout +def test_vm_tools_vihost_filter_works_with_vcsim( + run_cli, run_govc, cli_connection_args +): + host_name = _get_host_paths(run_govc)[0].rsplit("/", 1)[-1] + + result = run_cli(["vm-tools", "--vihost", host_name] + cli_connection_args) + + assert result.returncode == 0, result.stdout + result.stderr + assert "OK:" in result.stdout + assert "VMs checked for VMware Tools state" in result.stdout + + def test_vm_guestfs_unknown_vm_returns_unknown(run_cli, cli_connection_args): result = run_cli( ["vm-guestfs", "--vm-name", "definitely-missing-vm"] + cli_connection_args @@ -192,6 +204,35 @@ def test_snapshots_age_threshold_with_created_snapshot( assert vm_name in result.stdout +def test_snapshots_vihost_filter_works_with_created_snapshot( + run_cli, run_govc, cli_connection_args +): + vm_path = _get_vm_paths(run_govc)[0] + vm_name = vm_path.rsplit("/", 1)[-1] + host_name = re.sub(r"_VM\d+$", "", vm_name) + snapshot_name = "pytest-snap-host" + + create_result = run_govc(["snapshot.create", "-vm", vm_path, snapshot_name]) + assert create_result.returncode == 0, create_result.stdout + create_result.stderr + + result = run_cli( + [ + "snapshots", + "--mode", + "count", + "--warning", + "0", + "--vihost", + host_name, + ] + + cli_connection_args + ) + + assert result.returncode == 1, result.stdout + result.stderr + assert "WARNING:" in result.stdout + assert "snapshots" in result.stdout + + def test_perf_host_maintenance_state_can_be_configured( run_cli, run_govc, cli_connection_args ): @@ -390,3 +431,20 @@ def test_media_changes_state_with_cdrom_insert( restored_result = run_cli(["media", "--allowed", allowed_regex] + cli_connection_args) assert restored_result.returncode == 0, restored_result.stdout + restored_result.stderr assert "OK:" in restored_result.stdout + + +def test_media_vihost_filter_works_with_vcsim(run_cli, run_govc, cli_connection_args): + vm_path = _get_vm_paths(run_govc)[0] + vm_name = vm_path.rsplit("/", 1)[-1] + host_name = re.sub(r"_VM\d+$", "", vm_name) + allowed_regex = "^{}$".format(re.escape(vm_name)) + + _disconnect_removable_devices(run_govc, vm_path) + + result = run_cli( + ["media", "--vihost", host_name, "--allowed", allowed_regex] + + cli_connection_args + ) + + assert result.returncode == 0, result.stdout + result.stderr + assert "OK:" in result.stdout diff --git a/tests/unit/test_vsan_healthtest.py b/tests/unit/test_vsan_healthtest.py new file mode 100644 index 0000000..2d432e3 --- /dev/null +++ b/tests/unit/test_vsan_healthtest.py @@ -0,0 +1,68 @@ +import types + +from monplugin import Status + +from checkvsphere.vcmd import vsan + + +class ExplodingClusterMoref: + @property + def name(self): + raise AssertionError("unexpected remote name fetch") + + +class FakeCheck: + def __init__(self): + self.messages = [] + + def add_message(self, status, message): + self.messages.append((status, message)) + + def check_messages(self, separator='\n', separator_all='\n', **opts): + return (Status.OK, separator.join(message for _, message in self.messages)) + + def exit(self, status, message): + raise SystemExit((status, message)) + + +def test_check_healthtest_uses_prefetched_cluster_name(monkeypatch): + monkeypatch.setattr(vsan, "args", types.SimpleNamespace( + banned=[], + allowed=[], + exclude_group=[], + include_group=[], + exclude_test=[], + include_test=[], + match_method="search", + verbose=0, + )) + + check = FakeCheck() + clusters = [{ + "name": "cluster-a", + "moref": ExplodingClusterMoref(), + "healthSummary": types.SimpleNamespace( + vsanConfig=types.SimpleNamespace(vsanEnabled=True), + groups=[ + types.SimpleNamespace( + groupName="group-a", + groupTests=[ + types.SimpleNamespace( + testHealth="green", + testName="test-a", + ) + ], + ) + ], + ), + }] + + try: + vsan.check_healthtest(check, clusters) + except SystemExit as exc: + status, message = exc.code + else: + raise AssertionError("expected check_healthtest to exit") + + assert status == Status.OK + assert "Cluster: cluster-a" in message