package/cloudshell/cp/vcenter/commands/connect_orchestrator.py
import traceback
from multiprocessing.pool import ThreadPool
import jsonpickle
from cloudshell.cp.vcenter.models.ActionResult import ActionResult
from cloudshell.cp.vcenter.models.DeployDataHolder import DeployDataHolder
from cloudshell.cp.vcenter.vm.dvswitch_connector import VmNetworkMapping, VmNetworkRemoveMapping
from cloudshell.cp.vcenter.common.vcenter.vm_location import VMLocation
from cloudshell.cp.vcenter.common.utilites.common_utils import get_error_message_from_exception
SUCCESSFULLY_REMOVED = 'VLAN Successfully removed'
ACTION_TYPE_SET_VLAN = 'setVlan'
ACTION_SUCCESS_MSG = 'VLAN successfully set'
INTERFACE = 'Interface'
ACTION_TYPE_REMOVE_VLAN = 'removeVlan'
class ConnectionCommandOrchestrator(object):
def __init__(self, connector, disconnector, resource_model_parser):
"""
:param connector:
:type connector: cloudshell.cp.vcenter.commands.connect_dvswitch.VirtualSwitchConnectCommand
:param disconnector:
:type disconnector: cloudshell.cp.vcenter.commands.disconnect_dvswitch.VirtualSwitchToMachineDisconnectCommand
:param resource_model_parser:
:return:
"""
self.connector = connector
self.disconnector = disconnector
self.resource_model_parser = resource_model_parser
self.vcenter_data_model = None
self.reserved_networks = []
self.dv_switch_path = ''
self.dv_switch_name = ''
self.default_network = ''
self.logger = None
def connect_bulk(self, si, logger, vcenter_data_model, request):
"""
:param si:
:param logger:
:param VMwarevCenterResourceModel vcenter_data_model:
:param request:
:return:
"""
self.logger = logger
self.logger.info('Apply connectivity changes has started')
self.logger.debug('Apply connectivity changes has started with the requet: {0}'.format(request))
holder = DeployDataHolder(jsonpickle.decode(request))
self.vcenter_data_model = vcenter_data_model
if vcenter_data_model.reserved_networks:
self.reserved_networks = [name.strip() for name in vcenter_data_model.reserved_networks.split(',')]
if not vcenter_data_model.default_dvswitch:
return self._handle_no_dvswitch_error(holder)
dvswitch_location = VMLocation.create_from_full_path(vcenter_data_model.default_dvswitch)
self.dv_switch_path = VMLocation.combine([vcenter_data_model.default_datacenter, dvswitch_location.path])
self.dv_switch_name = dvswitch_location.name
self.default_network = VMLocation.combine(
[vcenter_data_model.default_datacenter, vcenter_data_model.holding_network])
mappings = self._map_requsets(holder.driverRequest.actions)
self.logger.debug('Connectivity actions mappings: {0}'.format(jsonpickle.encode(mappings, unpicklable=False)))
pool = ThreadPool()
async_results = self._run_async_connection_actions(si, mappings, pool, logger)
results = self._get_async_results(async_results, pool)
self.logger.info('Apply connectivity changes done')
self.logger.debug('Apply connectivity has finished with the results: {0}'.format(jsonpickle.encode(results,
unpicklable=False)))
return results
def _handle_no_dvswitch_error(self, holder):
error = ValueError('Please set the attribute "Default DvSwitch" in order to execute any connectivity changes')
err_res = []
for action in holder.driverRequest.actions:
err_res.append(self._create_error_action_res(action, error))
return err_res
def _map_requsets(self, actions):
grouped_by_vm_by_requset_by_mode = self._group_action(actions)
vm_mapping = self._create_mapping_from_groupings(grouped_by_vm_by_requset_by_mode)
return vm_mapping
def _group_action(self, actions):
grouped_by_vm = self._group_action_by_vm(actions)
grouped_by_vm_by_requset = self._group_vm_actions_by_req_type(grouped_by_vm)
grouped_by_vm_by_requset_by_mode = self._group_vm_action_type_by_mode(grouped_by_vm_by_requset)
return grouped_by_vm_by_requset_by_mode
def _group_vm_action_type_by_mode(self, grouped_by_vm_by_requset):
grouped_by_vm_by_requset_by_mode = dict()
for machine, req_to_actions in grouped_by_vm_by_requset.items():
grouped_by_vm_by_requset_by_mode[machine] = dict()
for req_type, actions in req_to_actions.items():
grouped_by_vm_by_requset_by_mode[machine][req_type] = dict()
for action in actions:
self._add_safely_to_dict(dictionary=grouped_by_vm_by_requset_by_mode[machine][req_type],
key=action.connectionParams.mode,
value=action)
return grouped_by_vm_by_requset_by_mode
def _group_vm_actions_by_req_type(self, grouped_by_vm):
grouped_by_vm_by_requset = dict()
for machine, actions in grouped_by_vm.items():
grouped_by_vm_by_requset[machine] = dict()
for action in actions:
self._add_safely_to_dict(dictionary=grouped_by_vm_by_requset[machine], key=action.type, value=action)
return grouped_by_vm_by_requset
def _group_action_by_vm(self, actions):
grouped_by_vm = dict()
for action in actions:
vm_uuid = ConnectionCommandOrchestrator._get_vm_uuid(action)
self._add_safely_to_dict(dictionary=grouped_by_vm, key=vm_uuid, value=action)
return grouped_by_vm
def _create_mapping_from_groupings(self, grouped_by_vm_by_requset_by_mode):
vm_mapping = dict()
for vm, req_to_modes in grouped_by_vm_by_requset_by_mode.items():
actions_mapping = self.ActionsMapping()
remove_mappings = self._get_remove_mappings(req_to_modes, vm)
actions_mapping.remove_mapping = remove_mappings
set_mappings = self._get_set_mappings(req_to_modes)
actions_mapping.set_mapping = set_mappings
actions_mapping.action_tree = req_to_modes
vm_mapping[vm] = actions_mapping
return vm_mapping
def _get_remove_mappings(self, req_to_modes, vm):
remove_mappings = []
if ACTION_TYPE_REMOVE_VLAN in req_to_modes:
for mode, actions in req_to_modes[ACTION_TYPE_REMOVE_VLAN].items():
for action in actions:
macs = self._get_macs_from_action(action)
for mac in macs:
vm_network_remove_mapping = VmNetworkRemoveMapping()
vm_network_remove_mapping.mac_address = mac
vm_network_remove_mapping.vm_uuid = vm
remove_mappings.append(vm_network_remove_mapping)
return remove_mappings
def _get_macs_from_action(self, action):
interface_attributes = \
[attr.attributeValue for attr in action.connectorAttributes
if attr.attributeName == INTERFACE]
macs = []
for interface_attribute in interface_attributes:
macs += self._split_names(interface_attribute)
return macs
def _get_set_mappings(self, req_to_modes):
set_mappings = []
if ACTION_TYPE_SET_VLAN in req_to_modes:
set_requests = req_to_modes[ACTION_TYPE_SET_VLAN]
for mode, actions in set_requests.items():
for action in actions:
vnic_name = self._get_vnic_name(action)
vnic_names = self._split_names(vnic_name)
for name in vnic_names:
vnic_to_network = self._create_map(action.connectionParams.vlanId, mode, name)
set_mappings.append(vnic_to_network)
# this line makes sure that the vNICS with names are first
return sorted(set_mappings, key=lambda x: x.vnic_name, reverse=True)
def _create_map(self, vlan_id, mode, vnic_name):
vnic_to_network = VmNetworkMapping()
vnic_to_network.vnic_name = self._validate_vnic_name(vnic_name)
vnic_to_network.dv_switch_path = self.dv_switch_path
vnic_to_network.dv_switch_name = self.dv_switch_name
vnic_to_network.vlan_id = vlan_id
vnic_to_network.vlan_spec = mode
return vnic_to_network
def _run_async_connection_actions(self, si, mappings, pool, logger):
async_results = []
for vm, action_mappings in mappings.items():
async_results.append(pool.apply_async(self._apply_connectivity_changes, (si, vm, action_mappings, logger)))
return async_results
def _apply_connectivity_changes(self, si, vm_uuid, action_mappings, logger):
results = []
if action_mappings.remove_mapping:
remove_results = self._remove_vlan(action_mappings, si, vm_uuid, logger)
results += remove_results
if action_mappings.set_mapping:
set_results = self._set_vlan(action_mappings, si, vm_uuid, logger)
results += set_results
return results
def _set_vlan(self, action_mappings, si, vm_uuid, logger):
results = []
set_vlan_actions = action_mappings.action_tree[ACTION_TYPE_SET_VLAN]
try:
self.logger.info('connecting vm({0})'.format(vm_uuid))
self.logger.debug('connecting vm({0}) with the mappings'.format(vm_uuid,
jsonpickle.encode(action_mappings,
unpicklable=False)))
connection_results = self.connector.connect_to_networks(
si=si,
logger=logger,
vm_uuid=vm_uuid,
vm_network_mappings=action_mappings.set_mapping,
default_network_name=self.default_network,
reserved_networks=self.reserved_networks,
dv_switch_name=self.dv_switch_name,
promiscuous_mode=self.vcenter_data_model.promiscuous_mode)
connection_res_map = self._prepare_connection_results_for_extraction(connection_results)
act_by_mode_by_vlan = self._group_action_by_vlan_id(set_vlan_actions)
act_by_mode_by_vlan_by_nic = self._group_actions_by_vlan_by_vnic(act_by_mode_by_vlan)
results += self._get_set_vlan_result_suc(act_by_mode_by_vlan_by_nic, connection_res_map)
except Exception as e:
self.logger.exception('Exception raised while connecting vm({})'.format(vm_uuid))
for mode, actions in set_vlan_actions.items():
for action in actions:
error_result = self._create_error_action_res(action, e)
results.append(error_result)
results = self._consolidate_duplicate_results(results)
return results
def _prepare_connection_results_for_extraction(self, connection_results):
connection_res_map = dict()
for connection_result in connection_results:
vlan_spec = connection_result.network_name.split('_')
mode = vlan_spec[len(vlan_spec) - 1]
id = vlan_spec[len(vlan_spec) - 2]
if mode not in connection_res_map:
connection_res_map[mode] = dict()
if id not in connection_res_map[mode]:
connection_res_map[mode][id] = dict()
self._add_safely_to_dict(dictionary=connection_res_map[mode][id],
key=connection_result.requested_vnic,
value=connection_result)
return connection_res_map
def _get_set_vlan_result_suc(self, act_by_mode_by_vlan_by_nic, connection_res_map):
results = []
for mode, vlans_to_nics in act_by_mode_by_vlan_by_nic.items():
for vlan_id, nics_to_actions in vlans_to_nics.items():
for nic_name, actions in nics_to_actions.items():
nic_name = self._validate_vnic_name(nic_name)
for action in actions:
res = connection_res_map[mode][vlan_id][nic_name][0]
connection_res_map[mode][vlan_id][nic_name].remove(res)
result = ActionResult()
result.actionId = action.actionId
result.success = True
result.errorMessage = None
result.infoMessage = ACTION_SUCCESS_MSG
result.type = ACTION_TYPE_SET_VLAN
result.updatedInterface = res.mac_address
results.append(result)
final_res = self._consolidate_duplicate_results(results)
return final_res
def _consolidate_duplicate_results(self, results):
mapping = dict()
final_res = []
for result in results:
self._add_safely_to_dict(value=result, dictionary=mapping, key=result.actionId)
for action_id, actions in mapping.items():
final_act = actions[0]
for action in actions[1:]:
final_act.updatedInterface = '{0},{1}'.format(final_act.updatedInterface, action.updatedInterface)
final_res.append(final_act)
return final_res
def _group_actions_by_vlan_by_vnic(self, set_actions_grouped_by_vlan_id):
set_act_group_by_mode_by_vlan_by_requsted_vnic = dict()
for mode, vlan_to_action in set_actions_grouped_by_vlan_id.items():
set_act_group_by_mode_by_vlan_by_requsted_vnic[mode] = dict()
for vlan_id, actions in vlan_to_action.items():
set_act_group_by_mode_by_vlan_by_requsted_vnic[mode][vlan_id] = dict()
for action in actions:
name = self._get_vnic_name(action)
names = self._split_names(name)
for v_name in names:
self._add_safely_to_dict(
dictionary=set_act_group_by_mode_by_vlan_by_requsted_vnic[mode][vlan_id],
key=v_name,
value=action)
return set_act_group_by_mode_by_vlan_by_requsted_vnic
@staticmethod
def _split_names(name):
if not name:
return [name]
return [v_name for v_name in name.strip().split(',') if v_name]
def _group_action_by_vlan_id(self, set_vlan_actions):
set_actions_grouped_by_vlan_id = dict()
for mode, actions in set_vlan_actions.items():
set_actions_grouped_by_vlan_id[mode] = dict()
for action in actions:
vlan_id = action.connectionParams.vlanId
self._add_safely_to_dict(dictionary=set_actions_grouped_by_vlan_id[mode], key=vlan_id, value=action)
return set_actions_grouped_by_vlan_id
def _remove_vlan(self, action_mappings, si, vm_uuid, logger):
final_res = []
mode_to_actions = action_mappings.action_tree[ACTION_TYPE_REMOVE_VLAN]
try:
self.logger.info('disconnecting vm({0})'.format(vm_uuid))
self.logger.debug('disconnecting vm({0}) with the mappings'.format(vm_uuid,
jsonpickle.encode(action_mappings,
unpicklable=False)))
connection_results = self.disconnector.disconnect_from_networks(si,
logger,
self.vcenter_data_model,
vm_uuid,
action_mappings.remove_mapping)
interface_to_action = dict()
for mode, actions in mode_to_actions.items():
for action in actions:
names = self._get_macs_from_action(action)
for name in names:
interface_to_action[name] = action
results = []
for res in connection_results:
action = interface_to_action[res.vnic_mac]
action_result = ActionResult()
action_result.actionId = action.actionId
action_result.success = True
action_result.infoMessage = SUCCESSFULLY_REMOVED
action_result.type = ACTION_TYPE_REMOVE_VLAN
action_result.errorMessage = None
action_result.updatedInterface = res.vnic_mac
results.append(action_result)
final_res = self._consolidate_duplicate_results(results)
except Exception as e:
self.logger.error('Exception raised while disconnecting vm({0}) with exception: {1}'
.format(vm_uuid, traceback.format_exc()))
for mode, actions in mode_to_actions.items():
for action in actions:
error_result = self._create_error_action_res(action, e)
final_res.append(error_result)
final_res = self._consolidate_duplicate_results(final_res)
return final_res
@staticmethod
def _create_error_action_res(action, e):
error_result = ActionResult()
error_result.actionId = action.actionId
error_result.type = action.type
error_result.errorMessage = get_error_message_from_exception(e)
error_result.infoMessage = None
error_result.success = False
error_result.updatedInterface = ConnectionCommandOrchestrator._get_mac(action)
return error_result
@staticmethod
def _add_safely_to_dict(value, dictionary, key):
if key not in dictionary:
dictionary[key] = []
dictionary[key].append(value)
@staticmethod
def _get_vm_uuid(action):
vm_uuid_values = [attr.attributeValue for attr in action.customActionAttributes
if attr.attributeName == 'VM_UUID']
if vm_uuid_values and vm_uuid_values[0]:
return vm_uuid_values[0]
raise ValueError('VM_UUID is missing on action attributes')
@staticmethod
def _get_vnic_name(action):
vnic_name_values = [attr.attributeValue for attr in action.customActionAttributes
if attr.attributeName == 'Vnic Name']
if vnic_name_values:
return vnic_name_values[0]
return None
@staticmethod
def _get_async_results(async_results, pool):
pool.close()
pool.join()
results = []
for async_result in async_results:
action_results = async_result.get()
results += action_results
return results
@staticmethod
def _get_mac(action):
for att in action.connectorAttributes:
if att.attributeName == INTERFACE:
return att.attributeValue
class ActionsMapping(object):
def __init__(self):
self.action_tree = ''
self.remove_mapping = ''
self.set_mapping = ''
@staticmethod
def _validate_vnic_name(vnic_name):
if not vnic_name:
return None
if str(vnic_name).isdigit():
vnic_name = 'Network adapter {0}'.format(vnic_name)
return vnic_name