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 ModelManager: def __init__(self): self.table = ModelTable() self.instance_table = InstanceTable() self.validate = ModelValidator() def create_model(self, model_data): try: new_model: Model = self.make_model(model_data) create_validation_result = self.validate.create_model_validation( new_model) if create_validation_result == Constants.API_SUCCESS: self.table.add_model(new_model) else: raise InvalidInputsError(create_validation_result) except InvalidInputsError as e: raise InvalidInputsError(e.message) except: raise InvalidInputsError("Unable to add the new model") def delete_model(self, model_data): vendor = self.check_null(model_data[Constants.VENDOR_KEY]) model_number = self.check_null(model_data[Constants.MODEL_NUMBER_KEY]) print("Got vendor and model_number") if vendor == "": raise InvalidInputsError("Must provide a vendor") if model_number == "": raise InvalidInputsError("Must provide a model number") print("Vendor and Model not blank") try: delete_validation_result = self.validate.delete_model_validation( vendor, model_number) print("Validation complete") print(delete_validation_result) if delete_validation_result == Constants.API_SUCCESS: print("Validation successful") self.table.delete_model_str(vendor, model_number) print("Successfully deleted from ModelTable") else: print("Validation unsuccessful") return InvalidInputsError(delete_validation_result) except InvalidInputsError as e: raise InvalidInputsError(e.message) except: print("SOMETHING BAD HAPPENED") return InvalidInputsError( "An error occured while trying to delete the model.") def detail_view(self, model_data): print("model data") print(model_data) vendor = self.check_null(model_data[Constants.VENDOR_KEY]) model_number = self.check_null(model_data[Constants.MODEL_NUMBER_KEY]) try: model = self.table.get_model_by_vendor_number(vendor, model_number) return model except: raise InvalidInputsError( "An error occured while trying to retrieve the model data.") def edit_model(self, model_data): try: updated_model = self.make_model(model_data) original_vendor = self.check_null( model_data.get(Constants.VENDOR_ORIG_KEY)) original_model_number = self.check_null( model_data.get(Constants.MODEL_NUMBER_ORIG_KEY)) original_height = self.check_null( model_data.get(Constants.HEIGHT_ORIG_KEY)) model_id = self.table.get_model_id_by_vendor_number( original_vendor, original_model_number) if original_height != updated_model.height: if model_id is None: return InvalidInputsError("Model not found") deployed_instances = self.instance_table.get_instances_by_model_id( model_id) if deployed_instances is not None: return InvalidInputsError( "Cannot edit height while instances are deployed") edit_validation_result = self.validate.edit_model_validation( self.make_model(model_data), original_vendor, original_model_number) if edit_validation_result == Constants.API_SUCCESS: self.table.edit_model(model_id, updated_model) else: return InvalidInputsError(edit_validation_result) except InvalidInputsError as e: raise InvalidInputsError(e.message) except: raise InvalidInputsError( "A failure occured while trying to edit the model.") def get_models(self, filter, limit: int): vendor = filter.get(Constants.VENDOR_KEY) model_number = filter.get(Constants.MODEL_NUMBER_KEY) height = filter.get(Constants.HEIGHT_KEY) try: model_list = self.table.get_models_with_filter( vendor=vendor, model_number=model_number, height=height, limit=limit) return model_list except: raise raise InvalidInputsError( "A failure occured while searching with the given filters.") def get_distinct_vendors_with_prefix(self, prefix_json): try: return_list = [] vendor_list = self.table.get_distinct_vendors() for vendor in vendor_list: # if vendor.startswith(prefix): return_list.append(vendor) return return_list except: raise InvalidInputsError( "An error occurred when trying to load previous vendors.") def make_model(self, model_data): try: return Model.from_json(json=model_data) except: raise def check_null(self, val): if val is None: return "" else: return val
def __init__(self): self.table = ModelTable() self.instance_table = InstanceTable() self.validate = ModelValidator()
db.drop_all() db.create_all() db.session.commit() encrypted_password = AuthManager().encrypt_pw(password="******") datacenters = ["*"] priv: Permission = Permission( model=True, asset=True, datacenters=datacenters, power=True, audit=True, admin=True, ) user: User = User( username="******", display_name="Admin", email="*****@*****.**", password=encrypted_password, privilege=priv.make_json(), datacenters=datacenters, ) UserTable().add_user(user=user) model: Model = Model(vendor="dell", model_number="1234", mount_type="rackmount", height=3) ModelTable().add_model(model=model)
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
def _get_model_from_id(model_id): model = ModelTable().get_model(model_id) if model is None: raise DBWriteException return model
class StatsManager: def __init__(self): self.dc_table = DatacenterTable() self.instance_table = InstanceTable() self.model_table = ModelTable() self.rack_table = RackTable() self.space_by_rack = {} self.space_by_vendor = {} self.space_by_model = {} self.space_by_owner = {} self.rack_count = {} self.rack_height = 42 def create_report(self, dc_name): self.reset_counters() if dc_name is None or dc_name == "": rack_list = self.rack_table.get_all_racks() else: dc_id = self.dc_table.get_datacenter_id_by_name(dc_name) rack_list = self.rack_table.get_rack_by_datacenter(dc_id) num_racks = len(rack_list) if num_racks == 0: raise ValueError( "Reports require existing racks. Please ensure that the datacenter exists and contains racks." ) for rack in rack_list: instance_list = self.instance_table.get_instances_by_rack( rack.label, rack.datacenter_id) self.iterate_instance(instance_list, rack.label) total_space_used = 0 for key in self.space_by_rack: total_space_used += self.space_by_rack[key] self.space_by_rack[key] = round( (self.space_by_rack[key] / (self.rack_count[key] * self.rack_height)) * 100, 2, ) all_space = num_racks * self.rack_height percent_total_used = round((total_space_used / all_space) * 100, 2) percent_total_free = 100 - percent_total_used self.space_by_vendor = self.divide_dict_by_space( self.space_by_vendor, all_space) self.space_by_model = self.divide_dict_by_space( self.space_by_model, all_space) self.space_by_owner = self.divide_dict_by_space( self.space_by_owner, all_space) rack_positionsage_json = json.dumps(self.space_by_rack, sort_keys=True) vendor_usage_json = json.dumps(self.space_by_vendor, sort_keys=True) model_usage_json = json.dumps(self.space_by_model, sort_keys=True) owner_usage_json = json.dumps(self.space_by_owner, sort_keys=True) returnJSON = { "totalUsage": percent_total_used, "totalFree": percent_total_free, "spaceUsage": rack_positionsage_json, "vendorUsage": vendor_usage_json, "modelUsage": model_usage_json, "ownerUsage": owner_usage_json, } return returnJSON def iterate_instance(self, instance_list, rack_label): rack_space_used = 0 for instance in instance_list: model = self.model_table.get_model(instance.model_id) rack_space_used += model.height if model.vendor in self.space_by_vendor: self.space_by_vendor[model.vendor] += model.height else: self.space_by_vendor[model.vendor] = model.height model_name = model.vendor + " " + model.model_number if model_name in self.space_by_model: self.space_by_model[model_name] += model.height else: self.space_by_model[model_name] = model.height if instance.owner is None or instance.owner == "": owner = "No owner listed" else: owner = instance.owner if owner in self.space_by_owner: self.space_by_owner[owner] += model.height else: self.space_by_owner[owner] = model.height if rack_label in self.rack_count: self.space_by_rack[rack_label] += rack_space_used self.rack_count[rack_label] += 1 else: self.space_by_rack[rack_label] = rack_space_used self.rack_count[rack_label] = 1 def divide_dict_by_space(self, dictionary, total_space_used): for key in dictionary: dictionary[key] = round((dictionary[key] / total_space_used) * 100, 2) return dictionary def reset_counters(self): self.space_by_rack = {} self.space_by_vendor = {} self.space_by_model = {} self.space_by_owner = {} self.rack_count = {} return
from typing import Any, Dict, List, Optional from app.constants import Constants from app.dal.datacenter_table import DatacenterTable from app.dal.model_table import ModelTable from app.main.types import JSON DCTABLE = DatacenterTable() MODELTABLE = ModelTable() # def _make_network_connections(model: Model): # network_connections: Dict[str, Any] = {} # ethernet_ports = model.ethernet_ports # if ethernet_ports is None: # return network_connections # for port in ethernet_ports: # network_connections[port] = { # Constants.MAC_ADDRESS_KEY: "", # Constants.CONNECTION_HOSTNAME: "", # Constants.CONNECTION_PORT: "", # } # return network_connections # class ModelDoesNotExistError(Exception): # """ # Raised when referenced model does not exist # """ # def __init__(self, vendor: str, model_number: str):