def __init__(self):
        self.table = InstanceTable()
        self.asset_num_file = "/asset_num.json"
        self.dirname = os.path.dirname(__file__)
        self.next_asset_num = 100001

        self.__setup_asset_num_file()
Example #2
0
 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
Example #3
0
 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 __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
Example #5
0
    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
Example #6
0
def new_instance():
    """ Create a new instance """
    data: JSON = request.get_json()
    instance_table: InstanceTable = InstanceTable()

    try:
        model_id: int = int(data["model_id"])
        hostname: str = data["hostname"]
        rack_label: str = data["rack_label"]
        rack_position: int = int(data["rack_position"])
        owner: Optional[str] = data.get("owner")
        comment: Optional[str] = data.get("comment")

        instance: Instance = Instance(
            model_id=model_id,
            hostname=hostname,
            rack_label=rack_label,
            rack_position=rack_position,
            owner=owner,
            comment=comment,
        )
        instance_table.add_instance(instance=instance)
    except:
        return HTTPStatus.BAD_REQUEST

    return HTTPStatus.OK
Example #7
0
 def __get_dc_name_from_asset_num(self, asset_number):
     print("asset number = ", asset_number)
     instance = InstanceTable().get_instance_by_asset_number(asset_number)
     datacenter_id = instance.datacenter_id
     datacenter_name = DatacenterTable().get_datacenter(datacenter_id)
     print("Datacenter name = ", datacenter_name.name)
     return datacenter_name.name
Example #8
0
    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
Example #9
0
def instance(identifier: int):
    """ Get an instance """
    instance_table: InstanceTable = InstanceTable()

    instance: Optional[Instance] = instance_table.get_instance(identifier=identifier)
    if instance is None:
        return HTTPStatus.NOT_FOUND

    return instance.make_json()
Example #10
0
def _delete_rack_modifier(label: str, datacenter_id: int,
                          datacenter_name: str) -> None:
    """ Delete a rack """
    instances: List[Instance] = InstanceTable().get_instances_by_rack(
        rack_label=label, datacenter_id=datacenter_id)
    if len(instances) != 0:
        raise RackNotEmptyError

    RackTable().delete_rack(label=label, datacenter_id=datacenter_id)
Example #11
0
def export_instances():
    """ Export instances with given filters """
    data: JSON = request.get_json()

    print(json.dumps(data, indent=4))
    try:
        filter = data["filter"]
        dc_name = data["datacenter_name"]
    except:
        return HTTPStatus.BAD_REQUEST

    limit: int = int(data.get("limit", 1000))
    instances_table: InstanceTable = InstanceTable()
    instance_manager: InstanceManager = InstanceManager()

    all_instances: List[Instance] = instance_manager.get_instances(
        filter=filter, dc_name=dc_name, limit=limit
    )
    text: str = ",".join(Instance.headers()) + "\n"

    for instance in all_instances:
        model: Model = ModelTable().get_model(identifier=instance.model_id)
        chassis_number = ""
        if instance.mount_type == Constants.BLADE_KEY:
            chassis_host = instance.chassis_hostname
            chassis = InstanceTable().get_instance_by_hostname(chassis_host)
            chassis_number = chassis.asset_number

        text += (
            instance.to_csv(
                vendor=model.vendor,
                model_number=model.model_number,
                chassis_number=chassis_number,
            )
            + "\n"
        )

    return {"csvData": text}
