source: ogClient-Git/src/virtual/ogOperations.py @ 082079a

Last change on this file since 082079a was cb9edc8, checked in by OpenGnSys Support Team <soporte-og@…>, 4 years ago

ogClient is AGPLv3+

Update license header in files.

  • Property mode set to 100644
File size: 21.9 KB
RevLine 
[c279325]1#
[cb9edc8]2# Copyright (C) 2020-2021 Soleta Networks <info@soleta.eu>
[c279325]3#
4# This program is free software: you can redistribute it and/or modify it under
5# the terms of the GNU Affero General Public License as published by the
[cb9edc8]6# Free Software Foundation; either version 3 of the License, or
7# (at your option) any later version.
[c279325]8
[b29b2eb]9from src.ogRest import ThreadState
[c279325]10import socket
11import errno
[1a83ebb]12import select
[c279325]13import json
14import subprocess
15import shutil
16import os
17import guestfs
18import hivex
19import pathlib
20import re
21import math
[8ec8572]22import sys
[6ca16dd]23import enum
[ff988b8]24import time
[c279325]25
[7bde2a0]26class OgVM:
27    DEFAULT_CPU = 'host'
[2e6b73d]28    DEFAULT_VGA = 'virtio-vga'
[ff988b8]29    DEFAULT_QMP_IP = 'localhost'
[7bde2a0]30    DEFAULT_QMP_PORT = 4444
31
[6ca16dd]32    class State(enum.Enum):
33        STOPPED = 0
34        RUNNING = 1
35
[7bde2a0]36    def __init__(self,
37                 partition_path,
38                 memory=None,
39                 cpu=DEFAULT_CPU,
40                 vga=DEFAULT_VGA,
[ff988b8]41                 qmp_ip=DEFAULT_QMP_IP,
42                 qmp_port=DEFAULT_QMP_PORT,
43                 vnc_params=None):
[7bde2a0]44        self.partition_path = partition_path
45        self.cpu = cpu
46        self.vga = vga
[ff988b8]47        self.qmp_ip = qmp_ip
[7bde2a0]48        self.qmp_port = qmp_port
49        self.proc = None
[ff988b8]50        self.vnc_params = vnc_params
[7bde2a0]51
52        if memory:
53            self.mem = memory
54        else:
55            available_ram = os.sysconf('SC_PAGE_SIZE') * os.sysconf('SC_PHYS_PAGES')
56            available_ram = available_ram / 2 ** 20
57            # Calculate the lower power of 2 amout of RAM memory for the VM.
58            self.mem = 2 ** math.floor(math.log(available_ram) / math.log(2))
59
60
61    def run_vm(self):
[ff988b8]62        if self.vnc_params:
63            vnc_str = f'-vnc 0.0.0.0:0,password'
64        else:
65            vnc_str = ''
66
67        cmd = (f'qemu-system-x86_64 -accel kvm -cpu {self.cpu} -smp 4 '
68               f'-drive file={self.partition_path},if=virtio '
69               f'-qmp tcp:localhost:4444,server,nowait '
[2e6b73d]70               f'-device {self.vga} -display gtk '
[ff988b8]71               f'-m {self.mem}M -boot c -full-screen {vnc_str}')
[7bde2a0]72        self.proc = subprocess.Popen([cmd], shell=True)
73
[ff988b8]74        if self.vnc_params:
75            # Wait for QMP to be available.
[de2ce69]76            time.sleep(20)
[ff988b8]77            cmd = { "execute": "change",
78                    "arguments": { "device": "vnc",
79                                   "target": "password",
80                                   "arg": str(self.vnc_params['pass']) } }
[de2ce69]81            with OgQMP(self.qmp_ip, self.qmp_port) as qmp:
82                qmp.talk(str(cmd))
[ff988b8]83
[c279325]84class OgQMP:
[1a83ebb]85    QMP_TIMEOUT = 5
[c863281]86    QMP_POWEROFF_TIMEOUT = 300
[c279325]87
88    def __init__(self, ip, port):
89        self.ip = ip
90        self.port = port
[de2ce69]91
92    def connect(self):
[c279325]93        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
[1a83ebb]94        self.sock.setblocking(0)
[c279325]95        try:
[de2ce69]96            self.sock.connect((self.ip, self.port))
[c279325]97        except socket.error as err:
[1a83ebb]98            if err.errno == errno.ECONNREFUSED:
99                raise Exception('cannot connect to qemu')
100            elif err.errno == errno.EINPROGRESS:
101                pass
[c279325]102
[1a83ebb]103        readset = [ self.sock ]
104        readable, writable, exception = select.select(readset,
[de2ce69]105                                                      [],
[1a83ebb]106                                                      [],
107                                                      OgQMP.QMP_TIMEOUT)
[de2ce69]108
109        if self.sock in readable:
[1a83ebb]110            try:
[de2ce69]111                out = self.recv()
112            except:
113                pass
[c279325]114
[deb2e07]115        if 'QMP' not in out:
116            raise Exception('cannot handshake qemu')
117
[de2ce69]118        out = self.talk(str({"execute": "qmp_capabilities"}))
119        if 'return' not in out:
120            raise Exception('cannot handshake qemu')
121
122    def disconnect(self):
[1a83ebb]123        try:
[de2ce69]124            self.sock.close()
[1a83ebb]125        except:
[de2ce69]126            pass
[c279325]127
[de2ce69]128    def __enter__(self):
129        self.connect()
130        return self
131
132    def __exit__(self, exc_type, exc_val, exc_tb):
133        self.disconnect()
134
135    def talk(self, data, timeout=QMP_TIMEOUT):
136        writeset = [ self.sock ]
137        readable, writable, exception = select.select([],
138                                                      writeset,
139                                                      [],
140                                                      timeout)
141        if self.sock in writable:
[1a83ebb]142            try:
[de2ce69]143                self.sock.send(bytes(data, 'utf-8'))
144            except:
[deb2e07]145                raise Exception('cannot talk to qemu')
146        else:
147            raise Exception('timeout when talking to qemu')
[de2ce69]148
149        return self.recv(timeout=timeout)
[deb2e07]150
151    def recv(self, timeout=QMP_TIMEOUT):
152        readset = [self.sock]
153        readable, _, _ = select.select(readset, [], [], timeout)
154
155        if self.sock in readable:
156            try:
157                out = self.sock.recv(4096).decode('utf-8')
158                out = json.loads(out)
[1a83ebb]159            except socket.error as err:
160                raise Exception('cannot talk to qemu')
161        else:
162            raise Exception('timeout when talking to qemu')
163        return out
[c279325]164
[99ae598]165class OgVirtualOperations:
166    def __init__(self):
167        self.IP = '127.0.0.1'
168        self.VIRTUAL_PORT = 4444
[7f646fc]169        self.USABLE_DISK = 0.75
[8ec8572]170        self.OG_PATH = os.path.dirname(os.path.realpath(sys.argv[0]))
171        self.OG_IMAGES_PATH = f'{self.OG_PATH}/images'
172        self.OG_PARTITIONS_PATH = f'{self.OG_PATH}/partitions'
[6ca16dd]173        self.OG_PARTITIONS_CFG_PATH = f'{self.OG_PATH}/partitions.json'
[8ec8572]174
175        if not os.path.exists(self.OG_IMAGES_PATH):
176            os.mkdir(self.OG_IMAGES_PATH, mode=0o755)
177        if not os.path.exists(self.OG_PARTITIONS_PATH):
178            os.mkdir(self.OG_PARTITIONS_PATH, mode=0o755)
[99ae598]179
[c863281]180    def poweroff_guest(self):
[1a83ebb]181        try:
[de2ce69]182            with OgQMP(self.IP, self.VIRTUAL_PORT) as qmp:
183                qmp.talk(str({"execute": "system_powerdown"}))
184                out = qmp.recv()
185                assert(out['event'] == 'POWERDOWN')
186                out = qmp.recv(timeout=OgQMP.QMP_POWEROFF_TIMEOUT)
187                assert(out['event'] == 'SHUTDOWN')
[1a83ebb]188        except:
[c863281]189            return
190
191    def poweroff_host(self):
192        subprocess.run(['/sbin/poweroff'])
193
194    def poweroff(self):
195        self.poweroff_guest()
196        self.poweroff_host()
[c279325]197
[99ae598]198    def reboot(self):
[1a83ebb]199        try:
[de2ce69]200            with OgQMP(self.IP, self.VIRTUAL_PORT) as qmp:
201                qmp.talk(str({"execute": "system_reset"}))
[1a83ebb]202        except:
203            pass
[c279325]204
[6ca16dd]205    def check_vm_state(self):
206        try:
[de2ce69]207            with OgQMP(self.IP, self.VIRTUAL_PORT) as qmp:
208                pass
[6ca16dd]209            return OgVM.State.RUNNING
210        except:
211            return OgVM.State.STOPPED
212
213    def get_installed_os(self):
214        installed_os = {}
215        try:
216            with open(self.OG_PARTITIONS_CFG_PATH, 'r') as f:
217                cfg = json.loads(f.read())
218            for part in cfg['partition_setup']:
219                if len(part['os']) > 0:
220                    installed_os[part['os']] = (part['disk'], part['partition'])
221        except:
222            pass
223        return installed_os
224
[b29b2eb]225    def check_vm_state_loop(self, ogRest):
226        POLLING_WAIT_TIME = 12
227        while True:
228            time.sleep(POLLING_WAIT_TIME)
229            state = self.check_vm_state()
230            installed_os = self.get_installed_os()
231            if state == OgVM.State.STOPPED and \
232               ogRest.state == ThreadState.IDLE and \
233               len(installed_os) > 0:
234                self.poweroff_host()
235
[269c7b5]236    def shellrun(self, request, ogRest):
237        return
[0ddf3f2]238
[99ae598]239    def session(self, request, ogRest):
240        disk = request.getDisk()
241        partition = request.getPartition()
[c279325]242
[7bde2a0]243        part_path = f'{self.OG_PARTITIONS_PATH}/disk{disk}_part{partition}.qcow2'
[ff988b8]244        if ogRest.CONFIG['vnc']['activate']:
245            qemu = OgVM(part_path, vnc_params=ogRest.CONFIG['vnc'])
246        else:
247            qemu = OgVM(part_path)
[7bde2a0]248        qemu.run_vm()
[c279325]249
[298e156]250    def partitions_cfg_to_json(self, data):
251        for part in data['partition_setup']:
252            part.pop('virt-drive')
253            for k, v in part.items():
254                part[k] = str(v)
[0411d2b]255        for disk in data['disk_setup']:
256            for k, v in disk.items():
257                disk[k] = str(v)
[298e156]258        return data
259
[99ae598]260    def refresh(self, ogRest):
[298e156]261        try:
262            # Return last partitions setup in case VM is running.
[de2ce69]263            with OgQMP(self.IP, self.VIRTUAL_PORT) as qmp:
264                pass
[404b8c7]265            with open(self.OG_PARTITIONS_CFG_PATH, 'r') as f:
[298e156]266                data = json.loads(f.read())
267            data = self.partitions_cfg_to_json(data)
268            return data
269        except:
270            pass
271
[99ae598]272        try:
[404b8c7]273            with open(self.OG_PARTITIONS_CFG_PATH, 'r+') as f:
[99ae598]274                data = json.loads(f.read())
275                for part in data['partition_setup']:
276                    if len(part['virt-drive']) > 0:
[84e0246]277                        if not os.path.exists(part['virt-drive']):
278                            part['code'] = '',
279                            part['filesystem'] = 'EMPTY'
280                            part['os'] = ''
281                            part['size'] = 0
282                            part['used_size'] = 0
283                            part['virt-drive'] = ''
284                            continue
285                        g = guestfs.GuestFS(python_return_dict=True)
286                        g.add_drive_opts(part['virt-drive'],
287                                         format="qcow2",
[b63bd72]288                                         readonly=1)
[84e0246]289                        g.launch()
290                        devices = g.list_devices()
291                        assert(len(devices) == 1)
292                        partitions = g.list_partitions()
293                        assert(len(partitions) == 1)
[28a2591]294                        filesystems_dict = g.list_filesystems()
295                        assert(len(filesystems_dict) == 1)
[84e0246]296                        g.mount(partitions[0], '/')
297                        used_disk = g.du('/')
298                        g.umount_all()
299                        total_size = g.disk_virtual_size(part['virt-drive']) / 1024
[28a2591]300
[84e0246]301                        part['used_size'] = int(100 * used_disk / total_size)
302                        part['size'] = total_size
303                        root = g.inspect_os()
304                        if len(root) == 1:
305                            part['os'] = f'{g.inspect_get_distro(root[0])} ' \
306                                         f'{g.inspect_get_product_name(root[0])}'
307                        else:
308                            part['os'] = ''
[28a2591]309                        filesystem = [fs for fs in filesystems_dict.values()][0]
310                        part['filesystem'] = filesystem.upper()
311                        if filesystem == 'ext4':
312                            part['code'] = '0083'
313                        elif filesystem == 'ntfs':
314                            part['code'] = '0007'
[84e0246]315                        g.close()
[99ae598]316                f.seek(0)
317                f.write(json.dumps(data, indent=4))
318                f.truncate()
319        except FileNotFoundError:
320            total_disk, used_disk, free_disk = shutil.disk_usage("/")
[7f646fc]321            free_disk = int(free_disk * self.USABLE_DISK)
[99ae598]322            data = {'serial_number': '',
[0411d2b]323                    'disk_setup': [{'disk': 1,
[99ae598]324                                   'partition': 0,
325                                   'code': '0',
326                                   'filesystem': '',
327                                   'os': '',
328                                   'size': int(free_disk / 1024),
[0411d2b]329                                   'used_size': int(100 * used_disk / total_disk)}],
[99ae598]330                    'partition_setup': []}
331            for i in range(4):
332                part_json = {'disk': 1,
333                             'partition': i + 1,
334                             'code': '',
335                             'filesystem': 'EMPTY',
336                             'os': '',
337                             'size': 0,
338                             'used_size': 0,
339                             'virt-drive': ''}
340                data['partition_setup'].append(part_json)
[404b8c7]341            with open(self.OG_PARTITIONS_CFG_PATH, 'w+') as f:
[99ae598]342                f.write(json.dumps(data, indent=4))
[2ab5db7]343        except:
344            with open(self.OG_PARTITIONS_CFG_PATH, 'r') as f:
345                data = json.load(f)
[99ae598]346
[298e156]347        data = self.partitions_cfg_to_json(data)
[99ae598]348
349        return data
350
351    def setup(self, request, ogRest):
[c863281]352        self.poweroff_guest()
[8ec8572]353        self.refresh(ogRest)
[99ae598]354
355        part_setup = request.getPartitionSetup()
356        disk = request.getDisk()
357
358        for i, part in enumerate(part_setup):
359            if int(part['format']) == 0:
360                continue
[c279325]361
[dfb69e9]362            drive_path = f'{self.OG_PARTITIONS_PATH}/disk{disk}_part{part["partition"]}.qcow2'
363            g = guestfs.GuestFS(python_return_dict=True)
364            g.disk_create(drive_path, "qcow2", int(part['size']) * 1024)
365            g.add_drive_opts(drive_path, format="qcow2", readonly=0)
366            g.launch()
367            devices = g.list_devices()
368            assert(len(devices) == 1)
369            g.part_disk(devices[0], "gpt")
370            partitions = g.list_partitions()
371            assert(len(partitions) == 1)
372            g.mkfs(part["filesystem"].lower(), partitions[0])
373            g.close()
[99ae598]374
[404b8c7]375            with open(self.OG_PARTITIONS_CFG_PATH, 'r+') as f:
[99ae598]376                data = json.loads(f.read())
377                if part['code'] == 'LINUX':
378                    data['partition_setup'][i]['code'] = '0083'
379                elif part['code'] == 'NTFS':
380                    data['partition_setup'][i]['code'] = '0007'
381                elif part['code'] == 'DATA':
382                    data['partition_setup'][i]['code'] = '00da'
383                data['partition_setup'][i]['filesystem'] = part['filesystem']
384                data['partition_setup'][i]['size'] = int(part['size'])
385                data['partition_setup'][i]['virt-drive'] = drive_path
386                f.seek(0)
387                f.write(json.dumps(data, indent=4))
388                f.truncate()
389
[8ec8572]390        return self.refresh(ogRest)
[99ae598]391
392    def image_create(self, path, request, ogRest):
393        disk = request.getDisk()
394        partition = request.getPartition()
395        name = request.getName()
396        repo = request.getRepo()
[7ccc498]397        samba_config = ogRest.samba_config
[99ae598]398
[c863281]399        self.poweroff_guest()
[c279325]400
[8ec8572]401        self.refresh(ogRest)
402
403        drive_path = f'{self.OG_PARTITIONS_PATH}/disk{disk}_part{partition}.qcow2'
[c279325]404
[7ccc498]405        cmd = f'mount -t cifs //{repo}/ogimages {self.OG_IMAGES_PATH} -o ' \
406              f'rw,nolock,serverino,acl,' \
407              f'username={samba_config["user"]},' \
408              f'password={samba_config["pass"]}'
409        subprocess.run([cmd], shell=True)
[c279325]410
[99ae598]411        try:
[8ec8572]412            shutil.copy(drive_path, f'{self.OG_IMAGES_PATH}/{name}')
[99ae598]413        except:
414            return None
[c279325]415
[8ec8572]416        subprocess.run([f'umount {self.OG_IMAGES_PATH}'], shell=True)
417
[99ae598]418        return True
419
420    def image_restore(self, request, ogRest):
421        disk = request.getDisk()
422        partition = request.getPartition()
423        name = request.getName()
424        repo = request.getRepo()
425        # TODO Multicast? Unicast? Solo copia con samba.
426        ctype = request.getType()
427        profile = request.getProfile()
428        cid = request.getId()
[7ccc498]429        samba_config = ogRest.samba_config
[99ae598]430
[c863281]431        self.poweroff_guest()
[8ec8572]432        self.refresh(ogRest)
[c279325]433
[8ec8572]434        drive_path = f'{self.OG_PARTITIONS_PATH}/disk{disk}_part{partition}.qcow2'
[c279325]435
[99ae598]436        if os.path.exists(drive_path):
437            os.remove(drive_path)
[c279325]438
[7ccc498]439        cmd = f'mount -t cifs //{repo}/ogimages {self.OG_IMAGES_PATH} -o ' \
440              f'ro,nolock,serverino,acl,' \
441              f'username={samba_config["user"]},' \
442              f'password={samba_config["pass"]}'
443        subprocess.run([cmd], shell=True)
444
[99ae598]445        try:
[8ec8572]446            shutil.copy(f'{self.OG_IMAGES_PATH}/{name}', drive_path)
[99ae598]447        except:
448            return None
[c279325]449
[7ccc498]450        subprocess.run([f'umount {self.OG_IMAGES_PATH}'], shell=True)
[3205e0f]451        self.refresh(ogRest)
[7ccc498]452
[99ae598]453        return True
[c279325]454
[99ae598]455    def software(self, request, path, ogRest):
456        DPKG_PATH = '/var/lib/dpkg/status'
[c279325]457
[99ae598]458        disk = request.getDisk()
459        partition = request.getPartition()
[8ec8572]460        drive_path = f'{self.OG_PARTITIONS_PATH}/disk{disk}_part{partition}.qcow2'
[99ae598]461        g = guestfs.GuestFS(python_return_dict=True)
462        g.add_drive_opts(drive_path, readonly=1)
463        g.launch()
464        root = g.inspect_os()[0]
[c279325]465
[99ae598]466        os_type = g.inspect_get_type(root)
467        os_major_version = g.inspect_get_major_version(root)
468        os_minor_version = g.inspect_get_minor_version(root)
469        os_distro = g.inspect_get_distro(root)
[c279325]470
[99ae598]471        software = []
[c279325]472
[99ae598]473        if 'linux' in os_type:
474            g.mount_ro(g.list_partitions()[0], '/')
475            try:
476                g.download('/' + DPKG_PATH, 'dpkg_list')
477            except:
478                pass
479            g.umount_all()
480            if os.path.isfile('dpkg_list'):
481                pkg_pattern = re.compile('Package: (.+)')
482                version_pattern = re.compile('Version: (.+)')
483                with open('dpkg_list', 'r') as f:
484                    for line in f:
485                        pkg_match = pkg_pattern.match(line)
486                        version_match = version_pattern.match(line)
487                        if pkg_match:
488                            pkg = pkg_match.group(1)
489                        elif version_match:
490                            version = version_match.group(1)
491                        elif line == '\n':
492                            software.append(pkg + ' ' + version)
493                        else:
494                            continue
495                os.remove('dpkg_list')
496        elif 'windows' in os_type:
497            g.mount_ro(g.list_partitions()[0], '/')
498            hive_file_path = g.inspect_get_windows_software_hive(root)
499            g.download('/' + hive_file_path, 'win_reg')
500            g.umount_all()
501            h = hivex.Hivex('win_reg')
[c279325]502            key = h.root()
503            key = h.node_get_child (key, 'Microsoft')
504            key = h.node_get_child (key, 'Windows')
505            key = h.node_get_child (key, 'CurrentVersion')
506            key = h.node_get_child (key, 'Uninstall')
507            software += [h.node_name(x) for x in h.node_children(key)]
[99ae598]508            # Just for 64 bit Windows versions, check for 32 bit software.
509            if (os_major_version == 5 and os_minor_version >= 2) or \
510               (os_major_version >= 6):
511                key = h.root()
512                key = h.node_get_child (key, 'Wow6432Node')
513                key = h.node_get_child (key, 'Microsoft')
514                key = h.node_get_child (key, 'Windows')
515                key = h.node_get_child (key, 'CurrentVersion')
516                key = h.node_get_child (key, 'Uninstall')
517                software += [h.node_name(x) for x in h.node_children(key)]
518            os.remove('win_reg')
519
[2e3d47b]520        return '\n'.join(software)
[99ae598]521
522    def parse_pci(self, path='/usr/share/misc/pci.ids'):
523        data = {}
524        with open(path, 'r') as f:
525            for line in f:
526                if line[0] == '#':
527                    continue
528                elif len(line.strip()) == 0:
529                    continue
[c279325]530                else:
[99ae598]531                    if line[:2] == '\t\t':
532                        fields = line.strip().split(maxsplit=2)
533                        data[last_vendor][last_device][(fields[0], fields[1])] = fields[2]
534                    elif line[:1] == '\t':
535                        fields = line.strip().split(maxsplit=1)
536                        last_device = fields[0]
537                        data[last_vendor][fields[0]] = {'name': fields[1]}
538                    else:
539                        fields = line.strip().split(maxsplit=1)
540                        if fields[0] == 'ffff':
541                            break
542                        last_vendor = fields[0]
543                        data[fields[0]] = {'name': fields[1]}
544        return data
545
546    def hardware(self, path, ogRest):
[1a83ebb]547        try:
[de2ce69]548            with OgQMP(self.IP, self.VIRTUAL_PORT) as qmp:
549                pci_data = qmp.talk(str({"execute": "query-pci"}))
550                mem_data = qmp.talk(str({"execute": "query-memory-size-summary"}))
551                cpu_data = qmp.talk(str({"execute": "query-cpus-fast"}))
[1a83ebb]552        except:
[de2ce69]553            return
[1a83ebb]554
555        pci_data = pci_data['return'][0]['devices']
[8ec8572]556        pci_list = self.parse_pci()
[99ae598]557        device_names = {}
[1a83ebb]558        for device in pci_data:
[99ae598]559            vendor_id = hex(device['id']['vendor'])[2:]
560            device_id = hex(device['id']['device'])[2:]
561            subvendor_id = hex(device['id']['subsystem-vendor'])[2:]
562            subdevice_id = hex(device['id']['subsystem'])[2:]
563            description = device['class_info']['desc'].lower()
564            name = ''
565            try:
566                name = pci_list[vendor_id]['name']
567                name += ' ' + pci_list[vendor_id][device_id]['name']
568                name += ' ' + pci_list[vendor_id][device_id][(subvendor_id, subdevice_id)]
569            except KeyError:
570                if vendor_id == '1234':
571                    name = 'VGA Cirrus Logic GD 5446'
572                else:
573                    pass
574
575            if 'usb' in description:
576                device_names['usb'] = name
577            elif 'ide' in description:
578                device_names['ide'] = name
579            elif 'ethernet' in description:
580                device_names['net'] = name
581            elif 'vga' in description:
582                device_names['vga'] = name
583
584            elif 'audio' in description or 'sound' in description:
585                device_names['aud'] = name
586            elif 'dvd' in description:
587                device_names['cdr'] = name
588
[1a83ebb]589        ram_size = int(mem_data['return']['base-memory']) * 2 ** -20
[99ae598]590        device_names['mem'] = f'QEMU {int(ram_size)}MiB'
591
[1a83ebb]592        cpu_arch = cpu_data['return'][0]['arch']
593        cpu_target = cpu_data['return'][0]['target']
594        cpu_cores = len(cpu_data['return'])
[99ae598]595        device_names['cpu'] = f'CPU arch:{cpu_arch} target:{cpu_target} ' \
596                              f'cores:{cpu_cores}'
[c279325]597
[99ae598]598        with open(path, 'w+') as f:
599            f.seek(0)
600            for k, v in device_names.items():
601                f.write(f'{k}={v}\n')
602            f.truncate()
[bd98dd1]603
604    def probe(self, ogRest):
605        return {'status': 'VDI' if ogRest.state != ThreadState.BUSY else 'BSY'}
Note: See TracBrowser for help on using the repository browser.