From c9a3a763ddf91ccf3d382a29996c4f2f5e767d9a Mon Sep 17 00:00:00 2001 From: "Jose M. Guisado" Date: Wed, 1 Mar 2023 17:59:04 +0100 Subject: legacy: rewrite ogGetImageInfo Rewrites this legacy script behavior using native Python code, using subprocess module when executing programs like partclone.info or lzop ogGetImageInfo is a bash script that retrieves information regarding an OpenGnsys partition image, specifically: - clonator - compressor - filesystem - datasize (size of the partition image) This rewrite only supports partclone and lzop compressed images. This is standard behavior, we have no reports of other programs or compression algorithms in use. Keep this legacy function with hungarian notation to emphasize this is still a legacy component that may be replaced in the future. --- src/ogRest.py | 8 ++-- src/utils/legacy.py | 118 +++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 108 insertions(+), 18 deletions(-) diff --git a/src/ogRest.py b/src/ogRest.py index 8e54612..02d3d0c 100644 --- a/src/ogRest.py +++ b/src/ogRest.py @@ -205,7 +205,7 @@ class ogThread(): return kibi = 1024 - datasize = int(image_info['datasize']) * kibi + datasize = int(image_info.datasize) * kibi json_body = jsonBody() json_body.add_element('disk', request.getDisk()) @@ -215,9 +215,9 @@ class ogThread(): json_body.add_element('name', request.getName()) json_body.add_element('repository', request.getRepo()) json_body.add_element('software', software) - json_body.add_element('clonator', image_info['clonator']) - json_body.add_element('compressor', image_info['compressor']) - json_body.add_element('filesystem', image_info['filesystem']) + json_body.add_element('clonator', image_info.clonator) + json_body.add_element('compressor', image_info.compressor) + json_body.add_element('filesystem', image_info.filesystem) json_body.add_element('datasize', datasize) response = restResponse(ogResponses.OK, json_body) diff --git a/src/utils/legacy.py b/src/utils/legacy.py index 509b9b0..51d98f3 100644 --- a/src/utils/legacy.py +++ b/src/utils/legacy.py @@ -5,28 +5,118 @@ import subprocess import shlex import shutil -from subprocess import PIPE, DEVNULL, CalledProcessError +from subprocess import PIPE, DEVNULL, STDOUT, CalledProcessError from src.utils.fs import umount -def ogGetImageInfo(path): +class ImageInfo: """ - Bash function 'ogGetImageInfo' wrapper (client/engine/Image.lib) + Class that stores the OpenGnsys partition image information. """ - proc = subprocess.run(f'ogGetImageInfo {path}', - stdout=PIPE, shell=True, - encoding='utf-8') + def __init__(self, filesystem=None, datasize=None): + self.filesystem = filesystem + self.datasize = datasize + self.clonator = 'PARTCLONE' + self.compressor = 'LZOP' - if proc.stdout.count(':') != 3: - return '' - image_info = {} - (image_info['clonator'], - image_info['compressor'], - image_info['filesystem'], - image_info['datasize']) = proc.stdout.rstrip().split(':', 4) - image_info['clientname'] = os.getenv('HOSTNAME') +def human_to_kb(size, unit): + """ + Returns the KB conversion of a human readable string size and unit. + """ + size = float(size.replace(',', '.')) + if unit == 'GB': + return size * 1000000 + if unit == 'MB': + return size * 1000 + return 0 + + +def fill_imageinfo(line, image_info): + """ + Updates ImageInfo object with information processed from + a single partclone.info output line. + + ImageInfo object may not be updated if the line is invalid or does not + contain filesystem or device size information. + """ + if 'File system' in line: + filesystem = line.rstrip().split(' ')[-1] + image_info.filesystem = filesystem + elif 'Device size' in line: + l = [word for word in line.rstrip().split(' ') if word][2:4] + device_size = human_to_kb(*l) + image_info.datasize = device_size + + +def process_output_partcloneinfo(output): + """ + Parses image information from partclone.info output. + + Returns an ImageInfo object. + """ + image_info = ImageInfo() + for n, line in enumerate(output.split('\n')): + if n < 2: + continue + if image_info.datasize and image_info.filesystem: + break + fill_imageinfo(line, image_info) + + if not image_info.datasize: + raise ValueError("Missing device size from partclone.info output") + elif not image_info.filesystem: + raise ValueError("Missing filesystem from partclone.info output") + + return image_info + + +def process_image_partcloneinfo(filename): + """ + Decompress using lzop and executes partclone.info to + fetch a partition image's information. + + Returns partclone.info stdout and stderr. + """ + cmd1 = f'{shutil.which("lzop")} -dc {filename}' + cmd2 = f'{shutil.which("partclone.info")} -s -' + args1 = shlex.split(cmd1) + args2 = shlex.split(cmd2) + p1 = subprocess.Popen(args1, stdout=PIPE, stderr=DEVNULL) + p2 = subprocess.Popen(args2, stdout=PIPE, stdin=p1.stdout, + stderr=STDOUT, encoding='utf-8') + p1.stdout.close() + p2_out, p2_err = p2.communicate() + + if p2.returncode != 0: + raise ValueError('Unable to process image {filename}') + + return p2_out + + +def ogGetImageInfo(filename): + """ + Obtain filesystem and device size information of an OpenGnsys partition + image. + + This method supports compressed images with lzop that have been created + with partclone. + + Returns an ImageInfo object. + + >>> image_info = ogGetImageInfo('/opt/opengnsys/images/foobar.img') + >>> image_info.filesystem + >>> 'NTFS' + >>> image_info.datasize + >>> 140000000 + >>> image_info.compressor + >>> 'LZOP' + >>> image_info.clonator + >>> 'PARTCLONE' + """ + out = process_image_partcloneinfo(filename) + image_info = process_output_partcloneinfo(out) return image_info -- cgit v1.2.3-18-g5258