Example #12
0
def export_connections():
    """ Export instances with given filters """
    data: JSON = request.get_json()

    print(json.dumps(data, indent=4))
    try:
        filter = data["filter"]
        dc_name = data["datacenter_name"]
    except:
        return HTTPStatus.BAD_REQUEST

    limit: int = int(data.get("limit", 1000))
    instances_table: InstanceTable = InstanceTable()
    instance_manager: InstanceManager = InstanceManager()

    all_instances: List[Instance] = instance_manager.get_instances(
        filter=filter, dc_name=dc_name, limit=limit
    )
    text: str = ",".join(
        [
            Constants.CSV_SRC_HOST,
            Constants.CSV_SRC_PORT,
            Constants.CSV_SRC_MAC,
            Constants.CSV_DEST_HOST,
            Constants.CSV_DEST_PORT,
        ]
    ) + "\n"

    for instance in all_instances:
        src_hostname = instance.hostname
        network_connections = instance.network_connections
        for port in network_connections.keys():
            connection = []
            connection.append(src_hostname)
            connection.append(port)
            connection.append(network_connections[port][Constants.MAC_ADDRESS_KEY])
            connection.append(network_connections[port][Constants.CONNECTION_HOSTNAME])
            connection.append(network_connections[port][Constants.CONNECTION_PORT])
            if not (
                network_connections[port][Constants.CONNECTION_HOSTNAME] == ""
                and network_connections[port][Constants.CONNECTION_PORT] == ""
            ):
                text += ",".join(connection)
                text += "\n"

    return {"csvData": text}
Example #13
0
def _get_rack_modifier(label: str, datacenter_id: int,
                       datacenter_name: str) -> JSON:
    """ Get rack details """
    # Make sure rack exists
    rack_entry: Optional[Rack] = RackTable().get_rack(
        label=label, datacenter_id=datacenter_id)
    if rack_entry is None:
        raise RackDoesNotExistError(rack_label=label)

    instance_entries = InstanceTable().get_instances_by_rack(
        rack_label=label, datacenter_id=datacenter_id)

    dc = Datacenter(datacenter_name, "", False)

    return {
        label:
        list(
            map(
                lambda x: x.make_json_with_model_and_datacenter(
                    _get_model_from_id(x.model_id), dc),
                instance_entries,
            )),
    }
class DecommissionManager:
    def __init__(self):
        self.decommission_table = DecommissionTable()
        self.instance_table = InstanceTable()
        self.instance_manager = InstanceManager()

    def decommission_asset(self, asset_data):
        try:
            asset_number = self.check_null(
                asset_data[Constants.ASSET_NUMBER_KEY])
            decommission_user = self.check_null(
                asset_data[Constants.DECOM_USER_KEY])

            timestamp = datetime.datetime.now()
            try:
                asset = self.instance_table.get_instance_by_asset_number(
                    asset_number)
                print("got asset")
                network_neighborhood = self.instance_manager.get_network_neighborhood(
                    asset_number)
            except:
                raise InvalidInputsError(
                    "An error occurred when retrieving asset data.")

            decommission = self.make_decommission(
                asset=asset,
                timestamp=timestamp,
                decommission_user=decommission_user,
                network_neighborhood=network_neighborhood,
            )

            try:
                asset_data = {
                    Constants.ASSET_NUMBER_KEY: asset_number,
                }
                self.instance_manager.delete_instance(asset_data)
                self.decommission_table.add_decommission(decommission)
            except:
                raise InvalidInputsError(
                    "An error occurred when attempting to decommission the 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 decommission the asset.")

    def get_decommissions(self, filter):
        decommission_user = filter.get(Constants.DECOM_USER_KEY)
        start_date = filter.get(Constants.START_DATE_KEY)
        end_date = filter.get(Constants.END_DATE_KEY)

        # try:
        decommission_list = self.decommission_table.get_decommissions_with_filters(
            user=decommission_user,
            start_date=start_date,
            end_date=end_date,
        )
        return decommission_list
        # except Exception as e:
        #     print(str(e))
        #     raise InvalidInputsError(
        #         "An error occurred when retrieving decommissioned assets."
        #     )

    def make_decommission(self, asset: Instance, timestamp, decommission_user,
                          network_neighborhood):
        model = self.instance_manager.get_model_from_id(asset.model_id)
        datacenter = self.instance_manager.get_dc_from_id(asset.datacenter_id)

        return Decommission(
            vendor=model.vendor,
            model_number=model.model_number,
            height=model.height,
            hostname=asset.hostname,
            rack_label=asset.rack_label,
            rack_position=asset.rack_position,
            owner=asset.owner,
            comment=asset.comment,
            datacenter_name=datacenter.name,
            network_connections=asset.network_connections,
            power_connections=asset.power_connections,
            asset_number=asset.asset_number,
            chassis_hostname=asset.chassis_hostname,
            chassis_slot=asset.chassis_slot,
            timestamp=timestamp,
            decommission_user=decommission_user,
            network_neighborhood=network_neighborhood,
        )

    def check_null(self, val):
        if val is None:
            return ""
        else:
            return val
