From 883c01053f627e8f167e6a4a94f8bc9aaad7358c Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Fri, 25 Mar 2022 13:55:05 +0100 Subject: [PATCH] fix a lot of bugs and add storage and disk to the snapshot --- ereuse_devicehub/parser/parser.py | 121 +++++++++++------- ereuse_devicehub/resources/action/schemas.py | 15 ++- .../resources/action/views/snapshot.py | 23 ++-- .../resources/action/views/views.py | 103 +++++++++------ ereuse_devicehub/resources/enums.py | 36 ++++-- 5 files changed, 195 insertions(+), 103 deletions(-) diff --git a/ereuse_devicehub/parser/parser.py b/ereuse_devicehub/parser/parser.py index b6f8f716..5d304089 100644 --- a/ereuse_devicehub/parser/parser.py +++ b/ereuse_devicehub/parser/parser.py @@ -13,7 +13,7 @@ from ereuse_devicehub.parser.computer import ( class ParseSnapshot: def __init__(self, snapshot, default="n/a"): self.default = default - self.dmidecode_raw = snapshot["data"]["demidecode"] + self.dmidecode_raw = snapshot["data"]["dmidecode"] self.smart_raw = snapshot["data"]["smart"] self.hwinfo_raw = snapshot["data"]["hwinfo"] self.device = {"actions": []} @@ -24,16 +24,16 @@ class ParseSnapshot: self.hwinfo = self.parse_hwinfo() self.set_basic_datas() + self.set_components() self.snapshot_json = { "device": self.device, "software": "Workbench", - "components": self.components(), + "components": self.components, "uuid": snapshot['uuid'], "type": snapshot['type'], "version": snapshot["version"], - "endTime": snapshot["endTime"], - "elapsed": 0, - "closed": True, + "endTime": snapshot["timestamp"], + "elapsed": 1, } def set_basic_datas(self): @@ -43,12 +43,14 @@ class ParseSnapshot: self.device['type'] = self.get_type() self.device['sku'] = self.get_sku() self.device['version'] = self.get_version() - self.device['uuid'] = self.get_uuid() + # self.device['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 @@ -57,7 +59,7 @@ class ParseSnapshot: { "actions": [], "type": "Processor", - "speed": cpu.get('Max Speed'), + "speed": self.get_cpu_speed(cpu), "cores": int(cpu.get('Core Count', 1)), "model": cpu.get('Version'), "threads": int(cpu.get('Thread Count', 1)), @@ -80,8 +82,8 @@ class ParseSnapshot: "speed": self.get_ram_speed(ram), "manufacturer": ram.get("Manufacturer", self.default), "serialNumber": ram.get("Serial Number", self.default), - "interface": ram.get("Type", self.default), - "format": ram.get("Format", self.default), # "DIMM", + "interface": ram.get("Type", "DDR"), + "format": ram.get("Format", "DIMM"), # "DIMM", "model": ram.get( "Model", self.default ), # "48594D503131325336344350362D53362020", @@ -98,11 +100,11 @@ class ParseSnapshot: "version": moder_board.get("Version"), "serialNumber": moder_board.get("Serial Number"), "manufacturer": moder_board.get("Manufacturer"), - "ramSlots": self.get_ram_slots(), - "ramMaxSize": self.get_max_ram_size(), - "slots": len(self.dmi.get("Number Of Devices")), "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(), # ?? @@ -112,57 +114,70 @@ class ParseSnapshot: def get_usb_num(self): return len( - [u for u in self.get("Port Connector") if u.get("Port Type") == "USB"] + [u for u in self.dmi.get("Port Connector") if u.get("Port Type") == "USB"] ) def get_serial_num(self): return len( - [u for u in self.get("Port Connector") if u.get("Port Type") == "SERIAL"] + [ + u + for u in self.dmi.get("Port Connector") + if u.get("Port Type") == "SERIAL" + ] ) def get_pcmcia_num(self): return len( - [u for u in self.get("Port Connector") if u.get("Port Type") == "PCMCIA"] + [ + u + for u in self.dmi.get("Port Connector") + if u.get("Port Type") == "PCMCIA" + ] ) def get_bios_date(self): - return self.get("BIOS")[0].get("Release Date", self.default) + return self.dmi.get("BIOS")[0].get("Release Date", self.default) def get_firmware(self): - return self.get("BIOS")[0].get("Firmware Revision", self.default) + return int(float(self.dmi.get("BIOS")[0].get("Firmware Revision", 1))) def get_max_ram_size(self): - size = self.dmi.get("Physical Memory Array") - if size: - size = size.get("Maximum Capacity") + size = 0 + for slot in self.dmi.get("Physical Memory Array"): + capacity = slot.get("Maximum Capacity", '0').split(" ")[0] + size += int(capacity) - return size.split(" GB")[0] if size else self.default + return size def get_ram_slots(self): - slots = self.dmi.get("Physical Memory Array") - if slots: - slots = slots.get("Number Of Devices") - return int(slots) if slots else self.default + 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") - return size.split(" MB")[0] if size else self.default + size = ram.get("Size", "0") + return int(size.split(" ")[0]) def get_ram_speed(self, ram): - size = ram.get("Speed") - return size.split(" MT/s")[0] if size else self.default + size = ram.get("Speed", "0") + return int(size.split(" ")[0]) + + def get_cpu_speed(self, cpu): + speed = cpu.get('Max Speed', "0") + return float(speed.split(" ")[0]) / 1024 def get_sku(self): - return self.get("System")[0].get("SKU Number", self.default) + return self.dmi.get("System")[0].get("SKU Number", self.default) def get_version(self): - return self.get("System")[0].get("Version", self.default) + return self.dmi.get("System")[0].get("Version", self.default) def get_uuid(self): - return self.get("System")[0].get("UUID", self.default) + return self.dmi.get("System")[0].get("UUID", self.default) def get_chassis(self): - return self.get("Chassis")[0].get("Type", self.default) + return self.dmi.get("Chassis")[0].get("Type", self.default) def get_type(self): chassis_type = self.get_chassis() @@ -205,6 +220,7 @@ class ParseSnapshot: def get_data_storage(self): for sm in self.smart: + # import pdb; pdb.set_trace() model = sm.get('model_name') manufacturer = None if len(model.split(" ")) == 2: @@ -241,23 +257,42 @@ class ParseSnapshot: return x.get(total_capacity) / 1024**2 def get_networks(self): - addr = [] - for line in self.hwinfo: - for y in line: - if "Permanent HW Address:" in y: - mac = y.split(" Permanent HW Address: ")[1] - addr.extend(mac) + hw_class = " Hardware Class: " + mac = " Permanent HW Address: " + model = " Model: " + wireless = "wireless" - return addr + 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, dict) or isinstance(x, list): - return x - return json.loads(x) + if isinstance(x, str): + return json.loads(x) + return x class LsHw: diff --git a/ereuse_devicehub/resources/action/schemas.py b/ereuse_devicehub/resources/action/schemas.py index dad9c208..ae885ade 100644 --- a/ereuse_devicehub/resources/action/schemas.py +++ b/ereuse_devicehub/resources/action/schemas.py @@ -416,11 +416,18 @@ class Install(ActionWithOneDevice): address = Integer(validate=OneOf({8, 16, 32, 64, 128, 256})) -class Snapshot2(MarshmallowSchema): - uuid = UUID() - version = Version(required=True, description='The version of the software.') +class Snapshot_lite_data(MarshmallowSchema): + dmidecode = String(required=False) + hwinfo = String(required=False) + smart = String(required=False) + + +class Snapshot_lite(MarshmallowSchema): + uuid = String() + version = String() type = String() - endTime = DateTime('iso', dump_only=True, description=m.Thing.updated.comment) + timestamp = String() + data = Nested(Snapshot_lite_data) @validates_schema def validate_workbench_version(self, data: dict): diff --git a/ereuse_devicehub/resources/action/views/snapshot.py b/ereuse_devicehub/resources/action/views/snapshot.py index e07421ab..640391be 100644 --- a/ereuse_devicehub/resources/action/views/snapshot.py +++ b/ereuse_devicehub/resources/action/views/snapshot.py @@ -14,7 +14,7 @@ from ereuse_devicehub.db import db from ereuse_devicehub.parser.parser import ParseSnapshot from ereuse_devicehub.resources.action.models import RateComputer, Snapshot from ereuse_devicehub.resources.action.rate.v1_0 import CannotRate -from ereuse_devicehub.resources.action.schemas import Snapshot2 +from ereuse_devicehub.resources.action.schemas import Snapshot_lite from ereuse_devicehub.resources.device.models import Computer from ereuse_devicehub.resources.enums import Severity, SnapshotSoftware from ereuse_devicehub.resources.user.exceptions import InsufficientPermission @@ -136,14 +136,14 @@ class SnapshotView: if db_device.owner_id != g.user.id: raise InsufficientPermission() # Compute ratings - try: - rate_computer, price = RateComputer.compute(db_device) - except CannotRate: - pass - else: - snapshot.actions.add(rate_computer) - if price: - snapshot.actions.add(price) + # try: + # rate_computer, price = RateComputer.compute(db_device) + # except CannotRate: + # pass + # else: + # snapshot.actions.add(rate_computer) + # if price: + # snapshot.actions.add(price) elif snapshot.software == SnapshotSoftware.WorkbenchAndroid: pass # TODO try except to compute RateMobile # Check if HID is null and add Severity:Warning to Snapshot @@ -158,10 +158,11 @@ class SnapshotView: return ret def validate_json(self, snapshot_json): - self.schema2 = Snapshot2() + self.schema2 = Snapshot_lite() self.snapshot_json = self.schema2.load(snapshot_json) def build2(self): snap = ParseSnapshot(self.snapshot_json) - self.snapshot_json = snap.snapshot_json + snap_json = snap.snapshot_json + self.snapshot_json = self.resource_def.schema.load(snap_json) return self.build() diff --git a/ereuse_devicehub/resources/action/views/views.py b/ereuse_devicehub/resources/action/views/views.py index 88c95438..17c9b6b3 100644 --- a/ereuse_devicehub/resources/action/views/views.py +++ b/ereuse_devicehub/resources/action/views/views.py @@ -1,35 +1,49 @@ """ This is the view for Snapshots """ -import jwt -import ereuse_utils from datetime import timedelta from distutils.version import StrictVersion from uuid import UUID -from flask import current_app as app, request, g +import ereuse_utils +import jwt +from flask import current_app as app +from flask import g, request from teal.db import ResourceNotFound from teal.marshmallow import ValidationError from teal.resource import View from ereuse_devicehub.db import db from ereuse_devicehub.query import things_response -from ereuse_devicehub.resources.action.models import (Action, Snapshot, VisualTest, - InitTransfer, Live, Allocate, Deallocate, - Trade, Confirm, Revoke) +from ereuse_devicehub.resources.action.models import ( + Action, + Allocate, + Confirm, + Deallocate, + InitTransfer, + Live, + Revoke, + Snapshot, + Trade, + VisualTest, +) from ereuse_devicehub.resources.action.views import trade as trade_view -from ereuse_devicehub.resources.action.views.snapshot import SnapshotView, save_json, move_json from ereuse_devicehub.resources.action.views.documents import ErasedView -from ereuse_devicehub.resources.device.models import Device, Computer, DataStorage +from ereuse_devicehub.resources.action.views.snapshot import ( + SnapshotView, + move_json, + save_json, +) +from ereuse_devicehub.resources.device.models import Computer, DataStorage, Device from ereuse_devicehub.resources.enums import Severity SUPPORTED_WORKBENCH = StrictVersion('11.0') -class AllocateMix(): +class AllocateMix: model = None def post(self): - """ Create one res_obj """ + """Create one res_obj""" res_json = request.get_json() res_obj = self.model(**res_json) db.session.add(res_obj) @@ -40,13 +54,18 @@ class AllocateMix(): return ret def find(self, args: dict): - res_objs = self.model.query.filter_by(author=g.user) \ - .order_by(self.model.created.desc()) \ + res_objs = ( + self.model.query.filter_by(author=g.user) + .order_by(self.model.created.desc()) .paginate(per_page=200) + ) return things_response( self.schema.dump(res_objs.items, many=True, nested=0), - res_objs.page, res_objs.per_page, res_objs.total, - res_objs.prev_num, res_objs.next_num + res_objs.page, + res_objs.per_page, + res_objs.total, + res_objs.prev_num, + res_objs.next_num, ) @@ -99,7 +118,9 @@ class LiveView(View): if not serial_number: """There aren't any disk""" - raise ResourceNotFound("There aren't any disk in this device {}".format(device)) + raise ResourceNotFound( + "There aren't any disk in this device {}".format(device) + ) return usage_time_hdd, serial_number def get_hid(self, snapshot): @@ -109,8 +130,11 @@ class LiveView(View): return None if not components: return device.hid - macs = [c.serial_number for c in components - if c.type == 'NetworkAdapter' and c.serial_number is not None] + macs = [ + c.serial_number + for c in components + if c.type == 'NetworkAdapter' and c.serial_number is not None + ] macs.sort() mac = '' hid = device.hid @@ -124,12 +148,10 @@ class LiveView(View): def live(self, snapshot): """If the device.allocated == True, then this snapshot create an action live.""" hid = self.get_hid(snapshot) - if not hid or not Device.query.filter( - Device.hid == hid).count(): + if not hid or not Device.query.filter(Device.hid == hid).count(): raise ValidationError('Device not exist.') - device = Device.query.filter( - Device.hid == hid, Device.allocated == True).one() + device = Device.query.filter(Device.hid == hid, Device.allocated == True).one() # Is not necessary if not device: raise ValidationError('Device not exist.') @@ -138,16 +160,18 @@ class LiveView(View): usage_time_hdd, serial_number = self.get_hdd_details(snapshot, device) - data_live = {'usage_time_hdd': usage_time_hdd, - 'serial_number': serial_number, - 'snapshot_uuid': snapshot['uuid'], - 'description': '', - 'software': snapshot['software'], - 'software_version': snapshot['version'], - 'licence_version': snapshot['licence_version'], - 'author_id': device.owner_id, - 'agent_id': device.owner.individual.id, - 'device': device} + data_live = { + 'usage_time_hdd': usage_time_hdd, + 'serial_number': serial_number, + 'snapshot_uuid': snapshot['uuid'], + 'description': '', + 'software': snapshot['software'], + 'software_version': snapshot['version'], + 'licence_version': snapshot['licence_version'], + 'author_id': device.owner_id, + 'agent_id': device.owner.individual.id, + 'device': device, + } live = Live(**data_live) @@ -172,7 +196,12 @@ class LiveView(View): def decode_snapshot(data): try: - return jwt.decode(data['data'], app.config['JWT_PASS'], algorithms="HS256", json_encoder=ereuse_utils.JSONEncoder) + return jwt.decode( + data['data'], + app.config['JWT_PASS'], + algorithms="HS256", + json_encoder=ereuse_utils.JSONEncoder, + ) except jwt.exceptions.InvalidSignatureError as err: txt = 'Invalid snapshot' raise ValidationError(txt) @@ -200,13 +229,13 @@ class ActionView(View): # TODO @cayop uncomment at four weeks # if not 'data' in json: - # txt = 'Invalid snapshot' - # raise ValidationError(txt) + # txt = 'Invalid snapshot' + # raise ValidationError(txt) # snapshot_data = decode_snapshot(json) snapshot_data = json - if 'data' in json: + if 'data' in json and not json.get("data", {}).get("dmidecode"): snapshot_data = decode_snapshot(json) if not snapshot_data: @@ -248,7 +277,9 @@ class ActionView(View): return confirm.post() if json['type'] == 'ConfirmRevokeDocument': - confirm_revoke = trade_view.ConfirmRevokeDocumentView(json, resource_def, self.schema) + confirm_revoke = trade_view.ConfirmRevokeDocumentView( + json, resource_def, self.schema + ) return confirm_revoke.post() if json['type'] == 'DataWipe': diff --git a/ereuse_devicehub/resources/enums.py b/ereuse_devicehub/resources/enums.py index cff77056..79958baf 100644 --- a/ereuse_devicehub/resources/enums.py +++ b/ereuse_devicehub/resources/enums.py @@ -8,6 +8,7 @@ import inflection @unique class SnapshotSoftware(Enum): """The software used to perform the Snapshot.""" + Workbench = 'Workbench' WorkbenchAndroid = 'WorkbenchAndroid' AndroidApp = 'AndroidApp' @@ -36,6 +37,7 @@ class RatingRange(IntEnum): 3. Medium. 4. High. """ + VERY_LOW = 1 LOW = 2 MEDIUM = 3 @@ -69,6 +71,7 @@ class PriceSoftware(Enum): @unique class AppearanceRange(Enum): """Grades the imperfections that aesthetically affect the device, but not its usage.""" + Z = 'Z. The device is new' A = 'A. Is like new; without visual damage' B = 'B. Is in really good condition; small visual damage in difficult places to spot' @@ -83,6 +86,7 @@ class AppearanceRange(Enum): @unique class FunctionalityRange(Enum): """Grades the defects of a device that affect its usage.""" + A = 'A. All the buttons works perfectly, no screen/camera defects and chassis without usage issues' B = 'B. There is a button difficult to press or unstable it, a screen/camera defect or chassis problem' C = 'C. Chassis defects or multiple buttons don\'t work; broken or unusable it, some screen/camera defect' @@ -95,6 +99,7 @@ class FunctionalityRange(Enum): @unique class BatteryHealthRange(Enum): """Grade the battery health status, depending on self report Android system""" + A = 'A. The battery health is very good' B = 'B. Battery health is good' C = 'C. Battery health is overheat / over voltage status but can stand the minimum duration' @@ -109,6 +114,7 @@ class BatteryHealthRange(Enum): @unique class BiosAccessRange(Enum): """How difficult it has been to set the bios to boot from the network.""" + A = 'A. If by pressing a key you could access a boot menu with the network boot' B = 'B. You had to get into the BIOS, and in less than 5 steps you could set the network boot' C = 'C. Like B, but with more than 5 steps' @@ -139,6 +145,7 @@ class ImageSoftware(Enum): @unique class ImageMimeTypes(Enum): """Supported image Mimetypes for Devicehub.""" + jpg = 'image/jpeg' png = 'image/png' @@ -149,6 +156,7 @@ BOX_RATE_3 = 1, 3 # After looking at own databases + @unique class RamInterface(Enum): """ @@ -163,6 +171,7 @@ class RamInterface(Enum): here for those cases where there is no more specific information. Please, try to always use DDRø-6 denominations. """ + SDRAM = 'SDRAM' DDR = 'DDR SDRAM' DDR2 = 'DDR2 SDRAM' @@ -170,6 +179,7 @@ class RamInterface(Enum): DDR4 = 'DDR4 SDRAM' DDR5 = 'DDR5 SDRAM' DDR6 = 'DDR6 SDRAM' + LPDDR3 = 'LPDDR3' def __str__(self): return self.value @@ -189,6 +199,7 @@ class DataStorageInterface(Enum): ATA = 'ATA' USB = 'USB' PCI = 'PCI' + NVMe = 'NVMe' def __str__(self): return self.value @@ -211,6 +222,7 @@ class DisplayTech(Enum): @unique class ComputerChassis(Enum): """The chassis of a computer.""" + Tower = 'Tower' Docking = 'Docking' AllInOne = 'All in one' @@ -235,6 +247,7 @@ class ReceiverRole(Enum): The role that the receiver takes in the reception; the meaning of the reception. """ + Intermediary = 'Generic user in the workflow of the device.' FinalUser = 'The user that will use the device.' CollectionPoint = 'A collection point.' @@ -244,6 +257,7 @@ class ReceiverRole(Enum): class PrinterTechnology(Enum): """Technology of the printer.""" + Toner = 'Toner / Laser' Inkjet = 'Liquid inkjet' SolidInk = 'Solid ink' @@ -260,6 +274,7 @@ class CameraFacing(Enum): @unique class BatteryHealth(Enum): """The battery health status as in Android.""" + Cold = 'Cold' Dead = 'Dead' Good = 'Good' @@ -274,6 +289,7 @@ class BatteryTechnology(Enum): https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-power adding ``Alkaline``. """ + LiIon = 'Lithium-ion' NiCd = 'Nickel-Cadmium' NiMH = 'Nickel-metal hydride' @@ -329,10 +345,11 @@ class PhysicalErasureMethod(Enum): and non able to be re-built. """ - Shred = 'Reduction of the data-storage to the required certified ' \ - 'standard sizes.' - Disintegration = 'Reduction of the data-storage to smaller sizes ' \ - 'than the certified standard ones.' + Shred = 'Reduction of the data-storage to the required certified ' 'standard sizes.' + Disintegration = ( + 'Reduction of the data-storage to smaller sizes ' + 'than the certified standard ones.' + ) def __str__(self): return self.name @@ -362,20 +379,21 @@ class ErasureStandards(Enum): def from_data_storage(cls, erasure) -> Set['ErasureStandards']: """Returns a set of erasure standards.""" from ereuse_devicehub.resources.action import models as actions + standards = set() if isinstance(erasure, actions.EraseSectors): with suppress(ValueError): first_step, *other_steps = erasure.steps - if isinstance(first_step, actions.StepZero) \ - and all(isinstance(step, actions.StepRandom) for step in other_steps): + if isinstance(first_step, actions.StepZero) and all( + isinstance(step, actions.StepRandom) for step in other_steps + ): standards.add(cls.HMG_IS5) return standards @unique class TransferState(IntEnum): - """State of transfer for a given Lot of devices. - """ + """State of transfer for a given Lot of devices.""" """ * Initial: No transfer action in place. @@ -393,7 +411,7 @@ class TransferState(IntEnum): def __str__(self): return self.name - + @unique class SessionType(IntEnum):