""" This is the view for Snapshots """ from datetime import timedelta from distutils.version import StrictVersion from uuid import UUID 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, 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.documents import ErasedView 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: model = None def post(self): """Create one res_obj""" res_json = request.get_json() res_obj = self.model(**res_json) db.session.add(res_obj) db.session().final_flush() ret = self.schema.jsonify(res_obj) ret.status_code = 201 db.session.commit() return ret def find(self, args: dict): 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, ) class AllocateView(AllocateMix, View): model = Allocate class DeallocateView(AllocateMix, View): model = Deallocate class LiveView(View): def post(self): """Posts an action.""" res_json = request.get_json(validate=False) tmp_snapshots = app.config['TMP_LIVES'] path_live = save_json(res_json, tmp_snapshots, '', live=True) res_json.pop('debug', None) res_json.pop('elapsed', None) res_json.pop('os', None) res_json_valid = self.schema.load(res_json) live = self.live(res_json_valid) db.session.add(live) db.session().final_flush() ret = self.schema.jsonify(live) ret.status_code = 201 db.session.commit() move_json(tmp_snapshots, path_live, '', live=True) return ret def get_hdd_details(self, snapshot, device): """We get the liftime and serial_number of the disk""" usage_time_hdd = None serial_number = None components = [c for c in snapshot['components']] components.sort(key=lambda x: x.created) for hd in components: if not isinstance(hd, DataStorage): continue serial_number = hd.serial_number for act in hd.actions: if not act.type == "TestDataStorage": continue usage_time_hdd = act.lifetime break if usage_time_hdd: break if not serial_number: """There aren't any disk""" raise ResourceNotFound( "There aren't any disk in this device {}".format(device) ) return usage_time_hdd, serial_number def get_hid(self, snapshot): device = snapshot.get('device') # type: Computer components = snapshot.get('components') if not device: 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.sort() mac = '' hid = device.hid if not hid: return hid if macs: mac = "-{mac}".format(mac=macs[0]) # hid += mac return hid def live(self, snapshot): """If the device.allocated == True, then this snapshot create an action live.""" for c in snapshot['components']: c.parent = snapshot['device'] snapshot['device'].set_hid() hid = self.get_hid(snapshot) 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() # Is not necessary if not device: raise ValidationError('Device not exist.') if not device.allocated: raise ValidationError('Sorry this device is not allocated.') 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, } live = Live(**data_live) if not usage_time_hdd: warning = f"We don't found any TestDataStorage for disk sn: {serial_number}" live.severity = Severity.Warning live.description = warning return live live.sort_actions() diff_time = live.diff_time() if diff_time is None: warning = "Don't exist one previous live or snapshot as reference" live.description += warning live.severity = Severity.Warning elif diff_time < timedelta(0): warning = "The difference with the last live/snapshot is negative" live.description += warning live.severity = Severity.Warning return live def decode_snapshot(data): try: 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) class ActionView(View): def post(self): """Posts an action.""" json = request.get_json(validate=False) if not json or 'type' not in json: raise ValidationError('Post request needs a json.') # todo there should be a way to better get subclassess resource # defs resource_def = app.resources[json['type']] if json['type'] == Snapshot.t: if json.get('software') == 'Web' and json['device'] == 'Computer': txt = 'Invalid snapshot' raise ValidationError(txt) if json.get('software') == 'Web': snapshot = SnapshotView(json, resource_def, self.schema) return snapshot.post() # TODO @cayop uncomment at four weeks # if not 'data' in json: # txt = 'Invalid snapshot' # raise ValidationError(txt) # snapshot_data = decode_snapshot(json) snapshot_data = json if 'data' in json and isinstance(json['data'], str): snapshot_data = decode_snapshot(json) if not snapshot_data: txt = 'Invalid snapshot' raise ValidationError(txt) snapshot = SnapshotView(snapshot_data, resource_def, self.schema) return snapshot.post() if json['type'] == VisualTest.t: pass # TODO JN add compute rate with new visual test and old components device if json['type'] == InitTransfer.t: return self.transfer_ownership() if json['type'] == Trade.t: trade = trade_view.TradeView(json, resource_def, self.schema) return trade.post() if json['type'] == Confirm.t: confirm = trade_view.ConfirmView(json, resource_def, self.schema) return confirm.post() if json['type'] == Revoke.t: revoke = trade_view.RevokeView(json, resource_def, self.schema) return revoke.post() if json['type'] == 'ConfirmRevoke': revoke = trade_view.RevokeView(json, resource_def, self.schema) return revoke.post() if json['type'] == 'RevokeDocument': revoke = trade_view.RevokeDocumentView(json, resource_def, self.schema) return revoke.post() if json['type'] == 'ConfirmDocument': confirm = trade_view.ConfirmDocumentView(json, resource_def, self.schema) return confirm.post() if json['type'] == 'ConfirmRevokeDocument': confirm_revoke = trade_view.ConfirmRevokeDocumentView( json, resource_def, self.schema ) return confirm_revoke.post() if json['type'] == 'DataWipe': erased = ErasedView(json, resource_def.schema) return erased.post() a = resource_def.schema.load(json) Model = db.Model._decl_class_registry.data[json['type']]() action = Model(**a) db.session.add(action) db.session().final_flush() ret = self.schema.jsonify(action) ret.status_code = 201 db.session.commit() return ret def one(self, id: UUID): """Gets one action.""" action = Action.query.filter_by(id=id).one() return self.schema.jsonify(action) def transfer_ownership(self): """Perform a InitTransfer action to change author_id of device""" pass