class AssetNumGenerator:
    def __init__(self):
        self.table = InstanceTable()
        self.asset_num_file = "/asset_num.json"
        self.dirname = os.path.dirname(__file__)
        self.next_asset_num = 100001

        self.__setup_asset_num_file()

    def __setup_asset_num_file(self):
        asset_num_file_template = {"start_num": 100001, "next_num": 100001}

        if not os.path.exists(self.dirname + self.asset_num_file):
            try:
                with open(self.dirname + self.asset_num_file, "w+") as outfile:
                    json.dump(asset_num_file_template, outfile, indent=4)
            except IOError as e:
                print(str(e))
                raise InvalidInputsError(
                    "Could not create asset number data file")

    def __get_asset_num_data(self):
        asset_num_data = {}
        try:
            with open(self.dirname + self.asset_num_file, "r") as infile:
                asset_num_data = json.load(infile)
        except IOError as e:
            print(str(e))
            raise InvalidInputsError("Failed to load current asset number")

        return asset_num_data

    def __get_next_asset_num(self):
        data = self.__get_asset_num_data()

        return data.get("next_num")

    def __update_next_asset_num(self, next_asset_num):
        self.next_asset_num = next_asset_num
        asset_num_data = self.__get_asset_num_data()
        asset_num_data["next_num"] = next_asset_num
        try:
            with open(self.dirname + self.asset_num_file, "w") as outfile:
                json.dump(asset_num_data, outfile, indent=4)
        except IOError as e:
            print(str(e))
            raise InvalidInputsError("Failed to update asset number data file")

    def __find_valid_asset_num(self, offset):
        next_asset_num = self.__get_next_asset_num() + offset
        asset = self.table.get_instance_by_asset_number(next_asset_num)
        while asset is not None:
            next_asset_num += 1
            asset = self.table.get_instance_by_asset_number(next_asset_num)

        return next_asset_num

    def get_next_asset_number(self):
        asset_num = self.__find_valid_asset_num(0)
        next_asset_num = self.__find_valid_asset_num(0)
        self.__update_next_asset_num(next_asset_num)

        return asset_num
