Skip to content

Commit

Permalink
Merge pull request #908 from QualiSystems/develop
Browse files Browse the repository at this point in the history
Merge dev to master for 1.11.0 release
  • Loading branch information
alexazarh authored Feb 27, 2018
2 parents c6e7723 + fcc0489 commit f87ff5d
Show file tree
Hide file tree
Showing 26 changed files with 536 additions and 36 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ install:
- pip install "cloudshell-core>=2.2.0,<2.3.0" --extra-index-url https://testpypi.python.org/simple
- chmod 777 ./cloudshell_shell_core_install.sh
- ./cloudshell_shell_core_install.sh
- pip install "cloudshell-automation-api>=8.2.0.0,<8.3.0.0" --extra-index-url https://testpypi.python.org/simple
- pip install "cloudshell-automation-api>=8.3.0.0,<8.3.1.0" --extra-index-url https://testpypi.python.org/simple
language: python
notifications:
webhools: "https://qualisystems.getbadges.io/api/app/webhook/63350e33-4119-49c3-8127-075aaa022926"
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ A repository for projects providing out of the box capabilities within CloudShel
The CloudShell driver for controlling vCenter via CloudShell.

## Installation
* [QualiSystems CloudShell 7.0](http://www.qualisystems.com/products/cloudshell/cloudshell-overview/)
* [QualiSystems CloudShell](http://www.qualisystems.com/products/cloudshell/cloudshell-overview/)
* [pyvmomi 6.0](https://github.com/vmware/pyvmomi)
* [jsonpickle latest](https://jsonpickle.github.io/)

Expand All @@ -47,7 +47,8 @@ A repository for projects providing out of the box capabilities within CloudShel
9. Run Connect All command

## Links
[VmWare vCenter] (https://github.com/vmware/pyvmomi/tree/master/docs)
* [Offline Package] (https://support.quali.com/hc/en-us/articles/231613247)
* [VmWare vCenter] (https://github.com/vmware/pyvmomi/tree/master/docs)

## License
[Apache License 2.0](https://github.com/QualiSystems/vCenterShell/blob/master/LICENSE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,18 @@
from pyVim.connect import SmartConnect, Disconnect

from cloudshell.cp.vcenter.commands.refresh_ip import RefreshIpCommand
from cloudshell.cp.vcenter.commands.vm_details import VmDetailsCommand
from cloudshell.cp.vcenter.common.vcenter.task_waiter import SynchronousTaskWaiter
from cloudshell.cp.vcenter.common.vcenter.vmomi_service import pyVmomiService
from cloudshell.cp.vcenter.models.VCenterConnectionDetails import VCenterConnectionDetails
from cloudshell.cp.vcenter.vm.ip_manager import VMIPManager
from cloudshell.cp.vcenter.vm.vm_details_provider import VmDetailsProvider
from cloudshell.tests.utils.testing_credentials import TestCredentials
from cloudshell.tests.utils import helpers


class TestRefreshIpCommand(TestCase):

def integrationtest_refresh_ip(self):
resource_context = Mock()
resource_context.attributes = {"vCenter Template": "vCenter/Alex/test"}
Expand Down
33 changes: 28 additions & 5 deletions package/cloudshell/cp/vcenter/commands/command_orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
from datetime import datetime, date
import jsonpickle

from cloudshell.cp.vcenter.commands.vm_details import VmDetailsCommand
from cloudshell.cp.vcenter.models.DeployFromImageDetails import DeployFromImageDetails
from cloudshell.shell.core.context import ResourceRemoteCommandContext

from cloudshell.cp.vcenter.models.OrchestrationSaveResult import OrchestrationSaveResult
from cloudshell.cp.vcenter.models.OrchestrationSavedArtifactsInfo import OrchestrationSavedArtifactsInfo
from cloudshell.cp.vcenter.models.OrchestrationSavedArtifact import OrchestrationSavedArtifact
Expand Down Expand Up @@ -46,6 +48,7 @@
from cloudshell.cp.vcenter.vm.dvswitch_connector import VirtualSwitchToMachineConnector
from cloudshell.cp.vcenter.vm.ip_manager import VMIPManager
from cloudshell.cp.vcenter.vm.portgroup_configurer import VirtualMachinePortGroupConfigurer
from cloudshell.cp.vcenter.vm.vm_details_provider import VmDetailsProvider
from cloudshell.cp.vcenter.vm.vnic_to_network_mapper import VnicToNetworkMapper
from cloudshell.cp.vcenter.models.DeployFromTemplateDetails import DeployFromTemplateDetails

Expand All @@ -67,10 +70,15 @@ def __init__(self):

self.vm_loader = VMLoader(pv_service)

ip_manager = VMIPManager()
vm_details_provider = VmDetailsProvider(pyvmomi_service=pv_service,
ip_manager=ip_manager)

vm_deployer = VirtualMachineDeployer(pv_service=pv_service,
name_generator=generate_unique_name,
ovf_service=ovf_service,
resource_model_parser=ResourceModelParser())
resource_model_parser=ResourceModelParser(),
vm_details_provider=vm_details_provider)

dv_port_group_creator = DvPortGroupCreator(pyvmomi_service=pv_service,
synchronous_task_waiter=synchronous_task_waiter)
Expand All @@ -82,6 +90,7 @@ def __init__(self):
name_gen=port_group_name_generator)
virtual_switch_to_machine_connector = VirtualSwitchToMachineConnector(dv_port_group_creator,
virtual_machine_port_group_configurer)

# Command Wrapper
self.command_wrapper = CommandWrapper(pv_service=pv_service,
resource_model_parser=self.resource_model_parser,
Expand Down Expand Up @@ -119,13 +128,15 @@ def __init__(self):
VirtualMachinePowerManagementCommand(pyvmomi_service=pv_service,
synchronous_task_waiter=synchronous_task_waiter)

ip_manager = VMIPManager()

# Refresh IP command
self.refresh_ip_command = RefreshIpCommand(pyvmomi_service=pv_service,
resource_model_parser=ResourceModelParser(),
ip_manager=ip_manager)

# Get Vm Details command
self.vm_details = VmDetailsCommand(pyvmomi_service=pv_service,
vm_details_provider=vm_details_provider)

# Save Snapshot
self.snapshot_saver = SaveSnapshotCommand(pyvmomi_service=pv_service,
task_waiter=synchronous_task_waiter)
Expand Down Expand Up @@ -391,20 +402,32 @@ def get_vm_uuid_by_name(self, context, vm_name):
vm_name)
return set_command_result(result=res, unpicklable=False)

def save_snapshot(self, context, snapshot_name):
def get_vm_details(self, context,cancellation_context, requests_json):
requests = DeployDataHolder(jsonpickle.decode(requests_json)).items
res = self.command_wrapper.execute_command_with_connection(context,
self.vm_details.get_vm_details,
context.resource,
requests,
cancellation_context)
return set_command_result(result=res, unpicklable=False)

def save_snapshot(self, context, snapshot_name, save_memory='No'):
"""
Saves virtual machine to a snapshot
:param context: resource context of the vCenterShell
:type context: models.QualiDriverModels.ResourceCommandContext
:param snapshot_name: snapshot name to save to
:type snapshot_name: str
:param save_memory: Snapshot the virtual machine's memory. Lookup, Yes / No
:type save_memory: str
:return:
"""
resource_details = self._parse_remote_model(context)
created_snapshot_path = self.command_wrapper.execute_command_with_connection(context,
self.snapshot_saver.save_snapshot,
resource_details.vm_uuid,
snapshot_name)
snapshot_name,
save_memory)
return set_command_result(created_snapshot_path)

def restore_snapshot(self, context, snapshot_name):
Expand Down
2 changes: 1 addition & 1 deletion package/cloudshell/cp/vcenter/commands/refresh_ip.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,4 @@ def _get_custom_param(custom_params, custom_param_name):

if custom_param_values:
return custom_param_values[0]
return None
return None
20 changes: 16 additions & 4 deletions package/cloudshell/cp/vcenter/commands/save_snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def __init__(self, pyvmomi_service, task_waiter):
self.pyvmomi_service = pyvmomi_service
self.task_waiter = task_waiter

def save_snapshot(self, si, logger, vm_uuid, snapshot_name):
def save_snapshot(self, si, logger, vm_uuid, snapshot_name, save_memory):
"""
Creates a snapshot of the current state of the virtual machine
Expand All @@ -31,21 +31,33 @@ def save_snapshot(self, si, logger, vm_uuid, snapshot_name):
:type vm_uuid: str
:param snapshot_name: Snapshot name to save the snapshot to
:type snapshot_name: str
:param save_memory: Snapshot the virtual machine's memory. Lookup, Yes / No
:type save_memory: str
"""
vm = self.pyvmomi_service.find_by_uuid(si, vm_uuid)

snapshot_path_to_be_created = SaveSnapshotCommand._get_snapshot_name_to_be_created(snapshot_name, vm)

save_vm_memory_to_snapshot = SaveSnapshotCommand._get_save_vm_memory_to_snapshot(save_memory)

SaveSnapshotCommand._verify_snapshot_uniquness(snapshot_path_to_be_created, vm)

task = self._create_snapshot(logger, snapshot_name, vm)
task = self._create_snapshot(logger, snapshot_name, vm, save_vm_memory_to_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):
def _get_save_vm_memory_to_snapshot(save_memory):
return True if save_memory is not None and save_memory.lower() == 'yes' else False

@staticmethod
def _create_snapshot(logger, snapshot_name, vm, save_vm_memory_to_snapshot):
"""
:type save_vm_memory_to_snapshot: bool
"""
logger.info("Create virtual machine snapshot")
dump_memory = False
dump_memory = save_vm_memory_to_snapshot
quiesce = True
task = vm.CreateSnapshot(snapshot_name, 'Created by CloudShell vCenterShell', dump_memory, quiesce)
return task
Expand Down
79 changes: 79 additions & 0 deletions package/cloudshell/cp/vcenter/commands/vm_details.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import traceback
import time

from cloudshell.cp.vcenter.vm.vm_details_provider import VmDetails, VmDataField


class VmDetailsCommand(object):
def __init__(self, pyvmomi_service, vm_details_provider):
self.pyvmomi_service = pyvmomi_service
self.vm_details_provider = vm_details_provider
self.timeout = 30
self.delay = 1

def get_vm_details(self, si, logger, resource_context, requests, cancellation_context):
results = []
for request in requests:
if cancellation_context.is_cancelled:
break
app_name = request.deployedAppJson.name
try:
vm = self.pyvmomi_service.find_by_uuid(si, request.deployedAppJson.vmdetails.uid)
self._wait_for_vm_to_be_ready(vm, request, logger)
result = self.vm_details_provider.create(
vm=vm,
name=app_name,
reserved_networks=resource_context.attributes.get('Reserved Networks', '').split(';'),
ip_regex=next((p.value for p in request.deployedAppJson.vmdetails.vmCustomParams if p.name=='ip_regex'), None),
deployment_details_provider=DeploymentDetailsProviderFromAppJson(request.appRequestJson.deploymentService),
logger=logger)
except Exception as e:
logger.error("Error getting vm details for '{0}': {1}".format(app_name, traceback.format_exc()))
result = VmDetails(app_name)
result.error = e.message

results.append(result)

return results

def _wait_for_vm_to_be_ready(self, vm, request, logger):
start_time = time.time()
while time.time()-start_time<self.timeout and (self._not_guest_net(vm) or self._no_guest_ip(vm, request)):
time.sleep(self.delay)
logger.info('_wait_for_vm_to_be_ready: '+str(time.time()-start_time)+' sec')

@staticmethod
def _not_guest_net(vm):
return not vm.guest.net

@staticmethod
def _no_guest_ip(vm, request):
wait_for_ip = next((p.value for p in request.deployedAppJson.vmdetails.vmCustomParams if p.name == 'wait_for_ip'), False)
return wait_for_ip and not vm.guest.ipAddress


class DeploymentDetailsProviderFromAppJson(object):
def __init__(self, deployment_service):
self.deployment = deployment_service.model
self.dep_attributes = dict((att.name, att.value) for att in deployment_service.attributes)

def get_details(self):
"""
:rtype list[VmDataField]
"""
data = []
if self.deployment == 'vCenter Clone VM From VM':
data.append(VmDataField('Cloned VM Name', self.dep_attributes.get('vCenter VM', '')))

if self.deployment == 'VCenter Deploy VM From Linked Clone':
template = self.dep_attributes.get('vCenter VM', '')
snapshot = self.dep_attributes.get('vCenter VM Snapshot', '')
data.append(VmDataField('Cloned VM Name', '{0} (snapshot: {1})'.format(template, snapshot)))

if self.deployment == 'vCenter VM From Image':
data.append(VmDataField('Base Image Name', self.dep_attributes.get('vCenter Image', '').split('/')[-1]))

if self.deployment == 'vCenter VM From Template':
data.append(VmDataField('Template Name', self.dep_attributes.get('vCenter Template', '')))

return data
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
from cloudshell.cp.vcenter.common.vcenter.task_waiter import SynchronousTaskWaiter
from cloudshell.shell.core.session.cloudshell_session import CloudShellSessionContext

from cloudshell.cp.vcenter.common.utilites.common_utils import back_slash_to_front_converter

DOMAIN = 'Global'
ADDRESS = 'address'
USER = 'User'
Expand All @@ -30,6 +32,9 @@
VM_STORAGE = 'VM Storage'
SHUTDOWN_METHODS = ['soft', 'hard']

ATTRIBUTE_NAMES_THAT_ARE_SLASH_BACKSLASH_AGNOSTIC = [DEFAULT_DVSWITCH, DEFAULT_DATACENTER, VM_LOCATION, VM_STORAGE,
VM_RESOURCE_POOL, VM_CLUSTER]


class VCenterAutoModelDiscovery(object):
def __init__(self):
Expand Down Expand Up @@ -59,6 +64,8 @@ def validate_and_discover(self, context):
resource = context.resource
si = self._check_if_vcenter_user_pass_valid(context, cloudshell_session, resource.attributes)

resource.attributes = VCenterAutoModelDiscovery._make_attributes_slash_backslash_agnostic(resource.attributes)

auto_attr = []
if not si:
error_message = 'Could not connect to the vCenter: {0}, with given credentials'\
Expand Down Expand Up @@ -298,3 +305,20 @@ def get_full_name(dc_name, managed_object, name=''):
if name:
curr_path = '{0}/{1}'.format(managed_object.name, name)
return VCenterAutoModelDiscovery.get_full_name(dc_name, managed_object.parent, curr_path)

@staticmethod
def _make_attributes_slash_backslash_agnostic(attributes_with_slash_or_backslash):
"""
:param attributes_with_slash_or_backslash: resource attributes from
cloudshell.cp.vcenter.models.QualiDriverModels.ResourceContextDetails
:type attributes_with_slash_or_backslash: dict[str,str]
:return: attributes_with_slash
:rtype attributes_with_slash: dict[str,str]
"""
attributes_with_slash = dict()
for key, value in attributes_with_slash_or_backslash.items():
if key in ATTRIBUTE_NAMES_THAT_ARE_SLASH_BACKSLASH_AGNOSTIC:
value = back_slash_to_front_converter(value)
attributes_with_slash[key] = value

return attributes_with_slash
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,9 @@ def execute_command_with_connection(self, context, command, *args):
logger.debug(DEBUG_COMMAND_RESULT.format(str(results)))

return results
except Exception:
except Exception as ex:
logger.exception(COMMAND_ERROR.format(command_name))
logger.exception(str(type(ex)) + ': '+ str(ex))
raise
finally:
logger.info(LOG_FORMAT.format(END, command_name))
Expand Down
3 changes: 2 additions & 1 deletion package/cloudshell/cp/vcenter/models/DeployResultModel.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class DeployResult(object):
def __init__(self, vm_name, vm_uuid, cloud_provider_resource_name, ip_regex, refresh_ip_timeout, auto_power_on,
auto_power_off, wait_for_ip, auto_delete, autoload):
auto_power_off, wait_for_ip, auto_delete, autoload, vm_details_data):
"""
:param str vm_name: The name of the virtual machine
:param uuid uuid: The UUID
Expand All @@ -24,3 +24,4 @@ def __init__(self, vm_name, vm_uuid, cloud_provider_resource_name, ip_regex, ref
self.wait_for_ip = str(wait_for_ip)
self.auto_delete = str(auto_delete)
self.autoload = str(autoload)
self.vm_details_data = vm_details_data
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ def __init__(self):
self.vm_uuid = ''
self.cloud_provider = ''
self.fullname = ''
self.vm_custom_params = None
self.vm_custom_params = None
11 changes: 10 additions & 1 deletion package/cloudshell/cp/vcenter/network/vnic/vnic_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def _network_get_network_by_connection(vm, port_connection, pyvmomi_service):
return network

@staticmethod
def vnic_get_network_attached(vm, device, pyvmomi_service, logger):
def get_network_by_device(vm, device, pyvmomi_service, logger):
"""
Get a Network connected to a particular Device (vNIC)
@see https://github.com/vmware/pyvmomi/blob/master/docs/vim/dvs/PortConnection.rst
Expand Down Expand Up @@ -296,3 +296,12 @@ def set_vnic_connectivity_status(nic_spec, to_connect):
nic_spec.device.connectable = vim.vm.device.VirtualDevice.ConnectInfo()
nic_spec.device.connectable.connected = to_connect
nic_spec.device.connectable.startConnected = to_connect

@staticmethod
def get_network_vlan_id(network):
if hasattr(network, 'config'):
if hasattr(network.config, 'defaultPortConfig'):
if hasattr(network.config.defaultPortConfig, 'vlan'):
if hasattr(network.config.defaultPortConfig.vlan, 'vlanId'):
return network.config.defaultPortConfig.vlan.vlanId
return None
Loading

0 comments on commit f87ff5d

Please sign in to comment.