import json import logging import uuid from dmidecode import DMIParse from flask import request from marshmallow.exceptions import ValidationError from ereuse_devicehub.parser import base2 from ereuse_devicehub.parser.computer import Computer from ereuse_devicehub.parser.models import SnapshotsLog from ereuse_devicehub.resources.action.schemas import Snapshot from ereuse_devicehub.resources.enums import DataStorageInterface, Severity logger = logging.getLogger(__name__) class ParseSnapshot: def __init__(self, snapshot, default="n/a"): self.default = default self.dmidecode_raw = snapshot["data"]["dmidecode"] self.smart_raw = snapshot["data"]["smart"] self.hwinfo_raw = snapshot["data"]["hwinfo"] self.device = {"actions": []} self.components = [] self.dmi = DMIParse(self.dmidecode_raw) self.smart = self.loads(self.smart_raw) self.hwinfo = self.parse_hwinfo() self.set_basic_datas() self.set_components() self.snapshot_json = { "device": self.device, "software": "Workbench", "components": self.components, "uuid": snapshot['uuid'], "type": snapshot['type'], "version": "14.0.0", "endTime": snapshot["timestamp"], "elapsed": 1, "sid": snapshot["sid"], } def get_snapshot(self): return Snapshot().load(self.snapshot_json) def set_basic_datas(self): self.device['manufacturer'] = self.dmi.manufacturer() self.device['model'] = self.dmi.model() self.device['serialNumber'] = self.dmi.serial_number() self.device['type'] = self.get_type() self.device['sku'] = self.get_sku() self.device['version'] = self.get_version() self.device['system_uuid'] = self.get_uuid() def set_components(self): self.get_cpu() self.get_ram() self.get_mother_board() self.get_data_storage() self.get_networks() def get_cpu(self): # TODO @cayop generation, brand and address not exist in dmidecode for cpu in self.dmi.get('Processor'): self.components.append( { "actions": [], "type": "Processor", "speed": self.get_cpu_speed(cpu), "cores": int(cpu.get('Core Count', 1)), "model": cpu.get('Version'), "threads": int(cpu.get('Thread Count', 1)), "manufacturer": cpu.get('Manufacturer'), "serialNumber": cpu.get('Serial Number'), "generation": cpu.get('Generation'), "brand": cpu.get('Brand'), "address": cpu.get('Address'), } ) def get_ram(self): # TODO @cayop format and model not exist in dmidecode for ram in self.dmi.get("Memory Device"): self.components.append( { "actions": [], "type": "RamModule", "size": self.get_ram_size(ram), "speed": self.get_ram_speed(ram), "manufacturer": ram.get("Manufacturer", self.default), "serialNumber": ram.get("Serial Number", self.default), "interface": self.get_ram_type(ram), "format": self.get_ram_format(ram), "model": ram.get("Part Number", self.default), } ) def get_mother_board(self): # TODO @cayop model, not exist in dmidecode for moder_board in self.dmi.get("Baseboard"): self.components.append( { "actions": [], "type": "Motherboard", "version": moder_board.get("Version"), "serialNumber": moder_board.get("Serial Number"), "manufacturer": moder_board.get("Manufacturer"), "biosDate": self.get_bios_date(), # "firewire": self.get_firmware(), "ramMaxSize": self.get_max_ram_size(), "ramSlots": len(self.dmi.get("Memory Device")), "slots": self.get_ram_slots(), "model": moder_board.get("Product Name"), # ?? "pcmcia": self.get_pcmcia_num(), # ?? "serial": self.get_serial_num(), # ?? "usb": self.get_usb_num(), } ) def get_usb_num(self): return len( [ u for u in self.dmi.get("Port Connector") if "USB" in u.get("Port Type", "").upper() ] ) def get_serial_num(self): return len( [ u for u in self.dmi.get("Port Connector") if "SERIAL" in u.get("Port Type", "").upper() ] ) def get_pcmcia_num(self): return len( [ u for u in self.dmi.get("Port Connector") if "PCMCIA" in u.get("Port Type", "").upper() ] ) def get_bios_date(self): return self.dmi.get("BIOS")[0].get("Release Date", self.default) def get_firmware(self): return self.dmi.get("BIOS")[0].get("Firmware Revision", '1') def get_max_ram_size(self): size = 0 for slot in self.dmi.get("Physical Memory Array"): capacity = slot.get("Maximum Capacity", '0').split(" ")[0] size += int(capacity) return size def get_ram_slots(self): slots = 0 for x in self.dmi.get("Physical Memory Array"): slots += int(x.get("Number Of Devices", 0)) return slots def get_ram_size(self, ram): size = ram.get("Size", "0") return int(size.split(" ")[0]) def get_ram_speed(self, ram): size = ram.get("Speed", "0") return int(size.split(" ")[0]) def get_ram_type(self, ram): TYPES = {'ddr', 'sdram', 'sodimm'} for t in TYPES: if t in ram.get("Type", "DDR"): return t def get_ram_format(self, ram): channel = ram.get("Locator", "DIMM") return 'SODIMM' if 'SODIMM' in channel else 'DIMM' def get_cpu_speed(self, cpu): speed = cpu.get('Max Speed', "0") return float(speed.split(" ")[0]) / 1024 def get_sku(self): return self.dmi.get("System")[0].get("SKU Number", self.default) def get_version(self): return self.dmi.get("System")[0].get("Version", self.default) def get_uuid(self): return self.dmi.get("System")[0].get("UUID", self.default) def get_chassis(self): return self.dmi.get("Chassis")[0].get("Type", self.default) def get_type(self): chassis_type = self.get_chassis() return self.translation_to_devicehub(chassis_type) def translation_to_devicehub(self, original_type): lower_type = original_type.lower() CHASSIS_TYPE = { 'Desktop': [ 'desktop', 'low-profile', 'tower', 'docking', 'all-in-one', 'pizzabox', 'mini-tower', 'space-saving', 'lunchbox', 'mini', 'stick', ], 'Laptop': [ 'portable', 'laptop', 'convertible', 'tablet', 'detachable', 'notebook', 'handheld', 'sub-notebook', ], 'Server': ['server'], 'Computer': ['_virtual'], } for k, v in CHASSIS_TYPE.items(): if lower_type in v: return k return self.default def get_data_storage(self): for sm in self.smart: model = sm.get('model_name') manufacturer = None if len(model.split(" ")) == 2: manufacturer, model = model.split(" ") self.components.append( { "actions": [], "type": self.get_data_storage_type(sm), "model": model, "manufacturer": manufacturer, "serialNumber": sm.get('serial_number'), "size": self.get_data_storage_size(sm), "variant": sm.get("firmware_version"), "interface": self.get_data_storage_interface(sm), } ) def get_data_storage_type(self, x): # TODO @cayop add more SSDS types SSDS = ["nvme"] SSD = 'SolidStateDrive' HDD = 'HardDrive' type_dev = x.get('device', {}).get('type') return SSD if type_dev in SSDS else HDD def get_data_storage_interface(self, x): return x.get('device', {}).get('protocol', 'ATA') def get_data_storage_size(self, x): type_dev = x.get('device', {}).get('type') total_capacity = "{type}_total_capacity".format(type=type_dev) # convert bytes to Mb return x.get(total_capacity) / 1024**2 def get_networks(self): hw_class = " Hardware Class: " mac = " Permanent HW Address: " model = " Model: " wireless = "wireless" for line in self.hwinfo: iface = { "variant": "1", "actions": [], "speed": 100.0, "type": "NetworkAdapter", "wireless": False, "manufacturer": "Ethernet", } for y in line: if hw_class in y and not y.split(hw_class)[1] == 'network': break if mac in y: iface["serialNumber"] = y.split(mac)[1] if model in y: iface["model"] = y.split(model)[1] if wireless in y: iface["wireless"] = True if iface.get("serialNumber"): self.components.append(iface) def parse_hwinfo(self): hw_blocks = self.hwinfo_raw.split("\n\n") return [x.split("\n") for x in hw_blocks] def loads(self, x): if isinstance(x, str): return json.loads(x) return x class ParseSnapshotLsHw: def __init__(self, snapshot, default="n/a"): self.default = default self.uuid = snapshot.get("uuid") self.sid = snapshot.get("sid") self.version = str(snapshot.get("version")) self.dmidecode_raw = snapshot["data"]["dmidecode"] self.smart = snapshot["data"]["smart"] self.hwinfo_raw = snapshot["data"]["hwinfo"] self.lshw = snapshot["data"]["lshw"] self.device = {"actions": []} self.components = [] self.components_obj = [] self._errors = [] self.dmi = DMIParse(self.dmidecode_raw) self.hwinfo = self.parse_hwinfo() self.set_basic_datas() self.set_components() self.snapshot_json = { "type": "Snapshot", "device": self.device, "software": "Workbench", "components": self.components, "uuid": snapshot['uuid'], "version": "14.0.0", "endTime": snapshot["timestamp"], "elapsed": 1, "sid": snapshot["sid"], } def get_snapshot(self): return Snapshot().load(self.snapshot_json) def parse_hwinfo(self): hw_blocks = self.hwinfo_raw.split("\n\n") return [x.split("\n") for x in hw_blocks] def loads(self, x): if isinstance(x, str): return json.loads(x) return x def set_basic_datas(self): try: pc, self.components_obj = Computer.run( self.lshw, self.hwinfo_raw, self.uuid, self.sid, self.version ) pc = pc.dump() minimum_hid = None in [pc['manufacturer'], pc['model'], pc['serialNumber']] if minimum_hid and not self.components_obj: # if no there are hid and any components return 422 raise Exception except Exception: msg = """It has not been possible to create the device because we lack data. You can find more information at: {}""".format( request.url_root ) txt = json.dumps({'sid': self.sid, 'message': msg}) raise ValidationError(txt) self.device = pc self.device['system_uuid'] = self.get_uuid() def set_components(self): memory = None for x in self.components_obj: if x.type == 'RamModule': memory = 1 if x.type == 'Motherboard': x.slots = self.get_ram_slots() self.components.append(x.dump()) if not memory: self.get_ram() self.get_data_storage() def get_ram(self): for ram in self.dmi.get("Memory Device"): self.components.append( { "actions": [], "type": "RamModule", "size": self.get_ram_size(ram), "speed": self.get_ram_speed(ram), "manufacturer": ram.get("Manufacturer", self.default), "serialNumber": ram.get("Serial Number", self.default), "interface": self.get_ram_type(ram), "format": self.get_ram_format(ram), "model": ram.get("Part Number", self.default), } ) def get_ram_size(self, ram): size = ram.get("Size") if not len(size.split(" ")) == 2: txt = ( "Error: Snapshot: {uuid}, Sid: {sid} have this ram Size: {size}".format( uuid=self.uuid, size=size, sid=self.sid ) ) self.errors(txt, severity=Severity.Warning) return 128 size, units = size.split(" ") return base2.Quantity(float(size), units).to('MiB').m def get_ram_speed(self, ram): speed = ram.get("Speed", "100") if not len(speed.split(" ")) == 2: txt = "Error: Snapshot: {uuid}, Sid: {sid} have this ram Speed: {speed}".format( uuid=self.uuid, speed=speed, sid=self.sid ) self.errors(txt, severity=Severity.Warning) return 100 speed, units = speed.split(" ") return float(speed) # TODO @cayop is neccesary change models for accept sizes more high of speed or change to string # return base2.Quantity(float(speed), units).to('MHz').m def get_ram_slots(self): slots = 0 for x in self.dmi.get("Physical Memory Array"): slots += int(x.get("Number Of Devices", 0)) return slots def get_ram_type(self, ram): TYPES = {'ddr', 'sdram', 'sodimm'} for t in TYPES: if t in ram.get("Type", "DDR"): return t def get_ram_format(self, ram): channel = ram.get("Locator", "DIMM") return 'SODIMM' if 'SODIMM' in channel else 'DIMM' def get_uuid(self): dmi_uuid = 'undefined' if self.dmi.get("System"): dmi_uuid = self.dmi.get("System")[0].get("UUID") try: uuid.UUID(dmi_uuid) except (ValueError, AttributeError) as err: self.errors("{}".format(err)) txt = "Error: Snapshot: {uuid} sid: {sid} have this uuid: {device}".format( uuid=self.uuid, device=dmi_uuid, sid=self.sid ) self.errors(txt, severity=Severity.Warning) dmi_uuid = None return dmi_uuid def get_data_storage(self): for sm in self.smart: if sm.get('smartctl', {}).get('exit_status') == 1: continue model = sm.get('model_name') manufacturer = None if model and len(model.split(" ")) > 1: mm = model.split(" ") model = mm[-1] manufacturer = " ".join(mm[:-1]) self.components.append( { "actions": [self.get_test_data_storage(sm)], "type": self.get_data_storage_type(sm), "model": model, "manufacturer": manufacturer, "serialNumber": sm.get('serial_number'), "size": self.get_data_storage_size(sm), "variant": sm.get("firmware_version"), "interface": self.get_data_storage_interface(sm), } ) def get_data_storage_type(self, x): # TODO @cayop add more SSDS types SSDS = ["nvme"] SSD = 'SolidStateDrive' HDD = 'HardDrive' type_dev = x.get('device', {}).get('type') trim = x.get('trim', {}).get("supported") in [True, "true"] return SSD if type_dev in SSDS or trim else HDD def get_data_storage_interface(self, x): interface = x.get('device', {}).get('protocol', 'ATA') try: DataStorageInterface(interface.upper()) except ValueError as err: txt = "Sid: {}, interface {} is not in DataStorageInterface Enum".format( self.sid, interface ) self.errors("{}".format(err)) self.errors(txt, severity=Severity.Warning) return "ATA" def get_data_storage_size(self, x): total_capacity = x.get('user_capacity', {}).get('bytes') if not total_capacity: return 1 # convert bytes to Mb return total_capacity / 1024**2 def get_test_data_storage(self, smart): hours = smart.get("power_on_time", {}).get('hours', 0) action = { "status": "Completed without error", "reallocatedSectorCount": smart.get("reallocated_sector_count", 0), "currentPendingSectorCount": smart.get("current_pending_sector_count", 0), "assessment": True, "severity": "Info", "offlineUncorrectable": smart.get("offline_uncorrectable", 0), "lifetime": hours, "powerOnHours": hours, "type": "TestDataStorage", "length": "Short", "elapsed": 0, "reportedUncorrectableErrors": smart.get( "reported_uncorrectable_errors", 0 ), "powerCycleCount": smart.get("power_cycle_count", 0), } return action def get_hid_datas(self): self.device.family = self.get_family() def get_family(self): return self.dmi.get("System", [{}])[0].get("Family", '') def errors(self, txt=None, severity=Severity.Error): if not txt: return self._errors logger.error(txt) self._errors.append(txt) error = SnapshotsLog( description=txt, snapshot_uuid=self.uuid, severity=severity, sid=self.sid, version=self.version, ) error.save()