Example #16
0
def _parse_connection_csv(csv_input) -> Tuple[int, int, int]:
    instance_table: InstanceTable = InstanceTable()
    model_table: ModelTable = ModelTable()
    instance_validator: InstanceValidator = InstanceValidator()

    # Extract header row ----
    try:
        headers: List[str] = next(csv_input)
    except StopIteration:
        raise InvalidFormatError(message="No header row.")

    snapshots: Dict[int, Dict[str, str]] = {}
    instances: List[Instance] = []
    for row in csv_input:
        # Ensure proper input length of csv row
        if len(row) != len(headers):
            raise TooFewInputsError(message=",".join(row))

        # Generate dictionary that maps column header to value
        values: Dict[str, Any] = {}
        for index, item in enumerate(row):
            values[headers[index]] = item

        src_mac_addr: str = values[Constants.CSV_SRC_MAC]
        src_hostname: str = values[Constants.CSV_SRC_HOST]
        dst_hostname: str = values[Constants.CSV_DEST_HOST]
        # values[Constants.CSV_SRC_PORT]
        # values[Constants.CSV_DEST_PORT]

        src_instance = instance_table.get_instance_by_hostname(src_hostname)
        dst_instance = instance_table.get_instance_by_hostname(dst_hostname)
        src_net_conn = src_instance.network_connections
        dst_net_conn = dst_instance.network_connections

        if src_instance.asset_number in snapshots.keys():
            src_net_conn = snapshots[src_instance.asset_number]
        if dst_instance.asset_number in snapshots.keys():
            dst_net_conn = snapshots[dst_instance.asset_number]

        # If model does not exist, throw error
        if src_instance is None:
            raise InstanceDoesNotExistError(
                f"Source instance with hostname {src_hostname} does not exist"
            )

        if dst_instance is None:
            raise InstanceDoesNotExistError(
                f"Destination instance with hostname {dst_hostname} does not exist"
            )

        # # replace mac if existing is blank
        # conn_exists = False
        # if connection in connections:
        #     print(f"EXISTS: {connection}")
        #     conn_exists = True
        #     if src_instance.network_connections[values[Constants.CSV_SRC_PORT]][Constants.MAC_ADDRESS_KEY] == "":
        #         src_instance.network_connections[
        #             values[Constants.CSV_SRC_PORT]
        #         ][Constants.MAC_ADDRESS_KEY] = src_mac_addr
        #
        # else:
        # print("SRC CHANGES")
        # print(src_instance.network_connections)
        src_net_conn[values[Constants.CSV_SRC_PORT]][
            Constants.MAC_ADDRESS_KEY
        ] = values[Constants.CSV_SRC_MAC]
        src_net_conn[values[Constants.CSV_SRC_PORT]][
            Constants.CONNECTION_HOSTNAME
        ] = values[Constants.CSV_DEST_HOST]
        src_net_conn[values[Constants.CSV_SRC_PORT]][
            Constants.CONNECTION_PORT
        ] = values[Constants.CSV_DEST_PORT]
        # print(src_instance.network_connections)
        # print("DST CHANGES")
        # print(dst_instance.network_connections)
        dst_net_conn[values[Constants.CSV_DEST_PORT]][
            Constants.CONNECTION_HOSTNAME
        ] = values[Constants.CSV_SRC_HOST]
        dst_net_conn[values[Constants.CSV_DEST_PORT]][
            Constants.CONNECTION_PORT
        ] = values[Constants.CSV_SRC_PORT]
        # print(dst_instance.network_connections)
        # print("DONE")

        snapshots[src_instance.asset_number] = src_net_conn
        snapshots[dst_instance.asset_number] = dst_net_conn

        src_instance.network_connections = src_net_conn
        dst_instance.network_connections = dst_net_conn

        # src_instance_json = src_instance.make_json()
        # dst_instance_json = dst_instance.make_json()

        # src_instance_json[Constants.ASSET_NUMBER_ORIG_KEY] = src_instance.asset_number
        # dst_instance_json[Constants.ASSET_NUMBER_ORIG_KEY] = dst_instance.asset_number

        # Create the connection; Raise an exception if some columns are missing
        # try:
        #     # instance: Instance = Instance.from_csv(csv_row=values)
        #     instance_manager.edit_instance(src_instance_json)
        #     instance_manager.edit_instance(dst_instance_json)
        # except Exception as e:
        #     print(str(e))
        #     raise InvalidFormatError(message="Columns are missing.")

        validation: str = instance_validator.validate_connections(
            src_instance.network_connections, src_instance.hostname
        )

        if validation != "success":
            raise InvalidFormatError(message=validation)

        instances.append(src_instance)
        instances.append(dst_instance)

    # print("INSTANCEs")
    # print(instances)
    # for inst in instances:
    #     print(inst.network_connections)

    added, updated, ignored = 0, 0, 0
    for instance in instances:
        # Write to database
        try:
            add, update, ignore = instance_table.add_or_update(instance=instance)
            added += add
            updated += update
            ignored += ignore
        except (RackDoesNotExistError, DBWriteException):
            print("I BROKE HERE")
            raise

    return added, updated // 2, ignored // 2
