diff --git a/AUTHORS b/AUTHORS index c80dacb0..33933ea0 100644 --- a/AUTHORS +++ b/AUTHORS @@ -30,5 +30,7 @@ francisco lasko malakars namob +rajeshk2013 <43401283+rajeshk2013@users.noreply.github.com> rocknes sahithi +vikramarsid diff --git a/ChangeLog b/ChangeLog index d1903b2f..3832f1fa 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,14 @@ CHANGES ======= +* Add more VM commands +* [VP-1184, VP-1201] Provided command line support for convert to advanced gateway and enable distributed routing (#260) +* Support added for Create gateway and delete gateway (#252) + +21.0.0 +------ + +* [VCDA-764] Update OSL files for vcd-cli (#247) * [VCDA-752] Updated docs as of SHA ee2c3c3 (#248) * [VCDA-761] Fix command vcd login session list chrome for Windows (#246) * added migrate-vms CLI (#241) diff --git a/system_tests/vm_tests.py b/system_tests/vm_tests.py new file mode 100644 index 00000000..1844203c --- /dev/null +++ b/system_tests/vm_tests.py @@ -0,0 +1,143 @@ +# VMware vCloud Director vCD CLI +# Copyright (c) 2018 VMware, Inc. All Rights Reserved. +# +# 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. +import os + +from click.testing import CliRunner +from pyvcloud.system_test_framework.base_test import BaseTestCase +from pyvcloud.system_test_framework.environment import Environment + +from vcd_cli.login import login, logout +from vcd_cli.vm import vm + + +class VMTest(BaseTestCase): + """Test vm-related commands + + Tests cases in this module do not have ordering dependencies, + so setup is accomplished using Python unittest setUp and tearDown + methods. + + Be aware that this test will delete existing vcd-cli sessions. + """ + + def setUp(self): + """Load configuration and create a click runner to invoke CLI.""" + os.environ["LC_ALL"] = "en_US.UTF-8" + os.environ["LANG"] = "en_US.UTF-8" + self._config = Environment.get_config() + self._logger = Environment.get_default_logger() + self._runner = CliRunner() + + def tearDown(self): + """Logout ignoring any errors to ensure test session is gone.""" + self._logout() + + def _login(self): + """Logs in using admin credentials""" + host = self._config['vcd']['host'] + org = self._config['vcd']['sys_org_name'] + admin_user = self._config['vcd']['sys_admin_username'] + admin_pass = self._config['vcd']['sys_admin_pass'] + login_args = [ + host, org, admin_user, "-i", "-w", + "--password={0}".format(admin_pass) + ] + result = self._runner.invoke(login, args=login_args) + self.assertEqual(0, result.exit_code) + self.assertTrue("logged in" in result.output) + + def _logout(self): + """Logs out current session, ignoring errors""" + self._runner.invoke(logout) + + def test_0010_vm_list(self): + """user can list vms + """ + self._login() + result = self._runner.invoke(vm, args=['list', 'build-vms']) + self._logger.debug("VM List: {0}".format(result.output)) + self.assertEqual(0, result.exit_code) + + def test_0020_vm_show_snapshot(self): + """user can show-snapshot vm + """ + self._login() + result = self._runner.invoke(vm, args=['show-snapshot', 'build-vms', 'build-03-422']) + self._logger.debug("VM Snapshot List: {0}".format(result.output)) + self.assertEqual(0, result.exit_code) + + def test_0030_vm_create_snapshot(self): + """user can create-snapshot vm + """ + self._login() + result = self._runner.invoke(vm, args=['create-snapshot', 'build-vms', 'build-03-422', 'vanilla']) + self._logger.debug("VM Snapshot Create: {0}".format(result.output)) + self.assertEqual(0, result.exit_code) + + def test_0030_vm_revert_snapshot(self): + """user can revert-snapshot vm + """ + self._login() + result = self._runner.invoke(vm, args=['revert-snapshot', 'build-vms', 'build-03-422']) + self._logger.debug("VM Snapshot Revert: {0}".format(result.output)) + self.assertEqual(0, result.exit_code) + + def test_0040_vm_remove_snapshot(self): + """user can remove-snapshot vm + """ + self._login() + result = self._runner.invoke(vm, args=['remove-snapshot', 'build-vms', 'build-03-422']) + self._logger.debug("VM Snapshot Revert: {0}".format(result.output)) + self.assertEqual(0, result.exit_code) + + def test_0050_vm_power_on(self): + """user can power-on vm + """ + self._login() + result = self._runner.invoke(vm, args=['power-on', 'build-vms', 'build-03-422']) + self._logger.debug("VM Power On: {0}".format(result.output)) + self.assertEqual(0, result.exit_code) + + def test_0060_vm_power_off(self): + """user can power-off vm + """ + self._login() + result = self._runner.invoke(vm, args=['power-off', 'build-vms', 'build-03-422']) + self._logger.debug("VM Power On: {0}".format(result.output)) + self.assertEqual(0, result.exit_code) + + def test_0070_vm_power_reset(self): + """user can power-reset vm + """ + self._login() + result = self._runner.invoke(vm, args=['power-reset', 'build-vms', 'build-03-422']) + self._logger.debug("VM Power On: {0}".format(result.output)) + self.assertEqual(0, result.exit_code) + + def test_0080_vm_reboot(self): + """user can reboot vm + """ + self._login() + result = self._runner.invoke(vm, args=['reboot', 'build-vms', 'build-03-422']) + self._logger.debug("VM Reboot: {0}".format(result.output)) + self.assertEqual(0, result.exit_code) + + def test_0090_vm_shutdown(self): + """user can shutdown vm + """ + self._login() + result = self._runner.invoke(vm, args=['shutdown', 'build-vms', 'build-03-422']) + self._logger.debug("VM Shutdown: {0}".format(result.output)) + self.assertEqual(0, result.exit_code) diff --git a/vcd_cli/vm.py b/vcd_cli/vm.py index a6251bf4..c4e7c286 100644 --- a/vcd_cli/vm.py +++ b/vcd_cli/vm.py @@ -11,6 +11,7 @@ # code for the these subcomponents is subject to the terms and # conditions of the subcomponent's license, as noted in the LICENSE file. # +import json import click from pyvcloud.vcd.vapp import VApp @@ -47,15 +48,61 @@ def vm(ctx): vcd vm update vapp1 vm1 --cpu 2 --memory 512 Modifies the VM 'vm1' in vApp 'vapp1' to be configured with 2 cpu and the specified memory . +\b + vcd vm power-on vapp1 vm1 + Power On the VM 'vm1' in vApp 'vapp1'. +\b + vcd vm power-off vapp1 vm1 + Power Off the VM 'vm1' in vApp 'vapp1'. +\b + vcd vm power-reset vapp1 vm1 + Power reset the VM 'vm1' in vApp 'vapp1'. +\b + vcd vm reboot vapp1 vm1 + Reboot the VM 'vm1' in vApp 'vapp1'. +\b + vcd vm shutdown vapp1 vm1 + Shutdown the VM 'vm1' in vApp 'vapp1'. +\b + vcd vm show-snapshot vapp1 vm1 + Show snapshot for the VM 'vm1' in vApp 'vapp1'. +\b + vcd vm create-snapshot vapp1 vm1 + Create snapshot for the VM 'vm1' in vApp 'vapp1'. +\b + vcd vm revert-snapshot vapp1 vm1 + Revert to the current snapshot for the VM 'vm1' in vApp 'vapp1'. +\b + vcd vm remove-snapshot vapp1 vm1 + Remove all snapshots for the VM 'vm1' in vApp 'vapp1'. """ pass -@vm.command('list', short_help='list VMs') +@vm.command('list', short_help='list VMs in a vApp') @click.pass_context -def list_vms(ctx): +@click.argument('vapp-name', metavar='', required=True) +def list_vms(ctx, vapp_name): try: - raise Exception('not implemented') + restore_session(ctx, vdc_required=True) + client = ctx.obj['client'] + vdc_href = ctx.obj['profiles'].get('vdc_href') + vdc = VDC(client, href=vdc_href) + vapp_resource = vdc.get_vapp(vapp_name) + vapp = VApp(client, resource=vapp_resource) + vm_list = vapp.get_all_vms() + stdout_vm_list = [] + for vm_obj in vm_list: + vm_attributes = dict(vm_obj.attrib) + if "type" in vm_attributes: + del vm_attributes["type"] + if hasattr(vm_obj, "Description"): + vm_attributes["description"] = vm_obj.Description.text + if hasattr(vm_obj, "DateCreated"): + vm_attributes["date_created"] = vm_obj.DateCreated.text + stdout_vm_list.append(vm_attributes) + result = {'vm_list': json.dumps(stdout_vm_list, indent=4)} + stdout(result, ctx) except Exception as e: stderr(e, ctx) @@ -72,8 +119,7 @@ def info(ctx, vapp_name, vm_name): vdc = VDC(client, href=vdc_href) vapp_resource = vdc.get_vapp(vapp_name) vapp = VApp(client, resource=vapp_resource) - result = {} - result['primary_ip'] = vapp.get_primary_ip(vm_name) + result = {'primary_ip': vapp.get_primary_ip(vm_name)} stdout(result, ctx) except Exception as e: stderr(e, ctx) @@ -114,14 +160,273 @@ def update(ctx, vapp_name, vm_name, cpu, cores, memory): vapp_resource = vdc.get_vapp(vapp_name) vapp = VApp(client, resource=vapp_resource) vm_resource = vapp.get_vm(vm_name) - vm = VM(client, resource=vm_resource) + vm_obj = VM(client, resource=vm_resource) if cpu is not None: - task_cpu_update = vm.modify_cpu(cpu, cores) + task_cpu_update = vm_obj.modify_cpu(cpu, cores) stdout("Updating cpu (and core(s) if specified) for the VM") stdout(task_cpu_update, ctx) if memory is not None: - task_memory_update = vm.modify_memory(memory) + task_memory_update = vm_obj.modify_memory(memory) stdout("Updating memory for the VM") stdout(task_memory_update, ctx) except Exception as e: stderr(e, ctx) + + +@vm.command('power-on', short_help='Powers on the vm.') +@click.pass_context +@click.argument('vapp-name', metavar='', required=True) +@click.argument('vm-name', metavar='', required=True) +def power_on(ctx, vapp_name, vm_name): + try: + restore_session(ctx, vdc_required=True) + client = ctx.obj['client'] + vdc_href = ctx.obj['profiles'].get('vdc_href') + vdc = VDC(client, href=vdc_href) + vapp_resource = vdc.get_vapp(vapp_name) + vapp = VApp(client, resource=vapp_resource) + vm_resource = vapp.get_vm(vm_name) + vm_obj = VM(client, resource=vm_resource) + + if not vm_obj.is_powered_on(vm_resource): + stdout("Powering on the virtual machine") + power_on_command = vm_obj.power_on() + exec_results = dict(power_on_command.attrib) + result = {'vm_power_on': json.dumps(exec_results, indent=4)} + else: + result = {'vm_power_on': "Already powered on."} + stdout(result, ctx) + except Exception as e: + stderr(e, ctx) + + +@vm.command('power-off', short_help='Powers off the vm.') +@click.pass_context +@click.argument('vapp-name', metavar='', required=True) +@click.argument('vm-name', metavar='', required=True) +def power_off(ctx, vapp_name, vm_name): + try: + restore_session(ctx, vdc_required=True) + client = ctx.obj['client'] + vdc_href = ctx.obj['profiles'].get('vdc_href') + vdc = VDC(client, href=vdc_href) + vapp_resource = vdc.get_vapp(vapp_name) + vapp = VApp(client, resource=vapp_resource) + vm_resource = vapp.get_vm(vm_name) + vm_obj = VM(client, resource=vm_resource) + if not vm_obj.is_powered_off(vm_resource): + stdout("Powering off the virtual machine") + power_off_command = vm_obj.power_off() + exec_results = dict(power_off_command.attrib) + result = {'vm_power_off': json.dumps(exec_results, indent=4)} + else: + result = {'vm_power_off': "Already powered off."} + stdout(result, ctx) + except Exception as e: + stderr(e, ctx) + + +@vm.command('power-reset', short_help='Powers reset the vm.') +@click.pass_context +@click.argument('vapp-name', metavar='', required=True) +@click.argument('vm-name', metavar='', required=True) +def power_reset(ctx, vapp_name, vm_name): + try: + restore_session(ctx, vdc_required=True) + client = ctx.obj['client'] + vdc_href = ctx.obj['profiles'].get('vdc_href') + vdc = VDC(client, href=vdc_href) + vapp_resource = vdc.get_vapp(vapp_name) + vapp = VApp(client, resource=vapp_resource) + vm_resource = vapp.get_vm(vm_name) + vm_obj = VM(client, resource=vm_resource) + if vm_obj.is_powered_on(vm_resource): + stdout("Resetting the virtual machine") + power_reset_command = vm_obj.power_reset() + exec_results = dict(power_reset_command.attrib) + result = {'vm_power_reset': json.dumps(exec_results, indent=4)} + else: + result = { + 'vm_power_reset': + "VM is powered off, please power on VM to reset power." + } + + stdout(result, ctx) + except Exception as e: + stderr(e, ctx) + + +@vm.command('reboot', short_help='Reboots the vm.') +@click.pass_context +@click.argument('vapp-name', metavar='', required=True) +@click.argument('vm-name', metavar='', required=True) +def reboot(ctx, vapp_name, vm_name): + try: + restore_session(ctx, vdc_required=True) + client = ctx.obj['client'] + vdc_href = ctx.obj['profiles'].get('vdc_href') + vdc = VDC(client, href=vdc_href) + vapp_resource = vdc.get_vapp(vapp_name) + vapp = VApp(client, resource=vapp_resource) + vm_resource = vapp.get_vm(vm_name) + vm_obj = VM(client, resource=vm_resource) + if vm_obj.is_powered_on(vm_resource): + stdout("Rebooting on the virtual machine") + reboot_command = vm_obj.reboot() + exec_results = dict(reboot_command.attrib) + result = {'vm_reboot': json.dumps(exec_results, indent=4)} + else: + result = { + 'vm_reboot': "VM is powered off, please power on VM to reboot." + } + stdout(result, ctx) + except Exception as e: + stderr(e, ctx) + + +@vm.command('shutdown', short_help='Shutdown the vm.') +@click.pass_context +@click.argument('vapp-name', metavar='', required=True) +@click.argument('vm-name', metavar='', required=True) +def shutdown(ctx, vapp_name, vm_name): + try: + restore_session(ctx, vdc_required=True) + client = ctx.obj['client'] + vdc_href = ctx.obj['profiles'].get('vdc_href') + vdc = VDC(client, href=vdc_href) + vapp_resource = vdc.get_vapp(vapp_name) + vapp = VApp(client, resource=vapp_resource) + vm_resource = vapp.get_vm(vm_name) + vm_obj = VM(client, resource=vm_resource) + if vm_obj.is_powered_on(vm_resource): + stdout("Shutting down on the virtual machine") + shutdown_command = vm_obj.shutdown() + exec_results = dict(shutdown_command.attrib) + result = {'vm_shutdown': json.dumps(exec_results, indent=4)} + else: + result = { + 'vm_shutdown': + "VM is powered off, please power on VM to shutdown." + } + stdout(result, ctx) + except Exception as e: + stderr(e, ctx) + + +@vm.command('show-snapshot', short_help='Show VM snapshot.') +@click.pass_context +@click.argument('vapp-name', metavar='', required=True) +@click.argument('vm-name', metavar='', required=True) +def show_snapshot(ctx, vapp_name, vm_name): + try: + restore_session(ctx, vdc_required=True) + client = ctx.obj['client'] + vdc_href = ctx.obj['profiles'].get('vdc_href') + vdc = VDC(client, href=vdc_href) + vapp_resource = vdc.get_vapp(vapp_name) + vapp = VApp(client, resource=vapp_resource) + vm_resource = vapp.get_vm(vm_name) + vm_obj = VM(client, resource=vm_resource) + vm_resource = vm_obj.get_resource() + snapshot_list = [] + for snapshot in vm_resource.SnapshotSection: + if not hasattr(snapshot, "Snapshot"): + continue + snapshot_list.append(dict(snapshot.Snapshot.attrib)) + result = {'vm_snapshot': json.dumps(snapshot_list, indent=4)} + stdout(result, ctx) + except Exception as e: + stderr(e, ctx) + + +@vm.command('create-snapshot', short_help='Create a snapshot of the vm.') +@click.pass_context +@click.argument('vapp-name', metavar='', required=True) +@click.argument('vm-name', metavar='', required=True) +@click.argument('snapshot-name', metavar='', required=False) +@click.option( + 'quiesce', + '--quiesce', + required=False, + default=True, + metavar='', + type=click.BOOL, + help='Should snapshot include the virtual machine’s memory.') +@click.option( + 'memory', + '--memory', + required=False, + default=True, + metavar='', + type=click.BOOL, + help='Set this flag if you need file system of the virtual ' + 'machine be quiesced before the snapshot is created. ' + 'Requires VMware tools to be installed on the vm.') +def create_snapshot(ctx, vapp_name, vm_name, snapshot_name, quiesce, memory): + try: + restore_session(ctx, vdc_required=True) + client = ctx.obj['client'] + vdc_href = ctx.obj['profiles'].get('vdc_href') + vdc = VDC(client, href=vdc_href) + vapp_resource = vdc.get_vapp(vapp_name) + vapp = VApp(client, resource=vapp_resource) + vm_resource = vapp.get_vm(vm_name) + vm_obj = VM(client, resource=vm_resource) + stdout("Creating snapshot for the virtual machine") + create_command = vm_obj.snapshot_create(memory, quiesce, snapshot_name) + exec_results = dict(create_command.attrib) + result = {'vm_create_snapshot': json.dumps(exec_results, indent=4)} + stdout(result, ctx) + except Exception as e: + stderr(e, ctx) + + +@vm.command( + 'revert-snapshot', + short_help='Reverts a virtual machine to the current snapshot, if any.') +@click.pass_context +@click.argument('vapp-name', metavar='', required=True) +@click.argument('vm-name', metavar='', required=True) +def revert_snapshot(ctx, vapp_name, vm_name): + try: + restore_session(ctx, vdc_required=True) + client = ctx.obj['client'] + vdc_href = ctx.obj['profiles'].get('vdc_href') + vdc = VDC(client, href=vdc_href) + vapp_resource = vdc.get_vapp(vapp_name) + vapp = VApp(client, resource=vapp_resource) + vm_resource = vapp.get_vm(vm_name) + vm_obj = VM(client, resource=vm_resource) + stdout("Reverting to the latest snapshot of the virtual machine") + revert_command = vm_obj.snapshot_revert_to_current() + exec_results = dict(revert_command.attrib) + result = {'vm_snapshot_revert': json.dumps(exec_results, indent=4)} + stdout(result, ctx) + except Exception as e: + note = "please check if VM snapshots exist. You can run show-snapshot command to list VM snapshots." + stderr(str(e) + "\n{}".format(note), ctx) + + +@vm.command( + 'remove-snapshot', + short_help='Removes all user created snapshots of a virtual machine.') +@click.pass_context +@click.argument('vapp-name', metavar='', required=True) +@click.argument('vm-name', metavar='', required=True) +def remove_snapshot(ctx, vapp_name, vm_name): + try: + restore_session(ctx, vdc_required=True) + client = ctx.obj['client'] + vdc_href = ctx.obj['profiles'].get('vdc_href') + vdc = VDC(client, href=vdc_href) + vapp_resource = vdc.get_vapp(vapp_name) + vapp = VApp(client, resource=vapp_resource) + vm_resource = vapp.get_vm(vm_name) + vm_obj = VM(client, resource=vm_resource) + stdout("Removing all snapshots of the virtual machine") + remove_command = vm_obj.snapshot_remove_all() + exec_results = dict(remove_command.attrib) + result = {'vm_snapshot_remove': json.dumps(exec_results, indent=4)} + stdout(result, ctx) + except Exception as e: + stderr(e, ctx)