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/package/cloudshell/cp/vcenter/commands/command_orchestrator.py b/package/cloudshell/cp/vcenter/commands/command_orchestrator.py
index 33202c8d..40b85c92 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
@@ -17,7 +20,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 +409,11 @@ 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)
+ 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):
"""
@@ -439,3 +443,50 @@ 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)
+
+ 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
+
+ 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)
+
+ @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):
+ """
+
+ :param context:
+ :param saved_details:
+ :return:
+ """
+ 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/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/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..26a87410 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')
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
diff --git a/vcentershell_driver/driver.py b/vcentershell_driver/driver.py
index c371c1f3..cab7300a 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, ports, mode="shallow", custom_params=None):
+ return self.command_orchestrator.orchestration_save(context, mode, custom_params)
+
+ 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):
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 fb848b5f..728ce2b1 100644
--- a/vcentershell_driver/drivermetadata.xml
+++ b/vcentershell_driver/drivermetadata.xml
@@ -25,6 +25,8 @@
+
+