Example #17
0
def _parse_instance_csv(csv_input) -> Tuple[int, int, int]:
    instance_table: InstanceTable = InstanceTable()
    model_table: ModelTable = ModelTable()
    instance_validator: InstanceValidator = InstanceValidator()

    # Extract header row
    try:
        headers: List[str] = next(csv_input)
    except StopIteration:
        raise InvalidFormatError(message="No header row.")

    instances: List[Instance] = []
    for row in csv_input:
        # Ensure proper input length of csv row
        if len(row) != len(headers):
            raise TooFewInputsError(message=",".join(row))

        # Generate dictionary that maps column header to value
        values: Dict[str, Any] = {}
        for index, item in enumerate(row):
            values[headers[index]] = item

        vendor: str = values["vendor"]
        model_number: str = values["model_number"]
        model_id: Optional[int] = model_table.get_model_id_by_vendor_number(
            vendor=vendor, model_number=model_number
        )

        # If model does not exist, throw error
        if model_id is None:
            raise ModelDoesNotExistError(vendor=vendor, model_number=model_number)

        values["model_id"] = model_id

        # Create the instance; Raise an exception if some columns are missing
        try:
            # instance: Instance = Instance.from_csv(csv_row=values)
            instance: Instance = _make_instance_from_csv(csv_row=values)
        except KeyError as e:
            print(str(e))
            raise InvalidFormatError(message="Columns are missing.")

        # print("")
        # print(instance)
        # print("ADJUSTING FOR CHASSIS")
        # print("")
        #  Set chassis hostname and slot within an instance if it is a blade
        if values[Constants.CSV_CHASSIS_NUMBER] != "":  # Indicates asset is a blade
            chassis_number = values[Constants.CSV_CHASSIS_NUMBER]
            # print(f"CHASSIS NUMBER: {chassis_number}")
            chassis = InstanceTable().get_instance_by_asset_number(chassis_number)
            if chassis is not None:  # Chassis already existed
                instance.chassis_hostname = chassis.hostname
                instance.chassis_slot = int(values[Constants.CSV_CHASSIS_SLOT])
                instance.datacenter_id = chassis.datacenter_id
            else:  # Chassis is either being created in this import or doesn't exist
                found = False
                for inst in instances:
                    # print(inst)
                    if inst.asset_number == chassis_number:
                        instance.chassis_hostname = inst.hostname
                        instance.chassis_slot = int(values[Constants.CSV_CHASSIS_SLOT])
                        instance.datacenter_id = inst.datacenter_id
                        found = True
                if not found:
                    raise InstanceDoesNotExistError(
                        f"Chassis {chassis_number} does not exist"
                    )

        # validation: str = instance_validator.edit_instance_validation(
        #     instance=instance,
        #     original_asset_number=instance.asset_number
        # )
        # print(validation)
        # dc_id = DCTABLE.get_datacenter_id_by_abbreviation(
        #     values[Constants.CSV_DC_NAME_KEY]
        # )
        # if dc_id is None:
        #     raise DatacenterDoesNotExistError(values[Constants.CSV_DC_NAME_KEY])

        existing_instance = instance_table.get_instance_by_asset_number(
            instance.asset_number
        )

        if existing_instance is None:
            validation: str = instance_validator.create_instance_validation(
                instance=instance, queue=instances
            )
            if (
                validation != "success"
                and validation
                != f"An instance with hostname {instance.hostname} exists at location {instance.rack_label} U{instance.rack_position}"
            ):
                raise InvalidFormatError(message=validation)

        instances.append(instance)

    added, updated, ignored = 0, 0, 0
    for instance in instances:
        # Write to database
        try:
            add, update, ignore = instance_table.add_or_update(instance=instance)
            added += add
            updated += update
            ignored += ignore
        except (RackDoesNotExistError, DBWriteException):
            print("I BROKE HERE")
            raise

    return added, updated, ignored
Example #18
0
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
 def __init__(self):
     self.table = ModelTable()
     self.instance_table = InstanceTable()
     self.validate = ModelValidator()
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
Example #21
0
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
Example #22
0
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 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.decommission_table = DecommissionTable()
     self.instance_table = InstanceTable()
     self.instance_manager = InstanceManager()