diff --git a/.travis.yml b/.travis.yml index 1416aa74..c5f9a424 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,8 @@ python: - "2.7" install: - - pip install -r package/requirements.txt - - pip install cloudshell-shell-core + - pip install -r package/requirements.txt --extra-index-url https://testpypi.python.org/pypi + - pip install cloudshell-shell-core --extra-index-url https://testpypi.python.org/pypi - pip install -r test_requirements.txt - pip install coveralls diff --git a/README.md b/README.md index f190287a..16df1d81 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,32 @@ [![Build Status](https://travis-ci.org/QualiSystems/vCenterShell.svg?branch=master)](https://travis-ci.org/QualiSystems/vCenterShell) [![Coverage Status](https://coveralls.io/repos/QualiSystems/vCenterShell/badge.svg?branch=develop&service=github)](https://coveralls.io/github/QualiSystems/vCenterShell?branch=develop) [![Code Climate](https://codeclimate.com/github/QualiSystems/vCenterShell/badges/gpa.svg)](https://codeclimate.com/github/QualiSystems/vCenterShell) [ ![Foo](https://qualisystems.getbadges.io/shield/company/qualisystems) ](https://getbadges.io) [![Stories in Ready](https://badge.waffle.io/QualiSystems/vCenterShell.svg?label=ready&title=Ready)](http://waffle.io/QualiSystems/vCenterShell) [![Join the chat at https://gitter.im/QualiSystems/vCenterShell](https://badges.gitter.im/QualiSystems/vCenterShell.svg)](https://gitter.im/QualiSystems/vCenterShell?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Analytics](https://ga-beacon.appspot.com/UA-72194260-1/QualiSystems/vCenterShell)](https://github.com/QualiSystems/vCenterShell/) -A lightweight CloudShell 'shell' that allows integrating with vCenter as an app deployment option. +A repository for projects providing out of the box capabilities within CloudShell to define VMWare vCenter instances in CloudShell and leverage vCenter's capabilities to deploy and connect apps in CloudShell sandboxes. -## Requirements +## Projects +* **Deployment Drivers** + + These projects extend CloudShell apps with new deployment types + * **deploy_from_image** + App deployment type driver for deploying from vCenter OVF images + * **deploy_from_template** + App deployment type driver for cloning from vCenter templates + + +* **package** + + The vCenter Python package hosted in [PyPi](https://pypi.python.org/). The package contains most of the logic relate + to working with the vCenter API. + +* **vcentershell_driver** + + The CloudShell driver for controlling vCenter via CloudShell. + +## Installation * [QualiSystems CloudShell 7.0](http://www.qualisystems.com/products/cloudshell/cloudshell-overview/) * [pyvmomi 6.0](https://github.com/vmware/pyvmomi) * [jsonpickle latest](https://jsonpickle.github.io/) + ## Getting Started 1. Download vCenterShell.zip from Releases page diff --git a/environment_scripts/env_setup/setup_script.py b/environment_scripts/env_setup/setup_script.py index e874ad25..5a32f9dc 100644 --- a/environment_scripts/env_setup/setup_script.py +++ b/environment_scripts/env_setup/setup_script.py @@ -1,4 +1,3 @@ -# coding=utf-8 from multiprocessing.pool import ThreadPool from threading import Lock @@ -16,7 +15,9 @@ class EnvironmentSetup(object): def __init__(self): self.reservation_id = helpers.get_reservation_context_details().id - self.logger = qs_logger.get_qs_logger(name="CloudShell Sandbox Setup", reservation_id=self.reservation_id) + self.logger = qs_logger.get_qs_logger(log_file_prefix="CloudShell Sandbox Setup", + log_group=self.reservation_id, + log_category='Setup') @profileit(scriptName='Setup') def execute(self): @@ -62,6 +63,7 @@ def _try_exeucte_autoload(self, api, reservation_details, deploy_result, resourc """ if deploy_result is None: + self.logger.info("No apps to discover") api.WriteMessageToReservationOutput(reservationId=self.reservation_id, message='No apps to discover') return @@ -162,6 +164,7 @@ def _run_async_power_on_refresh_ip_install(self, api, reservation_details, deplo api.WriteMessageToReservationOutput( reservationId=self.reservation_id, message='No resources to power on or install') + self._validate_all_apps_deployed(deploy_results) return pool = ThreadPool(len(resources)) @@ -184,7 +187,10 @@ def _run_async_power_on_refresh_ip_install(self, api, reservation_details, deplo if not res[0]: raise Exception("Reservation is Active with Errors - " + res[1]) - if deploy_results and hasattr(deploy_results, "ResultItems"): + self._validate_all_apps_deployed(deploy_results) + + def _validate_all_apps_deployed(self, deploy_results): + if deploy_results is not None: for deploy_res in deploy_results.ResultItems: if not deploy_res.Success: raise Exception("Reservation is Active with Errors - " + deploy_res.Error) @@ -216,7 +222,7 @@ def _power_on_refresh_ip_install(self, api, lock, message_status, resource, depl resource_details = api.GetResourceDetails(deployed_app_name) # check if deployed app - if hasattr(resource_details, "VmDetails"): + if not hasattr(resource_details.VmDetails, "UID"): self.logger.debug("Resource {0} is not a deployed app, nothing to do with it".format(deployed_app_name)) return True, "" diff --git a/environment_scripts/env_teardown/teardown_script.py b/environment_scripts/env_teardown/teardown_script.py index b691c98c..40e6d07a 100644 --- a/environment_scripts/env_teardown/teardown_script.py +++ b/environment_scripts/env_teardown/teardown_script.py @@ -12,7 +12,9 @@ class EnvironmentTeardown: def __init__(self): self.reservation_id = helpers.get_reservation_context_details().id - self.logger = qs_logger.get_qs_logger(name="CloudShell Sandbox Teardown", reservation_id=self.reservation_id) + self.logger = qs_logger.get_qs_logger(log_file_prefix="CloudShell Sandbox Teardown", + log_group=self.reservation_id, + log_category='Teardown') @profileit(scriptName="Teardown") def execute(self): diff --git a/orchestration_service/context_based_logger_factory.py b/orchestration_service/context_based_logger_factory.py index 10708068..ab85fece 100644 --- a/orchestration_service/context_based_logger_factory.py +++ b/orchestration_service/context_based_logger_factory.py @@ -10,7 +10,7 @@ def create_logger_for_context(self, logger_name, context): :param logger_name: :type logger_name: str :param context: - :return: + :return: logging.Logger """ if self._is_instance_of(context, 'AutoLoadCommandContext'): reservation_id = 'Autoload' @@ -23,9 +23,9 @@ def create_logger_for_context(self, logger_name, context): handler_name = context.remote_endpoints[0].name else: raise Exception(ContextBasedLoggerFactory.UNSUPPORTED_CONTEXT_PROVIDED, context) - logger = get_qs_logger(name=logger_name, - handler_name=handler_name, - reservation_id=reservation_id) + logger = get_qs_logger(log_file_prefix=handler_name, + log_group=reservation_id, + log_category=logger_name) return logger @staticmethod diff --git a/orchestration_service/orchestrator.py b/orchestration_service/orchestrator.py index 7f87ff54..038c673b 100644 --- a/orchestration_service/orchestrator.py +++ b/orchestration_service/orchestrator.py @@ -14,7 +14,7 @@ def __init__(self): def deploy(self, context): """ Deploys app from template - :type context: cloudshell.shell.core.driver_context.ResourceCommandContext + :type context: cloudshell.shell.core.context.ResourceCommandContext """ logger = self.context_based_logger_factory.create_logger_for_context( logger_name='DeployAppOrchestrationDriver', @@ -223,7 +223,7 @@ def _try_execute_autoload(self, session, reservation_id, deployed_app_name, logg exc.rawxml)) self._write_message(deployed_app_name, reservation_id, session, 'discovery failed: {1}'.format(deployed_app_name, exc.message)) - raise + raise except Exception as exc: print "Error executing Autoload command on deployed app {0}. Error: {1}".format(deployed_app_name, str(exc)) diff --git a/package/cloudshell/cp/vcenter/commands/power_manager_vm.py b/package/cloudshell/cp/vcenter/commands/power_manager_vm.py index e3c2e106..c0b2fed2 100644 --- a/package/cloudshell/cp/vcenter/commands/power_manager_vm.py +++ b/package/cloudshell/cp/vcenter/commands/power_manager_vm.py @@ -29,12 +29,12 @@ def power_off(self, si, logger, session, vcenter_data_model, vm_uuid, resource_f vm = self.pv_service.find_by_uuid(si, vm_uuid) if vm.summary.runtime.powerState == 'poweredOff': - _logger.info('vm already powered off') + logger.info('vm already powered off') task_result = 'already powered off' else: # hard power off logger.info('{0} powering of vm'.format(vcenter_data_model.shutdown_method)) - if vcenter_data_model.shutdown_method.lower() == 'soft': + if vcenter_data_model.shutdown_method.lower() != 'soft': task = vm.PowerOff() task_result = self.synchronous_task_waiter.wait_for_task(task=task, logger=logger, diff --git a/package/cloudshell/cp/vcenter/common/utilites/context_based_logger_factory.py b/package/cloudshell/cp/vcenter/common/utilites/context_based_logger_factory.py index 04b167e6..82b9da14 100644 --- a/package/cloudshell/cp/vcenter/common/utilites/context_based_logger_factory.py +++ b/package/cloudshell/cp/vcenter/common/utilites/context_based_logger_factory.py @@ -23,9 +23,9 @@ def create_logger_for_context(self, logger_name, context): handler_name = context.remote_endpoints[0].name else: raise Exception(ContextBasedLoggerFactory.UNSUPPORTED_CONTEXT_PROVIDED, context) - logger = get_qs_logger(name=logger_name, - handler_name=handler_name, - reservation_id=reservation_id) + logger = get_qs_logger(log_file_prefix=handler_name, + log_group=reservation_id, + log_category=logger_name) return logger @staticmethod diff --git a/package/cloudshell/cp/vcenter/common/wrappers/command_wrapper.py b/package/cloudshell/cp/vcenter/common/wrappers/command_wrapper.py index e46998de..5a536907 100644 --- a/package/cloudshell/cp/vcenter/common/wrappers/command_wrapper.py +++ b/package/cloudshell/cp/vcenter/common/wrappers/command_wrapper.py @@ -42,7 +42,7 @@ def execute_command_with_connection(self, context, command, *args): Note: session & vcenter_data_model objects will be injected dynamically to the command :param command: :param context: instance of ResourceCommandContext or AutoLoadCommandContext - :type context: cloudshell.shell.core.driver_context.ResourceCommandContext + :type context: cloudshell.shell.core.context.ResourceCommandContext :param args: """ diff --git a/package/cloudshell/tests/test_commands/test_power_management_vm.py b/package/cloudshell/tests/test_commands/test_power_management_vm.py index 26fa1a14..8aa480d1 100644 --- a/package/cloudshell/tests/test_commands/test_power_management_vm.py +++ b/package/cloudshell/tests/test_commands/test_power_management_vm.py @@ -5,6 +5,54 @@ class TestVirtualMachinePowerManagementCommand(TestCase): + def test_power_off_already(self): + vm_uuid = 'uuid' + si = Mock(spec=vim.ServiceInstance) + vm = Mock(spec=vim.VirtualMachine) + vm.summary = Mock() + vm.summary.runtime = Mock() + vm.summary.runtime.powerState = 'poweredOff' + session = Mock() + pv_service = Mock() + pv_service.find_by_uuid = Mock(return_value=vm) + + power_manager = VirtualMachinePowerManagementCommand(pv_service, Mock()) + + # act + res = power_manager.power_off(si=si, + logger=Mock(), + session=session, + vcenter_data_model=Mock(), + vm_uuid=vm_uuid, + resource_fullname=None) + + # assert + self.assertTrue(res, 'already powered off') + self.assertFalse(vm.PowerOn.called) + + def test_power_on_already(self): + vm_uuid = 'uuid' + si = Mock(spec=vim.ServiceInstance) + vm = Mock(spec=vim.VirtualMachine) + vm.summary = Mock() + vm.summary.runtime = Mock() + vm.summary.runtime.powerState = 'poweredOn' + session = Mock() + pv_service = Mock() + pv_service.find_by_uuid = Mock(return_value=vm) + + power_manager = VirtualMachinePowerManagementCommand(pv_service, Mock()) + + # act + res = power_manager.power_on(si=si, + logger=Mock(), + session=session, + vm_uuid=vm_uuid, + resource_fullname=None) + + # assert + self.assertTrue(res, 'already powered on') + self.assertFalse(vm.PowerOn.called) def test_power_on(self): # arrange @@ -68,7 +116,7 @@ def test_power_off_soft(self): # assert self.assertTrue(res) - self.assertTrue(vm.PowerOff.called) + self.assertTrue(vm.ShutdownGuest.called) self.assertTrue(power_manager._connect_to_vcenter.called_with(vcenter_name)) self.assertTrue(power_manager._get_vm.called_with(si, vm_uuid)) self.assertTrue(synchronous_task_waiter.wait_for_task.called_with(task)) @@ -105,7 +153,7 @@ def test_power_off_hard(self): # assert self.assertTrue(res) - self.assertTrue(vm.ShutdownGuest.called) + self.assertTrue(vm.PowerOff.called) self.assertTrue(power_manager._connect_to_vcenter.called_with(vcenter_name)) self.assertTrue(power_manager._get_vm.called_with(si, vm_uuid)) - self.assertTrue(synchronous_task_waiter.wait_for_task.called_with(task)) \ No newline at end of file + self.assertTrue(synchronous_task_waiter.wait_for_task.called_with(task)) diff --git a/package/cloudshell/tests/test_common/test_utilities/test_command_wrapper.py b/package/cloudshell/tests/test_common/test_utilities/test_command_wrapper.py index d23ffeef..489b87b8 100644 --- a/package/cloudshell/tests/test_common/test_utilities/test_command_wrapper.py +++ b/package/cloudshell/tests/test_common/test_utilities/test_command_wrapper.py @@ -1,5 +1,5 @@ from unittest import TestCase -from cloudshell.shell.core.driver_context import ResourceCommandContext, \ +from cloudshell.shell.core.context import ResourceCommandContext, \ ReservationContextDetails, ResourceContextDetails, ConnectivityContext from mock import Mock, create_autospec diff --git a/package/cloudshell/tests/test_common/test_vcenter/test_vmomi_service.py b/package/cloudshell/tests/test_common/test_vcenter/test_vmomi_service.py index db1369cd..2f993f7c 100644 --- a/package/cloudshell/tests/test_common/test_vcenter/test_vmomi_service.py +++ b/package/cloudshell/tests/test_common/test_vcenter/test_vmomi_service.py @@ -7,7 +7,6 @@ from pyVmomi import vim from cloudshell.cp.vcenter.common.vcenter.vmomi_service import pyVmomiService from cloudshell.tests.utils.testing_credentials import TestCredentials -logger = get_qs_logger() class TestVmomiService(unittest.TestCase): @@ -34,7 +33,6 @@ def integration_clone_vm_destory(self): now = datetime.now() res = pv_service.clone_vm(clone_params=params, logger=Mock()) - logger.debug('clone took: %s' % (str(datetime.now() - now))) '#assert' self.assertTrue(type(res.vm), vim.VirtualMachine) diff --git a/package/setup.py b/package/setup.py index 1885d733..c64eb0a1 100644 --- a/package/setup.py +++ b/package/setup.py @@ -7,26 +7,25 @@ with open('requirements.txt') as f_required: required = f_required.read().splitlines() - setup( - name="cloudshell-cp-vcenter", - author="Quali", - author_email="support@qualisystems.com", - description=("This shell enables setting up vCenter as a cloud provider in" - "CloudShell. It supports connectivity, deployment and management operations" - "used for Cloudshel sanboxes."), - packages=find_packages(), - test_suite='nose.collector', - test_requires=['Nose'], - package_data={'': ['*.txt']}, - install_requires=required, - version=version_from_file, - include_package_data=True, - keywords = "sandbox cloud virtualization vcenter cmp cloudshell", - classifiers=[ - "Development Status :: 4 - Beta", - "Topic :: Software Development :: Libraries", - "License :: OSI Approved :: Apache Software License", - ] + name="cloudshell-cp-vcenter", + author="Quali", + author_email="support@qualisystems.com", + description=("This shell enables setting up vCenter as a cloud provider in" + "CloudShell. It supports connectivity, deployment and management operations" + "used for Cloudshel sanboxes."), + packages=find_packages(), + test_suite='nose.collector', + test_requires=['Nose'], + package_data={'': ['*.txt']}, + install_requires=required, + version=version_from_file, + include_package_data=True, + keywords="sandbox cloud virtualization vcenter cmp cloudshell", + classifiers=[ + "Development Status :: 4 - Beta", + "Topic :: Software Development :: Libraries", + "License :: OSI Approved :: Apache Software License", + ] ) diff --git a/vlan_service_scripts/connect/connect_all.py b/vlan_service_scripts/connect/connect_all.py index ada29e0f..b96d4817 100644 --- a/vlan_service_scripts/connect/connect_all.py +++ b/vlan_service_scripts/connect/connect_all.py @@ -5,7 +5,9 @@ class ConnectAll: def __init__(self): self.reservation_id = helpers.get_reservation_context_details().id - self.logger = qs_logger.get_qs_logger(name="Connect All", reservation_id=self.reservation_id) + self.logger = qs_logger.get_qs_logger(log_file_prefix='Connect_All', + log_group=self.reservation_id, + log_category="Connect All") def execute(self): api = helpers.get_api_session()