class ChangePlanValidator: def __init__(self): self.instance_table = InstanceTable() self.model_table = ModelTable() self.rack_table = RackTable() self.user_table = UserTable() self.dc_table = DatacenterTable() self.cp_table = ChangePlanTable() self.cp_action_table = ChangePlanActionTable() self.instance_manager = InstanceManager() self.rack_height = 42 self.cp_asset_set = set() self.decom_asset_set = set() self.no_pow_conflict = False def validate_action(self, cp_action, all_cp_actions): self.cp_asset_set = set() self.decom_asset_set = set() self.no_pow_conflict = False cp_validation_result = self._validate_change_plan( cp_action.change_plan_id) if cp_validation_result != Constants.API_SUCCESS: return cp_validation_result if cp_action.action == Constants.CREATE_KEY: return self._create_action_validate(cp_action, all_cp_actions) elif cp_action.action == Constants.UPDATE_KEY: return self._edit_action_validate(cp_action, all_cp_actions) elif cp_action.action == Constants.DECOMMISSION_KEY: return self._decom_action_validate(cp_action, all_cp_actions) else: return Constants.API_SUCCESS def _validate_change_plan(self, cp_id: int): change_plan = self.cp_table.get_change_plan(cp_id) if change_plan is None: return "Change plan actions must correspond to an existing change plan." if change_plan.executed: return "Cannot modify a change plan that has already been executed" return Constants.API_SUCCESS def _create_action_validate(self, cp_action, all_cp_actions): instance = self.instance_manager.make_instance(cp_action.new_record) datacenter = self.dc_table.get_datacenter(instance.datacenter_id) if datacenter is None: return "The datacenter does not exist. Please select a valid datacenter." input_validation_result = self._validate_inputs(instance, datacenter) if input_validation_result != Constants.API_SUCCESS: return input_validation_result model_template = self.model_table.get_model(instance.model_id) if model_template is None: return "The model does not exist." if (instance.mount_type != Constants.BLADE_KEY) and (not datacenter.is_offline_storage): instance_bottom = int(instance.rack_position) instance_top = instance_bottom + int(model_template.height) - 1 if instance_top > self.rack_height: return "The placement of the instance exceeds the height of the rack." else: instance_bottom = 0 instance_top = 0 new_asset_number = instance.asset_number new_hostname = instance.hostname pow_connections = set(instance.power_connections) prev_action_val_result = self._validate_vs_prev_actions( instance, all_cp_actions, cp_action, new_asset_number, new_hostname, pow_connections, instance_bottom, instance_top, datacenter, ) if prev_action_val_result != Constants.API_SUCCESS: return prev_action_val_result common_val_result = self._common_validations(instance, cp_action, instance_bottom, instance_top, datacenter) if common_val_result != Constants.API_SUCCESS: return common_val_result if (not datacenter.is_offline_storage) and (instance.mount_type != Constants.BLADE_KEY): if not self.no_pow_conflict: rack = self.rack_table.get_rack(instance.rack_label, instance.datacenter_id) for p_connection in instance.power_connections: char1 = p_connection[0].upper() num = int(p_connection[1:]) if char1 == "L": pdu_arr = rack.pdu_left elif char1 == "R": pdu_arr = rack.pdu_right else: return "Invalid power connection. Please specify left or right PDU." if pdu_arr[num - 1] == 1: return f"There is already an asset connected at PDU {char1}{num}. Please pick an empty PDU port." connection_validation_result = self._validate_connections( instance.network_connections, instance.hostname, cp_action, all_cp_actions, ) if connection_validation_result != Constants.API_SUCCESS: return connection_validation_result return Constants.API_SUCCESS def _edit_action_validate(self, cp_action, all_cp_actions): instance = self.instance_manager.make_instance(cp_action.new_record) datacenter = self.dc_table.get_datacenter(instance.datacenter_id) if datacenter is None: return "The datacenter does not exist. Please select a valid datacenter." input_validation_result = self._validate_inputs(instance, datacenter) if input_validation_result != Constants.API_SUCCESS: return input_validation_result model_template = self.model_table.get_model(instance.model_id) if model_template is None: return "The model does not exist." if (instance.mount_type != Constants.BLADE_KEY) and (not datacenter.is_offline_storage): instance_bottom = int(instance.rack_position) instance_top = instance_bottom + int(model_template.height) - 1 if instance_top > self.rack_height: return "The placement of the instance exceeds the height of the rack." else: instance_bottom = 0 instance_top = 0 prev_update_in_plan = self.cp_action_table.get_newest_asset_record_in_plan( cp_action.change_plan_id, cp_action.original_asset_number, 999999999, ) if (prev_update_in_plan is not None and prev_update_in_plan.action != Constants.COLLATERAL_KEY and prev_update_in_plan.step != cp_action.step): return f"This asset is already being modified in step {prev_update_in_plan.step}. Please update your desired information there." new_asset_number = instance.asset_number new_hostname = instance.hostname pow_connections = set(instance.power_connections) prev_action_val_result = self._validate_vs_prev_actions( instance, all_cp_actions, cp_action, new_asset_number, new_hostname, pow_connections, instance_bottom, instance_top, datacenter, ) if prev_action_val_result != Constants.API_SUCCESS: return prev_action_val_result common_val_result = self._common_validations(instance, cp_action, instance_bottom, instance_top, datacenter) if common_val_result != Constants.API_SUCCESS: return common_val_result if (not datacenter.is_offline_storage) and (instance.mount_type != Constants.BLADE_KEY): if prev_update_in_plan is not None: prev_connections = prev_update_in_plan.new_record.get( Constants.NETWORK_CONNECTIONS_KEY) else: prev_instance = self.instance_table.get_instance_by_asset_number( cp_action.original_asset_number) prev_connections = prev_instance.network_connections connection_validation_result = self._validate_connections_edit( instance.network_connections, instance.hostname, cp_action, all_cp_actions, prev_connections, ) if connection_validation_result != Constants.API_SUCCESS: return connection_validation_result return Constants.API_SUCCESS def _decom_action_validate(self, cp_action, all_cp_actions): asset = self.instance_table.get_instance_by_asset_number( cp_action.original_asset_number) if asset is None: return f"Asset with asset number {cp_action.original_asset_number} does not exist." prev_update_in_plan = self.cp_action_table.get_newest_asset_record_in_plan( cp_action.change_plan_id, cp_action.original_asset_number, 999999999, ) if (prev_update_in_plan is not None and prev_update_in_plan.action != Constants.COLLATERAL_KEY and prev_update_in_plan.step != cp_action.step): return f"This asset is already being modified in step {prev_update_in_plan.step}. Please update your desired information there." if asset.mount_type == Constants.CHASIS_KEY: no_conflict = set() for a in all_cp_actions: if a.new_record.get( Constants.CHASSIS_HOSTNAME_KEY) == asset.hostname: return f"A blade chassis must be empty before it can be decommissioned. A blade is placed in the chassis in step {a.step} of the change plan." else: no_conflict.add(a.original_asset_number) blade_list = self.instance_table.get_blades_by_chassis_hostname( asset.hostname) if blade_list is not None: for blade in blade_list: if not blade.asset_number in no_conflict: return f"A blade chassis must be empty before it can be decommissioned. Blade with asset number {blade.asset_number} is in the chassis." return Constants.API_SUCCESS def _validate_inputs(self, instance, datacenter): if (not datacenter.is_offline_storage) and (instance.mount_type != Constants.BLADE_KEY): rack = self.rack_table.get_rack(instance.rack_label, instance.datacenter_id) if rack is None: return f"Rack {instance.rack_label} does not exist in datacenter {datacenter.name}. Assets must be created on preexisting racks" rack_u_pattern = re.compile("[0-9]+") if rack_u_pattern.fullmatch(str(instance.rack_position)) is None: return "The value for Rack U must be a positive integer." asset_pattern = re.compile("[0-9]{6}") if asset_pattern.fullmatch(str(instance.asset_number)) is None: return "Asset numbers must be 6 digits long and only contain numbers." if instance.hostname is not None and instance.hostname != "": if len(instance.hostname) > 64: return "Hostnames must be 64 characters or less" host_pattern = re.compile("[a-zA-Z]*[A-Za-z0-9-]*[A-Za-z0-9]") if host_pattern.fullmatch(instance.hostname) is None: return "Hostnames must start with a letter, only contain letters, numbers, periods, and hyphens, and end with a letter or number." if instance.owner != "" and self.user_table.get_user( instance.owner) is None: return f"The owner {instance.owner} is not an existing user. Please enter the username of an existing user." return Constants.API_SUCCESS def _validate_vs_prev_actions( self, instance, all_cp_actions, cp_action, new_asset_number, new_hostname, pow_connections, instance_bottom: int, instance_top: int, datacenter, ): for prev_action in all_cp_actions: if prev_action.step >= cp_action.step: continue if prev_action.action != Constants.CREATE_KEY: self.cp_asset_set.add(prev_action.original_asset_number) if prev_action.action == Constants.DECOMMISSION_KEY: self.decom_asset_set.add(prev_action.original_asset_number) # Asset Number Validation if (prev_action.original_asset_number == new_asset_number and prev_action.action != Constants.DECOMMISSION_KEY): return f"Asset numbers must be unique. An asset with asset number {instance.asset_number} already exists." if (prev_action.new_record.get( Constants.ASSET_NUMBER_KEY) == new_asset_number and prev_action.action == Constants.CREATE_KEY): return f"Asset with asset number {new_asset_number} is already being created in step {prev_action.step} of the change plan." # Hostname Validation if new_hostname is not None and new_hostname != "": if (prev_action.new_record.get( Constants.HOSTNAME_KEY) == new_hostname and prev_action.action != Constants.DECOMMISSION_KEY): return f"Asset with hostname {new_hostname} already exists in step {prev_action.step} of the change plan." # Location Validation if not datacenter.is_offline_storage: if instance.mount_type != Constants.BLADE_KEY: if (prev_action.action == Constants.CREATE_KEY or prev_action.action == Constants.UPDATE_KEY): model_id = self.instance_manager.get_model_id_from_name( prev_action.new_record.get(Constants.MODEL_KEY)) model = self.model_table.get_model(model_id) other_bottom = int( prev_action.new_record.get( Constants.RACK_POSITION_KEY)) other_top = (int( prev_action.new_record.get( Constants.RACK_POSITION_KEY)) + model.height - 1) if (other_bottom >= instance_bottom and other_bottom <= instance_top): result = f"The asset placement conflicts with asset with asset number {prev_action.new_record.get(Constants.ASSET_NUMBER_KEY)} " result += f"edited in step {prev_action.step}." return result elif other_top >= instance_bottom and other_top <= instance_top: result = f"The asset placement conflicts with asset with asset number {prev_action.new_record.get(Constants.ASSET_NUMBER_KEY)} " result += f"edited in step {prev_action.step}." return result elif (instance.mount_type == Constants.BLADE_KEY and prev_action.new_record.get( Constants.MOUNT_TYPE_KEY) == Constants.BLADE_KEY): if instance.chassis_hostname == prev_action.new_record.get( Constants.CHASSIS_HOSTNAME_KEY ) and instance.chassis_slot == prev_action.new_record.get( Constants.CHASSIS_SLOT_KEY): return f"A blade is already being placed in chassis with hostname {instance.chassis_hostname} at position {instance.chassis_slot} in step {prev_action.step} of the change plan." # Power Connection Validation if (not datacenter.is_offline_storage) and (instance.mount_type != Constants.BLADE_KEY): if (prev_action.action != Constants.DECOMMISSION_KEY and prev_action.action != Constants.COLLATERAL_KEY): prev_pow_connections = prev_action.new_record.get( Constants.POWER_CONNECTIONS_KEY) print(prev_action.new_record) prev_pow_connections = set(prev_pow_connections) pow_intersection = pow_connections.intersection( prev_pow_connections) if len(pow_intersection) > 0: return f"There is already an asset connected at PDU port {pow_intersection.pop()}. Please pick an empty PDU port." if (prev_action.action == Constants.UPDATE_KEY or prev_action.action == Constants.DECOMMISSION_KEY): old_pow_connections = prev_action.old_record.get( Constants.POWER_CONNECTIONS_KEY) if old_pow_connections is None: self.no_pow_conflict = True else: old_pow_connections = set(old_pow_connections) old_pow_intersection = pow_connections.intersection( old_pow_connections) if len(old_pow_intersection) > 0: self.no_pow_conflict = True return Constants.API_SUCCESS def _common_validations(self, instance, cp_action, instance_bottom, instance_top, datacenter): if (self.instance_table.get_instance_by_asset_number( instance.asset_number) is not None and not instance.asset_number in self.decom_asset_set and instance.asset_number != cp_action.original_asset_number): return f"Asset numbers must be unique. An asset with asset number {instance.asset_number} already exists." if instance.hostname != "" and instance.hostname is not None: duplicate_hostname = self.instance_table.get_instance_by_hostname( instance.hostname) if (duplicate_hostname is not None and not duplicate_hostname.asset_number in self.cp_asset_set and duplicate_hostname.asset_number != instance.asset_number): return f"An asset with hostname {duplicate_hostname.hostname} exists at location {duplicate_hostname.rack_label} U{duplicate_hostname.rack_position}" if not datacenter.is_offline_storage: if instance.mount_type != Constants.BLADE_KEY: instance_list = self.instance_table.get_instances_by_rack( instance.rack_label, instance.datacenter_id) if instance_list is not None: for current_instance in instance_list: if (current_instance.asset_number in self.cp_asset_set or current_instance.asset_number == cp_action.original_asset_number): continue model = self.model_table.get_model(instance.model_id) current_instance_top = ( current_instance.rack_position + model.height - 1) if (current_instance.rack_position >= instance_bottom and current_instance.rack_position <= instance_top): return self.return_conflict(current_instance) elif (current_instance_top >= instance_bottom and current_instance_top <= instance_top): return self.return_conflict(current_instance) elif instance.mount_type == Constants.BLADE_KEY: blade_conflict = self.instance_table.get_blade_by_chassis_and_slot( instance.chassis_hostname, instance.chassis_slot) if (blade_conflict is not None and not blade_conflict.asset_number in self.cp_asset_set): return f"A blade with hostname {blade_conflict.hostname} is already located in position {instance.chassis_slot} of chassis with hostname {instance.chassis_hostname}." return Constants.API_SUCCESS def _validate_connections(self, network_connections, hostname, cp_action, all_cp_actions): result = "" new_connections = {} for my_port in network_connections: mac_adress = network_connections[my_port][ Constants.MAC_ADDRESS_KEY] connection_hostname = network_connections[my_port][ "connection_hostname"] connection_port = network_connections[my_port]["connection_port"] if connection_hostname in new_connections.keys(): if new_connections[connection_hostname] == connection_port: result += "Cannot make two network connections to the same port." elif connection_hostname != "" and connection_port != "": new_connections[connection_hostname] = connection_port mac_pattern = re.compile( "[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}" ) if mac_adress != "" and mac_pattern.fullmatch( mac_adress.lower()) is None: result += f"Invalid MAC address for port {my_port}. MAC addresses must be 6 byte, colon separated hexidecimal strings (i.e. a1:b2:c3:d4:e5:f6). \n" if (connection_hostname == "" or connection_hostname is None) and ( connection_port == "" or connection_port is None): continue if not (connection_hostname != "" and connection_port != ""): result += "Connections require both a hostname and connection port." other_instance = None for prev_action in all_cp_actions: if prev_action.step >= cp_action.step: continue if (prev_action.new_record[Constants.HOSTNAME_KEY] == connection_hostname): other_instance = self.instance_manager.make_instance( prev_action.new_record) if other_instance is None: other_instance = self.instance_table.get_instance_by_hostname( connection_hostname) if other_instance is None: result += f"The asset with hostname {connection_hostname} does not exist. Connections must be between assets with existing hostnames. \n" continue if connection_port in other_instance.network_connections: if (other_instance.network_connections[connection_port] ["connection_port"] != my_port) and ( other_instance.network_connections[connection_port] ["connection_port"] != ""): result += f"The port {connection_port} on asset with hostname {connection_hostname} is connected to another asset. \n" continue if (other_instance.network_connections[connection_port] ["connection_hostname"] != hostname) and ( other_instance.network_connections[connection_port] ["connection_hostname"] != ""): result += f"The port {connection_port} on asset with hostname {connection_hostname} is already connected to another asset." if result == "": return Constants.API_SUCCESS else: return result def _validate_connections_edit(self, network_connections, hostname, cp_action, all_cp_actions, prev_connections): result = "" new_connections = {} for my_port in network_connections: mac_adress = network_connections[my_port][ Constants.MAC_ADDRESS_KEY] connection_hostname = network_connections[my_port][ "connection_hostname"] connection_port = network_connections[my_port]["connection_port"] if connection_hostname in new_connections.keys(): if new_connections[connection_hostname] == connection_port: result += "Cannot make two network connections to the same port." elif connection_hostname != "" and connection_port != "": new_connections[connection_hostname] = connection_port mac_pattern = re.compile( "[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}" ) if mac_adress != "" and mac_pattern.fullmatch( mac_adress.lower()) is None: result += f"Invalid MAC address for port {my_port}. MAC addresses must be 6 byte, colon separated hexidecimal strings (i.e. a1:b2:c3:d4:e5:f6). \n" if (connection_hostname == "" or connection_hostname is None) and ( connection_port == "" or connection_port is None): continue if (connection_hostname == prev_connections[my_port]["connection_hostname"] and connection_port == prev_connections[my_port]["connection_port"]): continue if not (connection_hostname != "" and connection_port != ""): result += "Connections require both a hostname and connection port." other_instance = None for prev_action in all_cp_actions: if prev_action.step >= cp_action.step: continue if (prev_action.new_record[Constants.HOSTNAME_KEY] == connection_hostname): other_instance = self.instance_manager.make_instance( prev_action.new_record) if other_instance is None: other_instance = self.instance_table.get_instance_by_hostname( connection_hostname) if other_instance is None: result += f"The asset with hostname {connection_hostname} does not exist. Connections must be between assets with existing hostnames. \n" continue if connection_port in other_instance.network_connections: if (other_instance.network_connections[connection_port] ["connection_port"] != my_port) and ( other_instance.network_connections[connection_port] ["connection_port"] != ""): result += f"The port {connection_port} on asset with hostname {connection_hostname} is connected to another asset. \n" continue if (other_instance.network_connections[connection_port] ["connection_hostname"] != hostname) and ( other_instance.network_connections[connection_port] ["connection_hostname"] != ""): result += f"The port {connection_port} on asset with hostname {connection_hostname} is already connected to another asset." if result == "": return Constants.API_SUCCESS else: return result def return_conflict(self, current_instance): result = f"The asset placement conflicts with asset with asset number {current_instance.asset_number} " result += f"on rack {current_instance.rack_label} at height U{current_instance.rack_position}." return result
class InstanceManager: def __init__(self): self.table = InstanceTable() self.model_table = ModelTable() self.dc_table = DatacenterTable() self.rack_table = RackTable() self.validate = InstanceValidator() self.asset_num_generator = AssetNumGenerator() def create_instance(self, instance_data): print("INSTANCE DATA") print(instance_data) try: try: new_instance = self.make_instance(instance_data) if type(new_instance) is InvalidInputsError: return new_instance except InvalidInputsError as e: return e.message create_validation_result = Constants.API_SUCCESS try: create_validation_result = self.validate.create_instance_validation( new_instance) if create_validation_result != Constants.API_SUCCESS: raise InvalidInputsError(create_validation_result) except InvalidInputsError as e: raise InvalidInputsError(e.message) # try: is_in_offline_storage = (DatacenterTable().get_datacenter_by_name( instance_data.get(Constants.DC_NAME_KEY)).is_offline_storage) self.table.add_instance(new_instance) if (new_instance.mount_type != Constants.BLADE_KEY and not is_in_offline_storage): power_result = self.add_power_connections(new_instance) print("POWER RESULT") print(power_result) if power_result != Constants.API_SUCCESS: self.table.delete_instance_by_asset_number( new_instance.asset_number) raise InvalidInputsError( "An error occurred when trying to add power connections." ) connect_result = self.make_corresponding_connections( new_instance.network_connections, new_instance.hostname) if connect_result != Constants.API_SUCCESS: self.table.delete_instance_by_asset_number( new_instance.asset_number) raise InvalidInputsError(connect_result) # except: # raise InvalidInputsError("Unable to create asset") except InvalidInputsError as e: print(e.message) raise InvalidInputsError(e.message) # except Exception as e: # print(str(e)) # raise InvalidInputsError( # "An error occurred when attempting to create the asset." # ) def delete_instance(self, instance_data): asset_number = self.check_null( instance_data[Constants.ASSET_NUMBER_KEY]) if asset_number == "": raise InvalidInputsError("Must provide an asset number") asset = self.table.get_instance_by_asset_number(asset_number) if asset is None: raise InvalidInputsError( "The asset you are trying to delete was not found.") if asset.mount_type == Constants.CHASIS_KEY: blade_list = self.table.get_blades_by_chassis_hostname( asset.hostname) if blade_list is not None: raise InvalidInputsError( "A blade chassis must be empty before it can be decommissioned or deleted." ) if asset.mount_type != Constants.BLADE_KEY: delete_power_result = self.delete_power_connections(asset) print(delete_power_result) if delete_power_result != Constants.API_SUCCESS: raise InvalidInputsError( "An error occurred when trying to remove power connections." ) delete_connection_result = self.delete_connections(asset) if delete_connection_result != Constants.API_SUCCESS: raise InvalidInputsError(delete_connection_result) try: self.table.delete_instance_by_asset_number(asset_number) except: raise InvalidInputsError( "An error occurred when trying to delete the specified asset.") def detail_view(self, instance_data): print(instance_data) asset_number = instance_data.get(Constants.ASSET_NUMBER_KEY) try: print("Get these things") print(asset_number) instance = self.table.get_instance_by_asset_number(asset_number) return instance except: raise InvalidInputsError( "An error occured while retrieving data for this asset.") def edit_instance(self, instance_data): print("INSTANCE DATA") print(instance_data) try: original_asset_number = instance_data.get( Constants.ASSET_NUMBER_ORIG_KEY) if original_asset_number is None: raise InvalidInputsError("Unable to find the asset to edit.") original_asset = self.table.get_instance_by_asset_number( original_asset_number) if original_asset is None: raise InvalidInputsError("Could not find asset to update.") new_instance = self.make_instance(instance_data) if type(new_instance) is InvalidInputsError: return new_instance self.delete_power_connections(original_asset) delete_connection_result = self.delete_connections(original_asset) if delete_connection_result != Constants.API_SUCCESS: raise InvalidInputsError( "Failed to update network connections.") edit_validation_result = self.validate.edit_instance_validation( new_instance, original_asset_number) if edit_validation_result != Constants.API_SUCCESS: original_instance = self.table.get_instance_by_asset_number( original_asset_number) self.make_corresponding_connections( original_instance.network_connections, original_instance.hostname) raise InvalidInputsError(edit_validation_result) except InvalidInputsError as e: raise InvalidInputsError(e.message) self.add_power_connections(new_instance) edit_connection_result = self.make_corresponding_connections( new_instance.network_connections, new_instance.hostname) if edit_connection_result != Constants.API_SUCCESS: original_instance = self.table.get_instance_by_asset_number( original_asset_number) self.make_corresponding_connections( original_instance.network_connections, original_instance.hostname) raise InvalidInputsError(edit_connection_result) self.table.edit_instance(new_instance, original_asset_number) if new_instance.mount_type == Constants.CHASIS_KEY and ( original_asset.hostname != new_instance.hostname or original_asset.datacenter_id != new_instance.datacenter_id): blade_list = self.table.get_blades_by_chassis_hostname( original_asset.hostname) if blade_list is not None: for blade in blade_list: blade.chassis_hostname = new_instance.hostname blade.datacenter_id = new_instance.datacenter_id self.table.edit_instance(blade, blade.asset_number) def get_instances(self, filter, dc_name, limit: int): model_name = filter.get(Constants.MODEL_KEY) try: if model_name is not None and model_name != "": print("MODEL_NAME") print(model_name) model_id = self.get_model_id_from_name(model_name) else: model_id = None except: raise InvalidInputsError( "An error occurred while trying to filter by model name. Please input a different model name" ) try: if dc_name is not None: dc_id = self.get_datacenter_id_from_name(dc_name) if dc_id == -1: dc_id = None except: raise InvalidInputsError( "An error occurred while trying to filter by datacenter name. Please input a different model name" ) hostname = filter.get(Constants.HOSTNAME_KEY) rack_label = filter.get(Constants.RACK_KEY) rack_position = filter.get(Constants.RACK_POSITION_KEY) try: instance_list = self.table.get_instances_with_filters( model_id=model_id, hostname=hostname, rack_label=rack_label, rack_position=rack_position, datacenter_id=dc_id, limit=limit, ) return instance_list except: raise InvalidInputsError( "An error occurred while trying to retrieve instance data.") def get_possible_models_with_filters(self, prefix_json): try: return_list = [] model_list = self.model_table.get_all_models() for model in model_list: model_name = model.vendor + " " + model.model_number # if model_name.startswith(prefix): return_list.append(model_name) return return_list except: raise InvalidInputsError( "An error occurred while trying to retrieve model options.") def make_instance(self, instance_data): model_name = self.check_null(instance_data[Constants.MODEL_KEY]) model_id = self.get_model_id_from_name(model_name) model = self.get_model_from_id(model_id) mount_type = model.mount_type datacenter_name = self.check_null(instance_data[Constants.DC_NAME_KEY]) datacenter_id = self.get_datacenter_id_from_name(datacenter_name) try: hostname = self.check_null(instance_data[Constants.HOSTNAME_KEY]) rack = self.check_null(instance_data[Constants.RACK_KEY].upper()) rack_position = self.check_null( instance_data[Constants.RACK_POSITION_KEY]) owner = self.check_null(instance_data[Constants.OWNER_KEY]) comment = self.check_null(instance_data[Constants.COMMENT_KEY]) network_connections = self.check_null( instance_data[Constants.NETWORK_CONNECTIONS_KEY]) power_connections = self.check_null( instance_data[Constants.POWER_CONNECTIONS_KEY]) asset_number = self.check_null( instance_data[Constants.ASSET_NUMBER_KEY]) except: raise InvalidInputsError( "Could not read data fields correctly. Client-server error occurred." ) display_color = self.asset_or_model_val( instance_data.get(Constants.DISPLAY_COLOR_KEY), model.display_color) cpu = self.asset_or_model_val(instance_data.get(Constants.CPU_KEY), model.cpu) self.asset_or_model_val(instance_data.get(Constants.CPU_KEY), model.cpu) memory = self.asset_or_model_val( instance_data.get(Constants.MEMORY_KEY), model.memory) storage = self.asset_or_model_val( instance_data.get(Constants.STORAGE_KEY), model.storage) if mount_type == Constants.BLADE_KEY: chassis_hostname = instance_data.get( Constants.CHASSIS_HOSTNAME_KEY) chassis_slot = instance_data.get(Constants.CHASSIS_SLOT_KEY) else: chassis_hostname = "" chassis_slot = -1 # if rack == "": # return InvalidInputsError("Must provide a rack location") # if rack_position == "": # return InvalidInputsError("Must provide a rack location") if asset_number == "": return InvalidInputsError("Must provide an asset number") return Instance( model_id, hostname, rack, rack_position, owner, comment, datacenter_id, network_connections, power_connections, asset_number, mount_type, display_color, cpu, memory, storage, chassis_hostname, chassis_slot, ) def get_model_id_from_name(self, model_name): try: model_list = self.model_table.get_all_models() for model in model_list: if model.vendor + " " + model.model_number == model_name: print("FOUND MATCH") model_id = self.model_table.get_model_id_by_vendor_number( model.vendor, model.model_number) if model_id is None: model_id = -1 return model_id return -1 except: raise InvalidInputsError( "An error occurred while trying to retrieve model info corresponding to the instance." ) def get_datacenter_id_from_name(self, datacenter_name): try: datacenter_id = self.dc_table.get_datacenter_id_by_name( datacenter_name) if datacenter_id is None: return -1 return datacenter_id except: raise InvalidInputsError( "An error occurred while trying to retrieve datacenter info corresponding to the instance." ) def get_model_from_id(self, model_id): model = self.model_table.get_model(model_id) if model is None: raise InvalidInputsError( "An error occurred while trying to retrieve model info corresponding to the instance." ) return model def get_dc_from_id(self, dc_id): datacenter = self.dc_table.get_datacenter(dc_id) if datacenter is None: raise InvalidInputsError( "An error occurred while trying to retrieve datacenter info corresponding to the instance." ) return datacenter def make_corresponding_connections(self, network_connections, hostname): for port in network_connections: connection_hostname = network_connections[port][ "connection_hostname"] connection_port = network_connections[port]["connection_port"] if (connection_hostname == "" or connection_hostname is None) and ( connection_port == "" or connection_port is None): continue # print("SEARCH " + connection_hostname) other_instance = self.table.get_instance_by_hostname( connection_hostname) print("COMPLETE") if other_instance is None: return f"An error occurred when attempting to add the network connection. Could not find asset with hostname {connection_hostname}." other_instance.network_connections[connection_port][ "connection_hostname"] = hostname other_instance.network_connections[connection_port][ "connection_port"] = port print(other_instance.network_connections) try: print("EDITIG") self.table.edit_instance(other_instance, other_instance.asset_number) print("EDITED SUCCESS") except: return f"Could not add new network connections to asset with hostname {other_instance.hostname}." return Constants.API_SUCCESS def delete_connections(self, asset): if asset is None: return "Failed to find the asset to delete" for port in asset.network_connections: connection_hostname = asset.network_connections[port][ "connection_hostname"] connection_port = asset.network_connections[port][ "connection_port"] if (connection_hostname == "" or connection_hostname is None) and ( connection_port == "" or connection_port is None): continue other_instance = self.table.get_instance_by_hostname( connection_hostname) if other_instance is None: return f"An error occurred when attempting to delete the network connection. Could not find asset with hostname {connection_hostname}." other_instance.network_connections[connection_port][ "connection_hostname"] = "" other_instance.network_connections[connection_port][ "connection_port"] = "" print(other_instance.network_connections) try: print("EDITIG") self.table.edit_instance(other_instance, other_instance.asset_number) print("EDITED SUCCESS") except: return f"Could not add new network connections to asset with hostname {other_instance.hostname}." return Constants.API_SUCCESS def add_power_connections(self, instance): rack = self.rack_table.get_rack(instance.rack_label, instance.datacenter_id) if rack is None: return f"Could not find rack {instance.rack_label}" for p_connection in instance.power_connections: char1 = p_connection[0].upper() num = int(p_connection[1:]) if char1 == "L": rack.pdu_left[num - 1] = 1 elif char1 == "R": rack.pdu_right[num - 1] = 1 else: return "Invalid power connection. Please specify left or right PDU." self.rack_table.edit_rack(rack) return Constants.API_SUCCESS def delete_power_connections(self, instance): if instance is None: return "Asset could not be found." rack = self.rack_table.get_rack(instance.rack_label, instance.datacenter_id) if rack is None: return f"Could not find rack {instance.rack_label}" for p_connection in instance.power_connections: char1 = p_connection[0].upper() num = int(p_connection[1:]) if char1 == "L": rack.pdu_left[num - 1] = 0 elif char1 == "R": rack.pdu_right[num - 1] = 0 else: return "Invalid power connection. Please specify left or right PDU." self.rack_table.edit_rack(rack) return Constants.API_SUCCESS def get_network_neighborhood(self, asset_number): if asset_number is None or asset_number == "": raise InvalidInputsError("No asset number found in the request.") asset = self.table.get_instance_by_asset_number(asset_number) if asset is None: raise InvalidInputsError("The asset requested could not be found.") connections_dict = {} if asset.mount_type == Constants.CHASIS_KEY: blade_list = self.table.get_blades_by_chassis_hostname( asset.hostname) if blade_list is not None and len(blade_list) != 0: for blade in blade_list: connections_dict[blade.hostname] = [] is_blade = asset.mount_type == Constants.BLADE_KEY if is_blade: connected_asset = self.table.get_instance_by_hostname( asset.chassis_hostname) if connected_asset is None: raise InvalidInputsError( f"Connection to asset with hostname {asset.chassis_hostname} was not found." ) two_deep_list = self.make_two_deep_list(connected_asset) connections_dict[connected_asset.hostname] = two_deep_list else: for port in asset.network_connections: hostname = asset.network_connections[port][ "connection_hostname"] if hostname is None or hostname == "": continue connected_asset = self.table.get_instance_by_hostname(hostname) if connected_asset is None: raise InvalidInputsError( f"Connection to asset with hostname {hostname} was not found." ) two_deep_list = self.make_two_deep_list(connected_asset) connections_dict[hostname] = two_deep_list print(connections_dict) return connections_dict def make_two_deep_list(self, connected_asset): two_deep_list = [] if connected_asset.mount_type == Constants.CHASIS_KEY: blade_list = self.table.get_blades_by_chassis_hostname( connected_asset.hostname) if blade_list is not None and len(blade_list) != 0: for blade in blade_list: two_deep_list.append(blade.hostname) for port2 in connected_asset.network_connections: host2 = connected_asset.network_connections[port2][ "connection_hostname"] two_deep_list.append(host2) return two_deep_list def get_all_chassis(self): try: chassis_list = self.table.get_asset_by_mount_type( Constants.CHASIS_KEY) if chassis_list is None: chassis_list = [] return chassis_list except: raise InvalidInputsError( "An error occurred while trying to retrieve blade chassis.") def get_blades_in_chassis(self, asset_data): try: chassis_hostname = asset_data.get(Constants.CHASSIS_HOSTNAME_KEY) if chassis_hostname is None or chassis_hostname == "": raise InvalidInputsError( "Must provide a valid blade chassis hostname.") blade_list = self.table.get_blades_by_chassis_hostname( chassis_hostname) if blade_list is None: return [] return blade_list except InvalidInputsError as e: raise InvalidInputsError(e.message) except: raise InvalidInputsError( "An error occurred while trying to retrieve blade chassis.") def check_null(self, val): if val is None: return "" else: return val def asset_or_model_val(self, instance_val, model_val): if self.check_null(instance_val) != "": return instance_val else: return model_val
class InstanceValidator: def __init__(self): self.instance_table = InstanceTable() self.model_table = ModelTable() self.rack_table = RackTable() self.user_table = UserTable() self.dc_table = DatacenterTable() self.rack_height = 42 def create_instance_validation(self, instance, queue=None): basic_val_result = self.basic_validations(instance, -1) if basic_val_result != Constants.API_SUCCESS: return basic_val_result model_template = self.model_table.get_model(instance.model_id) if model_template is None: return "The model does not exist." dc_template = self.dc_table.get_datacenter(instance.datacenter_id) if dc_template is None: return "The datacenter does not exist." # Check that connections are only within a single datacenter net_cons = instance.network_connections for port in net_cons.keys(): dest_hostname = net_cons[port][Constants.CONNECTION_HOSTNAME] if dest_hostname != "" and dest_hostname is not None: dc_id = (InstanceTable().get_instance_by_hostname( dest_hostname).datacenter_id) if dc_id != instance.datacenter_id: raise InvalidInputsError( "Network connections cannot span multiple datacenters") if dc_template.is_offline_storage: return Constants.API_SUCCESS if instance.mount_type == Constants.BLADE_KEY: return self.blade_validation(instance, -1, queue) else: return self.rackmount_validation(instance, -1, model_template, dc_template) def edit_instance_validation(self, instance, original_asset_number): basic_val_result = self.basic_validations(instance, original_asset_number) if basic_val_result != Constants.API_SUCCESS: return basic_val_result model_template = self.model_table.get_model(instance.model_id) if model_template is None: return "The model does not exist." dc_template = self.dc_table.get_datacenter(instance.datacenter_id) if dc_template is None: return "The datacenter does not exist." # Check that connections are only within a single datacenter net_cons = instance.network_connections for port in net_cons.keys(): dest_hostname = net_cons[port][Constants.CONNECTION_HOSTNAME] if dest_hostname != "" and dest_hostname is not None: dc_id = (InstanceTable().get_instance_by_hostname( dest_hostname).datacenter_id) if dc_id != instance.datacenter_id: raise InvalidInputsError( "Network connections cannot span multiple datacenters") if dc_template.is_offline_storage: return Constants.API_SUCCESS if instance.mount_type == Constants.BLADE_KEY: return self.blade_validation(instance, original_asset_number) else: return self.rackmount_validation(instance, original_asset_number, model_template, dc_template) return Constants.API_SUCCESS def basic_validations(self, instance, original_asset_number): if (instance.asset_number != original_asset_number and original_asset_number != -1): asset_pattern = re.compile("[0-9]{6}") if asset_pattern.fullmatch(str(instance.asset_number)) is None: return "Asset numbers must be 6 digits long and only contain numbers." if (self.instance_table.get_instance_by_asset_number( instance.asset_number) is not None): return f"Asset numbers must be unique. An asset with asset number {instance.asset_number} already exists." if instance.hostname != "" and instance.hostname is not None: duplicate_hostname = self.instance_table.get_instance_by_hostname( instance.hostname) if duplicate_hostname is not None: if duplicate_hostname.asset_number != original_asset_number: print("DUPLICATE NUMBER", duplicate_hostname.asset_number) print("ORIGINAL NUMBER", original_asset_number) return f"An asset with hostname {duplicate_hostname.hostname} already exists. Please provide a unique hostname." if len(instance.hostname) > 64: return "Hostnames must be 64 characters or less" host_pattern = re.compile("[a-zA-Z]*[A-Za-z0-9-]*[A-Za-z0-9]") if host_pattern.fullmatch(instance.hostname) is None: return "Hostnames must start with a letter, only contain letters, numbers, periods, and hyphens, and end with a letter or number." if instance.owner != "" and self.user_table.get_user( instance.owner) is None: return f"The owner {instance.owner} is not an existing user. Please enter the username of an existing user." return Constants.API_SUCCESS def rackmount_validation(self, instance, original_asset_number, model_template, dc_template): if instance.mount_type == Constants.CHASIS_KEY and instance.hostname == "": if (self.instance_table.get_blades_by_chassis_hostname( instance.hostname) is not None): return "Blade chassis with blades require a hostname." rack = self.rack_table.get_rack(instance.rack_label, instance.datacenter_id) if rack is None and not dc_template.is_offline_storage: return f"Rack {instance.rack_label} does not exist in datacenter {dc_template}. Assets must be created on preexisting racks" p_connection_set = set() for p_connection in instance.power_connections: if p_connection in p_connection_set: return "Cannot connect two asset power ports to the same PDU port." else: p_connection_set.add(p_connection) char1 = p_connection[0].upper() print("PRINGINDISNFISNF") print(p_connection) num = int(p_connection[1:]) if char1 == "L": pdu_arr = rack.pdu_left elif char1 == "R": pdu_arr = rack.pdu_right else: return "Invalid power connection. Please specify left or right PDU." if pdu_arr[num - 1] == 1: return f"There is already an asset connected at PDU {char1}{num}. Please pick an empty PDU port." pattern = re.compile("[0-9]+") if pattern.fullmatch(str(instance.rack_position)) is None: return "The value for Rack U must be a positive integer." instance_bottom = int(instance.rack_position) instance_top = instance_bottom + int(model_template.height) - 1 if instance_top > self.rack_height: return "The placement of the instance exceeds the height of the rack." instance_list = self.instance_table.get_instances_by_rack( instance.rack_label, instance.datacenter_id) if instance_list is not None: for current_instance in instance_list: if current_instance.asset_number == original_asset_number: continue model = self.model_table.get_model(current_instance.model_id) current_instance_top = current_instance.rack_position + model.height - 1 if (current_instance.rack_position >= instance_bottom and current_instance.rack_position <= instance_top): return self.return_conflict(current_instance) elif (current_instance_top >= instance_bottom and current_instance_top <= instance_top): return self.return_conflict(current_instance) connection_validation_result = self.validate_connections( instance.network_connections, instance.hostname) if connection_validation_result != Constants.API_SUCCESS: return connection_validation_result return Constants.API_SUCCESS def blade_validation(self, instance, original_asset_number, queue=None): blade_chassis = self.instance_table.get_instance_by_hostname( instance.chassis_hostname) print("\n\n") print("CHECKING THE QUEUE") # In the case of import instance, need to check queue of instances as well as db if queue is not None and blade_chassis is None: for item in queue: print(item) if item.hostname == instance.chassis_hostname: blade_chassis = item print("BLADE CHASSIS") print(blade_chassis) if blade_chassis is None: return f"No blade chassis exists with hostname {instance.chassis_hostname}." elif blade_chassis.mount_type != Constants.CHASIS_KEY: return f"Asset with hostname {instance.chassis_hostname} is not a blade chassis. Blades can only be installed in a blade chassis." if blade_chassis.datacenter_id != instance.datacenter_id: return f"The blade chassis selected is in datacenter {blade_chassis.datacenter_id}. A blade and its chassis must be in the same datacenter." if instance.chassis_slot > 14 or instance.chassis_slot < 1: return f"A blade chassis contains 14 slots. Please provide a position between 1 and 14." blade_conflict = self.instance_table.get_blade_by_chassis_and_slot( instance.chassis_hostname, instance.chassis_slot) if (blade_conflict is not None and blade_conflict.asset_number != original_asset_number): return f"Blade with asset number {blade_conflict.asset_number} already exists at slot {instance.chassis_slot} of chassis with hostname {instance.chassis_hostname}." return Constants.API_SUCCESS def validate_connections(self, network_connections, hostname): # print("validating connections") result = "" new_connections = {} for my_port in network_connections: mac_adress = network_connections[my_port][ Constants.MAC_ADDRESS_KEY] connection_hostname = network_connections[my_port][ "connection_hostname"] connection_port = network_connections[my_port]["connection_port"] if connection_hostname in new_connections.keys(): if new_connections[connection_hostname] == connection_port: result += "Cannot make two network connections to the same port." elif connection_hostname != "" and connection_port != "": new_connections[connection_hostname] = connection_port mac_pattern = re.compile( "[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}" ) if mac_adress != "" and mac_pattern.fullmatch( mac_adress.lower()) is None: result += f"Invalid MAC address for port {my_port}. MAC addresses must be 6 byte, colon separated hexidecimal strings (i.e. a1:b2:c3:d4:e5:f6). \n" if (connection_hostname == "" or connection_hostname is None) and ( connection_port == "" or connection_port is None): continue if not (connection_hostname != "" and connection_port != ""): result += "Connections require both a hostname and connection port." other_instance = self.instance_table.get_instance_by_hostname( connection_hostname) if other_instance is None: result += f"The asset with hostname {connection_hostname} does not exist. Connections must be between assets with existing hostnames. \n" continue if connection_port in other_instance.network_connections: if (other_instance.network_connections[connection_port] ["connection_port"] != my_port) and ( other_instance.network_connections[connection_port] ["connection_port"] != ""): result += f"The port {connection_port} on asset with hostname {connection_hostname} is connected to another asset. \n" continue if (other_instance.network_connections[connection_port] ["connection_hostname"] != hostname) and ( other_instance.network_connections[connection_port] ["connection_hostname"] != ""): result += f"The port {connection_port} on asset with hostname {connection_hostname} is already connected to another asset. \n" # print("finished connection validation") if result == "": return Constants.API_SUCCESS else: return result def return_conflict(self, current_instance): result = f"The asset placement conflicts with asset with asset number {current_instance.asset_number} " result += f"on rack {current_instance.rack_label} at height U{current_instance.rack_position}." return result