From 6a6024f458fd1f5c53046a5ac15a801b74bee068 Mon Sep 17 00:00:00 2001 From: Boris Modylevsky Date: Wed, 27 Jul 2016 11:48:13 +0300 Subject: [PATCH 1/4] initial commit on orchestration save --- .../vcenter/commands/command_orchestrator.py | 55 +++++++++++++++++-- .../cp/vcenter/commands/save_snapshot.py | 19 ++++--- vcentershell_driver/driver.py | 6 ++ vcentershell_driver/drivermetadata.xml | 2 + 4 files changed, 69 insertions(+), 13 deletions(-) diff --git a/package/cloudshell/cp/vcenter/commands/command_orchestrator.py b/package/cloudshell/cp/vcenter/commands/command_orchestrator.py index 5df24b80..9c7626e1 100644 --- a/package/cloudshell/cp/vcenter/commands/command_orchestrator.py +++ b/package/cloudshell/cp/vcenter/commands/command_orchestrator.py @@ -1,5 +1,5 @@ import time - +from datetime import date import jsonpickle from pyVim.connect import SmartConnect, Disconnect @@ -17,7 +17,7 @@ from cloudshell.cp.vcenter.common.cloud_shell.driver_helper import CloudshellDriverHelper from cloudshell.cp.vcenter.common.cloud_shell.resource_remover import CloudshellResourceRemover from cloudshell.cp.vcenter.common.model_factory import ResourceModelParser -from cloudshell.cp.vcenter.common.utilites.command_result import set_command_result +from cloudshell.cp.vcenter.common.utilites.command_result import set_command_result, get_result_from_command_output from cloudshell.cp.vcenter.common.utilites.common_name import generate_unique_name from cloudshell.cp.vcenter.common.utilites.common_utils import back_slash_to_front_converter from cloudshell.cp.vcenter.common.utilites.context_based_logger_factory import ContextBasedLoggerFactory @@ -406,10 +406,10 @@ def save_snapshot(self, context, snapshot_name): :return: """ resource_details = self._parse_remote_model(context) - self.command_wrapper.execute_command_with_connection(context, - self.snapshot_saver.save_snapshot, - resource_details.vm_uuid, - snapshot_name) + return self.command_wrapper.execute_command_with_connection(context, + self.snapshot_saver.save_snapshot, + resource_details.vm_uuid, + snapshot_name) def restore_snapshot(self, context, snapshot_name): """ @@ -438,3 +438,46 @@ def get_snapshots(self, context): self.snapshots_retriever.get_snapshots, resource_details.vm_uuid) return set_command_result(result=res, unpicklable=False) + + def orchestration_save(self, context, mode="shallow", custom_params=None): + """ + Creates a snapshot with a unique name and returns SavedResults as JSON + :param context: resource context of the vCenterShell + :param mode: Snapshot save mode, default shallow. Currently not it use + :param custom_params: Set of custom parameter to be supported in the future + :return: SavedResults serialized as JSON + :rtype: SavedResults + """ + resource_details = self._parse_remote_model(context) + created_date = date.today() + snapshot_name = created_date.strftime('%y_%m_%d %H_%M_%S_%f') + created_snapshot_path = self.save_snapshot(context=context, snapshot_name=snapshot_name) + saved_results = SavedResults(saved_artifacts_info={ + 'resource_name': resource_details.cloud_provider, + 'created_date': created_date, + 'restore_rules': { + 'requires_same_resource': True + } + }, + saved_artifact={ + 'artifact_type': 'vcenter_snapshot', + 'identifier': created_snapshot_path + }) + return set_command_result(result=saved_results, unpicklable=False) + + def orchestration_restore(self, context, saved_details): + """ + + :param context: + :param saved_details: + :return: + """ + saved_details_obj = get_result_from_command_output(saved_details) + snapshot_name = saved_details_obj.saved_artifact['identifier'] + return self.restore_snapshot(context=context, snapshot_name=snapshot_name) + + +class SavedResults(object): + def __init__(self, saved_artifacts_info, saved_artifact): + self.saved_artifacts_info = saved_artifacts_info + self.saved_artifact = saved_artifact diff --git a/package/cloudshell/cp/vcenter/commands/save_snapshot.py b/package/cloudshell/cp/vcenter/commands/save_snapshot.py index 71c5b4c3..cffae668 100644 --- a/package/cloudshell/cp/vcenter/commands/save_snapshot.py +++ b/package/cloudshell/cp/vcenter/commands/save_snapshot.py @@ -34,11 +34,13 @@ def save_snapshot(self, si, logger, vm_uuid, snapshot_name): """ vm = self.pyvmomi_service.find_by_uuid(si, vm_uuid) - self._verify_snapshot_name_does_not_exists(snapshot_name, vm) + snapshot_path_to_be_created = SaveSnapshotCommand._get_snapshot_name_to_be_created(snapshot_name, vm) + SaveSnapshotCommand._verify_snapshot_uniquness(snapshot_path_to_be_created, vm) task = self._create_snapshot(logger, snapshot_name, vm) - return self.task_waiter.wait_for_task(task=task, logger=logger, action_name='Create Snapshot') + self.task_waiter.wait_for_task(task=task, logger=logger, action_name='Create Snapshot') + return snapshot_path_to_be_created @staticmethod def _create_snapshot(logger, snapshot_name, vm): @@ -49,11 +51,14 @@ def _create_snapshot(logger, snapshot_name, vm): return task @staticmethod - def _verify_snapshot_name_does_not_exists(snapshot_name, vm): - current_snapshot_name = SnapshotRetriever.get_current_snapshot_name(vm) - if not current_snapshot_name: - return - snapshot_path_to_be_created = SnapshotRetriever.combine(current_snapshot_name, snapshot_name) + def _verify_snapshot_uniquness(snapshot_path_to_be_created, vm): all_snapshots = SnapshotRetriever.get_vm_snapshots(vm) if snapshot_path_to_be_created in all_snapshots: raise SnapshotAlreadyExistsException(SNAPSHOT_ALREADY_EXISTS) + + @staticmethod + def _get_snapshot_name_to_be_created(snapshot_name, vm): + current_snapshot_name = SnapshotRetriever.get_current_snapshot_name(vm) + if not current_snapshot_name: + return '' + return SnapshotRetriever.combine(current_snapshot_name, snapshot_name) diff --git a/vcentershell_driver/driver.py b/vcentershell_driver/driver.py index c371c1f3..1ea854f7 100644 --- a/vcentershell_driver/driver.py +++ b/vcentershell_driver/driver.py @@ -105,5 +105,11 @@ def remote_get_snapshots(self, context, ports): """ return self.command_orchestrator.get_snapshots(context) + def orchestration_save(self, context, mode="shallow", custom_params=None): + return self.command_orchestrator.orchestration_save(context, mode, custom_params) + + def orchestration_restore(self, context, saved_details): + return self.command_orchestrator.orchestration_restore(context, saved_details) + def get_vm_uuid(self, context, vm_name): return self.command_orchestrator.get_vm_uuid_by_name(context, vm_name) diff --git a/vcentershell_driver/drivermetadata.xml b/vcentershell_driver/drivermetadata.xml index f576db66..4c8432c9 100644 --- a/vcentershell_driver/drivermetadata.xml +++ b/vcentershell_driver/drivermetadata.xml @@ -25,6 +25,8 @@ + + From 2dde63bd8836ea22542050a5913b0ade3cf6c60c Mon Sep 17 00:00:00 2001 From: Boris Modylevsky Date: Thu, 28 Jul 2016 12:49:18 +0300 Subject: [PATCH 2/4] implemented orchestration save and restore --- .../vcenter/commands/command_orchestrator.py | 38 +++++++------ .../vcenter/models/OrchestrationSaveResult.py | 6 ++ .../models/OrchestrationSavedArtifact.py | 4 ++ .../models/OrchestrationSavedArtifactsInfo.py | 12 ++++ .../test_command_orchestrator.py | 57 ++++++++++++++++++- 5 files changed, 97 insertions(+), 20 deletions(-) create mode 100644 package/cloudshell/cp/vcenter/models/OrchestrationSaveResult.py create mode 100644 package/cloudshell/cp/vcenter/models/OrchestrationSavedArtifact.py create mode 100644 package/cloudshell/cp/vcenter/models/OrchestrationSavedArtifactsInfo.py diff --git a/package/cloudshell/cp/vcenter/commands/command_orchestrator.py b/package/cloudshell/cp/vcenter/commands/command_orchestrator.py index 0b04c9e7..4f0c6a99 100644 --- a/package/cloudshell/cp/vcenter/commands/command_orchestrator.py +++ b/package/cloudshell/cp/vcenter/commands/command_orchestrator.py @@ -1,6 +1,9 @@ import time from datetime import date import jsonpickle +from cloudshell.cp.vcenter.models.OrchestrationSaveResult import OrchestrationSaveResult +from cloudshell.cp.vcenter.models.OrchestrationSavedArtifactsInfo import OrchestrationSavedArtifactsInfo +from cloudshell.cp.vcenter.models.OrchestrationSavedArtifact import OrchestrationSavedArtifact from pyVim.connect import SmartConnect, Disconnect from cloudshell.cp.vcenter.commands.connect_dvswitch import VirtualSwitchConnectCommand @@ -453,18 +456,20 @@ def orchestration_save(self, context, mode="shallow", custom_params=None): created_date = date.today() snapshot_name = created_date.strftime('%y_%m_%d %H_%M_%S_%f') created_snapshot_path = self.save_snapshot(context=context, snapshot_name=snapshot_name) - saved_results = SavedResults(saved_artifacts_info={ - 'resource_name': resource_details.cloud_provider, - 'created_date': created_date, - 'restore_rules': { - 'requires_same_resource': True - } - }, - saved_artifact={ - 'artifact_type': 'vcenter_snapshot', - 'identifier': created_snapshot_path - }) - return set_command_result(result=saved_results, unpicklable=False) + + orchestration_saved_artifact = OrchestrationSavedArtifact() + orchestration_saved_artifact.artifact_type = 'vcenter_snapshot' + orchestration_saved_artifact.identifier = created_snapshot_path + + saved_artifacts_info = OrchestrationSavedArtifactsInfo( + resource_name=resource_details.cloud_provider, + created_date=created_date, + restore_rules={'requires_same_resource': True}, + saved_artifact=orchestration_saved_artifact) + + orchestration_save_result = OrchestrationSaveResult(saved_artifacts_info) + + return set_command_result(result=orchestration_save_result, unpicklable=False) def orchestration_restore(self, context, saved_details): """ @@ -473,12 +478,9 @@ def orchestration_restore(self, context, saved_details): :param saved_details: :return: """ - saved_details_obj = get_result_from_command_output(saved_details) - snapshot_name = saved_details_obj.saved_artifact['identifier'] + saved_artifacts_info = get_result_from_command_output(saved_details) + snapshot_name = saved_artifacts_info['saved_artifacts_info']['saved_artifact']['identifier'] return self.restore_snapshot(context=context, snapshot_name=snapshot_name) -class SavedResults(object): - def __init__(self, saved_artifacts_info, saved_artifact): - self.saved_artifacts_info = saved_artifacts_info - self.saved_artifact = saved_artifact + diff --git a/package/cloudshell/cp/vcenter/models/OrchestrationSaveResult.py b/package/cloudshell/cp/vcenter/models/OrchestrationSaveResult.py new file mode 100644 index 00000000..8d35c8c4 --- /dev/null +++ b/package/cloudshell/cp/vcenter/models/OrchestrationSaveResult.py @@ -0,0 +1,6 @@ +class OrchestrationSaveResult(object): + def __init__(self, saved_artifacts_info): + """ + :type saved_artifacts_info: OrchestrationSavedArtifactsInfo + """ + self.saved_artifacts_info = saved_artifacts_info diff --git a/package/cloudshell/cp/vcenter/models/OrchestrationSavedArtifact.py b/package/cloudshell/cp/vcenter/models/OrchestrationSavedArtifact.py new file mode 100644 index 00000000..31d1f046 --- /dev/null +++ b/package/cloudshell/cp/vcenter/models/OrchestrationSavedArtifact.py @@ -0,0 +1,4 @@ +class OrchestrationSavedArtifact(object): + def __init__(self): + self.artifact_type = '' + self.identifier = '' diff --git a/package/cloudshell/cp/vcenter/models/OrchestrationSavedArtifactsInfo.py b/package/cloudshell/cp/vcenter/models/OrchestrationSavedArtifactsInfo.py new file mode 100644 index 00000000..aae5c257 --- /dev/null +++ b/package/cloudshell/cp/vcenter/models/OrchestrationSavedArtifactsInfo.py @@ -0,0 +1,12 @@ +class OrchestrationSavedArtifactsInfo(object): + def __init__(self, resource_name, created_date, restore_rules, saved_artifact): + """ + :type resource_name: str + :type created_date: date + :type restore_rules: dict + :type saved_artifact: OrchestrationSavedArtifact + """ + self.resource_name = resource_name + self.created_date = created_date + self.restore_rules = restore_rules + self.saved_artifact = saved_artifact diff --git a/package/cloudshell/tests/test_commands/test_command_orchestrator.py b/package/cloudshell/tests/test_commands/test_command_orchestrator.py index 703d3bb1..eeea7e64 100644 --- a/package/cloudshell/tests/test_commands/test_command_orchestrator.py +++ b/package/cloudshell/tests/test_commands/test_command_orchestrator.py @@ -1,8 +1,13 @@ from unittest import TestCase +import jsonpickle from cloudshell.api.cloudshell_api import ResourceInfo from cloudshell.cp.vcenter.commands.command_orchestrator import CommandOrchestrator -from mock import Mock, create_autospec +from cloudshell.shell.core.driver_context import ResourceRemoteCommandContext, ResourceContextDetails, AppContext +from mock import Mock, create_autospec, patch + +RESTORE_SNAPSHOT = 'cloudshell.cp.vcenter.commands.command_orchestrator.CommandOrchestrator.restore_snapshot' +SAVE_SNAPSHOT = 'cloudshell.cp.vcenter.commands.command_orchestrator.CommandOrchestrator.save_snapshot' class TestCommandOrchestrator(TestCase): @@ -124,4 +129,52 @@ def test_restore_snapshot(self): def test_get_snapshots(self): self.command_orchestrator.get_snapshots(self.context) - self.assertTrue(self.command_orchestrator.command_wrapper.execute_command_with_connection.called) \ No newline at end of file + self.assertTrue(self.command_orchestrator.command_wrapper.execute_command_with_connection.called) + + def test_orchestration_save(self): + # Arrange + with patch(SAVE_SNAPSHOT) as save_snapshot_mock: + save_snapshot_mock.return_value = 'new_snapshot' + + remote_command_context = create_autospec(ResourceRemoteCommandContext) + remote_command_context.resource = create_autospec(ResourceContextDetails) + remote_command_context.resource.fullname = 'vcenter' + endpoint = create_autospec(ResourceContextDetails) + endpoint.fullname = 'vm_111' + endpoint.app_context = create_autospec(AppContext) + endpoint.app_context.deployed_app_json = '{"vmdetails": {"uid": "vm_uuid1"}}' + remote_command_context.remote_endpoints = [endpoint] + + # Act + saved_result = CommandOrchestrator().orchestration_save(context=remote_command_context, + mode='shallow', + custom_params=None) + + # Assert + save_snapshot_mock.assert_called_once() + saved_result_dict = jsonpickle.decode(saved_result) + self.assertEqual(saved_result_dict['saved_artifacts_info']['saved_artifact']['artifact_type'], + 'vcenter_snapshot') + self.assertEqual(saved_result_dict['saved_artifacts_info']['saved_artifact']['identifier'], 'new_snapshot') + self.assertEqual(saved_result_dict['saved_artifacts_info']['resource_name'], 'vcenter') + self.assertIsNotNone(saved_result_dict['saved_artifacts_info']['created_date']) + + def test_orchestration_restore(self): + # Arrange + with patch(RESTORE_SNAPSHOT) as mock_restore_snapshot: + # Act + self.command_orchestrator.orchestration_restore(self.context, '''{ + "saved_artifacts_info": { + "resource_name": "ex cillum sed laboris", + "created_date": "4313-10-12T18:03:15.053Z", + "restore_rules": { + "requires_same_resource": false + }, + "saved_artifact": { + "artifact_type": "veniam in qui", + "identifier": "deserunt1" + } + } + }''') + # Assert + mock_restore_snapshot.assert_called_once_with(context=self.context, snapshot_name='deserunt1') From 2d0fffa9e9728780fafbadec6a2f831585bb7ccb Mon Sep 17 00:00:00 2001 From: Boris Modylevsky Date: Thu, 28 Jul 2016 14:32:00 +0300 Subject: [PATCH 3/4] fix install requirements in ci --- .travis.yml | 9 ++++----- external_requirements.txt | 3 +++ test_requirements.txt | 4 ++-- 3 files changed, 9 insertions(+), 7 deletions(-) create mode 100644 external_requirements.txt diff --git a/.travis.yml b/.travis.yml index eb323e67..fdc5ba3e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,15 +3,14 @@ python: - "2.7" install: - - pip install -r package/requirements.txt --extra-index-url https://testpypi.python.org/simple - - pip install cloudshell-shell-core --extra-index-url https://testpypi.python.org/simple - - pip install cloudshell-core --extra-index-url https://testpypi.python.org/simple + - pip install -r external_requirements.txt - pip install -r test_requirements.txt - - pip install coveralls + - pip install "cloudshell-core>=2.0.0,<2.1.0" --extra-index-url https://testpypi.python.org/simple + - pip install "cloudshell-shell-core>=2.0.0,<2.1.0" --extra-index-url https://testpypi.python.org/simple + - pip install "cloudshell-automation-api>=7.1.0.0,<7.2.0.0" --extra-index-url https://testpypi.python.org/simple script: - pushd package - - pip install "cloudshell-automation-api>=7.1.0.0,<7.2.0.0" --extra-index-url https://testpypi.python.org/simple - python setup.py develop - popd - python runtests.py --with-coverage --cover-package=package --exclude-dir=integration diff --git a/external_requirements.txt b/external_requirements.txt new file mode 100644 index 00000000..f0a84092 --- /dev/null +++ b/external_requirements.txt @@ -0,0 +1,3 @@ +pyvmomi==6.0.0 +jsonpickle==0.9.3 +enum==0.4.6 \ No newline at end of file diff --git a/test_requirements.txt b/test_requirements.txt index 7d963ba7..b238846d 100644 --- a/test_requirements.txt +++ b/test_requirements.txt @@ -3,5 +3,5 @@ coverage unittest2 mock teamcity-messages -jsonpickle -nose-exclude \ No newline at end of file +nose-exclude +coveralls \ No newline at end of file From 4fca571bd6e3867b6f50de4f050ac3823f369e9f Mon Sep 17 00:00:00 2001 From: Boris Modylevsky Date: Thu, 28 Jul 2016 18:37:54 +0300 Subject: [PATCH 4/4] orchestration save/restore works! --- .../vcenter/commands/command_orchestrator.py | 20 ++++++++++++------- .../test_command_orchestrator.py | 2 +- vcentershell_driver/driver.py | 4 ++-- vcentershell_driver/drivermetadata.xml | 6 +++--- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/package/cloudshell/cp/vcenter/commands/command_orchestrator.py b/package/cloudshell/cp/vcenter/commands/command_orchestrator.py index 4f0c6a99..40b85c92 100644 --- a/package/cloudshell/cp/vcenter/commands/command_orchestrator.py +++ b/package/cloudshell/cp/vcenter/commands/command_orchestrator.py @@ -409,10 +409,11 @@ def save_snapshot(self, context, snapshot_name): :return: """ resource_details = self._parse_remote_model(context) - return self.command_wrapper.execute_command_with_connection(context, - self.snapshot_saver.save_snapshot, - resource_details.vm_uuid, - snapshot_name) + created_snapshot_path = self.command_wrapper.execute_command_with_connection(context, + self.snapshot_saver.save_snapshot, + resource_details.vm_uuid, + snapshot_name) + return set_command_result(created_snapshot_path) def restore_snapshot(self, context, snapshot_name): """ @@ -457,6 +458,8 @@ def orchestration_save(self, context, mode="shallow", custom_params=None): snapshot_name = created_date.strftime('%y_%m_%d %H_%M_%S_%f') created_snapshot_path = self.save_snapshot(context=context, snapshot_name=snapshot_name) + created_snapshot_path = self._strip_double_quotes(created_snapshot_path) + orchestration_saved_artifact = OrchestrationSavedArtifact() orchestration_saved_artifact.artifact_type = 'vcenter_snapshot' orchestration_saved_artifact.identifier = created_snapshot_path @@ -471,6 +474,12 @@ def orchestration_save(self, context, mode="shallow", custom_params=None): return set_command_result(result=orchestration_save_result, unpicklable=False) + @staticmethod + def _strip_double_quotes(created_snapshot_path): + if created_snapshot_path.startswith('"') and created_snapshot_path.endswith('"'): + created_snapshot_path = created_snapshot_path[1:-1] + return created_snapshot_path + def orchestration_restore(self, context, saved_details): """ @@ -481,6 +490,3 @@ def orchestration_restore(self, context, saved_details): saved_artifacts_info = get_result_from_command_output(saved_details) snapshot_name = saved_artifacts_info['saved_artifacts_info']['saved_artifact']['identifier'] return self.restore_snapshot(context=context, snapshot_name=snapshot_name) - - - diff --git a/package/cloudshell/tests/test_commands/test_command_orchestrator.py b/package/cloudshell/tests/test_commands/test_command_orchestrator.py index eeea7e64..26a87410 100644 --- a/package/cloudshell/tests/test_commands/test_command_orchestrator.py +++ b/package/cloudshell/tests/test_commands/test_command_orchestrator.py @@ -134,7 +134,7 @@ def test_get_snapshots(self): def test_orchestration_save(self): # Arrange with patch(SAVE_SNAPSHOT) as save_snapshot_mock: - save_snapshot_mock.return_value = 'new_snapshot' + save_snapshot_mock.return_value = '"new_snapshot"' remote_command_context = create_autospec(ResourceRemoteCommandContext) remote_command_context.resource = create_autospec(ResourceContextDetails) diff --git a/vcentershell_driver/driver.py b/vcentershell_driver/driver.py index 1ea854f7..cab7300a 100644 --- a/vcentershell_driver/driver.py +++ b/vcentershell_driver/driver.py @@ -105,10 +105,10 @@ def remote_get_snapshots(self, context, ports): """ return self.command_orchestrator.get_snapshots(context) - def orchestration_save(self, context, mode="shallow", custom_params=None): + def orchestration_save(self, context, ports, mode="shallow", custom_params=None): return self.command_orchestrator.orchestration_save(context, mode, custom_params) - def orchestration_restore(self, context, saved_details): + def orchestration_restore(self, context, ports, saved_details): return self.command_orchestrator.orchestration_restore(context, saved_details) def get_vm_uuid(self, context, vm_name): diff --git a/vcentershell_driver/drivermetadata.xml b/vcentershell_driver/drivermetadata.xml index 4c8432c9..b4227adb 100644 --- a/vcentershell_driver/drivermetadata.xml +++ b/vcentershell_driver/drivermetadata.xml @@ -1,4 +1,4 @@ - + @@ -25,8 +25,8 @@ - - + +