X-Git-Url: https://code.grnet.gr/git/snf-image-creator/blobdiff_plain/09ed3d463dbe809486c0b9ea0b801b434a5062c6..d3f3bfbfbe2a3cc4a39ea3bda97fcdd3887995b1:/image_creator/dialog_wizard.py diff --git a/image_creator/dialog_wizard.py b/image_creator/dialog_wizard.py index d290669..31aa4a2 100644 --- a/image_creator/dialog_wizard.py +++ b/image_creator/dialog_wizard.py @@ -33,81 +33,285 @@ # interpreted as representing official policies, either expressed # or implied, of GRNET S.A. -import dialog +import time +import StringIO -class WizardPage: - NEXT = 1 - PREV = -1 +from image_creator.kamaki_wrapper import Kamaki, ClientError +from image_creator.util import MD5, FatalError +from image_creator.output.cli import OutputWthProgress +from image_creator.dialog_util import extract_image, update_background_title + +PAGE_WIDTH = 70 + + +class WizardExit(Exception): + pass + + +class WizardInvalidData(Exception): + pass - def __init__(self, session, title): + +class Wizard: + def __init__(self, session): self.session = session - self.title = title + self.pages = [] + self.session['wizard'] = {} + self.d = session['dialog'] + + def add_page(self, page): + self.pages.append(page) def run(self): + idx = 0 + while True: + try: + idx += self.pages[idx].run(self.session, idx, len(self.pages)) + except WizardExit: + return False + except WizardInvalidData: + continue + + if idx >= len(self.pages): + msg = "All necessary information has been gathered:\n\n" + for page in self.pages: + msg += " * %s\n" % page.info + msg += "\nContinue with the image creation process?" + + ret = self.d.yesno( + msg, width=PAGE_WIDTH, height=8 + len(self.pages), + ok_label="Yes", cancel="Back", extra_button=1, + extra_label="Quit", title="Confirmation") + + if ret == self.d.DIALOG_CANCEL: + idx -= 1 + elif ret == self.d.DIALOG_EXTRA: + return False + elif ret == self.d.DIALOG_OK: + return True + + if idx < 0: + return False + + +class WizardPage(object): + NEXT = 1 + PREV = -1 + + def __init__(self, **kargs): + validate = kargs['validate'] if 'validate' in kargs else lambda x: x + setattr(self, "validate", validate) + + display = kargs['display'] if 'display' in kargs else lambda x: x + setattr(self, "display", display) + + def run(self, session, index, total): raise NotImplementedError -class ImageName(WizardPage): - def run(self): - d = self.session['dialog'] - w = self.session['wizard'] - - init = w['ImageName'] if 'ImageName' in w else "" - while 1: - (code, answer) = d.inputbox("Please provide a name for the image:", - init=init, width=INPUTBOX_WIDTH, - ok_label="Next", cancel="Back", - title=self.title) - - if code in (d.DIALOG_CANCEL, d.DIALOG_ESC): - return self.PREV - - name = answer.strip() - if len(name) == 0: - d.msgbox("Image name cannot be empty", width=MSGBOX_WIDTH) - continue - w['ImageName'] = name - break +class WizardRadioListPage(WizardPage): + + def __init__(self, name, printable, message, choices, **kargs): + super(WizardRadioListPage, self).__init__(**kargs) + self.name = name + self.printable = printable + self.message = message + self.choices = choices + self.title = kargs['title'] if 'title' in kargs else '' + self.default = kargs['default'] if 'default' in kargs else "" + + def run(self, session, index, total): + d = session['dialog'] + w = session['wizard'] + + choices = [] + for i in range(len(self.choices)): + default = 1 if self.choices[i][0] == self.default else 0 + choices.append((self.choices[i][0], self.choices[i][1], default)) + + (code, answer) = d.radiolist( + self.message, height=10, width=PAGE_WIDTH, ok_label="Next", + cancel="Back", choices=choices, + title="(%d/%d) %s" % (index + 1, total, self.title)) + + if code in (d.DIALOG_CANCEL, d.DIALOG_ESC): + return self.PREV + + w[self.name] = self.validate(answer) + self.default = answer + self.info = "%s: %s" % (self.printable, self.display(w[self.name])) return self.NEXT -class ImageDescription(WizardPage): - def run(self): - d = self.session['dialog'] - w = self.session['wizard'] +class WizardInputPage(WizardPage): - init = w['ImageDescription'] if 'ImageDescription' in w else "" + def __init__(self, name, printable, message, **kargs): + super(WizardInputPage, self).__init__(**kargs) + self.name = name + self.printable = printable + self.message = message + self.title = kargs['title'] if 'title' in kargs else '' + self.init = kargs['init'] if 'init' in kargs else '' - while 1: - (code, answer) = d.inputbox( - "Please provide a description for the image:", - init=init, width=INPUTBOX_WIDTH, - ok_label="Next", cancel="Back", - title=self.title) + def run(self, session, index, total): + d = session['dialog'] + w = session['wizard'] - if code in (d.DIALOG_CANCEL, d.DIALOG_ESC): - return self.PREV + (code, answer) = d.inputbox( + self.message, init=self.init, width=PAGE_WIDTH, ok_label="Next", + cancel="Back", title="(%d/%d) %s" % (index + 1, total, self.title)) - name = answer.strip() - if len(filename) == 0: - # Description is allowed to be empty - del w['ImageDescription'] - else: - w['ImageDescription'] = name - break + if code in (d.DIALOG_CANCEL, d.DIALOG_ESC): + return self.PREV + + value = answer.strip() + self.init = value + w[self.name] = self.validate(value) + self.info = "%s: %s" % (self.printable, self.display(w[self.name])) return self.NEXT def wizard(session): - session['wizard'] = {} + init_token = Kamaki.get_token() + if init_token is None: + init_token = "" + + name = WizardInputPage( + "ImageName", "Image Name", "Please provide a name for the image:", + title="Image Name", init=session['device'].distro) + + descr = WizardInputPage( + "ImageDescription", "Image Description", + "Please provide a description for the image:", + title="Image Description", init=session['metadata']['DESCRIPTION'] if + 'DESCRIPTION' in session['metadata'] else '') + + registration = WizardRadioListPage( + "ImageRegistration", "Registration Type", + "Please provide a registration type:", + [("Private", "Image is accessible only by this user"), + ("Public", "Everyone can create VMs from this image")], + title="Registration Type", default="Private") + + def validate_account(token): + d = session['dialog'] - steps = [] - steps.append(ImageName(session, "(1/5) Image Name")) - steps.append(ImageDescription(session, "(2/5) Image Description")) + if len(token) == 0: + d.msgbox("The token cannot be empty", width=PAGE_WIDTH) + raise WizardInvalidData + + account = Kamaki.get_account(token) + if account is None: + d.msgbox("The token you provided in not valid!", width=PAGE_WIDTH) + raise WizardInvalidData + + return account + + account = WizardInputPage( + "Account", "Account", + "Please provide your ~okeanos authentication token:", + title="~okeanos account", init=init_token, validate=validate_account, + display=lambda account: account['username']) + + w = Wizard(session) + + w.add_page(name) + w.add_page(descr) + w.add_page(registration) + w.add_page(account) + + if w.run(): + create_image(session) + else: + return False return True +def create_image(session): + d = session['dialog'] + disk = session['disk'] + device = session['device'] + snapshot = session['snapshot'] + image_os = session['image_os'] + wizard = session['wizard'] + + # Save Kamaki credentials + Kamaki.save_token(wizard['Account']['auth_token']) + + with_progress = OutputWthProgress(True) + out = disk.out + out.add(with_progress) + try: + out.clear() + + #Sysprep + device.mount(False) + image_os.do_sysprep() + metadata = image_os.meta + device.umount() + + #Shrink + size = device.shrink() + session['shrinked'] = True + update_background_title(session) + + metadata.update(device.meta) + metadata['DESCRIPTION'] = wizard['ImageDescription'] + + #MD5 + md5 = MD5(out) + session['checksum'] = md5.compute(snapshot, size) + + #Metadata + metastring = '\n'.join( + ['%s=%s' % (key, value) for (key, value) in metadata.items()]) + metastring += '\n' + + out.output() + try: + out.output("Uploading image to pithos:") + kamaki = Kamaki(wizard['Account'], out) + + name = "%s-%s.diskdump" % (wizard['ImageName'], + time.strftime("%Y%m%d%H%M")) + pithos_file = "" + with open(snapshot, 'rb') as f: + pithos_file = kamaki.upload(f, size, name, + "(1/4) Calculating block hashes", + "(2/4) Uploading missing blocks") + + out.output("(3/4) Uploading metadata file ...", False) + kamaki.upload(StringIO.StringIO(metastring), size=len(metastring), + remote_path="%s.%s" % (name, 'meta')) + out.success('done') + out.output("(4/4) Uploading md5sum file ...", False) + md5sumstr = '%s %s\n' % (session['checksum'], name) + kamaki.upload(StringIO.StringIO(md5sumstr), size=len(md5sumstr), + remote_path="%s.%s" % (name, 'md5sum')) + out.success('done') + out.output() + + is_public = True if wizard['ImageRegistration'] == "Public" else \ + False + out.output('Registering %s image with ~okeanos ...' % + wizard['ImageRegistration'].lower(), False) + kamaki.register(wizard['ImageName'], pithos_file, metadata, + is_public) + out.success('done') + out.output() + + except ClientError as e: + raise FatalError("Pithos client: %d %s" % (e.status, e.message)) + finally: + out.remove(with_progress) + + msg = "The %s image was successfully uploaded and registered with " \ + "~okeanos. Would you like to keep a local copy of the image?" \ + % wizard['ImageRegistration'].lower() + if not d.yesno(msg, width=PAGE_WIDTH): + extract_image(session) + # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :