fix a lot of bugs and add storage and disk to the snapshot

This commit is contained in:
Cayo Puigdefabregas 2022-03-25 13:55:05 +01:00
parent 7d1e1589b6
commit 883c01053f
5 changed files with 195 additions and 103 deletions

View File

@ -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
if isinstance(x, str):
return json.loads(x)
return x
class LsHw:

View File

@ -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):

View File

@ -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()

View File

@ -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,7 +160,8 @@ class LiveView(View):
usage_time_hdd, serial_number = self.get_hdd_details(snapshot, device)
data_live = {'usage_time_hdd': usage_time_hdd,
data_live = {
'usage_time_hdd': usage_time_hdd,
'serial_number': serial_number,
'snapshot_uuid': snapshot['uuid'],
'description': '',
@ -147,7 +170,8 @@ class LiveView(View):
'licence_version': snapshot['licence_version'],
'author_id': device.owner_id,
'agent_id': device.owner.individual.id,
'device': device}
'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)
@ -206,7 +235,7 @@ class ActionView(View):
# 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':

View File

@ -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 ' \
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.