Merge branch 'feature/server-side-render-parser-3021' into feature/list-snapshots-view-#3113
This commit is contained in:
commit
82bdfe3db5
0
ereuse_devicehub/api/__init__.py
Normal file
0
ereuse_devicehub/api/__init__.py
Normal file
84
ereuse_devicehub/api/views.py
Normal file
84
ereuse_devicehub/api/views.py
Normal file
|
@ -0,0 +1,84 @@
|
|||
import json
|
||||
from binascii import Error as asciiError
|
||||
|
||||
from flask import Blueprint
|
||||
from flask import current_app as app
|
||||
from flask import g, jsonify, request
|
||||
from flask.views import View
|
||||
from marshmallow import ValidationError
|
||||
from werkzeug.exceptions import Unauthorized
|
||||
|
||||
from ereuse_devicehub.auth import Auth
|
||||
from ereuse_devicehub.db import db
|
||||
from ereuse_devicehub.parser.models import SnapshotErrors
|
||||
from ereuse_devicehub.parser.parser import ParseSnapshotLsHw
|
||||
from ereuse_devicehub.parser.schemas import Snapshot_lite
|
||||
from ereuse_devicehub.resources.action.views.snapshot import (
|
||||
SnapshotMix,
|
||||
move_json,
|
||||
save_json,
|
||||
)
|
||||
from ereuse_devicehub.resources.enums import Severity
|
||||
|
||||
api = Blueprint('api', __name__, url_prefix='/api')
|
||||
|
||||
|
||||
class LoginMix(View):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.authenticate()
|
||||
|
||||
def authenticate(self):
|
||||
unauthorized = Unauthorized('Provide a suitable token.')
|
||||
basic_token = request.headers.get('Authorization', " ").split(" ")
|
||||
if not len(basic_token) == 2:
|
||||
raise unauthorized
|
||||
|
||||
token = basic_token[1]
|
||||
try:
|
||||
token = Auth.decode(token)
|
||||
except asciiError:
|
||||
raise unauthorized
|
||||
self.user = Auth().authenticate(token)
|
||||
g.user = self.user
|
||||
|
||||
|
||||
class InventoryView(LoginMix, SnapshotMix):
|
||||
methods = ['POST']
|
||||
|
||||
def dispatch_request(self):
|
||||
snapshot_json = json.loads(request.data)
|
||||
self.tmp_snapshots = app.config['TMP_SNAPSHOTS']
|
||||
self.path_snapshot = save_json(snapshot_json, self.tmp_snapshots, g.user.email)
|
||||
snapshot_json = self.validate(snapshot_json)
|
||||
try:
|
||||
self.snapshot_json = ParseSnapshotLsHw(snapshot_json).get_snapshot()
|
||||
except ValidationError:
|
||||
self.response = jsonify('')
|
||||
self.response.status_code = 201
|
||||
return self.response
|
||||
|
||||
snapshot = self.build()
|
||||
db.session.add(snapshot)
|
||||
db.session().final_flush()
|
||||
db.session.commit()
|
||||
self.response = self.schema.jsonify(snapshot)
|
||||
self.response.status_code = 201
|
||||
move_json(self.tmp_snapshots, self.path_snapshot, g.user.email)
|
||||
return self.response
|
||||
|
||||
def validate(self, snapshot_json):
|
||||
self.schema = Snapshot_lite()
|
||||
try:
|
||||
return self.schema.load(snapshot_json)
|
||||
except ValidationError as err:
|
||||
txt = "{}".format(err)
|
||||
uuid = snapshot_json.get('uuid')
|
||||
error = SnapshotErrors(
|
||||
description=txt, snapshot_uuid=uuid, severity=Severity.Error
|
||||
)
|
||||
error.save(commit=True)
|
||||
raise err
|
||||
|
||||
|
||||
api.add_url_rule('/inventory/', view_func=InventoryView.as_view('inventory'))
|
|
@ -1,39 +1,49 @@
|
|||
from distutils.version import StrictVersion
|
||||
from itertools import chain
|
||||
from typing import Set
|
||||
from decouple import config
|
||||
|
||||
from decouple import config
|
||||
from teal.auth import TokenAuth
|
||||
from teal.config import Config
|
||||
from teal.enums import Currency
|
||||
from teal.utils import import_resource
|
||||
|
||||
from ereuse_devicehub.resources import action, agent, deliverynote, inventory, \
|
||||
lot, tag, user
|
||||
from ereuse_devicehub.resources import (
|
||||
action,
|
||||
agent,
|
||||
deliverynote,
|
||||
inventory,
|
||||
lot,
|
||||
tag,
|
||||
user,
|
||||
)
|
||||
from ereuse_devicehub.resources.device import definitions
|
||||
from ereuse_devicehub.resources.documents import documents
|
||||
from ereuse_devicehub.resources.tradedocument import definitions as tradedocument
|
||||
from ereuse_devicehub.resources.enums import PriceSoftware
|
||||
from ereuse_devicehub.resources.versions import versions
|
||||
from ereuse_devicehub.resources.licences import licences
|
||||
from ereuse_devicehub.resources.metric import definitions as metric_def
|
||||
from ereuse_devicehub.resources.tradedocument import definitions as tradedocument
|
||||
from ereuse_devicehub.resources.versions import versions
|
||||
|
||||
|
||||
class DevicehubConfig(Config):
|
||||
RESOURCE_DEFINITIONS = set(chain(import_resource(definitions),
|
||||
import_resource(action),
|
||||
import_resource(user),
|
||||
import_resource(tag),
|
||||
import_resource(agent),
|
||||
import_resource(lot),
|
||||
import_resource(deliverynote),
|
||||
import_resource(documents),
|
||||
import_resource(tradedocument),
|
||||
import_resource(inventory),
|
||||
import_resource(versions),
|
||||
import_resource(licences),
|
||||
import_resource(metric_def),
|
||||
),)
|
||||
RESOURCE_DEFINITIONS = set(
|
||||
chain(
|
||||
import_resource(definitions),
|
||||
import_resource(action),
|
||||
import_resource(user),
|
||||
import_resource(tag),
|
||||
import_resource(agent),
|
||||
import_resource(lot),
|
||||
import_resource(deliverynote),
|
||||
import_resource(documents),
|
||||
import_resource(tradedocument),
|
||||
import_resource(inventory),
|
||||
import_resource(versions),
|
||||
import_resource(licences),
|
||||
import_resource(metric_def),
|
||||
),
|
||||
)
|
||||
PASSWORD_SCHEMES = {'pbkdf2_sha256'} # type: Set[str]
|
||||
SECRET_KEY = config('SECRET_KEY')
|
||||
DB_USER = config('DB_USER', 'dhub')
|
||||
|
@ -48,11 +58,12 @@ class DevicehubConfig(Config):
|
|||
db=DB_DATABASE,
|
||||
) # type: str
|
||||
SCHEMA = config('SCHEMA', 'dbtest')
|
||||
HOST = config('HOST', 'localhost')
|
||||
HOST = config('HOST', 'localhost')
|
||||
MIN_WORKBENCH = StrictVersion('11.0a1') # type: StrictVersion
|
||||
"""The minimum version of ereuse.org workbench that this devicehub
|
||||
accepts. we recommend not changing this value.
|
||||
"""
|
||||
WORKBENCH_LITE = ["1.0.0"]
|
||||
|
||||
TMP_SNAPSHOTS = config('TMP_SNAPSHOTS', '/tmp/snapshots')
|
||||
TMP_LIVES = config('TMP_LIVES', '/tmp/lives')
|
||||
|
@ -60,11 +71,7 @@ class DevicehubConfig(Config):
|
|||
"""This var is for save a snapshots in json format when fail something"""
|
||||
API_DOC_CONFIG_TITLE = 'Devicehub'
|
||||
API_DOC_CONFIG_VERSION = '0.2'
|
||||
API_DOC_CONFIG_COMPONENTS = {
|
||||
'securitySchemes': {
|
||||
'bearerAuth': TokenAuth.API_DOCS
|
||||
}
|
||||
}
|
||||
API_DOC_CONFIG_COMPONENTS = {'securitySchemes': {'bearerAuth': TokenAuth.API_DOCS}}
|
||||
API_DOC_CLASS_DISCRIMINATOR = 'type'
|
||||
|
||||
PRICE_SOFTWARE = PriceSoftware.Ereuse
|
||||
|
|
|
@ -3,8 +3,10 @@ import json
|
|||
from json.decoder import JSONDecodeError
|
||||
|
||||
from boltons.urlutils import URL
|
||||
from flask import current_app as app
|
||||
from flask import g, request
|
||||
from flask_wtf import FlaskForm
|
||||
from marshmallow import ValidationError
|
||||
from sqlalchemy import or_
|
||||
from sqlalchemy.util import OrderedSet
|
||||
from wtforms import (
|
||||
|
@ -25,14 +27,19 @@ from wtforms import (
|
|||
from wtforms.fields import FormField
|
||||
|
||||
from ereuse_devicehub.db import db
|
||||
from ereuse_devicehub.resources.action.models import RateComputer, Snapshot, Trade
|
||||
from ereuse_devicehub.resources.action.rate.v1_0 import CannotRate
|
||||
from ereuse_devicehub.parser.models import SnapshotErrors
|
||||
from ereuse_devicehub.parser.parser import ParseSnapshotLsHw
|
||||
from ereuse_devicehub.parser.schemas import Snapshot_lite
|
||||
from ereuse_devicehub.resources.action.models import Snapshot, Trade
|
||||
from ereuse_devicehub.resources.action.schemas import Snapshot as SnapshotSchema
|
||||
from ereuse_devicehub.resources.action.views.snapshot import move_json, save_json
|
||||
from ereuse_devicehub.resources.action.views.snapshot import (
|
||||
SnapshotMix,
|
||||
move_json,
|
||||
save_json,
|
||||
)
|
||||
from ereuse_devicehub.resources.device.models import (
|
||||
SAI,
|
||||
Cellphone,
|
||||
Computer,
|
||||
Device,
|
||||
Keyboard,
|
||||
MemoryCardReader,
|
||||
|
@ -43,12 +50,11 @@ from ereuse_devicehub.resources.device.models import (
|
|||
)
|
||||
from ereuse_devicehub.resources.device.sync import Sync
|
||||
from ereuse_devicehub.resources.documents.models import DataWipeDocument
|
||||
from ereuse_devicehub.resources.enums import Severity, SnapshotSoftware
|
||||
from ereuse_devicehub.resources.enums import Severity
|
||||
from ereuse_devicehub.resources.hash_reports import insert_hash
|
||||
from ereuse_devicehub.resources.lot.models import Lot
|
||||
from ereuse_devicehub.resources.tag.model import Tag
|
||||
from ereuse_devicehub.resources.tradedocument.models import TradeDocument
|
||||
from ereuse_devicehub.resources.user.exceptions import InsufficientPermission
|
||||
from ereuse_devicehub.resources.user.models import User
|
||||
|
||||
DEVICES = {
|
||||
|
@ -135,7 +141,7 @@ class LotForm(FlaskForm):
|
|||
return self.instance
|
||||
|
||||
|
||||
class UploadSnapshotForm(FlaskForm):
|
||||
class UploadSnapshotForm(FlaskForm, SnapshotMix):
|
||||
snapshot = MultipleFileField('Select a Snapshot File', [validators.DataRequired()])
|
||||
|
||||
def validate(self, extra_validators=None):
|
||||
|
@ -175,20 +181,47 @@ class UploadSnapshotForm(FlaskForm):
|
|||
|
||||
return True
|
||||
|
||||
def is_wb_lite_snapshot(self, version: str) -> bool:
|
||||
is_lite = False
|
||||
if version in app.config['WORKBENCH_LITE']:
|
||||
is_lite = True
|
||||
|
||||
return is_lite
|
||||
|
||||
def save(self, commit=True):
|
||||
if any([x == 'Error' for x in self.result.values()]):
|
||||
return
|
||||
# result = []
|
||||
self.sync = Sync()
|
||||
schema = SnapshotSchema()
|
||||
# self.tmp_snapshots = app.config['TMP_SNAPSHOTS']
|
||||
# TODO @cayop get correct var config
|
||||
self.tmp_snapshots = '/tmp/'
|
||||
schema_lite = Snapshot_lite()
|
||||
devices = []
|
||||
self.tmp_snapshots = app.config['TMP_SNAPSHOTS']
|
||||
for filename, snapshot_json in self.snapshots:
|
||||
path_snapshot = save_json(snapshot_json, self.tmp_snapshots, g.user.email)
|
||||
snapshot_json.pop('debug', None)
|
||||
snapshot_json = schema.load(snapshot_json)
|
||||
version = snapshot_json.get('schema_api')
|
||||
if self.is_wb_lite_snapshot(version):
|
||||
self.snapshot_json = schema_lite.load(snapshot_json)
|
||||
snapshot_json = ParseSnapshotLsHw(self.snapshot_json).snapshot_json
|
||||
|
||||
try:
|
||||
snapshot_json = schema.load(snapshot_json)
|
||||
except ValidationError as err:
|
||||
txt = "{}".format(err)
|
||||
uuid = snapshot_json.get('uuid')
|
||||
wbid = snapshot_json.get('wbid')
|
||||
error = SnapshotErrors(
|
||||
description=txt,
|
||||
snapshot_uuid=uuid,
|
||||
severity=Severity.Error,
|
||||
wbid=wbid,
|
||||
)
|
||||
error.save(commit=True)
|
||||
self.result[filename] = 'Error'
|
||||
continue
|
||||
|
||||
response = self.build(snapshot_json)
|
||||
db.session.add(response)
|
||||
devices.append(response.device)
|
||||
|
||||
if hasattr(response, 'type'):
|
||||
self.result[filename] = 'Ok'
|
||||
|
@ -199,69 +232,7 @@ class UploadSnapshotForm(FlaskForm):
|
|||
|
||||
if commit:
|
||||
db.session.commit()
|
||||
return response
|
||||
|
||||
def build(self, snapshot_json): # noqa: C901
|
||||
# this is a copy adaptated from ereuse_devicehub.resources.action.views.snapshot
|
||||
device = snapshot_json.pop('device') # type: Computer
|
||||
components = None
|
||||
if snapshot_json['software'] == (
|
||||
SnapshotSoftware.Workbench or SnapshotSoftware.WorkbenchAndroid
|
||||
):
|
||||
components = snapshot_json.pop('components', None)
|
||||
if isinstance(device, Computer) and device.hid:
|
||||
device.add_mac_to_hid(components_snap=components)
|
||||
snapshot = Snapshot(**snapshot_json)
|
||||
|
||||
# Remove new actions from devices so they don't interfere with sync
|
||||
actions_device = set(e for e in device.actions_one)
|
||||
device.actions_one.clear()
|
||||
if components:
|
||||
actions_components = tuple(
|
||||
set(e for e in c.actions_one) for c in components
|
||||
)
|
||||
for component in components:
|
||||
component.actions_one.clear()
|
||||
|
||||
assert not device.actions_one
|
||||
assert all(not c.actions_one for c in components) if components else True
|
||||
db_device, remove_actions = self.sync.run(device, components)
|
||||
|
||||
del device # Do not use device anymore
|
||||
snapshot.device = db_device
|
||||
snapshot.actions |= remove_actions | actions_device # Set actions to snapshot
|
||||
# commit will change the order of the components by what
|
||||
# the DB wants. Let's get a copy of the list so we preserve order
|
||||
ordered_components = OrderedSet(x for x in snapshot.components)
|
||||
|
||||
# Add the new actions to the db-existing devices and components
|
||||
db_device.actions_one |= actions_device
|
||||
if components:
|
||||
for component, actions in zip(ordered_components, actions_components):
|
||||
component.actions_one |= actions
|
||||
snapshot.actions |= actions
|
||||
|
||||
if snapshot.software == SnapshotSoftware.Workbench:
|
||||
# Check ownership of (non-component) device to from current.user
|
||||
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)
|
||||
elif snapshot.software == SnapshotSoftware.WorkbenchAndroid:
|
||||
pass # TODO try except to compute RateMobile
|
||||
# Check if HID is null and add Severity:Warning to Snapshot
|
||||
if snapshot.device.hid is None:
|
||||
snapshot.severity = Severity.Warning
|
||||
|
||||
db.session.add(snapshot)
|
||||
return snapshot
|
||||
return self.result, devices
|
||||
|
||||
|
||||
class NewDeviceForm(FlaskForm):
|
||||
|
|
|
@ -231,10 +231,11 @@ class UploadSnapshotView(GenericMixView):
|
|||
'version': __version__,
|
||||
}
|
||||
if form.validate_on_submit():
|
||||
snapshot = form.save(commit=False)
|
||||
snapshot, devices = form.save(commit=False)
|
||||
if lot_id:
|
||||
lot = lots.filter(Lot.id == lot_id).one()
|
||||
lot.devices.add(snapshot.device)
|
||||
for dev in devices:
|
||||
lot.devices.add(dev)
|
||||
db.session.add(lot)
|
||||
db.session.commit()
|
||||
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
"""change firewire
|
||||
|
||||
Revision ID: 17288b2a7440
|
||||
Revises: 8571fb32c912
|
||||
Create Date: 2022-03-29 11:49:39.270791
|
||||
|
||||
"""
|
||||
import citext
|
||||
import sqlalchemy as sa
|
||||
from alembic import context, op
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '17288b2a7440'
|
||||
down_revision = '8571fb32c912'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def get_inv():
|
||||
INV = context.get_x_argument(as_dictionary=True).get('inventory')
|
||||
if not INV:
|
||||
raise ValueError("Inventory value is not specified")
|
||||
return INV
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column(
|
||||
'computer',
|
||||
sa.Column('uuid', postgresql.UUID(as_uuid=True), nullable=True),
|
||||
schema=f'{get_inv()}',
|
||||
)
|
||||
op.add_column(
|
||||
'snapshot',
|
||||
sa.Column('wbid', citext.CIText(), nullable=True),
|
||||
schema=f'{get_inv()}',
|
||||
)
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_column('computer', 'uuid', schema=f'{get_inv()}')
|
||||
op.drop_column('snapshot', 'wbid', schema=f'{get_inv()}')
|
|
@ -0,0 +1,56 @@
|
|||
"""add snapshot errors
|
||||
|
||||
Revision ID: 23d9e7ebbd7d
|
||||
Revises: 17288b2a7440
|
||||
Create Date: 2022-04-04 19:27:48.675387
|
||||
|
||||
"""
|
||||
import citext
|
||||
import sqlalchemy as sa
|
||||
from alembic import context, op
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '23d9e7ebbd7d'
|
||||
down_revision = '17288b2a7440'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def get_inv():
|
||||
INV = context.get_x_argument(as_dictionary=True).get('inventory')
|
||||
if not INV:
|
||||
raise ValueError("Inventory value is not specified")
|
||||
return INV
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.create_table(
|
||||
'snapshot_errors',
|
||||
sa.Column(
|
||||
'updated',
|
||||
sa.TIMESTAMP(timezone=True),
|
||||
server_default=sa.text('CURRENT_TIMESTAMP'),
|
||||
nullable=False,
|
||||
comment='The last time Devicehub recorded a change for \n this thing.\n ',
|
||||
),
|
||||
sa.Column(
|
||||
'created',
|
||||
sa.TIMESTAMP(timezone=True),
|
||||
server_default=sa.text('CURRENT_TIMESTAMP'),
|
||||
nullable=False,
|
||||
comment='When Devicehub created this.',
|
||||
),
|
||||
sa.Column('id', sa.BigInteger(), nullable=False),
|
||||
sa.Column('description', citext.CIText(), nullable=False),
|
||||
sa.Column('snapshot_uuid', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('severity', sa.SmallInteger(), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
schema=f'{get_inv()}',
|
||||
)
|
||||
op.execute(f"CREATE SEQUENCE {get_inv()}.snapshot_errors_seq START 1;")
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_table('snapshot_errors', schema=f'{get_inv()}')
|
||||
op.execute(f"DROP SEQUENCE {get_inv()}.snapshot_errors_seq;")
|
|
@ -0,0 +1,57 @@
|
|||
"""add wbid user in snapshotErrors
|
||||
|
||||
Revision ID: 97bef94f7982
|
||||
Revises: 23d9e7ebbd7d
|
||||
Create Date: 2022-04-12 09:27:59.670911
|
||||
|
||||
"""
|
||||
import citext
|
||||
import sqlalchemy as sa
|
||||
from alembic import context, op
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '97bef94f7982'
|
||||
down_revision = '23d9e7ebbd7d'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def get_inv():
|
||||
INV = context.get_x_argument(as_dictionary=True).get('inventory')
|
||||
if not INV:
|
||||
raise ValueError("Inventory value is not specified")
|
||||
return INV
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column(
|
||||
'snapshot_errors',
|
||||
sa.Column('wbid', citext.CIText(), nullable=True),
|
||||
schema=f'{get_inv()}',
|
||||
)
|
||||
op.add_column(
|
||||
'snapshot_errors',
|
||||
sa.Column('owner_id', postgresql.UUID(), nullable=True),
|
||||
schema=f'{get_inv()}',
|
||||
)
|
||||
op.create_foreign_key(
|
||||
"fk_snapshot_errors_owner_id_user_id",
|
||||
"snapshot_errors",
|
||||
"user",
|
||||
["owner_id"],
|
||||
["id"],
|
||||
ondelete="SET NULL",
|
||||
source_schema=f'{get_inv()}',
|
||||
referent_schema='common',
|
||||
)
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_constraint(
|
||||
"fk_snapshot_errors_owner_id_user_id",
|
||||
"snapshot_errors",
|
||||
type_="foreignkey",
|
||||
schema=f'{get_inv()}',
|
||||
)
|
||||
op.drop_column('snapshot_errors', 'owner_id', schema=f'{get_inv()}')
|
25
ereuse_devicehub/parser/__init__.py
Normal file
25
ereuse_devicehub/parser/__init__.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
from pathlib import Path
|
||||
|
||||
from pint import UnitRegistry
|
||||
|
||||
# Sets up the unit handling
|
||||
unit_registry = Path(__file__).parent / 'unit_registry'
|
||||
|
||||
unit = UnitRegistry()
|
||||
unit.load_definitions(str(unit_registry / 'quantities.txt'))
|
||||
TB = unit.TB
|
||||
GB = unit.GB
|
||||
MB = unit.MB
|
||||
Mbs = unit.Mbit / unit.s
|
||||
MBs = unit.MB / unit.s
|
||||
Hz = unit.Hz
|
||||
GHz = unit.GHz
|
||||
MHz = unit.MHz
|
||||
Inch = unit.inch
|
||||
mAh = unit.hour * unit.mA
|
||||
mV = unit.mV
|
||||
|
||||
base2 = UnitRegistry()
|
||||
base2.load_definitions(str(unit_registry / 'base2.quantities.txt'))
|
||||
|
||||
GiB = base2.GiB
|
444
ereuse_devicehub/parser/computer.py
Normal file
444
ereuse_devicehub/parser/computer.py
Normal file
|
@ -0,0 +1,444 @@
|
|||
import re
|
||||
from contextlib import suppress
|
||||
from datetime import datetime
|
||||
from fractions import Fraction
|
||||
from math import hypot
|
||||
from typing import Iterator, List, Optional, Type, TypeVar
|
||||
|
||||
import dateutil.parser
|
||||
from ereuse_utils import getter, text
|
||||
from ereuse_utils.nested_lookup import (
|
||||
get_nested_dicts_with_key_containing_value,
|
||||
get_nested_dicts_with_key_value,
|
||||
)
|
||||
|
||||
from ereuse_devicehub.parser import base2, unit, utils
|
||||
from ereuse_devicehub.parser.utils import Dumpeable
|
||||
|
||||
|
||||
class Device(Dumpeable):
|
||||
"""
|
||||
Base class for a computer and each component, containing
|
||||
its physical characteristics (like serial number) and Devicehub
|
||||
actions. For Devicehub actions, this class has an interface to execute
|
||||
:meth:`.benchmarks`.
|
||||
"""
|
||||
|
||||
def __init__(self, *sources) -> None:
|
||||
"""Gets the device information."""
|
||||
self.actions = set()
|
||||
self.type = self.__class__.__name__
|
||||
super().__init__()
|
||||
|
||||
def from_lshw(self, lshw_node: dict):
|
||||
self.manufacturer = getter.dict(lshw_node, 'vendor', default=None, type=str)
|
||||
self.model = getter.dict(
|
||||
lshw_node,
|
||||
'product',
|
||||
remove={self.manufacturer} if self.manufacturer else set(),
|
||||
default=None,
|
||||
type=str,
|
||||
)
|
||||
self.serial_number = getter.dict(lshw_node, 'serial', default=None, type=str)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return ' '.join(x for x in (self.model, self.serial_number) if x)
|
||||
|
||||
|
||||
C = TypeVar('C', bound='Component')
|
||||
|
||||
|
||||
class Component(Device):
|
||||
@classmethod
|
||||
def new(cls, lshw, hwinfo, **kwargs) -> Iterator[C]:
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class Processor(Component):
|
||||
@classmethod
|
||||
def new(cls, lshw: dict, **kwargs) -> Iterator[C]:
|
||||
nodes = get_nested_dicts_with_key_value(lshw, 'class', 'processor')
|
||||
# We want only the physical cpu's, not the logic ones
|
||||
# In some cases we may get empty cpu nodes, we can detect them because
|
||||
# all regular cpus have at least a description (Intel Core i5...)
|
||||
return (
|
||||
cls(node)
|
||||
for node in nodes
|
||||
if 'logical' not in node['id']
|
||||
and node.get('description', '').lower() != 'co-processor'
|
||||
and not node.get('disabled')
|
||||
and 'co-processor' not in node.get('model', '').lower()
|
||||
and 'co-processor' not in node.get('description', '').lower()
|
||||
and 'width' in node
|
||||
)
|
||||
|
||||
def __init__(self, node: dict) -> None:
|
||||
super().__init__(node)
|
||||
self.from_lshw(node)
|
||||
self.speed = unit.Quantity(node['size'], node['units']).to('gigahertz').m
|
||||
self.address = node['width']
|
||||
try:
|
||||
self.cores = int(node['configuration']['cores'])
|
||||
self.threads = int(node['configuration']['threads'])
|
||||
except KeyError:
|
||||
self.threads = 1
|
||||
self.cores = 1
|
||||
self.serial_number = None # Processors don't have valid SN :-(
|
||||
self.brand, self.generation = self.processor_brand_generation(self.model)
|
||||
|
||||
assert not hasattr(self, 'cores') or 1 <= self.cores <= 16
|
||||
|
||||
@staticmethod # noqa: C901
|
||||
def processor_brand_generation(model: str):
|
||||
"""Generates the ``brand`` and ``generation`` fields for the given model.
|
||||
|
||||
This returns a tuple with:
|
||||
|
||||
- The brand as a string or None.
|
||||
- The generation as an int or None.
|
||||
Intel desktop processor numbers:
|
||||
https://www.intel.com/content/www/us/en/processors/processor-numbers.html
|
||||
Intel server processor numbers:
|
||||
https://www.intel.com/content/www/us/en/processors/processor-numbers-data-center.html
|
||||
"""
|
||||
if 'Duo' in model:
|
||||
return 'Core2 Duo', None
|
||||
if 'Quad' in model:
|
||||
return 'Core2 Quad', None
|
||||
if 'Atom' in model:
|
||||
return 'Atom', None
|
||||
if 'Celeron' in model:
|
||||
return 'Celeron', None
|
||||
if 'Pentium' in model:
|
||||
return 'Pentium', None
|
||||
if 'Xeon Platinum' in model:
|
||||
generation = int(re.findall(r'\bPlatinum \d{4}\w', model)[0][10])
|
||||
return 'Xeon Platinum', generation
|
||||
if 'Xeon Gold' in model:
|
||||
generation = int(re.findall(r'\bGold \d{4}\w', model)[0][6])
|
||||
return 'Xeon Gold', generation
|
||||
if 'Xeon' in model: # Xeon E5...
|
||||
generation = 1
|
||||
results = re.findall(r'\bV\d\b', model) # find V1, V2...
|
||||
if results:
|
||||
generation = int(results[0][1])
|
||||
return 'Xeon', generation
|
||||
results = re.findall(r'\bi\d-\w+', model) # i3-XXX..., i5-XXX...
|
||||
if results: # i3, i5...
|
||||
return 'Core i{}'.format(results[0][1]), int(results[0][3])
|
||||
results = re.findall(r'\bi\d CPU \w+', model)
|
||||
if results: # i3 CPU XXX
|
||||
return 'Core i{}'.format(results[0][1]), 1
|
||||
results = re.findall(r'\bm\d-\w+', model) # m3-XXXX...
|
||||
if results:
|
||||
return 'Core m{}'.format(results[0][1]), None
|
||||
return None, None
|
||||
|
||||
def __str__(self) -> str:
|
||||
return super().__str__() + (
|
||||
' ({} generation)'.format(self.generation) if self.generation else ''
|
||||
)
|
||||
|
||||
|
||||
class RamModule(Component):
|
||||
@classmethod
|
||||
def new(cls, lshw, **kwargs) -> Iterator[C]:
|
||||
# We can get flash memory (BIOS?), system memory and unknown types of memory
|
||||
memories = get_nested_dicts_with_key_value(lshw, 'class', 'memory')
|
||||
TYPES = {'ddr', 'sdram', 'sodimm'}
|
||||
for memory in memories:
|
||||
physical_ram = any(
|
||||
t in memory.get('description', '').lower() for t in TYPES
|
||||
)
|
||||
not_empty = 'size' in memory
|
||||
if physical_ram and not_empty:
|
||||
yield cls(memory)
|
||||
|
||||
def __init__(self, node: dict) -> None:
|
||||
# Node with no size == empty ram slot
|
||||
super().__init__(node)
|
||||
self.from_lshw(node)
|
||||
description = node['description'].upper()
|
||||
self.format = 'SODIMM' if 'SODIMM' in description else 'DIMM'
|
||||
self.size = base2.Quantity(node['size'], node['units']).to('MiB').m
|
||||
# self.size = int(utils.convert_capacity(node['size'], node['units'], 'MB'))
|
||||
for w in description.split():
|
||||
if w.startswith('DDR'): # We assume all DDR are SDRAM
|
||||
self.interface = w
|
||||
break
|
||||
elif w.startswith('SDRAM'):
|
||||
# Fallback. SDRAM is generic denomination for DDR types.
|
||||
self.interface = w
|
||||
if 'clock' in node:
|
||||
self.speed = unit.Quantity(node['clock'], 'Hz').to('MHz').m
|
||||
assert not hasattr(self, 'speed') or 100.0 <= self.speed <= 1000000000000.0
|
||||
|
||||
def __str__(self) -> str:
|
||||
return '{} {} {}'.format(super().__str__(), self.format, self.size)
|
||||
|
||||
|
||||
class GraphicCard(Component):
|
||||
@classmethod
|
||||
def new(cls, lshw, hwinfo, **kwargs) -> Iterator[C]:
|
||||
nodes = get_nested_dicts_with_key_value(lshw, 'class', 'display')
|
||||
return (cls(n) for n in nodes if n['configuration'].get('driver', None))
|
||||
|
||||
def __init__(self, node: dict) -> None:
|
||||
super().__init__(node)
|
||||
self.from_lshw(node)
|
||||
self.memory = self._memory(node['businfo'].split('@')[1])
|
||||
|
||||
@staticmethod
|
||||
def _memory(bus_info):
|
||||
"""The size of the memory of the gpu."""
|
||||
return None
|
||||
|
||||
def __str__(self) -> str:
|
||||
return '{} with {}'.format(super().__str__(), self.memory)
|
||||
|
||||
|
||||
class Motherboard(Component):
|
||||
INTERFACES = 'usb', 'firewire', 'serial', 'pcmcia'
|
||||
|
||||
@classmethod
|
||||
def new(cls, lshw, hwinfo, **kwargs) -> C:
|
||||
node = next(get_nested_dicts_with_key_value(lshw, 'description', 'Motherboard'))
|
||||
bios_node = next(get_nested_dicts_with_key_value(lshw, 'id', 'firmware'))
|
||||
# bios_node = '1'
|
||||
memory_array = next(
|
||||
getter.indents(hwinfo, 'Physical Memory Array', indent=' '), None
|
||||
)
|
||||
return cls(node, bios_node, memory_array)
|
||||
|
||||
def __init__(
|
||||
self, node: dict, bios_node: dict, memory_array: Optional[List[str]]
|
||||
) -> None:
|
||||
super().__init__(node)
|
||||
self.from_lshw(node)
|
||||
self.usb = self.num_interfaces(node, 'usb')
|
||||
self.firewire = self.num_interfaces(node, 'firewire')
|
||||
self.serial = self.num_interfaces(node, 'serial')
|
||||
self.pcmcia = self.num_interfaces(node, 'pcmcia')
|
||||
self.slots = int(2)
|
||||
# run(
|
||||
# 'dmidecode -t 17 | ' 'grep -o BANK | ' 'wc -l',
|
||||
# check=True,
|
||||
# universal_newlines=True,
|
||||
# shell=True,
|
||||
# stdout=PIPE,
|
||||
# ).stdout
|
||||
|
||||
self.bios_date = dateutil.parser.parse(bios_node['date']).isoformat()
|
||||
self.version = bios_node['version']
|
||||
self.ram_slots = self.ram_max_size = None
|
||||
if memory_array:
|
||||
self.ram_slots = getter.kv(memory_array, 'Slots', default=None)
|
||||
self.ram_max_size = getter.kv(memory_array, 'Max. Size', default=None)
|
||||
if self.ram_max_size:
|
||||
self.ram_max_size = next(text.numbers(self.ram_max_size))
|
||||
|
||||
@staticmethod
|
||||
def num_interfaces(node: dict, interface: str) -> int:
|
||||
interfaces = get_nested_dicts_with_key_containing_value(node, 'id', interface)
|
||||
if interface == 'usb':
|
||||
interfaces = (
|
||||
c
|
||||
for c in interfaces
|
||||
if 'usbhost' not in c['id'] and 'usb' not in c['businfo']
|
||||
)
|
||||
return len(tuple(interfaces))
|
||||
|
||||
def __str__(self) -> str:
|
||||
return super().__str__()
|
||||
|
||||
|
||||
class NetworkAdapter(Component):
|
||||
@classmethod
|
||||
def new(cls, lshw, hwinfo, **kwargs) -> Iterator[C]:
|
||||
nodes = get_nested_dicts_with_key_value(lshw, 'class', 'network')
|
||||
return (cls(node) for node in nodes)
|
||||
|
||||
def __init__(self, node: dict) -> None:
|
||||
super().__init__(node)
|
||||
self.from_lshw(node)
|
||||
self.speed = None
|
||||
if 'capacity' in node:
|
||||
self.speed = unit.Quantity(node['capacity'], 'bit/s').to('Mbit/s').m
|
||||
if 'logicalname' in node: # todo this was taken from 'self'?
|
||||
# If we don't have logicalname it means we don't have the
|
||||
# (proprietary) drivers fot that NetworkAdaptor
|
||||
# which means we can't access at the MAC address
|
||||
# (note that S/N == MAC) "sudo /sbin/lspci -vv" could bring
|
||||
# the MAC even if no drivers are installed however more work
|
||||
# has to be done in ensuring it is reliable, really needed,
|
||||
# and to parse it
|
||||
# https://www.redhat.com/archives/redhat-list/2010-October/msg00066.html
|
||||
# workbench-live includes proprietary firmwares
|
||||
self.serial_number = self.serial_number or utils.get_hw_addr(
|
||||
node['logicalname']
|
||||
)
|
||||
|
||||
self.variant = node.get('version', None)
|
||||
self.wireless = bool(node.get('configuration', {}).get('wireless', False))
|
||||
|
||||
def __str__(self) -> str:
|
||||
return '{} {} {}'.format(
|
||||
super().__str__(), self.speed, 'wireless' if self.wireless else 'ethernet'
|
||||
)
|
||||
|
||||
|
||||
class SoundCard(Component):
|
||||
@classmethod
|
||||
def new(cls, lshw, hwinfo, **kwargs) -> Iterator[C]:
|
||||
nodes = get_nested_dicts_with_key_value(lshw, 'class', 'multimedia')
|
||||
return (cls(node) for node in nodes)
|
||||
|
||||
def __init__(self, node) -> None:
|
||||
super().__init__(node)
|
||||
self.from_lshw(node)
|
||||
|
||||
|
||||
class Display(Component):
|
||||
TECHS = 'CRT', 'TFT', 'LED', 'PDP', 'LCD', 'OLED', 'AMOLED'
|
||||
"""Display technologies"""
|
||||
|
||||
@classmethod
|
||||
def new(cls, lshw, hwinfo, **kwargs) -> Iterator[C]:
|
||||
for node in getter.indents(hwinfo, 'Monitor'):
|
||||
yield cls(node)
|
||||
|
||||
def __init__(self, node: dict) -> None:
|
||||
super().__init__(node)
|
||||
self.model = getter.kv(node, 'Model')
|
||||
self.manufacturer = getter.kv(node, 'Vendor')
|
||||
self.serial_number = getter.kv(node, 'Serial ID', default=None, type=str)
|
||||
self.resolution_width, self.resolution_height, refresh_rate = text.numbers(
|
||||
getter.kv(node, 'Resolution')
|
||||
)
|
||||
self.refresh_rate = unit.Quantity(refresh_rate, 'Hz').m
|
||||
with suppress(StopIteration):
|
||||
# some monitors can have several resolutions, and the one
|
||||
# in "Detailed Timings" seems the highest one
|
||||
timings = next(getter.indents(node, 'Detailed Timings', indent=' '))
|
||||
self.resolution_width, self.resolution_height = text.numbers(
|
||||
getter.kv(timings, 'Resolution')
|
||||
)
|
||||
x, y = (
|
||||
unit.convert(v, 'millimeter', 'inch')
|
||||
for v in text.numbers(getter.kv(node, 'Size'))
|
||||
)
|
||||
self.size = hypot(x, y)
|
||||
self.technology = next((t for t in self.TECHS if t in node[0]), None)
|
||||
d = '{} {} 0'.format(
|
||||
getter.kv(node, 'Year of Manufacture'),
|
||||
getter.kv(node, 'Week of Manufacture'),
|
||||
)
|
||||
# We assume it has been produced the first day of such week
|
||||
self.production_date = datetime.strptime(d, '%Y %W %w').isoformat()
|
||||
self._aspect_ratio = Fraction(self.resolution_width, self.resolution_height)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return (
|
||||
'{0} {1.resolution_width}x{1.resolution_height} {1.size} inches {2}'.format(
|
||||
super().__str__(), self, self._aspect_ratio
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class Computer(Device):
|
||||
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'},
|
||||
}
|
||||
"""
|
||||
A translation dictionary whose keys are Devicehub types and values
|
||||
are possible chassis values that `dmi <https://ezix.org/src/pkg/
|
||||
lshw/src/master/src/core/dmi.cc#L632>`_ can offer.
|
||||
"""
|
||||
CHASSIS_DH = {
|
||||
'Tower': {'desktop', 'low-profile', 'tower', 'server'},
|
||||
'Docking': {'docking'},
|
||||
'AllInOne': {'all-in-one'},
|
||||
'Microtower': {'mini-tower', 'space-saving', 'mini'},
|
||||
'PizzaBox': {'pizzabox'},
|
||||
'Lunchbox': {'lunchbox'},
|
||||
'Stick': {'stick'},
|
||||
'Netbook': {'notebook', 'sub-notebook'},
|
||||
'Handheld': {'handheld'},
|
||||
'Laptop': {'portable', 'laptop'},
|
||||
'Convertible': {'convertible'},
|
||||
'Detachable': {'detachable'},
|
||||
'Tablet': {'tablet'},
|
||||
'Virtual': {'_virtual'},
|
||||
}
|
||||
"""
|
||||
A conversion table from DMI's chassis type value Devicehub
|
||||
chassis value.
|
||||
"""
|
||||
|
||||
COMPONENTS = list(Component.__subclasses__()) # type: List[Type[Component]]
|
||||
COMPONENTS.remove(Motherboard)
|
||||
|
||||
def __init__(self, node: dict) -> None:
|
||||
super().__init__(node)
|
||||
self.from_lshw(node)
|
||||
chassis = node.get('configuration', {}).get('chassis', '_virtual')
|
||||
self.type = next(
|
||||
t for t, values in self.CHASSIS_TYPE.items() if chassis in values
|
||||
)
|
||||
self.chassis = next(
|
||||
t for t, values in self.CHASSIS_DH.items() if chassis in values
|
||||
)
|
||||
self.sku = getter.dict(node, ('configuration', 'sku'), default=None, type=str)
|
||||
self.version = getter.dict(node, 'version', default=None, type=str)
|
||||
self._ram = None
|
||||
|
||||
@classmethod
|
||||
def run(cls, lshw, hwinfo_raw):
|
||||
"""
|
||||
Gets hardware information from the computer and its components,
|
||||
like serial numbers or model names, and benchmarks them.
|
||||
|
||||
This function uses ``LSHW`` as the main source of hardware information,
|
||||
which is obtained once when it is instantiated.
|
||||
"""
|
||||
hwinfo = hwinfo_raw.splitlines()
|
||||
computer = cls(lshw)
|
||||
components = []
|
||||
for Component in cls.COMPONENTS:
|
||||
if Component == Display and computer.type != 'Laptop':
|
||||
continue # Only get display info when computer is laptop
|
||||
components.extend(Component.new(lshw=lshw, hwinfo=hwinfo))
|
||||
components.append(Motherboard.new(lshw, hwinfo))
|
||||
|
||||
computer._ram = sum(
|
||||
ram.size for ram in components if isinstance(ram, RamModule)
|
||||
)
|
||||
return computer, components
|
||||
|
||||
def __str__(self) -> str:
|
||||
specs = super().__str__()
|
||||
return '{} with {} MB of RAM.'.format(specs, self._ram)
|
32
ereuse_devicehub/parser/models.py
Normal file
32
ereuse_devicehub/parser/models.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
from citext import CIText
|
||||
from flask import g
|
||||
from sqlalchemy import BigInteger, Column, Sequence, SmallInteger
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
|
||||
from ereuse_devicehub.db import db
|
||||
from ereuse_devicehub.resources.enums import Severity
|
||||
from ereuse_devicehub.resources.models import Thing
|
||||
from ereuse_devicehub.resources.user.models import User
|
||||
|
||||
|
||||
class SnapshotErrors(Thing):
|
||||
"""A Snapshot errors."""
|
||||
|
||||
id = Column(BigInteger, Sequence('snapshot_errors_seq'), primary_key=True)
|
||||
description = Column(CIText(), default='', nullable=False)
|
||||
wbid = Column(CIText(), nullable=True)
|
||||
severity = Column(SmallInteger, default=Severity.Info, nullable=False)
|
||||
snapshot_uuid = Column(UUID(as_uuid=True), nullable=False)
|
||||
owner_id = db.Column(
|
||||
UUID(as_uuid=True),
|
||||
db.ForeignKey(User.id),
|
||||
nullable=False,
|
||||
default=lambda: g.user.id,
|
||||
)
|
||||
owner = db.relationship(User, primaryjoin=owner_id == User.id)
|
||||
|
||||
def save(self, commit=False):
|
||||
db.session.add(self)
|
||||
|
||||
if commit:
|
||||
db.session.commit()
|
548
ereuse_devicehub/parser/parser.py
Normal file
548
ereuse_devicehub/parser/parser.py
Normal file
|
@ -0,0 +1,548 @@
|
|||
import json
|
||||
import logging
|
||||
import uuid
|
||||
|
||||
from dmidecode import DMIParse
|
||||
from marshmallow import ValidationError
|
||||
|
||||
from ereuse_devicehub.parser import base2
|
||||
from ereuse_devicehub.parser.computer import Computer
|
||||
from ereuse_devicehub.parser.models import SnapshotErrors
|
||||
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,
|
||||
"wbid": snapshot["wbid"],
|
||||
}
|
||||
|
||||
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['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 u.get("Port Type") == "USB"]
|
||||
)
|
||||
|
||||
def get_serial_num(self):
|
||||
return len(
|
||||
[
|
||||
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.dmi.get("Port Connector")
|
||||
if u.get("Port Type") == "PCMCIA"
|
||||
]
|
||||
)
|
||||
|
||||
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.wbid = snapshot.get("wbid")
|
||||
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,
|
||||
"wbid": snapshot["wbid"],
|
||||
}
|
||||
|
||||
def get_snapshot(self):
|
||||
try:
|
||||
return Snapshot().load(self.snapshot_json)
|
||||
except ValidationError as err:
|
||||
txt = "{}".format(err)
|
||||
uuid = self.snapshot_json.get('uuid')
|
||||
wbid = self.snapshot_json.get('wbid')
|
||||
error = SnapshotErrors(
|
||||
description=txt, snapshot_uuid=uuid, severity=Severity.Error, wbid=wbid
|
||||
)
|
||||
error.save(commit=True)
|
||||
raise err
|
||||
|
||||
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):
|
||||
pc, self.components_obj = Computer.run(self.lshw, self.hwinfo_raw)
|
||||
self.device = pc.dump()
|
||||
self.device['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}, tag: {wbid} have this ram Size: {size}".format(
|
||||
uuid=self.uuid, size=size, wbid=self.wbid
|
||||
)
|
||||
self.errors(txt)
|
||||
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}, tag: {wbid} have this ram Speed: {speed}".format(
|
||||
uuid=self.uuid, speed=speed, wbid=self.wbid
|
||||
)
|
||||
self.errors(txt)
|
||||
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} tag: {wbid} have this uuid: {device}".format(
|
||||
uuid=self.uuid, device=dmi_uuid, wbid=self.wbid
|
||||
)
|
||||
self.errors(txt)
|
||||
dmi_uuid = None
|
||||
return dmi_uuid
|
||||
|
||||
def get_data_storage(self):
|
||||
|
||||
for sm in self.smart:
|
||||
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") == "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 = "tag: {}, interface {} is not in DataStorageInterface Enum".format(
|
||||
interface, self.wbid
|
||||
)
|
||||
self.errors("{}".format(err))
|
||||
self.errors(txt)
|
||||
return "ATA"
|
||||
|
||||
def get_data_storage_size(self, x):
|
||||
type_dev = x.get('device', {}).get('protocol', '').lower()
|
||||
total_capacity = "{type}_total_capacity".format(type=type_dev)
|
||||
if not x.get(total_capacity):
|
||||
return 1
|
||||
# convert bytes to Mb
|
||||
return x.get(total_capacity) / 1024**2
|
||||
|
||||
def get_test_data_storage(self, smart):
|
||||
log = "smart_health_information_log"
|
||||
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": 0,
|
||||
"type": "TestDataStorage",
|
||||
"length": "Short",
|
||||
"elapsed": 0,
|
||||
"reportedUncorrectableErrors": smart.get(
|
||||
"reported_uncorrectable_errors", 0
|
||||
),
|
||||
"powerCycleCount": smart.get("power_cycle_count", 0),
|
||||
}
|
||||
|
||||
for k in smart.keys():
|
||||
if log in k:
|
||||
action['lifetime'] = smart[k].get("power_on_hours", 0)
|
||||
action['powerOnHours'] = smart[k].get("power_on_hours", 0)
|
||||
|
||||
return action
|
||||
|
||||
def errors(self, txt=None, severity=Severity.Info):
|
||||
if not txt:
|
||||
return self._errors
|
||||
|
||||
logger.error(txt)
|
||||
self._errors.append(txt)
|
||||
error = SnapshotErrors(
|
||||
description=txt, snapshot_uuid=self.uuid, severity=severity, wbid=self.wbid
|
||||
)
|
||||
error.save()
|
36
ereuse_devicehub/parser/schemas.py
Normal file
36
ereuse_devicehub/parser/schemas.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
from flask import current_app as app
|
||||
from marshmallow import Schema as MarshmallowSchema
|
||||
from marshmallow import ValidationError, validates_schema
|
||||
from marshmallow.fields import Dict, List, Nested, String
|
||||
|
||||
from ereuse_devicehub.resources.schemas import Thing
|
||||
|
||||
|
||||
class Snapshot_lite_data(MarshmallowSchema):
|
||||
dmidecode = String(required=False)
|
||||
hwinfo = String(required=False)
|
||||
smart = List(Dict(), required=False)
|
||||
lshw = Dict(required=False)
|
||||
lspci = String(required=False)
|
||||
|
||||
|
||||
class Snapshot_lite(Thing):
|
||||
uuid = String(required=True)
|
||||
version = String(required=True)
|
||||
schema_api = String(required=True)
|
||||
software = String(required=True)
|
||||
wbid = String(required=True)
|
||||
type = String(required=True)
|
||||
timestamp = String(required=True)
|
||||
data = Nested(Snapshot_lite_data)
|
||||
|
||||
@validates_schema
|
||||
def validate_workbench_version(self, data: dict):
|
||||
if data['schema_api'] not in app.config['WORKBENCH_LITE']:
|
||||
raise ValidationError(
|
||||
'Min. supported Workbench version is '
|
||||
'{} but yours is {}.'.format(
|
||||
app.config['WORKBENCH_LITE'][0], data['version']
|
||||
),
|
||||
field_names=['version'],
|
||||
)
|
38
ereuse_devicehub/parser/snapshot.py
Normal file
38
ereuse_devicehub/parser/snapshot.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
from datetime import datetime, timezone
|
||||
from typing import List
|
||||
|
||||
from ereuse_workbench.computer import Component, Computer, DataStorage
|
||||
from ereuse_workbench.utils import Dumpeable
|
||||
|
||||
|
||||
class Snapshot(Dumpeable):
|
||||
"""
|
||||
Generates the Snapshot report for Devicehub by obtaining the
|
||||
data from the computer, performing benchmarks and tests...
|
||||
|
||||
After instantiating the class, run :meth:`.computer` before any
|
||||
other method.
|
||||
"""
|
||||
|
||||
def __init__(self, uuid, software, version, lshw, hwinfo):
|
||||
self.type = 'Snapshot'
|
||||
self.uuid = uuid
|
||||
self.software = software
|
||||
self.version = version
|
||||
self.lshw = lshw
|
||||
self.hwinfo = hwinfo
|
||||
self.endTime = datetime.now(timezone.utc)
|
||||
self.closed = False
|
||||
self.elapsed = None
|
||||
self.device = None # type: Computer
|
||||
self.components = None # type: List[Component]
|
||||
self._storages = None
|
||||
|
||||
def computer(self):
|
||||
"""Retrieves information about the computer and components."""
|
||||
self.device, self.components = Computer.run(self.lshw, self.hwinfo)
|
||||
self._storages = tuple(c for c in self.components if isinstance(c, DataStorage))
|
||||
|
||||
def close(self):
|
||||
"""Closes the Snapshot"""
|
||||
self.closed = True
|
|
@ -0,0 +1,4 @@
|
|||
K = KiB = k = kb = KB
|
||||
M = MiB = m = mb = MB
|
||||
G = GiB = g = gb = GB
|
||||
T = TiB = t = tb = TB
|
9
ereuse_devicehub/parser/unit_registry/quantities.txt
Normal file
9
ereuse_devicehub/parser/unit_registry/quantities.txt
Normal file
|
@ -0,0 +1,9 @@
|
|||
HZ = hertz = hz
|
||||
KHZ = kilohertz = khz
|
||||
MHZ = megahertz = mhz
|
||||
GHZ = gigahertz = ghz
|
||||
B = byte = b = UNIT = unit
|
||||
KB = kilobyte = kb = K = k
|
||||
MB = megabyte = mb = M = m
|
||||
GB = gigabyte = gb = G = g
|
||||
T = terabyte = tb = T = t
|
38
ereuse_devicehub/parser/utils.py
Normal file
38
ereuse_devicehub/parser/utils.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
import datetime
|
||||
import fcntl
|
||||
import socket
|
||||
import struct
|
||||
from contextlib import contextmanager
|
||||
from enum import Enum
|
||||
|
||||
from ereuse_utils import Dumpeable
|
||||
|
||||
|
||||
class Severity(Enum):
|
||||
Info = 'Info'
|
||||
Error = 'Error'
|
||||
|
||||
|
||||
def get_hw_addr(ifname):
|
||||
# http://stackoverflow.com/a/4789267/1538221
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
info = fcntl.ioctl(s.fileno(), 0x8927, struct.pack('256s', ifname[:15]))
|
||||
return ':'.join('%02x' % ord(char) for char in info[18:24])
|
||||
|
||||
|
||||
class Measurable(Dumpeable):
|
||||
"""A base class that allows measuring execution times."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.elapsed = None
|
||||
|
||||
@contextmanager
|
||||
def measure(self):
|
||||
init = datetime.datetime.now(datetime.timezone.utc)
|
||||
yield
|
||||
self.elapsed = datetime.datetime.now(datetime.timezone.utc) - init
|
||||
try:
|
||||
assert self.elapsed.total_seconds() > 0
|
||||
except AssertionError:
|
||||
self.elapsed = datetime.timedelta(seconds=0)
|
File diff suppressed because it is too large
Load diff
|
@ -1,14 +1,28 @@
|
|||
import copy
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from dateutil.tz import tzutc
|
||||
from flask import current_app as app, g
|
||||
from marshmallow import Schema as MarshmallowSchema, ValidationError, fields as f, validates_schema, pre_load, post_load
|
||||
from marshmallow.fields import Boolean, DateTime, Decimal, Float, Integer, Nested, String, \
|
||||
TimeDelta, UUID
|
||||
from flask import current_app as app
|
||||
from flask import g
|
||||
from marshmallow import Schema as MarshmallowSchema
|
||||
from marshmallow import ValidationError
|
||||
from marshmallow import fields as f
|
||||
from marshmallow import post_load, pre_load, validates_schema
|
||||
from marshmallow.fields import (
|
||||
UUID,
|
||||
Boolean,
|
||||
DateTime,
|
||||
Decimal,
|
||||
Float,
|
||||
Integer,
|
||||
Nested,
|
||||
String,
|
||||
TimeDelta,
|
||||
)
|
||||
from marshmallow.validate import Length, OneOf, Range
|
||||
from sqlalchemy.util import OrderedSet
|
||||
from teal.enums import Country, Currency, Subdivision
|
||||
from teal.marshmallow import EnumField, IP, SanitizedStr, URL, Version
|
||||
from teal.marshmallow import IP, URL, EnumField, SanitizedStr, Version
|
||||
from teal.resource import Schema
|
||||
|
||||
from ereuse_devicehub.marshmallow import NestedOn
|
||||
|
@ -16,24 +30,32 @@ from ereuse_devicehub.resources import enums
|
|||
from ereuse_devicehub.resources.action import models as m
|
||||
from ereuse_devicehub.resources.agent import schemas as s_agent
|
||||
from ereuse_devicehub.resources.device import schemas as s_device
|
||||
from ereuse_devicehub.resources.tradedocument import schemas as s_document
|
||||
from ereuse_devicehub.resources.documents import schemas as s_generic_document
|
||||
from ereuse_devicehub.resources.enums import AppearanceRange, BiosAccessRange, FunctionalityRange, \
|
||||
PhysicalErasureMethod, R_POSITIVE, RatingRange, \
|
||||
Severity, SnapshotSoftware, TestDataStorageLength
|
||||
from ereuse_devicehub.resources.enums import (
|
||||
R_POSITIVE,
|
||||
AppearanceRange,
|
||||
BiosAccessRange,
|
||||
FunctionalityRange,
|
||||
PhysicalErasureMethod,
|
||||
RatingRange,
|
||||
Severity,
|
||||
SnapshotSoftware,
|
||||
TestDataStorageLength,
|
||||
)
|
||||
from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE
|
||||
from ereuse_devicehub.resources.schemas import Thing
|
||||
from ereuse_devicehub.resources.tradedocument import schemas as s_document
|
||||
from ereuse_devicehub.resources.tradedocument.models import TradeDocument
|
||||
from ereuse_devicehub.resources.user import schemas as s_user
|
||||
from ereuse_devicehub.resources.user.models import User
|
||||
from ereuse_devicehub.resources.tradedocument.models import TradeDocument
|
||||
|
||||
|
||||
class Action(Thing):
|
||||
__doc__ = m.Action.__doc__
|
||||
id = UUID(dump_only=True)
|
||||
name = SanitizedStr(default='',
|
||||
validate=Length(max=STR_BIG_SIZE),
|
||||
description=m.Action.name.comment)
|
||||
name = SanitizedStr(
|
||||
default='', validate=Length(max=STR_BIG_SIZE), description=m.Action.name.comment
|
||||
)
|
||||
closed = Boolean(missing=True, description=m.Action.closed.comment)
|
||||
severity = EnumField(Severity, description=m.Action.severity.comment)
|
||||
description = SanitizedStr(default='', description=m.Action.description.comment)
|
||||
|
@ -43,16 +65,18 @@ class Action(Thing):
|
|||
agent = NestedOn(s_agent.Agent, description=m.Action.agent_id.comment)
|
||||
author = NestedOn(s_user.User, dump_only=True, exclude=('token',))
|
||||
components = NestedOn(s_device.Component, dump_only=True, many=True)
|
||||
parent = NestedOn(s_device.Computer, dump_only=True, description=m.Action.parent_id.comment)
|
||||
parent = NestedOn(
|
||||
s_device.Computer, dump_only=True, description=m.Action.parent_id.comment
|
||||
)
|
||||
url = URL(dump_only=True, description=m.Action.url.__doc__)
|
||||
|
||||
@validates_schema
|
||||
def validate_times(self, data: dict):
|
||||
unix_time = datetime.fromisoformat("1970-01-02 00:00:00+00:00")
|
||||
if 'end_time' in data and data['end_time'] < unix_time:
|
||||
if 'end_time' in data and data['end_time'].replace(tzinfo=tzutc()) < unix_time:
|
||||
data['end_time'] = unix_time
|
||||
|
||||
if 'start_time' in data and data['start_time'] < unix_time:
|
||||
if 'start_time' in data and data['start_time'].replace(tzinfo=tzutc()) < unix_time:
|
||||
data['start_time'] = unix_time
|
||||
|
||||
if data.get('end_time') and data.get('start_time'):
|
||||
|
@ -67,24 +91,27 @@ class ActionWithOneDevice(Action):
|
|||
|
||||
class ActionWithMultipleDocuments(Action):
|
||||
__doc__ = m.ActionWithMultipleTradeDocuments.__doc__
|
||||
documents = NestedOn(s_document.TradeDocument,
|
||||
many=True,
|
||||
required=True, # todo test ensuring len(devices) >= 1
|
||||
only_query='id',
|
||||
collection_class=OrderedSet)
|
||||
documents = NestedOn(
|
||||
s_document.TradeDocument,
|
||||
many=True,
|
||||
required=True, # todo test ensuring len(devices) >= 1
|
||||
only_query='id',
|
||||
collection_class=OrderedSet,
|
||||
)
|
||||
|
||||
|
||||
class ActionWithMultipleDevices(Action):
|
||||
__doc__ = m.ActionWithMultipleDevices.__doc__
|
||||
devices = NestedOn(s_device.Device,
|
||||
many=True,
|
||||
required=True, # todo test ensuring len(devices) >= 1
|
||||
only_query='id',
|
||||
collection_class=OrderedSet)
|
||||
devices = NestedOn(
|
||||
s_device.Device,
|
||||
many=True,
|
||||
required=True, # todo test ensuring len(devices) >= 1
|
||||
only_query='id',
|
||||
collection_class=OrderedSet,
|
||||
)
|
||||
|
||||
|
||||
class ActionWithMultipleDevicesCheckingOwner(ActionWithMultipleDevices):
|
||||
|
||||
@post_load
|
||||
def check_owner_of_device(self, data):
|
||||
for dev in data['devices']:
|
||||
|
@ -102,20 +129,29 @@ class Remove(ActionWithOneDevice):
|
|||
|
||||
class Allocate(ActionWithMultipleDevicesCheckingOwner):
|
||||
__doc__ = m.Allocate.__doc__
|
||||
start_time = DateTime(data_key='startTime', required=True,
|
||||
description=m.Action.start_time.comment)
|
||||
end_time = DateTime(data_key='endTime', required=False,
|
||||
description=m.Action.end_time.comment)
|
||||
final_user_code = SanitizedStr(data_key="finalUserCode",
|
||||
validate=Length(min=1, max=STR_BIG_SIZE),
|
||||
required=False,
|
||||
description='This is a internal code for mainteing the secrets of the \
|
||||
personal datas of the new holder')
|
||||
transaction = SanitizedStr(validate=Length(min=1, max=STR_BIG_SIZE),
|
||||
required=False,
|
||||
description='The code used from the owner for \
|
||||
relation with external tool.')
|
||||
end_users = Integer(data_key='endUsers', validate=[Range(min=1, error="Value must be greater than 0")])
|
||||
start_time = DateTime(
|
||||
data_key='startTime', required=True, description=m.Action.start_time.comment
|
||||
)
|
||||
end_time = DateTime(
|
||||
data_key='endTime', required=False, description=m.Action.end_time.comment
|
||||
)
|
||||
final_user_code = SanitizedStr(
|
||||
data_key="finalUserCode",
|
||||
validate=Length(min=1, max=STR_BIG_SIZE),
|
||||
required=False,
|
||||
description='This is a internal code for mainteing the secrets of the \
|
||||
personal datas of the new holder',
|
||||
)
|
||||
transaction = SanitizedStr(
|
||||
validate=Length(min=1, max=STR_BIG_SIZE),
|
||||
required=False,
|
||||
description='The code used from the owner for \
|
||||
relation with external tool.',
|
||||
)
|
||||
end_users = Integer(
|
||||
data_key='endUsers',
|
||||
validate=[Range(min=1, error="Value must be greater than 0")],
|
||||
)
|
||||
|
||||
@validates_schema
|
||||
def validate_allocate(self, data: dict):
|
||||
|
@ -136,12 +172,15 @@ class Allocate(ActionWithMultipleDevicesCheckingOwner):
|
|||
|
||||
class Deallocate(ActionWithMultipleDevicesCheckingOwner):
|
||||
__doc__ = m.Deallocate.__doc__
|
||||
start_time = DateTime(data_key='startTime', required=True,
|
||||
description=m.Action.start_time.comment)
|
||||
transaction = SanitizedStr(validate=Length(min=1, max=STR_BIG_SIZE),
|
||||
required=False,
|
||||
description='The code used from the owner for \
|
||||
relation with external tool.')
|
||||
start_time = DateTime(
|
||||
data_key='startTime', required=True, description=m.Action.start_time.comment
|
||||
)
|
||||
transaction = SanitizedStr(
|
||||
validate=Length(min=1, max=STR_BIG_SIZE),
|
||||
required=False,
|
||||
description='The code used from the owner for \
|
||||
relation with external tool.',
|
||||
)
|
||||
|
||||
@validates_schema
|
||||
def validate_deallocate(self, data: dict):
|
||||
|
@ -232,7 +271,9 @@ class MeasureBattery(Test):
|
|||
__doc__ = m.MeasureBattery.__doc__
|
||||
size = Integer(required=True, description=m.MeasureBattery.size.comment)
|
||||
voltage = Integer(required=True, description=m.MeasureBattery.voltage.comment)
|
||||
cycle_count = Integer(data_key='cycleCount', description=m.MeasureBattery.cycle_count.comment)
|
||||
cycle_count = Integer(
|
||||
data_key='cycleCount', description=m.MeasureBattery.cycle_count.comment
|
||||
)
|
||||
health = EnumField(enums.BatteryHealth, description=m.MeasureBattery.health.comment)
|
||||
|
||||
|
||||
|
@ -289,28 +330,32 @@ class TestBios(Test):
|
|||
class VisualTest(Test):
|
||||
__doc__ = m.VisualTest.__doc__
|
||||
appearance_range = EnumField(AppearanceRange, data_key='appearanceRange')
|
||||
functionality_range = EnumField(FunctionalityRange,
|
||||
data_key='functionalityRange')
|
||||
functionality_range = EnumField(FunctionalityRange, data_key='functionalityRange')
|
||||
labelling = Boolean()
|
||||
|
||||
|
||||
class Rate(ActionWithOneDevice):
|
||||
__doc__ = m.Rate.__doc__
|
||||
rating = Integer(validate=Range(*R_POSITIVE),
|
||||
dump_only=True,
|
||||
description=m.Rate._rating.comment)
|
||||
version = Version(dump_only=True,
|
||||
description=m.Rate.version.comment)
|
||||
appearance = Integer(validate=Range(enums.R_NEGATIVE),
|
||||
dump_only=True,
|
||||
description=m.Rate._appearance.comment)
|
||||
functionality = Integer(validate=Range(enums.R_NEGATIVE),
|
||||
dump_only=True,
|
||||
description=m.Rate._functionality.comment)
|
||||
rating_range = EnumField(RatingRange,
|
||||
dump_only=True,
|
||||
data_key='ratingRange',
|
||||
description=m.Rate.rating_range.__doc__)
|
||||
rating = Integer(
|
||||
validate=Range(*R_POSITIVE), dump_only=True, description=m.Rate._rating.comment
|
||||
)
|
||||
version = Version(dump_only=True, description=m.Rate.version.comment)
|
||||
appearance = Integer(
|
||||
validate=Range(enums.R_NEGATIVE),
|
||||
dump_only=True,
|
||||
description=m.Rate._appearance.comment,
|
||||
)
|
||||
functionality = Integer(
|
||||
validate=Range(enums.R_NEGATIVE),
|
||||
dump_only=True,
|
||||
description=m.Rate._functionality.comment,
|
||||
)
|
||||
rating_range = EnumField(
|
||||
RatingRange,
|
||||
dump_only=True,
|
||||
data_key='ratingRange',
|
||||
description=m.Rate.rating_range.__doc__,
|
||||
)
|
||||
|
||||
|
||||
class RateComputer(Rate):
|
||||
|
@ -320,19 +365,25 @@ class RateComputer(Rate):
|
|||
data_storage = Float(dump_only=True, data_key='dataStorage')
|
||||
graphic_card = Float(dump_only=True, data_key='graphicCard')
|
||||
|
||||
data_storage_range = EnumField(RatingRange, dump_only=True, data_key='dataStorageRange')
|
||||
data_storage_range = EnumField(
|
||||
RatingRange, dump_only=True, data_key='dataStorageRange'
|
||||
)
|
||||
ram_range = EnumField(RatingRange, dump_only=True, data_key='ramRange')
|
||||
processor_range = EnumField(RatingRange, dump_only=True, data_key='processorRange')
|
||||
graphic_card_range = EnumField(RatingRange, dump_only=True, data_key='graphicCardRange')
|
||||
graphic_card_range = EnumField(
|
||||
RatingRange, dump_only=True, data_key='graphicCardRange'
|
||||
)
|
||||
|
||||
|
||||
class Price(ActionWithOneDevice):
|
||||
__doc__ = m.Price.__doc__
|
||||
currency = EnumField(Currency, required=True, description=m.Price.currency.comment)
|
||||
price = Decimal(places=m.Price.SCALE,
|
||||
rounding=m.Price.ROUND,
|
||||
required=True,
|
||||
description=m.Price.price.comment)
|
||||
price = Decimal(
|
||||
places=m.Price.SCALE,
|
||||
rounding=m.Price.ROUND,
|
||||
required=True,
|
||||
description=m.Price.price.comment,
|
||||
)
|
||||
version = Version(dump_only=True, description=m.Price.version.comment)
|
||||
rating = NestedOn(Rate, dump_only=True, description=m.Price.rating_id.comment)
|
||||
|
||||
|
@ -356,9 +407,11 @@ class EreusePrice(Price):
|
|||
|
||||
class Install(ActionWithOneDevice):
|
||||
__doc__ = m.Install.__doc__
|
||||
name = SanitizedStr(validate=Length(min=4, max=STR_BIG_SIZE),
|
||||
required=True,
|
||||
description='The name of the OS installed.')
|
||||
name = SanitizedStr(
|
||||
validate=Length(min=4, max=STR_BIG_SIZE),
|
||||
required=True,
|
||||
description='The name of the OS installed.',
|
||||
)
|
||||
elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True)
|
||||
address = Integer(validate=OneOf({8, 16, 32, 64, 128, 256}))
|
||||
|
||||
|
@ -372,18 +425,23 @@ class Snapshot(ActionWithOneDevice):
|
|||
See docs for more info.
|
||||
"""
|
||||
uuid = UUID()
|
||||
software = EnumField(SnapshotSoftware,
|
||||
required=True,
|
||||
description='The software that generated this Snapshot.')
|
||||
wbid = String(required=False)
|
||||
software = EnumField(
|
||||
SnapshotSoftware,
|
||||
required=True,
|
||||
description='The software that generated this Snapshot.',
|
||||
)
|
||||
version = Version(required=True, description='The version of the software.')
|
||||
actions = NestedOn(Action, many=True, dump_only=True)
|
||||
elapsed = TimeDelta(precision=TimeDelta.SECONDS)
|
||||
components = NestedOn(s_device.Component,
|
||||
many=True,
|
||||
description='A list of components that are inside of the device'
|
||||
'at the moment of this Snapshot.'
|
||||
'Order is preserved, so the component num 0 when'
|
||||
'submitting is the component num 0 when returning it back.')
|
||||
components = NestedOn(
|
||||
s_device.Component,
|
||||
many=True,
|
||||
description='A list of components that are inside of the device'
|
||||
'at the moment of this Snapshot.'
|
||||
'Order is preserved, so the component num 0 when'
|
||||
'submitting is the component num 0 when returning it back.',
|
||||
)
|
||||
|
||||
@validates_schema
|
||||
def validate_workbench_version(self, data: dict):
|
||||
|
@ -391,16 +449,21 @@ class Snapshot(ActionWithOneDevice):
|
|||
if data['version'] < app.config['MIN_WORKBENCH']:
|
||||
raise ValidationError(
|
||||
'Min. supported Workbench version is '
|
||||
'{} but yours is {}.'.format(app.config['MIN_WORKBENCH'], data['version']),
|
||||
field_names=['version']
|
||||
'{} but yours is {}.'.format(
|
||||
app.config['MIN_WORKBENCH'], data['version']
|
||||
),
|
||||
field_names=['version'],
|
||||
)
|
||||
|
||||
@validates_schema
|
||||
def validate_components_only_workbench(self, data: dict):
|
||||
if (data['software'] != SnapshotSoftware.Workbench) and (data['software'] != SnapshotSoftware.WorkbenchAndroid):
|
||||
if (data['software'] != SnapshotSoftware.Workbench) and (
|
||||
data['software'] != SnapshotSoftware.WorkbenchAndroid
|
||||
):
|
||||
if data.get('components', None) is not None:
|
||||
raise ValidationError('Only Workbench can add component info',
|
||||
field_names=['components'])
|
||||
raise ValidationError(
|
||||
'Only Workbench can add component info', field_names=['components']
|
||||
)
|
||||
|
||||
@validates_schema
|
||||
def validate_only_workbench_fields(self, data: dict):
|
||||
|
@ -408,22 +471,32 @@ class Snapshot(ActionWithOneDevice):
|
|||
# todo test
|
||||
if data['software'] == SnapshotSoftware.Workbench:
|
||||
if not data.get('uuid', None):
|
||||
raise ValidationError('Snapshots from Workbench and WorkbenchAndroid must have uuid',
|
||||
field_names=['uuid'])
|
||||
raise ValidationError(
|
||||
'Snapshots from Workbench and WorkbenchAndroid must have uuid',
|
||||
field_names=['uuid'],
|
||||
)
|
||||
if data.get('elapsed', None) is None:
|
||||
raise ValidationError('Snapshots from Workbench must have elapsed',
|
||||
field_names=['elapsed'])
|
||||
raise ValidationError(
|
||||
'Snapshots from Workbench must have elapsed',
|
||||
field_names=['elapsed'],
|
||||
)
|
||||
elif data['software'] == SnapshotSoftware.WorkbenchAndroid:
|
||||
if not data.get('uuid', None):
|
||||
raise ValidationError('Snapshots from Workbench and WorkbenchAndroid must have uuid',
|
||||
field_names=['uuid'])
|
||||
raise ValidationError(
|
||||
'Snapshots from Workbench and WorkbenchAndroid must have uuid',
|
||||
field_names=['uuid'],
|
||||
)
|
||||
else:
|
||||
if data.get('uuid', None):
|
||||
raise ValidationError('Only Snapshots from Workbench or WorkbenchAndroid can have uuid',
|
||||
field_names=['uuid'])
|
||||
raise ValidationError(
|
||||
'Only Snapshots from Workbench or WorkbenchAndroid can have uuid',
|
||||
field_names=['uuid'],
|
||||
)
|
||||
if data.get('elapsed', None):
|
||||
raise ValidationError('Only Snapshots from Workbench can have elapsed',
|
||||
field_names=['elapsed'])
|
||||
raise ValidationError(
|
||||
'Only Snapshots from Workbench can have elapsed',
|
||||
field_names=['elapsed'],
|
||||
)
|
||||
|
||||
|
||||
class ToRepair(ActionWithMultipleDevicesCheckingOwner):
|
||||
|
@ -440,16 +513,20 @@ class Ready(ActionWithMultipleDevicesCheckingOwner):
|
|||
|
||||
class ActionStatus(Action):
|
||||
rol_user = NestedOn(s_user.User, dump_only=True, exclude=('token',))
|
||||
devices = NestedOn(s_device.Device,
|
||||
many=True,
|
||||
required=False, # todo test ensuring len(devices) >= 1
|
||||
only_query='id',
|
||||
collection_class=OrderedSet)
|
||||
documents = NestedOn(s_document.TradeDocument,
|
||||
many=True,
|
||||
required=False, # todo test ensuring len(devices) >= 1
|
||||
only_query='id',
|
||||
collection_class=OrderedSet)
|
||||
devices = NestedOn(
|
||||
s_device.Device,
|
||||
many=True,
|
||||
required=False, # todo test ensuring len(devices) >= 1
|
||||
only_query='id',
|
||||
collection_class=OrderedSet,
|
||||
)
|
||||
documents = NestedOn(
|
||||
s_document.TradeDocument,
|
||||
many=True,
|
||||
required=False, # todo test ensuring len(devices) >= 1
|
||||
only_query='id',
|
||||
collection_class=OrderedSet,
|
||||
)
|
||||
|
||||
@pre_load
|
||||
def put_devices(self, data: dict):
|
||||
|
@ -508,20 +585,28 @@ class Live(ActionWithOneDevice):
|
|||
See docs for more info.
|
||||
"""
|
||||
uuid = UUID()
|
||||
software = EnumField(SnapshotSoftware,
|
||||
required=True,
|
||||
description='The software that generated this Snapshot.')
|
||||
software = EnumField(
|
||||
SnapshotSoftware,
|
||||
required=True,
|
||||
description='The software that generated this Snapshot.',
|
||||
)
|
||||
version = Version(required=True, description='The version of the software.')
|
||||
final_user_code = SanitizedStr(data_key="finalUserCode", dump_only=True)
|
||||
licence_version = Version(required=True, description='The version of the software.')
|
||||
components = NestedOn(s_device.Component,
|
||||
many=True,
|
||||
description='A list of components that are inside of the device'
|
||||
'at the moment of this Snapshot.'
|
||||
'Order is preserved, so the component num 0 when'
|
||||
'submitting is the component num 0 when returning it back.')
|
||||
usage_time_allocate = TimeDelta(data_key='usageTimeAllocate', required=False,
|
||||
precision=TimeDelta.HOURS, dump_only=True)
|
||||
components = NestedOn(
|
||||
s_device.Component,
|
||||
many=True,
|
||||
description='A list of components that are inside of the device'
|
||||
'at the moment of this Snapshot.'
|
||||
'Order is preserved, so the component num 0 when'
|
||||
'submitting is the component num 0 when returning it back.',
|
||||
)
|
||||
usage_time_allocate = TimeDelta(
|
||||
data_key='usageTimeAllocate',
|
||||
required=False,
|
||||
precision=TimeDelta.HOURS,
|
||||
dump_only=True,
|
||||
)
|
||||
|
||||
|
||||
class Organize(ActionWithMultipleDevices):
|
||||
|
@ -570,7 +655,7 @@ class Revoke(ActionWithMultipleDevices):
|
|||
@validates_schema
|
||||
def validate_documents(self, data):
|
||||
"""Check if there are or no one before confirmation,
|
||||
This is not checked in the view becouse the list of documents is inmutable
|
||||
This is not checked in the view becouse the list of documents is inmutable
|
||||
|
||||
"""
|
||||
if not data['devices'] == OrderedSet():
|
||||
|
@ -610,7 +695,7 @@ class ConfirmDocument(ActionWithMultipleDocuments):
|
|||
@validates_schema
|
||||
def validate_documents(self, data):
|
||||
"""If there are one device than have one confirmation,
|
||||
then remove the list this device of the list of devices of this action
|
||||
then remove the list this device of the list of devices of this action
|
||||
"""
|
||||
if data['documents'] == OrderedSet():
|
||||
return
|
||||
|
@ -636,7 +721,7 @@ class RevokeDocument(ActionWithMultipleDocuments):
|
|||
@validates_schema
|
||||
def validate_documents(self, data):
|
||||
"""Check if there are or no one before confirmation,
|
||||
This is not checked in the view becouse the list of documents is inmutable
|
||||
This is not checked in the view becouse the list of documents is inmutable
|
||||
|
||||
"""
|
||||
if data['documents'] == OrderedSet():
|
||||
|
@ -663,7 +748,7 @@ class ConfirmRevokeDocument(ActionWithMultipleDocuments):
|
|||
@validates_schema
|
||||
def validate_documents(self, data):
|
||||
"""Check if there are or no one before confirmation,
|
||||
This is not checked in the view becouse the list of documents is inmutable
|
||||
This is not checked in the view becouse the list of documents is inmutable
|
||||
|
||||
"""
|
||||
if data['documents'] == OrderedSet():
|
||||
|
@ -691,26 +776,23 @@ class Trade(ActionWithMultipleDevices):
|
|||
validate=Length(max=STR_SIZE),
|
||||
data_key='userToEmail',
|
||||
missing='',
|
||||
required=False
|
||||
required=False,
|
||||
)
|
||||
user_to = NestedOn(s_user.User, dump_only=True, data_key='userTo')
|
||||
user_from_email = SanitizedStr(
|
||||
validate=Length(max=STR_SIZE),
|
||||
data_key='userFromEmail',
|
||||
missing='',
|
||||
required=False
|
||||
required=False,
|
||||
)
|
||||
user_from = NestedOn(s_user.User, dump_only=True, data_key='userFrom')
|
||||
code = SanitizedStr(validate=Length(max=STR_SIZE), data_key='code', required=False)
|
||||
confirm = Boolean(
|
||||
data_key='confirms',
|
||||
missing=True,
|
||||
description="""If you need confirmation of the user you need actevate this field"""
|
||||
description="""If you need confirmation of the user you need actevate this field""",
|
||||
)
|
||||
lot = NestedOn('Lot',
|
||||
many=False,
|
||||
required=True,
|
||||
only_query='id')
|
||||
lot = NestedOn('Lot', many=False, required=True, only_query='id')
|
||||
|
||||
@pre_load
|
||||
def adding_devices(self, data: dict):
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
""" This is the view for Snapshots """
|
||||
|
||||
import os
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
|
||||
from flask import current_app as app, g
|
||||
from flask import current_app as app
|
||||
from flask import g
|
||||
from marshmallow import ValidationError
|
||||
from sqlalchemy.util import OrderedSet
|
||||
|
||||
from ereuse_devicehub.db import db
|
||||
from ereuse_devicehub.resources.action.models import RateComputer, Snapshot
|
||||
from ereuse_devicehub.parser.models import SnapshotErrors
|
||||
from ereuse_devicehub.resources.action.models import Snapshot
|
||||
from ereuse_devicehub.resources.device.models import Computer
|
||||
from ereuse_devicehub.resources.action.rate.v1_0 import CannotRate
|
||||
from ereuse_devicehub.resources.enums import SnapshotSoftware, Severity
|
||||
from ereuse_devicehub.resources.device.sync import Sync
|
||||
from ereuse_devicehub.resources.enums import Severity, SnapshotSoftware
|
||||
from ereuse_devicehub.resources.user.exceptions import InsufficientPermission
|
||||
|
||||
|
||||
|
@ -59,48 +62,35 @@ def move_json(tmp_snapshots, path_name, user, live=False):
|
|||
os.remove(path_name)
|
||||
|
||||
|
||||
class SnapshotView():
|
||||
"""Performs a Snapshot.
|
||||
class SnapshotMix:
|
||||
sync = Sync()
|
||||
|
||||
See `Snapshot` section in docs for more info.
|
||||
"""
|
||||
# Note that if we set the device / components into the snapshot
|
||||
# model object, when we flush them to the db we will flush
|
||||
# snapshot, and we want to wait to flush snapshot at the end
|
||||
|
||||
def __init__(self, snapshot_json: dict, resource_def, schema):
|
||||
self.schema = schema
|
||||
self.resource_def = resource_def
|
||||
self.tmp_snapshots = app.config['TMP_SNAPSHOTS']
|
||||
self.path_snapshot = save_json(snapshot_json, self.tmp_snapshots, g.user.email)
|
||||
snapshot_json.pop('debug', None)
|
||||
self.snapshot_json = resource_def.schema.load(snapshot_json)
|
||||
self.response = self.build()
|
||||
move_json(self.tmp_snapshots, self.path_snapshot, g.user.email)
|
||||
|
||||
def post(self):
|
||||
return self.response
|
||||
|
||||
def build(self):
|
||||
device = self.snapshot_json.pop('device') # type: Computer
|
||||
def build(self, snapshot_json=None): # noqa: C901
|
||||
if not snapshot_json:
|
||||
snapshot_json = self.snapshot_json
|
||||
device = snapshot_json.pop('device') # type: Computer
|
||||
components = None
|
||||
if self.snapshot_json['software'] == (SnapshotSoftware.Workbench or SnapshotSoftware.WorkbenchAndroid):
|
||||
components = self.snapshot_json.pop('components', None) # type: List[Component]
|
||||
if snapshot_json['software'] == (
|
||||
SnapshotSoftware.Workbench or SnapshotSoftware.WorkbenchAndroid
|
||||
):
|
||||
components = snapshot_json.pop('components', None) # type: List[Component]
|
||||
if isinstance(device, Computer) and device.hid:
|
||||
device.add_mac_to_hid(components_snap=components)
|
||||
snapshot = Snapshot(**self.snapshot_json)
|
||||
snapshot = Snapshot(**snapshot_json)
|
||||
|
||||
# Remove new actions from devices so they don't interfere with sync
|
||||
actions_device = set(e for e in device.actions_one)
|
||||
device.actions_one.clear()
|
||||
if components:
|
||||
actions_components = tuple(set(e for e in c.actions_one) for c in components)
|
||||
actions_components = tuple(
|
||||
set(e for e in c.actions_one) for c in components
|
||||
)
|
||||
for component in components:
|
||||
component.actions_one.clear()
|
||||
|
||||
assert not device.actions_one
|
||||
assert all(not c.actions_one for c in components) if components else True
|
||||
db_device, remove_actions = self.resource_def.sync.run(device, components)
|
||||
db_device, remove_actions = self.sync.run(device, components)
|
||||
|
||||
del device # Do not use device anymore
|
||||
snapshot.device = db_device
|
||||
|
@ -120,24 +110,49 @@ class SnapshotView():
|
|||
# Check ownership of (non-component) device to from current.user
|
||||
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)
|
||||
elif snapshot.software == SnapshotSoftware.WorkbenchAndroid:
|
||||
pass # TODO try except to compute RateMobile
|
||||
# Check if HID is null and add Severity:Warning to Snapshot
|
||||
if snapshot.device.hid is None:
|
||||
snapshot.severity = Severity.Warning
|
||||
|
||||
return snapshot
|
||||
|
||||
|
||||
class SnapshotView(SnapshotMix):
|
||||
"""Performs a Snapshot.
|
||||
|
||||
See `Snapshot` section in docs for more info.
|
||||
"""
|
||||
|
||||
# Note that if we set the device / components into the snapshot
|
||||
# model object, when we flush them to the db we will flush
|
||||
# snapshot, and we want to wait to flush snapshot at the end
|
||||
|
||||
def __init__(self, snapshot_json: dict, resource_def, schema):
|
||||
self.schema = schema
|
||||
self.resource_def = resource_def
|
||||
self.tmp_snapshots = app.config['TMP_SNAPSHOTS']
|
||||
self.path_snapshot = save_json(snapshot_json, self.tmp_snapshots, g.user.email)
|
||||
snapshot_json.pop('debug', None)
|
||||
try:
|
||||
self.snapshot_json = resource_def.schema.load(snapshot_json)
|
||||
except ValidationError as err:
|
||||
txt = "{}".format(err)
|
||||
uuid = snapshot_json.get('uuid')
|
||||
error = SnapshotErrors(
|
||||
description=txt, snapshot_uuid=uuid, severity=Severity.Error
|
||||
)
|
||||
error.save(commit=True)
|
||||
raise err
|
||||
|
||||
snapshot = self.build()
|
||||
db.session.add(snapshot)
|
||||
db.session().final_flush()
|
||||
ret = self.schema.jsonify(snapshot) # transform it back
|
||||
ret.status_code = 201
|
||||
self.response = self.schema.jsonify(snapshot) # transform it back
|
||||
self.response.status_code = 201
|
||||
db.session.commit()
|
||||
return ret
|
||||
move_json(self.tmp_snapshots, self.path_snapshot, g.user.email)
|
||||
|
||||
def post(self):
|
||||
return self.response
|
||||
|
|
|
@ -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 isinstance(json['data'], str):
|
||||
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':
|
||||
|
|
|
@ -663,6 +663,7 @@ class Computer(Device):
|
|||
db.ForeignKey(User.id),
|
||||
nullable=True)
|
||||
receiver = db.relationship(User, primaryjoin=receiver_id == User.id)
|
||||
uuid = db.Column(UUID(as_uuid=True), nullable=True)
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
if args:
|
||||
|
|
|
@ -136,6 +136,7 @@ class Computer(Device):
|
|||
owner_id = UUID(data_key='ownerID')
|
||||
transfer_state = EnumField(enums.TransferState, description=m.Computer.transfer_state.comment)
|
||||
receiver_id = UUID(data_key='receiverID')
|
||||
uuid = UUID(required=False)
|
||||
|
||||
|
||||
class Desktop(Computer):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -5,6 +5,7 @@ Use this as a starting point.
|
|||
"""
|
||||
from flask_wtf.csrf import CSRFProtect
|
||||
|
||||
from ereuse_devicehub.api.views import api
|
||||
from ereuse_devicehub.config import DevicehubConfig
|
||||
from ereuse_devicehub.devicehub import Devicehub
|
||||
from ereuse_devicehub.inventory.views import devices
|
||||
|
@ -15,6 +16,7 @@ app = Devicehub(inventory=DevicehubConfig.DB_SCHEMA)
|
|||
app.register_blueprint(core)
|
||||
app.register_blueprint(devices)
|
||||
app.register_blueprint(labels)
|
||||
app.register_blueprint(api)
|
||||
|
||||
# configure & enable CSRF of Flask-WTF
|
||||
# NOTE: enable by blueprint to exclude API views
|
||||
|
|
|
@ -33,7 +33,7 @@ SQLAlchemy==1.3.24
|
|||
SQLAlchemy-Utils==0.33.11
|
||||
teal==0.2.0a38
|
||||
webargs==5.5.3
|
||||
Werkzeug==0.15.3
|
||||
Werkzeug==0.15.5
|
||||
sqlalchemy-citext==1.3.post0
|
||||
flask-weasyprint==0.5
|
||||
weasyprint==44
|
||||
|
@ -43,3 +43,5 @@ tqdm==4.32.2
|
|||
python-decouple==3.3
|
||||
python-dotenv==0.14.0
|
||||
pyjwt==2.0.0a1
|
||||
pint==0.9
|
||||
py-dmidecode==0.1.0
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import io
|
||||
import json
|
||||
import uuid
|
||||
import jwt
|
||||
import ereuse_utils
|
||||
|
@ -22,6 +23,7 @@ from ereuse_devicehub.resources.tag import Tag
|
|||
from ereuse_devicehub.resources.user.models import User
|
||||
from ereuse_devicehub.resources.user.models import Session
|
||||
from ereuse_devicehub.resources.enums import SessionType
|
||||
from ereuse_devicehub.api.views import api
|
||||
|
||||
STARTT = datetime(year=2000, month=1, day=1, hour=1)
|
||||
"""A dummy starting time to use in tests."""
|
||||
|
@ -68,6 +70,7 @@ def app(request, _app: Devicehub) -> Devicehub:
|
|||
tag_token=uuid.UUID('52dacef0-6bcb-4919-bfed-f10d2c96ecee'),
|
||||
erase=False,
|
||||
common=True)
|
||||
_app.register_blueprint(api)
|
||||
|
||||
with _app.app_context():
|
||||
try:
|
||||
|
@ -166,6 +169,11 @@ def file(name: str) -> dict:
|
|||
return json_encode(yaml2json(name))
|
||||
|
||||
|
||||
def file_json(name):
|
||||
with Path(__file__).parent.joinpath('files').joinpath(name).open() as f:
|
||||
return json.loads(f.read())
|
||||
|
||||
|
||||
def file_workbench(name: str) -> dict:
|
||||
"""Opens and parses a YAML file from the ``files`` subdir."""
|
||||
with Path(__file__).parent.joinpath('workbench_files').joinpath(name + '.json').open() as f:
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,2 +1,2 @@
|
|||
Type,Chassis,Serial Number,Model,Manufacturer,Registered in,Physical state,Trading state,Price,Processor,RAM (MB),Data Storage Size (MB),Rate,Range,Processor Rate,Processor Range,RAM Rate,RAM Range,Data Storage Rate,Data Storage Range
|
||||
Desktop,Microtower,d1s,d1ml,d1mr,Tue Jul 2 10:35:10 2019,,,,p1ml,0,0,1.0,Very low,1.0,Very low,1.0,Very low,1.0,Very low
|
||||
Type;Chassis;Serial Number;Model;Manufacturer;Registered in;Physical state;Trading state;Price;Processor;RAM (MB);Data Storage Size (MB)
|
||||
Desktop;Microtower;d1s;d1ml;d1mr;Tue Mar 29 18:13:05 2022;;;;p1ml;0;0
|
||||
|
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
3379
tests/files/snapshot-error-timestamp.json
Normal file
3379
tests/files/snapshot-error-timestamp.json
Normal file
File diff suppressed because one or more lines are too long
1
tests/files/snapshotErrors.json
Normal file
1
tests/files/snapshotErrors.json
Normal file
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
|||
{"device": {"dataStorageSize": 99, "serialNumber": "02:00:00:00:00:00", "model": "Motorola One Vision", "type": "Mobile", "ramSize": 31138, "displaySize": 9, "manufacturer": "Motorola"}, "software": "WorkbenchAndroid", "type": "Snapshot", "uuid": "958d697f-af34-4410-85d6-adb906d46161", "version": "0.0.2"}
|
|
@ -30,6 +30,7 @@ def test_api_docs(client: Client):
|
|||
assert set(docs['paths'].keys()) == {
|
||||
'/actions/',
|
||||
'/apidocs',
|
||||
'/api/inventory/',
|
||||
'/allocates/',
|
||||
'/deallocates/',
|
||||
'/deliverynotes/',
|
||||
|
|
|
@ -130,6 +130,7 @@ def test_physical_properties():
|
|||
'model': 'foo',
|
||||
'receiver_id': None,
|
||||
'serial_number': 'foo-bar',
|
||||
'uuid': None,
|
||||
'transfer_state': TransferState.Initial
|
||||
}
|
||||
|
||||
|
@ -480,7 +481,7 @@ def test_get_device_permissions(app: Devicehub, user: UserClient, user2: UserCli
|
|||
s, _ = user.post(file('asus-eee-1000h.snapshot.11'), res=m.Snapshot)
|
||||
pc, res = user.get(res=d.Device, item=s['device']['devicehubID'])
|
||||
assert res.status_code == 200
|
||||
assert len(pc['actions']) == 9
|
||||
assert len(pc['actions']) == 7
|
||||
|
||||
html, _ = client.get(res=d.Device, item=s['device']['devicehubID'], accept=ANY)
|
||||
assert 'intel atom cpu n270 @ 1.60ghz' in html
|
||||
|
|
|
@ -181,7 +181,7 @@ def test_device_query(user: UserClient):
|
|||
assert i['url'] == '/devices/'
|
||||
assert i['items'][0]['url'] == '/devices/%s' % snapshot['device']['devicehubID']
|
||||
pc = next(d for d in i['items'] if d['type'] == 'Desktop')
|
||||
assert len(pc['actions']) == 4
|
||||
assert len(pc['actions']) == 3
|
||||
assert len(pc['components']) == 3
|
||||
assert pc['tags'][0]['id'] == pc['devicehubID']
|
||||
|
||||
|
|
|
@ -337,6 +337,7 @@ def test_export_computer_monitor(user: UserClient):
|
|||
f = StringIO(csv_str)
|
||||
obj_csv = csv.reader(f, f)
|
||||
export_csv = list(obj_csv)
|
||||
|
||||
# Open fixture csv and transform to list
|
||||
with Path(__file__).parent.joinpath('files').joinpath('computer-monitor.csv').open() \
|
||||
as csv_file:
|
||||
|
@ -435,12 +436,14 @@ def test_report_devices_stock_control(user: UserClient, user2: UserClient):
|
|||
'Register in field is not a datetime'
|
||||
|
||||
# Pop dates fields from csv lists to compare them
|
||||
fixture_csv[1] = fixture_csv[1][0].split(";")
|
||||
fixture_csv[1] = fixture_csv[1][:5] + fixture_csv[1][6:]
|
||||
export_csv[1] = export_csv[1][:5] + export_csv[1][6:]
|
||||
|
||||
assert fixture_csv[0] == export_csv[0], 'Headers are not equal'
|
||||
export_header = [";".join(export_csv[0])]
|
||||
assert fixture_csv[0] == export_header, 'Headers are not equal'
|
||||
assert fixture_csv[1] == export_csv[1], 'Computer information are not equal'
|
||||
assert fixture_csv == export_csv
|
||||
assert fixture_csv == [export_header, export_csv[1]]
|
||||
|
||||
|
||||
@pytest.mark.mvp
|
||||
|
|
|
@ -1,38 +1,49 @@
|
|||
import os
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import pytest
|
||||
import uuid
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from requests.exceptions import HTTPError
|
||||
from operator import itemgetter
|
||||
from pathlib import Path
|
||||
from typing import List, Tuple
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
from boltons import urlutils
|
||||
from teal.db import UniqueViolation, DBError
|
||||
from teal.marshmallow import ValidationError
|
||||
from ereuse_utils.test import ANY
|
||||
from requests.exceptions import HTTPError
|
||||
from teal.db import DBError, UniqueViolation
|
||||
from teal.marshmallow import ValidationError
|
||||
|
||||
from ereuse_devicehub.client import UserClient
|
||||
from ereuse_devicehub.db import db
|
||||
from ereuse_devicehub.devicehub import Devicehub
|
||||
from ereuse_devicehub.resources.action.models import Action, BenchmarkDataStorage, \
|
||||
BenchmarkProcessor, EraseSectors, RateComputer, Snapshot, SnapshotRequest, VisualTest, \
|
||||
EreusePrice, Ready
|
||||
from ereuse_devicehub.parser.models import SnapshotErrors
|
||||
from ereuse_devicehub.resources.action.models import (
|
||||
Action,
|
||||
BenchmarkDataStorage,
|
||||
BenchmarkProcessor,
|
||||
EraseSectors,
|
||||
EreusePrice,
|
||||
Ready,
|
||||
Snapshot,
|
||||
SnapshotRequest,
|
||||
VisualTest,
|
||||
)
|
||||
from ereuse_devicehub.resources.action.views.snapshot import save_json
|
||||
from ereuse_devicehub.resources.device import models as m
|
||||
from ereuse_devicehub.resources.device.exceptions import NeedsId
|
||||
from ereuse_devicehub.resources.device.models import SolidStateDrive
|
||||
from ereuse_devicehub.resources.device.sync import MismatchBetweenProperties, \
|
||||
MismatchBetweenTagsAndHid
|
||||
from ereuse_devicehub.resources.device.sync import (
|
||||
MismatchBetweenProperties,
|
||||
MismatchBetweenTagsAndHid,
|
||||
)
|
||||
from ereuse_devicehub.resources.documents import documents
|
||||
from ereuse_devicehub.resources.enums import ComputerChassis, SnapshotSoftware
|
||||
from ereuse_devicehub.resources.tag import Tag
|
||||
from ereuse_devicehub.resources.user.models import User
|
||||
from ereuse_devicehub.resources.action.views.snapshot import save_json
|
||||
from ereuse_devicehub.resources.documents import documents
|
||||
from tests.conftest import file, yaml2json, json_encode
|
||||
from tests import conftest
|
||||
from tests.conftest import file, file_json, json_encode, yaml2json
|
||||
|
||||
|
||||
@pytest.mark.mvp
|
||||
|
@ -43,18 +54,22 @@ def test_snapshot_model():
|
|||
"""
|
||||
device = m.Desktop(serial_number='a1', chassis=ComputerChassis.Tower)
|
||||
# noinspection PyArgumentList
|
||||
snapshot = Snapshot(uuid=uuid4(),
|
||||
end_time=datetime.now(timezone.utc),
|
||||
version='1.0',
|
||||
software=SnapshotSoftware.DesktopApp,
|
||||
elapsed=timedelta(seconds=25))
|
||||
snapshot = Snapshot(
|
||||
uuid=uuid4(),
|
||||
end_time=datetime.now(timezone.utc),
|
||||
version='1.0',
|
||||
software=SnapshotSoftware.DesktopApp,
|
||||
elapsed=timedelta(seconds=25),
|
||||
)
|
||||
snapshot.device = device
|
||||
snapshot.request = SnapshotRequest(request={'foo': 'bar'})
|
||||
db.session.add(snapshot)
|
||||
db.session.commit()
|
||||
device = m.Desktop.query.one() # type: m.Desktop
|
||||
e1 = device.actions[0]
|
||||
assert isinstance(e1, Snapshot), 'Creation order must be preserved: 1. snapshot, 2. WR'
|
||||
assert isinstance(
|
||||
e1, Snapshot
|
||||
), 'Creation order must be preserved: 1. snapshot, 2. WR'
|
||||
db.session.delete(device)
|
||||
db.session.commit()
|
||||
assert Snapshot.query.one_or_none() is None
|
||||
|
@ -63,7 +78,9 @@ def test_snapshot_model():
|
|||
assert m.Desktop.query.one_or_none() is None
|
||||
assert m.Device.query.one_or_none() is None
|
||||
# Check properties
|
||||
assert device.url == urlutils.URL('http://localhost/devices/%s' % device.devicehub_id)
|
||||
assert device.url == urlutils.URL(
|
||||
'http://localhost/devices/%s' % device.devicehub_id
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.mvp
|
||||
|
@ -78,13 +95,12 @@ def test_snapshot_post(user: UserClient):
|
|||
"""Tests the post snapshot endpoint (validation, etc), data correctness,
|
||||
and relationship correctness.
|
||||
"""
|
||||
snapshot = snapshot_and_check(user, yaml2json('basic.snapshot'),
|
||||
action_types=(
|
||||
BenchmarkProcessor.t,
|
||||
VisualTest.t,
|
||||
RateComputer.t
|
||||
),
|
||||
perform_second_snapshot=False)
|
||||
snapshot = snapshot_and_check(
|
||||
user,
|
||||
yaml2json('basic.snapshot'),
|
||||
action_types=(BenchmarkProcessor.t, VisualTest.t),
|
||||
perform_second_snapshot=False,
|
||||
)
|
||||
assert snapshot['software'] == 'Workbench'
|
||||
assert snapshot['version'] == '11.0'
|
||||
assert snapshot['uuid'] == 'f5efd26e-8754-46bc-87bf-fbccc39d60d9'
|
||||
|
@ -98,14 +114,11 @@ def test_snapshot_post(user: UserClient):
|
|||
device['components'].sort(key=key)
|
||||
assert snapshot['components'] == device['components']
|
||||
|
||||
assert {c['type'] for c in snapshot['components']} == {m.GraphicCard.t, m.RamModule.t,
|
||||
m.Processor.t}
|
||||
rate = next(e for e in snapshot['actions'] if e['type'] == RateComputer.t)
|
||||
rate, _ = user.get(res=Action, item=rate['id'])
|
||||
assert rate['device']['id'] == snapshot['device']['id']
|
||||
rate['components'].sort(key=key)
|
||||
assert rate['components'] == snapshot['components']
|
||||
assert rate['snapshot']['id'] == snapshot['id']
|
||||
assert {c['type'] for c in snapshot['components']} == {
|
||||
m.GraphicCard.t,
|
||||
m.RamModule.t,
|
||||
m.Processor.t,
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.mvp
|
||||
|
@ -127,20 +140,26 @@ def test_same_device_tow_users(user: UserClient, user2: UserClient):
|
|||
assert pc['ownerID'] != pc2['ownerID']
|
||||
assert pc['hid'] == pc2['hid']
|
||||
|
||||
|
||||
@pytest.mark.mvp
|
||||
def test_snapshot_update_timefield_updated(user: UserClient):
|
||||
"""
|
||||
Tests for check if one computer have the time mark updated when one component of it is updated
|
||||
"""
|
||||
computer1 = yaml2json('1-device-with-components.snapshot')
|
||||
snapshot = snapshot_and_check(user,
|
||||
computer1,
|
||||
action_types=(BenchmarkProcessor.t,
|
||||
RateComputer.t),
|
||||
perform_second_snapshot=False)
|
||||
snapshot = snapshot_and_check(
|
||||
user,
|
||||
computer1,
|
||||
action_types=(BenchmarkProcessor.t,),
|
||||
perform_second_snapshot=False,
|
||||
)
|
||||
computer2 = yaml2json('2-second-device-with-components-of-first.snapshot')
|
||||
snapshot_and_check(user, computer2, action_types=('Remove', 'RateComputer'),
|
||||
perform_second_snapshot=False)
|
||||
snapshot_and_check(
|
||||
user,
|
||||
computer2,
|
||||
action_types=('Remove',),
|
||||
perform_second_snapshot=False,
|
||||
)
|
||||
pc1_devicehub_id = snapshot['device']['devicehubID']
|
||||
pc1, _ = user.get(res=m.Device, item=pc1_devicehub_id)
|
||||
assert pc1['updated'] != snapshot['device']['updated']
|
||||
|
@ -165,7 +184,10 @@ def test_snapshot_power_on_hours(user: UserClient):
|
|||
test_data_storage = ac
|
||||
break
|
||||
|
||||
assert test_data_storage.lifetime.total_seconds()/3600 == test_data_storage.power_on_hours
|
||||
assert (
|
||||
test_data_storage.lifetime.total_seconds() / 3600
|
||||
== test_data_storage.power_on_hours
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.mvp
|
||||
|
@ -176,10 +198,7 @@ def test_snapshot_component_add_remove(user: UserClient):
|
|||
|
||||
def get_actions_info(actions: List[dict]) -> tuple:
|
||||
return tuple(
|
||||
(
|
||||
e['type'],
|
||||
[c['serialNumber'] for c in e['components']]
|
||||
)
|
||||
(e['type'], [c['serialNumber'] for c in e['components']])
|
||||
for e in user.get_many(res=Action, resources=actions, key='id')
|
||||
)
|
||||
|
||||
|
@ -198,15 +217,19 @@ def test_snapshot_component_add_remove(user: UserClient):
|
|||
pc1, _ = user.get(res=m.Device, item=pc1_devicehub_id)
|
||||
update1_pc1 = pc1['updated']
|
||||
# Parent contains components
|
||||
assert tuple(c['serialNumber'] for c in pc1['components']) == ('p1c1s', 'p1c2s', 'p1c3s')
|
||||
assert tuple(c['serialNumber'] for c in pc1['components']) == (
|
||||
'p1c1s',
|
||||
'p1c2s',
|
||||
'p1c3s',
|
||||
)
|
||||
# Components contain parent
|
||||
assert all(c['parent'] == pc1_id for c in pc1['components'])
|
||||
# pc has three actions: Snapshot, BenchmarkProcessor and RateComputer
|
||||
assert len(pc1['actions']) == 3
|
||||
assert len(pc1['actions']) == 2
|
||||
assert pc1['actions'][1]['type'] == Snapshot.t
|
||||
# p1c1s has Snapshot
|
||||
p1c1s, _ = user.get(res=m.Device, item=pc1['components'][0]['devicehubID'])
|
||||
assert tuple(e['type'] for e in p1c1s['actions']) == ('Snapshot', 'RateComputer')
|
||||
assert tuple(e['type'] for e in p1c1s['actions']) == ('Snapshot',)
|
||||
|
||||
# We register a new device
|
||||
# It has the processor of the first one (p1c2s)
|
||||
|
@ -228,23 +251,32 @@ def test_snapshot_component_add_remove(user: UserClient):
|
|||
# PC1
|
||||
assert tuple(c['serialNumber'] for c in pc1['components']) == ('p1c1s', 'p1c3s')
|
||||
assert all(c['parent'] == pc1_id for c in pc1['components'])
|
||||
assert tuple(e['type'] for e in pc1['actions']) == ('BenchmarkProcessor', 'Snapshot', 'RateComputer', 'Remove')
|
||||
assert tuple(e['type'] for e in pc1['actions']) == (
|
||||
'BenchmarkProcessor',
|
||||
'Snapshot',
|
||||
'Remove',
|
||||
)
|
||||
# PC2
|
||||
assert tuple(c['serialNumber'] for c in pc2['components']) == ('p1c2s', 'p2c1s')
|
||||
assert all(c['parent'] == pc2_id for c in pc2['components'])
|
||||
assert tuple(e['type'] for e in pc2['actions']) == ('Snapshot', 'RateComputer')
|
||||
assert tuple(e['type'] for e in pc2['actions']) == ('Snapshot',)
|
||||
# p1c2s has two Snapshots, a Remove and an Add
|
||||
p1c2s, _ = user.get(res=m.Device, item=pc2['components'][0]['devicehubID'])
|
||||
assert tuple(e['type'] for e in p1c2s['actions']) == (
|
||||
'BenchmarkProcessor', 'Snapshot', 'RateComputer', 'Snapshot', 'Remove', 'RateComputer'
|
||||
'BenchmarkProcessor',
|
||||
'Snapshot',
|
||||
'Snapshot',
|
||||
'Remove',
|
||||
)
|
||||
|
||||
# We register the first device again, but removing motherboard
|
||||
# and moving processor from the second device to the first.
|
||||
# We have created 1 Remove (from PC2's processor back to PC1)
|
||||
# PC 0: p1c2s, p1c3s. PC 1: p2c1s
|
||||
s3 = yaml2json('3-first-device-but-removing-motherboard-and-adding-processor-from-2.snapshot')
|
||||
snapshot_and_check(user, s3, ('Remove', 'RateComputer'), perform_second_snapshot=False)
|
||||
s3 = yaml2json(
|
||||
'3-first-device-but-removing-motherboard-and-adding-processor-from-2.snapshot'
|
||||
)
|
||||
snapshot_and_check(user, s3, ('Remove',), perform_second_snapshot=False)
|
||||
pc1, _ = user.get(res=m.Device, item=pc1_devicehub_id)
|
||||
pc2, _ = user.get(res=m.Device, item=pc2_devicehub_id)
|
||||
# Check if the update_timestamp is updated
|
||||
|
@ -260,54 +292,49 @@ def test_snapshot_component_add_remove(user: UserClient):
|
|||
# id, type, components, snapshot
|
||||
('BenchmarkProcessor', []), # first BenchmarkProcessor
|
||||
('Snapshot', ['p1c1s', 'p1c2s', 'p1c3s']), # first Snapshot1
|
||||
('RateComputer', ['p1c1s', 'p1c2s', 'p1c3s']),
|
||||
('Remove', ['p1c2s']), # Remove Processor in Snapshot2
|
||||
('Snapshot', ['p1c2s', 'p1c3s']), # This Snapshot3
|
||||
('RateComputer', ['p1c2s', 'p1c3s'])
|
||||
)
|
||||
# PC2
|
||||
assert tuple(c['serialNumber'] for c in pc2['components']) == ('p2c1s',)
|
||||
assert all(c['parent'] == pc2_id for c in pc2['components'])
|
||||
assert tuple(e['type'] for e in pc2['actions']) == (
|
||||
'Snapshot', # Second Snapshot
|
||||
'RateComputer',
|
||||
'Remove' # the processor we added in 2.
|
||||
'Remove', # the processor we added in 2.
|
||||
)
|
||||
# p1c2s has Snapshot, Remove and Add
|
||||
p1c2s, _ = user.get(res=m.Device, item=pc1['components'][0]['devicehubID'])
|
||||
assert tuple(get_actions_info(p1c2s['actions'])) == (
|
||||
('BenchmarkProcessor', []), # first BenchmarkProcessor
|
||||
('Snapshot', ['p1c1s', 'p1c2s', 'p1c3s']), # First Snapshot to PC1
|
||||
('RateComputer', ['p1c1s', 'p1c2s', 'p1c3s']),
|
||||
('Snapshot', ['p1c2s', 'p2c1s']), # Second Snapshot to PC2
|
||||
('Remove', ['p1c2s']), # ...which caused p1c2s to be removed form PC1
|
||||
('RateComputer', ['p1c2s', 'p2c1s']),
|
||||
('Snapshot', ['p1c2s', 'p1c3s']), # The third Snapshot to PC1
|
||||
('Remove', ['p1c2s']), # ...which caused p1c2 to be removed from PC2
|
||||
('RateComputer', ['p1c2s', 'p1c3s'])
|
||||
)
|
||||
|
||||
# We register the first device but without the processor,
|
||||
# adding a graphic card and adding a new component
|
||||
s4 = yaml2json('4-first-device-but-removing-processor.snapshot-and-adding-graphic-card')
|
||||
snapshot4 = snapshot_and_check(user, s4, ('RateComputer',), perform_second_snapshot=False)
|
||||
s4 = yaml2json(
|
||||
'4-first-device-but-removing-processor.snapshot-and-adding-graphic-card'
|
||||
)
|
||||
pc1, _ = user.get(res=m.Device, item=pc1_devicehub_id)
|
||||
pc2, _ = user.get(res=m.Device, item=pc2_devicehub_id)
|
||||
# Check if the update_timestamp is updated
|
||||
update3_pc2 = pc2['updated']
|
||||
update4_pc1 = pc1['updated']
|
||||
assert not update4_pc1 in [update1_pc1, update2_pc1, update3_pc1]
|
||||
assert update4_pc1 in [update1_pc1, update2_pc1, update3_pc1]
|
||||
assert update3_pc2 == update2_pc2
|
||||
# PC 0: p1c3s, p1c4s. PC1: p2c1s
|
||||
assert {c['serialNumber'] for c in pc1['components']} == {'p1c3s'}
|
||||
assert {c['serialNumber'] for c in pc1['components']} == {'p1c2s', 'p1c3s'}
|
||||
assert all(c['parent'] == pc1_id for c in pc1['components'])
|
||||
# This last Action only
|
||||
assert get_actions_info(pc1['actions'])[-1] == ('RateComputer', ['p1c3s'])
|
||||
# PC2
|
||||
# We haven't changed PC2
|
||||
assert tuple(c['serialNumber'] for c in pc2['components']) == ('p2c1s',)
|
||||
assert all(c['parent'] == pc2_id for c in pc2['components'])
|
||||
|
||||
|
||||
@pytest.mark.mvp
|
||||
def test_snapshot_post_without_hid(user: UserClient):
|
||||
"""Tests the post snapshot endpoint (validation, etc), data correctness,
|
||||
|
@ -338,15 +365,16 @@ def test_snapshot_tag_inner_tag(user: UserClient, tag_id: str, app: Devicehub):
|
|||
b = yaml2json('basic.snapshot')
|
||||
b['device']['tags'] = [{'type': 'Tag', 'id': tag_id}]
|
||||
|
||||
snapshot_and_check(user, b,
|
||||
action_types=(RateComputer.t, BenchmarkProcessor.t, VisualTest.t))
|
||||
snapshot_and_check(user, b, action_types=(BenchmarkProcessor.t, VisualTest.t))
|
||||
with app.app_context():
|
||||
tag = Tag.query.all()[0] # type: Tag
|
||||
assert tag.device_id == 3, 'Tag should be linked to the first device'
|
||||
|
||||
|
||||
@pytest.mark.mvp
|
||||
def test_snapshot_tag_inner_tag_mismatch_between_tags_and_hid(user: UserClient, tag_id: str):
|
||||
def test_snapshot_tag_inner_tag_mismatch_between_tags_and_hid(
|
||||
user: UserClient, tag_id: str
|
||||
):
|
||||
"""Ensures one device cannot 'steal' the tag from another one."""
|
||||
pc1 = yaml2json('basic.snapshot')
|
||||
pc1['device']['tags'] = [{'type': 'Tag', 'id': tag_id}]
|
||||
|
@ -396,7 +424,7 @@ def test_snapshot_component_containing_components(user: UserClient):
|
|||
'type': 'Processor',
|
||||
'serialNumber': 'foo',
|
||||
'manufacturer': 'bar',
|
||||
'model': 'baz'
|
||||
'model': 'baz',
|
||||
}
|
||||
user.post(json_encode(s), res=Snapshot, status=ValidationError)
|
||||
|
||||
|
@ -435,13 +463,15 @@ def test_not_remove_ram_in_same_computer(user: UserClient):
|
|||
snap1, _ = user.post(json_encode(s), res=Snapshot)
|
||||
|
||||
s['uuid'] = '74caa7eb-2bad-4333-94f6-6f1b031d0774'
|
||||
s['components'].append({
|
||||
"actions": [],
|
||||
"manufacturer": "Intel Corporation",
|
||||
"model": "NM10/ICH7 Family High Definition Audio Controller",
|
||||
"serialNumber": "mp2pc",
|
||||
"type": "SoundCard"
|
||||
})
|
||||
s['components'].append(
|
||||
{
|
||||
"actions": [],
|
||||
"manufacturer": "Intel Corporation",
|
||||
"model": "NM10/ICH7 Family High Definition Audio Controller",
|
||||
"serialNumber": "mp2pc",
|
||||
"type": "SoundCard",
|
||||
}
|
||||
)
|
||||
dev1 = m.Device.query.filter_by(id=snap1['device']['id']).one()
|
||||
ram1 = [x.id for x in dev1.components if x.type == 'RamModule'][0]
|
||||
snap2, _ = user.post(json_encode(s), res=Snapshot)
|
||||
|
@ -455,28 +485,6 @@ def test_not_remove_ram_in_same_computer(user: UserClient):
|
|||
assert dev1.components == dev2.components
|
||||
|
||||
|
||||
@pytest.mark.mvp
|
||||
def test_ereuse_price(user: UserClient):
|
||||
"""Tests a Snapshot with EraseSectors and the resulting privacy
|
||||
properties.
|
||||
|
||||
This tests ensures that only the last erasure is picked up, as
|
||||
erasures have always custom endTime value set.
|
||||
"""
|
||||
s = yaml2json('erase-sectors.snapshot')
|
||||
assert s['components'][0]['actions'][0]['endTime'] == '2018-06-01T09:12:06+02:00'
|
||||
s['device']['type'] = 'Server'
|
||||
snapshot = snapshot_and_check(user, s, action_types=(
|
||||
EraseSectors.t,
|
||||
BenchmarkDataStorage.t,
|
||||
BenchmarkProcessor.t,
|
||||
RateComputer.t,
|
||||
EreusePrice.t
|
||||
), perform_second_snapshot=False)
|
||||
ereuse_price = snapshot['actions'][-1]
|
||||
assert len(ereuse_price) > 0
|
||||
|
||||
|
||||
@pytest.mark.mvp
|
||||
def test_erase_privacy_standards_endtime_sort(user: UserClient):
|
||||
"""Tests a Snapshot with EraseSectors and the resulting privacy
|
||||
|
@ -487,33 +495,48 @@ def test_erase_privacy_standards_endtime_sort(user: UserClient):
|
|||
"""
|
||||
s = yaml2json('erase-sectors.snapshot')
|
||||
assert s['components'][0]['actions'][0]['endTime'] == '2018-06-01T09:12:06+02:00'
|
||||
snapshot = snapshot_and_check(user, s, action_types=(
|
||||
EraseSectors.t,
|
||||
BenchmarkDataStorage.t,
|
||||
BenchmarkProcessor.t,
|
||||
RateComputer.t,
|
||||
EreusePrice.t
|
||||
), perform_second_snapshot=False)
|
||||
snapshot = snapshot_and_check(
|
||||
user,
|
||||
s,
|
||||
action_types=(
|
||||
EraseSectors.t,
|
||||
BenchmarkDataStorage.t,
|
||||
BenchmarkProcessor.t,
|
||||
),
|
||||
perform_second_snapshot=False,
|
||||
)
|
||||
# Perform a new snapshot changing the erasure time, as if
|
||||
# it is a new erasure performed after.
|
||||
erase = next(e for e in snapshot['actions'] if e['type'] == EraseSectors.t)
|
||||
assert erase['endTime'] == '2018-06-01T07:12:06+00:00'
|
||||
s['uuid'] = uuid4()
|
||||
s['components'][0]['actions'][0]['endTime'] = '2018-06-01T07:14:00+00:00'
|
||||
snapshot = snapshot_and_check(user, s, action_types=(
|
||||
EraseSectors.t,
|
||||
BenchmarkDataStorage.t,
|
||||
BenchmarkProcessor.t,
|
||||
RateComputer.t,
|
||||
EreusePrice.t
|
||||
), perform_second_snapshot=False)
|
||||
snapshot = snapshot_and_check(
|
||||
user,
|
||||
s,
|
||||
action_types=(
|
||||
EraseSectors.t,
|
||||
BenchmarkDataStorage.t,
|
||||
BenchmarkProcessor.t,
|
||||
),
|
||||
perform_second_snapshot=False,
|
||||
)
|
||||
|
||||
# The actual test
|
||||
storage = next(e for e in snapshot['components'] if e['type'] == SolidStateDrive.t)
|
||||
storage, _ = user.get(res=m.Device, item=storage['devicehubID']) # Let's get storage actions too
|
||||
storage, _ = user.get(
|
||||
res=m.Device, item=storage['devicehubID']
|
||||
) # Let's get storage actions too
|
||||
# order: endTime ascending
|
||||
# erasure1/2 have an user defined time and others actions endTime = created
|
||||
erasure1, erasure2, benchmark_hdd1, _snapshot1, _, _, benchmark_hdd2, _snapshot2 = storage['actions'][:8]
|
||||
(
|
||||
erasure1,
|
||||
erasure2,
|
||||
benchmark_hdd1,
|
||||
_snapshot1,
|
||||
benchmark_hdd2,
|
||||
_snapshot2,
|
||||
) = storage['actions'][:8]
|
||||
assert erasure1['type'] == erasure2['type'] == 'EraseSectors'
|
||||
assert benchmark_hdd1['type'] == benchmark_hdd2['type'] == 'BenchmarkDataStorage'
|
||||
assert _snapshot1['type'] == _snapshot2['type'] == 'Snapshot'
|
||||
|
@ -555,8 +578,7 @@ def test_test_data_storage(user: UserClient):
|
|||
s = file('erase-sectors-2-hdd.snapshot')
|
||||
snapshot, _ = user.post(res=Snapshot, data=s)
|
||||
incidence_test = next(
|
||||
ev for ev in snapshot['actions']
|
||||
if ev.get('reallocatedSectorCount', None) == 15
|
||||
ev for ev in snapshot['actions'] if ev.get('reallocatedSectorCount', None) == 15
|
||||
)
|
||||
assert incidence_test['severity'] == 'Error'
|
||||
|
||||
|
@ -584,22 +606,24 @@ def assert_similar_components(components1: List[dict], components2: List[dict]):
|
|||
assert_similar_device(c1, c2)
|
||||
|
||||
|
||||
def snapshot_and_check(user: UserClient,
|
||||
input_snapshot: dict,
|
||||
action_types: Tuple[str, ...] = tuple(),
|
||||
perform_second_snapshot=True) -> dict:
|
||||
def snapshot_and_check(
|
||||
user: UserClient,
|
||||
input_snapshot: dict,
|
||||
action_types: Tuple[str, ...] = tuple(),
|
||||
perform_second_snapshot=True,
|
||||
) -> dict:
|
||||
"""Performs a Snapshot and then checks if the result is ok:
|
||||
|
||||
- There have been performed the types of actions and in the same
|
||||
order as described in the passed-in ``action_types``.
|
||||
- The inputted devices are similar to the resulted ones.
|
||||
- There is no Remove action after the first Add.
|
||||
- All input components are now inside the parent device.
|
||||
- There have been performed the types of actions and in the same
|
||||
order as described in the passed-in ``action_types``.
|
||||
- The inputted devices are similar to the resulted ones.
|
||||
- There is no Remove action after the first Add.
|
||||
- All input components are now inside the parent device.
|
||||
|
||||
Optionally, it can perform a second Snapshot which should
|
||||
perform an exact result, except for the actions.
|
||||
Optionally, it can perform a second Snapshot which should
|
||||
perform an exact result, except for the actions.
|
||||
|
||||
:return: The last resulting snapshot.
|
||||
:return: The last resulting snapshot.
|
||||
"""
|
||||
snapshot, _ = user.post(res=Snapshot, data=json_encode(input_snapshot))
|
||||
assert all(e['type'] in action_types for e in snapshot['actions'])
|
||||
|
@ -610,18 +634,22 @@ def snapshot_and_check(user: UserClient,
|
|||
if action['type'] == 'Add':
|
||||
found_add = True
|
||||
if found_add:
|
||||
assert action['type'] != 'Receive', 'All Remove actions must be before the Add ones'
|
||||
assert (
|
||||
action['type'] != 'Receive'
|
||||
), 'All Remove actions must be before the Add ones'
|
||||
assert input_snapshot['device']
|
||||
assert_similar_device(input_snapshot['device'], snapshot['device'])
|
||||
if input_snapshot.get('components', None):
|
||||
assert_similar_components(input_snapshot['components'], snapshot['components'])
|
||||
assert all(c['parent'] == snapshot['device']['id'] for c in snapshot['components']), \
|
||||
'Components must be in their parent'
|
||||
assert all(
|
||||
c['parent'] == snapshot['device']['id'] for c in snapshot['components']
|
||||
), 'Components must be in their parent'
|
||||
if perform_second_snapshot:
|
||||
if 'uuid' in input_snapshot:
|
||||
input_snapshot['uuid'] = uuid4()
|
||||
return snapshot_and_check(user, input_snapshot, action_types,
|
||||
perform_second_snapshot=False)
|
||||
return snapshot_and_check(
|
||||
user, input_snapshot, action_types, perform_second_snapshot=False
|
||||
)
|
||||
else:
|
||||
return snapshot
|
||||
|
||||
|
@ -642,12 +670,12 @@ def test_erase_changing_hdd_between_pcs(user: UserClient):
|
|||
db.session.commit()
|
||||
|
||||
assert dev2.components[1].actions[2].parent == dev1
|
||||
doc1, response = user.get(res=documents.DocumentDef.t,
|
||||
item='erasures/{}'.format(dev1.id),
|
||||
accept=ANY)
|
||||
doc2, response = user.get(res=documents.DocumentDef.t,
|
||||
item='erasures/{}'.format(dev2.id),
|
||||
accept=ANY)
|
||||
doc1, response = user.get(
|
||||
res=documents.DocumentDef.t, item='erasures/{}'.format(dev1.id), accept=ANY
|
||||
)
|
||||
doc2, response = user.get(
|
||||
res=documents.DocumentDef.t, item='erasures/{}'.format(dev2.id), accept=ANY
|
||||
)
|
||||
assert 'dev1' in doc2
|
||||
assert 'dev2' in doc2
|
||||
|
||||
|
@ -667,10 +695,9 @@ def test_pc_2(user: UserClient):
|
|||
snapshot, _ = user.post(res=Snapshot, data=s)
|
||||
|
||||
|
||||
|
||||
@pytest.mark.mvp
|
||||
def test_save_snapshot_in_file(app: Devicehub, user: UserClient):
|
||||
""" This test check if works the function save_snapshot_in_file """
|
||||
"""This test check if works the function save_snapshot_in_file"""
|
||||
snapshot_no_hid = yaml2json('basic.snapshot.nohid')
|
||||
tmp_snapshots = app.config['TMP_SNAPSHOTS']
|
||||
path_dir_base = os.path.join(tmp_snapshots, user.user['email'], 'errors')
|
||||
|
@ -696,7 +723,7 @@ def test_save_snapshot_in_file(app: Devicehub, user: UserClient):
|
|||
|
||||
@pytest.mark.mvp
|
||||
def test_action_no_snapshot_without_save_file(app: Devicehub, user: UserClient):
|
||||
""" This test check if the function save_snapshot_in_file not work when we
|
||||
"""This test check if the function save_snapshot_in_file not work when we
|
||||
send one other action different to snapshot
|
||||
"""
|
||||
s = file('laptop-hp_255_g3_notebook-hewlett-packard-cnd52270fw.snapshot')
|
||||
|
@ -712,9 +739,10 @@ def test_action_no_snapshot_without_save_file(app: Devicehub, user: UserClient):
|
|||
|
||||
assert os.path.exists(tmp_snapshots) == False
|
||||
|
||||
|
||||
@pytest.mark.mvp
|
||||
def test_save_snapshot_with_debug(app: Devicehub, user: UserClient):
|
||||
""" This test check if works the function save_snapshot_in_file """
|
||||
"""This test check if works the function save_snapshot_in_file"""
|
||||
snapshot_file = yaml2json('basic.snapshot.with_debug')
|
||||
debug = snapshot_file['debug']
|
||||
user.post(res=Snapshot, data=json_encode(snapshot_file))
|
||||
|
@ -738,7 +766,7 @@ def test_save_snapshot_with_debug(app: Devicehub, user: UserClient):
|
|||
|
||||
@pytest.mark.mvp
|
||||
def test_backup_snapshot_with_errors(app: Devicehub, user: UserClient):
|
||||
""" This test check if the file snapshot is create when some snapshot is wrong """
|
||||
"""This test check if the file snapshot is create when some snapshot is wrong"""
|
||||
tmp_snapshots = app.config['TMP_SNAPSHOTS']
|
||||
path_dir_base = os.path.join(tmp_snapshots, user.user['email'], 'errors')
|
||||
snapshot_no_hid = yaml2json('basic.snapshot.badly_formed')
|
||||
|
@ -763,7 +791,7 @@ def test_backup_snapshot_with_errors(app: Devicehub, user: UserClient):
|
|||
|
||||
@pytest.mark.mvp
|
||||
def test_snapshot_failed_missing_cpu_benchmark(app: Devicehub, user: UserClient):
|
||||
""" This test check if the file snapshot is create when some snapshot is wrong """
|
||||
"""This test check if the file snapshot is create when some snapshot is wrong"""
|
||||
tmp_snapshots = app.config['TMP_SNAPSHOTS']
|
||||
path_dir_base = os.path.join(tmp_snapshots, user.user['email'], 'errors')
|
||||
snapshot_error = yaml2json('failed.snapshot.500.missing-cpu-benchmark')
|
||||
|
@ -788,7 +816,7 @@ def test_snapshot_failed_missing_cpu_benchmark(app: Devicehub, user: UserClient)
|
|||
|
||||
@pytest.mark.mvp
|
||||
def test_snapshot_failed_missing_hdd_benchmark(app: Devicehub, user: UserClient):
|
||||
""" This test check if the file snapshot is create when some snapshot is wrong """
|
||||
"""This test check if the file snapshot is create when some snapshot is wrong"""
|
||||
tmp_snapshots = app.config['TMP_SNAPSHOTS']
|
||||
path_dir_base = os.path.join(tmp_snapshots, user.user['email'], 'errors')
|
||||
snapshot_error = yaml2json('failed.snapshot.500.missing-hdd-benchmark')
|
||||
|
@ -813,7 +841,7 @@ def test_snapshot_failed_missing_hdd_benchmark(app: Devicehub, user: UserClient)
|
|||
|
||||
@pytest.mark.mvp
|
||||
def test_snapshot_not_failed_null_chassis(app: Devicehub, user: UserClient):
|
||||
""" This test check if the file snapshot is create when some snapshot is wrong """
|
||||
"""This test check if the file snapshot is create when some snapshot is wrong"""
|
||||
tmp_snapshots = app.config['TMP_SNAPSHOTS']
|
||||
path_dir_base = os.path.join(tmp_snapshots, user.user['email'], 'errors')
|
||||
snapshot_error = yaml2json('desktop-9644w8n-lenovo-0169622.snapshot')
|
||||
|
@ -831,7 +859,7 @@ def test_snapshot_not_failed_null_chassis(app: Devicehub, user: UserClient):
|
|||
|
||||
@pytest.mark.mvp
|
||||
def test_snapshot_failed_missing_chassis(app: Devicehub, user: UserClient):
|
||||
""" This test check if the file snapshot is create when some snapshot is wrong """
|
||||
"""This test check if the file snapshot is create when some snapshot is wrong"""
|
||||
tmp_snapshots = app.config['TMP_SNAPSHOTS']
|
||||
path_dir_base = os.path.join(tmp_snapshots, user.user['email'], 'errors')
|
||||
snapshot_error = yaml2json('failed.snapshot.422.missing-chassis')
|
||||
|
@ -856,7 +884,7 @@ def test_snapshot_failed_missing_chassis(app: Devicehub, user: UserClient):
|
|||
|
||||
@pytest.mark.mvp
|
||||
def test_snapshot_failed_end_time_bug(app: Devicehub, user: UserClient):
|
||||
""" This test check if the end_time = 0001-01-01 00:00:00+00:00
|
||||
"""This test check if the end_time = 0001-01-01 00:00:00+00:00
|
||||
and then we get a /devices, this create a crash
|
||||
"""
|
||||
snapshot_file = file('asus-end_time_bug88.snapshot')
|
||||
|
@ -870,9 +898,10 @@ def test_snapshot_failed_end_time_bug(app: Devicehub, user: UserClient):
|
|||
tmp_snapshots = app.config['TMP_SNAPSHOTS']
|
||||
shutil.rmtree(tmp_snapshots)
|
||||
|
||||
|
||||
@pytest.mark.mvp
|
||||
def test_snapshot_not_failed_end_time_bug(app: Devicehub, user: UserClient):
|
||||
""" This test check if the end_time != 0001-01-01 00:00:00+00:00
|
||||
"""This test check if the end_time != 0001-01-01 00:00:00+00:00
|
||||
and then we get a /devices, this create a crash
|
||||
"""
|
||||
snapshot_file = yaml2json('asus-end_time_bug88.snapshot')
|
||||
|
@ -891,7 +920,7 @@ def test_snapshot_not_failed_end_time_bug(app: Devicehub, user: UserClient):
|
|||
|
||||
@pytest.mark.mvp
|
||||
def test_snapshot_bug_smallint_hdd(app: Devicehub, user: UserClient):
|
||||
""" This test check if the end_time != 0001-01-01 00:00:00+00:00
|
||||
"""This test check if the end_time != 0001-01-01 00:00:00+00:00
|
||||
and then we get a /devices, this create a crash
|
||||
"""
|
||||
snapshot_file = file('asus-eee-1000h.snapshot.bug1857')
|
||||
|
@ -907,7 +936,7 @@ def test_snapshot_bug_smallint_hdd(app: Devicehub, user: UserClient):
|
|||
|
||||
@pytest.mark.mvp
|
||||
def test_snapshot_mobil(app: Devicehub, user: UserClient):
|
||||
""" This test check if the end_time != 0001-01-01 00:00:00+00:00
|
||||
"""This test check if the end_time != 0001-01-01 00:00:00+00:00
|
||||
and then we get a /devices, this create a crash
|
||||
"""
|
||||
snapshot_file = file('mobil')
|
||||
|
@ -921,8 +950,183 @@ def test_snapshot_mobil(app: Devicehub, user: UserClient):
|
|||
@pytest.mark.mvp
|
||||
def test_bug_141(user: UserClient):
|
||||
"""This test check one bug that create a problem when try to up one snapshot
|
||||
with a big number in the parameter command_timeout of the DataStorage
|
||||
with a big number in the parameter command_timeout of the DataStorage
|
||||
|
||||
"""
|
||||
dev = file('2021-5-4-13-41_time_out_test_datastorage')
|
||||
user.post(dev, res=Snapshot)
|
||||
|
||||
|
||||
@pytest.mark.mvp
|
||||
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||
def test_snapshot_wb_lite(user: UserClient):
|
||||
"""This test check the minimum validation of json that come from snapshot"""
|
||||
|
||||
snapshot = file_json(
|
||||
"2022-03-31_17h18m51s_ZQMPKKX51K67R68VO2X9RNZL08JPL_snapshot.json"
|
||||
)
|
||||
body, res = user.post(snapshot, uri="/api/inventory/")
|
||||
|
||||
ssd = [x for x in body['components'] if x['type'] == 'SolidStateDrive'][0]
|
||||
|
||||
assert body['device']['manufacturer'] == 'lenovo'
|
||||
# assert body['wbid'] == "LXVC"
|
||||
assert ssd['serialNumber'] == 's35anx0j401001'
|
||||
assert res.status == '201 CREATED'
|
||||
assert '00:28:f8:a6:d5:7e' in body['device']['hid']
|
||||
|
||||
dev = m.Device.query.filter_by(id=body['device']['id']).one()
|
||||
assert dev.actions[0].power_on_hours == 6032
|
||||
errors = SnapshotErrors.query.filter().all()
|
||||
assert errors == []
|
||||
|
||||
|
||||
@pytest.mark.mvp
|
||||
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||
def test_snapshot_wb_lite_qemu(user: UserClient):
|
||||
"""This test check the minimum validation of json that come from snapshot"""
|
||||
|
||||
snapshot = file_json(
|
||||
"2022-04-01_06h28m54s_YKPZ27NJ2NMRO4893M4L5NRZV5YJ1_snapshot.json"
|
||||
)
|
||||
# body, res = user.post(snapshot, res=Snapshot)
|
||||
body, res = user.post(snapshot, uri="/api/inventory/")
|
||||
|
||||
assert body['wbid'] == "YKPZ27NJ2NMRO4893M4L5NRZV5YJ1"
|
||||
assert res.status == '201 CREATED'
|
||||
|
||||
dev = m.Device.query.filter_by(id=body['device']['id']).one()
|
||||
assert dev.manufacturer == 'qemu'
|
||||
assert dev.model == 'standard'
|
||||
assert dev.serial_number is None
|
||||
assert dev.hid is None
|
||||
assert dev.actions[0].power_on_hours == 0
|
||||
assert dev.actions[1].power_on_hours == 0
|
||||
|
||||
|
||||
@pytest.mark.mvp
|
||||
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||
def test_snapshot_wb_lite_old_snapshots(user: UserClient):
|
||||
"""This test check the minimum validation of json that come from snapshot"""
|
||||
wb_dir = Path(__file__).parent.joinpath('files/wb_lite/')
|
||||
for f in os.listdir(wb_dir):
|
||||
file_name = "wb_lite/{}".format(f)
|
||||
snapshot_11 = file_json(file_name)
|
||||
if not snapshot_11.get('debug'):
|
||||
continue
|
||||
lshw = snapshot_11['debug']['lshw']
|
||||
hwinfo = snapshot_11['debug']['hwinfo']
|
||||
snapshot_lite = {
|
||||
'timestamp': snapshot_11['endTime'],
|
||||
'type': 'Snapshot',
|
||||
'uuid': str(uuid.uuid4()),
|
||||
'wbid': 'MLKO1',
|
||||
'software': 'Workbench',
|
||||
'version': '2022.03.00',
|
||||
"schema_api": "1.0.0",
|
||||
'data': {
|
||||
'lshw': lshw,
|
||||
'hwinfo': hwinfo,
|
||||
'smart': [],
|
||||
'dmidecode': '',
|
||||
'lspci': '',
|
||||
},
|
||||
}
|
||||
|
||||
body11, res = user.post(snapshot_11, res=Snapshot)
|
||||
bodyLite, res = user.post(snapshot_lite, uri="/api/inventory/")
|
||||
components11 = []
|
||||
componentsLite = []
|
||||
for c in body11.get('components', []):
|
||||
if c['type'] in ["HardDrive", "SolidStateDrive"]:
|
||||
continue
|
||||
components11.append({c.get('model'), c['type'], c.get('manufacturer')})
|
||||
for c in bodyLite.get('components', []):
|
||||
componentsLite.append({c.get('model'), c['type'], c.get('manufacturer')})
|
||||
|
||||
try:
|
||||
assert body11['device'].get('hid') == bodyLite['device'].get('hid')
|
||||
if body11['device'].get('hid'):
|
||||
assert body11['device']['id'] == bodyLite['device']['id']
|
||||
assert body11['device'].get('serialNumber') == bodyLite['device'].get(
|
||||
'serialNumber'
|
||||
)
|
||||
assert body11['device'].get('model') == bodyLite['device'].get('model')
|
||||
assert body11['device'].get('manufacturer') == bodyLite['device'].get(
|
||||
'manufacturer'
|
||||
)
|
||||
|
||||
# wbLite can find more components than wb11
|
||||
assert len(components11) <= len(componentsLite)
|
||||
for c in components11:
|
||||
assert c in componentsLite
|
||||
except Exception as err:
|
||||
# import pdb; pdb.set_trace()
|
||||
raise err
|
||||
|
||||
|
||||
@pytest.mark.mvp
|
||||
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||
def test_snapshot_errors(user: UserClient):
|
||||
"""This test check the minimum validation of json that come from snapshot"""
|
||||
snapshot_11 = file_json('snapshotErrors.json')
|
||||
lshw = snapshot_11['debug']['lshw']
|
||||
hwinfo = snapshot_11['debug']['hwinfo']
|
||||
snapshot_lite = {
|
||||
'timestamp': snapshot_11['endTime'],
|
||||
'type': 'Snapshot',
|
||||
'uuid': str(uuid.uuid4()),
|
||||
'wbid': 'MLKO1',
|
||||
'software': 'Workbench',
|
||||
'version': '2022.03.00',
|
||||
"schema_api": "1.0.0",
|
||||
'data': {
|
||||
'lshw': lshw,
|
||||
'hwinfo': hwinfo,
|
||||
'smart': [],
|
||||
'dmidecode': '',
|
||||
'lspci': '',
|
||||
},
|
||||
}
|
||||
|
||||
assert SnapshotErrors.query.all() == []
|
||||
body11, res = user.post(snapshot_11, res=Snapshot)
|
||||
assert SnapshotErrors.query.all() == []
|
||||
bodyLite, res = user.post(snapshot_lite, uri="/api/inventory/")
|
||||
assert len(SnapshotErrors.query.all()) == 2
|
||||
|
||||
assert body11['device'].get('hid') == bodyLite['device'].get('hid')
|
||||
assert body11['device']['id'] == bodyLite['device']['id']
|
||||
assert body11['device'].get('serialNumber') == bodyLite['device'].get(
|
||||
'serialNumber'
|
||||
)
|
||||
assert body11['device'].get('model') == bodyLite['device'].get('model')
|
||||
assert body11['device'].get('manufacturer') == bodyLite['device'].get(
|
||||
'manufacturer'
|
||||
)
|
||||
components11 = []
|
||||
componentsLite = []
|
||||
for c in body11['components']:
|
||||
if c['type'] == "HardDrive":
|
||||
continue
|
||||
components11.append({c['model'], c['type'], c['manufacturer']})
|
||||
for c in bodyLite['components']:
|
||||
componentsLite.append({c['model'], c['type'], c['manufacturer']})
|
||||
|
||||
assert len(components11) == len(componentsLite)
|
||||
for c in components11:
|
||||
assert c in componentsLite
|
||||
|
||||
|
||||
@pytest.mark.mvp
|
||||
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||
def test_snapshot_errors_timestamp(user: UserClient):
|
||||
"""This test check the minimum validation of json that come from snapshot"""
|
||||
snapshot_lite = file_json('snapshot-error-timestamp.json')
|
||||
|
||||
bodyLite, res = user.post(snapshot_lite, uri="/api/inventory/")
|
||||
assert res.status_code == 201
|
||||
assert len(SnapshotErrors.query.all()) == 1
|
||||
error = SnapshotErrors.query.all()[0]
|
||||
assert snapshot_lite['wbid'] == error.wbid
|
||||
assert user.user['id'] == str(error.owner_id)
|
||||
|
|
|
@ -41,7 +41,6 @@ def test_workbench_server_condensed(user: UserClient):
|
|||
('BenchmarkProcessorSysbench', cpu_id),
|
||||
('StressTest', pc_id),
|
||||
('EraseSectors', ssd_id),
|
||||
('EreusePrice', pc_id),
|
||||
('BenchmarkRamSysbench', pc_id),
|
||||
('BenchmarkProcessor', cpu_id),
|
||||
('Install', ssd_id),
|
||||
|
@ -49,7 +48,6 @@ def test_workbench_server_condensed(user: UserClient):
|
|||
('BenchmarkDataStorage', ssd_id),
|
||||
('BenchmarkDataStorage', hdd_id),
|
||||
('TestDataStorage', ssd_id),
|
||||
('RateComputer', pc_id)
|
||||
}
|
||||
assert snapshot['closed']
|
||||
assert snapshot['severity'] == 'Info'
|
||||
|
@ -61,10 +59,6 @@ def test_workbench_server_condensed(user: UserClient):
|
|||
assert device['networkSpeeds'] == [1000, 58]
|
||||
assert device['processorModel'] == device['components'][3]['model'] == 'p1-1ml'
|
||||
assert device['ramSize'] == 2048, 'There are 3 RAM: 2 x 1024 and 1 None sizes'
|
||||
assert device['rate']['closed']
|
||||
assert device['rate']['severity'] == 'Info'
|
||||
assert device['rate']['rating'] == 1
|
||||
assert device['rate']['type'] == RateComputer.t
|
||||
# TODO JN why haven't same order in actions on each execution?
|
||||
assert any([ac['type'] in [BenchmarkProcessor.t, BenchmarkRamSysbench.t] for ac in device['actions']])
|
||||
assert 'tag1' in [x['id'] for x in device['tags']]
|
||||
|
@ -145,8 +139,6 @@ def test_real_hp_11(user: UserClient):
|
|||
assert pc['hid'] == 'desktop-hewlett-packard-hp_compaq_8100_elite_sff-czc0408yjg-6c:62:6d:81:22:9f'
|
||||
assert pc['chassis'] == 'Tower'
|
||||
assert set(e['type'] for e in snapshot['actions']) == {
|
||||
'EreusePrice',
|
||||
'RateComputer',
|
||||
'BenchmarkDataStorage',
|
||||
'BenchmarkProcessor',
|
||||
'BenchmarkProcessorSysbench',
|
||||
|
@ -156,7 +148,8 @@ def test_real_hp_11(user: UserClient):
|
|||
'TestBios',
|
||||
'VisualTest'
|
||||
}
|
||||
assert len(list(e['type'] for e in snapshot['actions'])) == 10
|
||||
|
||||
assert len(list(e['type'] for e in snapshot['actions'])) == 8
|
||||
assert pc['networkSpeeds'] == [1000, None], 'Device has no WiFi'
|
||||
assert pc['processorModel'] == 'intel core i3 cpu 530 @ 2.93ghz'
|
||||
assert pc['ramSize'] == 8192
|
||||
|
@ -175,6 +168,7 @@ def test_snapshot_real_eee_1001pxd_with_rate(user: UserClient):
|
|||
"""Checks the values of the device, components,
|
||||
actions and their relationships of a real pc.
|
||||
"""
|
||||
# import pdb; pdb.set_trace()
|
||||
s = file('real-eee-1001pxd.snapshot.11')
|
||||
snapshot, _ = user.post(res=em.Snapshot, data=s)
|
||||
pc, _ = user.get(res=Device, item=snapshot['device']['devicehubID'])
|
||||
|
@ -186,19 +180,10 @@ def test_snapshot_real_eee_1001pxd_with_rate(user: UserClient):
|
|||
assert pc['hid'] == 'laptop-asustek_computer_inc-1001pxd-b8oaas048286-14:da:e9:42:f6:7c'
|
||||
assert len(pc['tags']) == 1
|
||||
assert pc['networkSpeeds'] == [100, 0], 'Although it has WiFi we do not know the speed'
|
||||
assert pc['rate']
|
||||
rate = pc['rate']
|
||||
# assert pc['actions'][0]['appearanceRange'] == 'A'
|
||||
# assert pc['actions'][0]['functionalityRange'] == 'B'
|
||||
# TODO add appearance and functionality Range in device[rate]
|
||||
|
||||
assert rate['processorRange'] == 'LOW'
|
||||
assert rate['ramRange'] == 'LOW'
|
||||
assert rate['ratingRange'] == 'LOW'
|
||||
assert rate['ram'] == 1.53
|
||||
# TODO add camelCase instead of snake_case
|
||||
assert rate['dataStorage'] == 3.76
|
||||
assert rate['type'] == 'RateComputer'
|
||||
components = snapshot['components']
|
||||
wifi = components[0]
|
||||
assert wifi['hid'] == 'networkadapter-qualcomm_atheros-' \
|
||||
|
@ -232,7 +217,7 @@ def test_snapshot_real_eee_1001pxd_with_rate(user: UserClient):
|
|||
assert em.BenchmarkRamSysbench.t in action_types
|
||||
assert em.StressTest.t in action_types
|
||||
assert em.Snapshot.t in action_types
|
||||
assert len(actions) == 8
|
||||
assert len(actions) == 6
|
||||
gpu = components[3]
|
||||
assert gpu['model'] == 'atom processor d4xx/d5xx/n4xx/n5xx integrated graphics controller'
|
||||
assert gpu['manufacturer'] == 'intel corporation'
|
||||
|
@ -242,7 +227,7 @@ def test_snapshot_real_eee_1001pxd_with_rate(user: UserClient):
|
|||
assert em.BenchmarkRamSysbench.t in action_types
|
||||
assert em.StressTest.t in action_types
|
||||
assert em.Snapshot.t in action_types
|
||||
assert len(action_types) == 6
|
||||
assert len(action_types) == 4
|
||||
sound = components[4]
|
||||
assert sound['model'] == 'nm10/ich7 family high definition audio controller'
|
||||
sound = components[5]
|
||||
|
@ -264,7 +249,7 @@ def test_snapshot_real_eee_1001pxd_with_rate(user: UserClient):
|
|||
assert em.TestDataStorage.t in action_types
|
||||
assert em.EraseBasic.t in action_types
|
||||
assert em.Snapshot.t in action_types
|
||||
assert len(action_types) == 9
|
||||
assert len(action_types) == 7
|
||||
erase = next(e for e in hdd['actions'] if e['type'] == em.EraseBasic.t)
|
||||
assert erase['endTime']
|
||||
assert erase['startTime']
|
||||
|
|
Reference in a new issue