package/cloudshell/cp/vcenter/common/vcenter/model_auto_discovery.py
import collections
from pyVim.connect import SmartConnect, Disconnect
from pyVmomi import vim
from cloudshell.cp.vcenter.common.utilites.context_based_logger_factory import ContextBasedLoggerFactory
from cloudshell.cp.vcenter.models.QualiDriverModels import AutoLoadDetails, AutoLoadAttribute
from cloudshell.cp.vcenter.models.VCenterConnectionDetails import VCenterConnectionDetails
from cloudshell.cp.vcenter.common.model_factory import ResourceModelParser
from cloudshell.cp.vcenter.common.vcenter.vmomi_service import pyVmomiService
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'
PASSWORD = 'Password'
DEFAULT_DVSWITCH = 'Default dvSwitch'
DEFAULT_DATACENTER = 'Default Datacenter'
EXECUTION_SERVER_SELECTOR = 'Execution Server Selector'
HOLDING_NETWORK = 'Holding Network'
OVF_TOOL_PATH = 'OVF Tool Path'
SHUTDOWN_METHOD = 'Shutdown Method'
VM_CLUSTER = 'VM Cluster'
VM_LOCATION = 'VM Location'
VM_RESOURCE_POOL = 'VM Resource Pool'
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 ResourcePoolParams(object):
def __init__(self):
self.cluster_name = ''
self.resource_pool = ''
self.si = None
class VCenterAutoModelDiscovery(object):
def __init__(self):
self.dc = None
self.parser = ResourceModelParser()
self.pv_service = pyVmomiService(SmartConnect, Disconnect, SynchronousTaskWaiter())
self.context_based_logger_factory = ContextBasedLoggerFactory()
def _get_logger(self, context):
"""Get Quali logger from the given context
:param context: models.QualiDriverModels.AutoLoadCommandContext
"""
return self.context_based_logger_factory.create_logger_for_context(
logger_name='vCenterShell',
context=context)
def validate_and_discover(self, context):
"""
:type context: models.QualiDriverModels.AutoLoadCommandContext
"""
logger = self._get_logger(context)
logger.info('Autodiscovery started')
si = None
resource = None
with CloudShellSessionContext(context) as cloudshell_session:
self._check_if_attribute_not_empty(context.resource, ADDRESS)
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'\
.format(context.resource.address)
logger.error(error_message)
raise ValueError(error_message)
try:
all_dc = self.pv_service.get_all_items_in_vcenter(si, vim.Datacenter)
dc = self._validate_datacenter(si, all_dc, auto_attr, resource.attributes)
self.dc = dc
all_items_in_dc = self.pv_service.get_all_items_in_vcenter(si, None, dc)
dc_name = dc.name
for key, value in resource.attributes.items():
if key in [USER, PASSWORD, DEFAULT_DATACENTER, VM_CLUSTER]:
continue
validation_method = self._get_validation_method(key)
validation_method(si, all_items_in_dc, auto_attr, dc_name, resource.attributes, key)
except vim.fault.NoPermission:
logger.exception('Autodiscovery failed due to permissions error:')
raise Exception("vCenter permissions for configured resource(s) are invalid")
logger.info('Autodiscovery completed')
return AutoLoadDetails([], auto_attr)
@staticmethod
def _get_default_from_vc_by_type_and_name(items_in_vc, vim_type, name=None):
items = []
if not isinstance(vim_type, collections.Iterable):
vim_type = [vim_type]
for item in items_in_vc:
if item.name == name:
return item
item_type = type(item)
if [t for t in vim_type if item_type is t]:
items.append(item)
if len(items) == 1:
return items[0]
if len(items) > 1:
return 'too many options'
return 'not found'
def _validate_datacenter(self, si, all_item_in_vc, auto_att, attributes):
dc = self._validate_attribute(si, attributes, vim.Datacenter, DEFAULT_DATACENTER)
if not dc:
dc = self._get_default(all_item_in_vc, vim.Datacenter, DEFAULT_DATACENTER)
auto_att.append(AutoLoadAttribute('', DEFAULT_DATACENTER, dc.name))
return dc
def _validate_default_dvswitch(self, si, all_items_in_vc, auto_att, dc_name, attributes, key):
dvs_path = attributes[key]
# optional pararm
if(not attributes[key]):
return
path = "{}/{}".format(dc_name, dvs_path)
dv = self.pv_service.find_dvs_by_path(si, path)
auto_att.append(AutoLoadAttribute('', DEFAULT_DVSWITCH, dvs_path))
return dv
def _validate_attribute(self, si, attributes, vim_type, name, prefix=''):
if name in attributes and attributes[name]:
att_value = self._get_attribute_value(attributes, name, prefix)
obj = self.pv_service.get_folder(si, att_value)
if not obj or isinstance(obj, str):
raise ValueError('Could not find {0}'.format(name))
if vim_type and not isinstance(obj, vim_type):
raise ValueError('The given {0}: {1} is not of the correct type'.format(name, attributes[name]))
return obj
return False
def _get_attribute_value(self, attributes, name, prefix):
att_value = attributes[name]
if prefix:
att_value = '{0}/{1}'.format(prefix, att_value)
return att_value
def _get_default(self, all_item_in_vc, vim_type, key):
obj = self._get_default_from_vc_by_type_and_name(all_item_in_vc, vim_type)
if isinstance(obj, str):
if obj == 'too many options':
raise ValueError('{0} must be selected'.format(key))
raise ValueError('Could not find {0}'.format(key))
return obj
def _check_if_vcenter_user_pass_valid(self, context, session, attributes):
self._check_if_attribute_not_empty(attributes, USER)
self._check_if_attribute_not_empty(attributes, PASSWORD)
connection_details = self._get_connection_details(session,
attributes[PASSWORD],
attributes[USER],
context.resource.address)
try:
si = self.pv_service.connect(connection_details.host,
connection_details.username,
connection_details.password,
connection_details.port)
except Exception:
logger = self._get_logger(context)
logger.exception("Could not connect to the vcenter")
raise ValueError('could not connect to the vcenter: {0}, with the given credentials ({1})'.format(
connection_details.host,
connection_details.username))
return si
def _validate_vm_storage(self, si, all_items_in_vc, auto_att, dc_name, attributes, key):
accepted_types = (vim.Datastore, vim.StoragePod)
datastore = self._validate_attribute(si, attributes, accepted_types, key, dc_name)
if not datastore:
datastore = self._get_default(all_items_in_vc, accepted_types, key)
d_name = self.get_full_name(dc_name, datastore)
# removing the upper folder
d_name = d_name.replace('datastore/', '')
else:
d_name = attributes[key]
auto_att.append(AutoLoadAttribute('', key, d_name))
def _validate_saved_sandbox_storage(self, si, all_items_in_vc, auto_att, dc_name, attributes, key):
accepted_types = (vim.Datastore, vim.StoragePod)
# saved sandbox storage isnt mandatory so if not configured exit without validation
if key not in attributes or not attributes[key]:
return
datastore = self._validate_attribute(si, attributes, accepted_types, key, dc_name)
if not datastore:
datastore = self._get_default(all_items_in_vc, accepted_types, key)
d_name = self.get_full_name(dc_name, datastore)
# removing the upper folder
d_name = d_name.replace('datastore/', '')
else:
d_name = attributes[key]
auto_att.append(AutoLoadAttribute('', key, d_name))
def _validate_vm_location(self, si, all_items_in_vc, auto_att, dc_name, attributes, key):
accepted_types = None
folder = self._validate_attribute(si, attributes, accepted_types, key, dc_name)
if not folder:
raise ValueError('VM Folder cannot be empty')
f_name = attributes[key]
auto_att.append(AutoLoadAttribute('', key, f_name))
def _validate_vm_cluster(self, si, all_items_in_vc, auto_att, dc_name, attributes, key):
"""
:param si:
:param all_items_in_vc:
:param auto_att:
:param dc_name:
:param attributes:
:param key:
:return:
"""
accepted_types = [[vim.ClusterComputeResource], [vim.HostSystem]]
cluster_name = attributes[key]
cluster = self.pv_service.get_obj(si.content, accepted_types, cluster_name)
if not cluster:
cluster = self._get_default(all_items_in_vc, accepted_types, key)
c_name = self.get_full_name(dc_name, cluster)
# removing the upper folder
c_name = c_name.replace('host/', '')
else:
c_name = attributes[key]
auto_att.append(AutoLoadAttribute('', key, c_name))
return cluster
def _validate_vm_resource_pool(self, si, all_items_in_vc, auto_att, dc_name, attributes, key):
if key not in attributes or not attributes[key]:
return
cluster = self._validate_vm_cluster(si, all_items_in_vc, auto_att, dc_name, attributes, VM_CLUSTER)
pool_name = attributes[key]
rp_params = ResourcePoolParams()
rp_params.cluster_name = cluster.name
rp_params.resource_pool = pool_name
rp_params.si = si
pool = self.pv_service.get_resource_pool(self.dc, rp_params)
if pool:
auto_att.append(AutoLoadAttribute('', key, pool_name))
return
raise ValueError('The given resource pool not found: {0}'.format(pool_name))
def _find_resource_pool_by_path(self, name, root):
paths = name.split('/')
for path in paths:
root = self._find_resource_pool(path, root)
if not root:
return None
return root
def _find_resource_pool(self, name, root):
if hasattr(root, 'resourcePool'):
resource_pool = root.resourcePool
if hasattr(resource_pool, 'name') and resource_pool.name == name:
return resource_pool
if isinstance(resource_pool, collections.Iterable):
for pool in resource_pool:
if pool.name == name:
return pool
if hasattr(resource_pool, 'resourcePool'):
return self._find_resource_pool(name, resource_pool)
return None
def _validate_holding_network(self, si, all_items_in_vc, auto_att, dc_name, attributes, key):
holding_network = self._validate_attribute(si, attributes, vim.Network, key, dc_name)
if not holding_network:
raise ValueError('Holding Network cannot be empty')
n_name = attributes[key]
auto_att.append(AutoLoadAttribute('', key, n_name))
def _validate_shutdown_method(self, si, all_items_in_vc, auto_att, dc_name, attributes, key):
method = attributes[key]
if self._is_in_array(key, method, SHUTDOWN_METHODS):
auto_att.append(AutoLoadAttribute('', key, method))
@staticmethod
def _is_in_array(key, value, arr):
if value in arr:
return True
raise ValueError('{0} can only be: {1} instead of: {2}'.format(key, arr, value))
@staticmethod
def _is_found(item, key):
if not item:
raise ValueError('The {0} could not be found in the given vCenter'.format(key))
@staticmethod
def _get_connection_details(session, password, user, address):
password = session.DecryptPassword(password).Value
return VCenterConnectionDetails(address, user, password)
@staticmethod
def _check_if_attribute_not_empty(attributes, name):
if not ((hasattr(attributes, name) and getattr(attributes, name)) or
(isinstance(attributes, dict) and name in attributes and attributes[name])):
raise ValueError('{0} cannot be empty'.format(name))
@staticmethod
def _validate_empty(si, all_items_in_vc, attributes, auto_attr, dc_name, key):
return
def _get_validation_method(self, name):
if not name:
return self._validate_empty
name = name.lower()
name = name.split(' ')
name = '_'.join(name)
method_name = '_validate_{0}'.format(name)
return getattr(self, method_name, self._validate_empty)
@staticmethod
def get_full_name(dc_name, managed_object, name=''):
if managed_object.name == dc_name:
return name
curr_path = 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