Support private images
authorNikos Skalkotos <skalkoto@grnet.gr>
Sun, 17 Mar 2013 21:55:02 +0000 (23:55 +0200)
committerNikos Skalkotos <skalkoto@grnet.gr>
Sun, 17 Mar 2013 22:13:15 +0000 (00:13 +0200)
Support images that can only be deployed by the user that registers
the image to cyclades

image_creator/bundle_volume.py
image_creator/dialog_menu.py
image_creator/dialog_wizard.py
image_creator/kamaki_wrapper.py
image_creator/main.py

index e32d90b..a5cc9d7 100644 (file)
@@ -143,9 +143,9 @@ class BundleVolume(object):
             # Copy the Secondary GPT Header
             table = GPTPartitionTable(self.disk.device.path)
             dd('if=%s' % self.disk.device.path, 'of=%s' % image,
-            'bs=%d' % self.disk.device.sectorSize, 'conv=notrunc',
-            'seek=%d' % table.primary.last_usable_lba,
-            'skip=%d' % table.primary.last_usable_lba)
+               'bs=%d' % self.disk.device.sectorSize, 'conv=notrunc',
+               'seek=%d' % table.primary.last_usable_lba,
+               'skip=%d' % table.primary.last_usable_lba)
 
         # Create the Extended boot records (EBRs) in the image
         extended = self.disk.getExtendedPartition()
index c8cfe27..d54af2a 100644 (file)
@@ -185,8 +185,10 @@ def register_image(session):
     d = session["dialog"]
     dev = session['device']
 
+    is_public = False
+
     if "account" not in session:
-        d.msgbox("You need to provide your ~okeanos login username before you "
+        d.msgbox("You need to provide your ~okeanos credentians before you "
                  "can register an images to cyclades",
                  width=SMALL_WIDTH)
         return False
@@ -206,6 +208,15 @@ def register_image(session):
         if len(name) == 0:
             d.msgbox("Registration name cannot be empty", width=SMALL_WIDTH)
             continue
+
+        ret = d.yesno("Make the image public?\\nA public image is accessible"
+                      "by every user of the service.", defaultno=1,
+                      width=WIDTH)
+        if ret not in (0, 1):
+            continue
+
+        is_public = True if ret == 0 else False
+
         break
 
     metadata = {}
@@ -214,15 +225,17 @@ def register_image(session):
         for key in session['task_metadata']:
             metadata[key] = 'yes'
 
+    img_type = "public" if is_public else "private"
     gauge = GaugeOutput(d, "Image Registration", "Registering image...")
     try:
         out = dev.out
         out.add(gauge)
         try:
-            out.output("Registering image with Cyclades...")
+            out.output("Registering %s image with Cyclades..." % img_type)
             try:
                 kamaki = Kamaki(session['account'], out)
-                kamaki.register(name, session['pithos_uri'], metadata)
+                kamaki.register(name, session['pithos_uri'], metadata,
+                                is_public)
                 out.success('done')
             except ClientError as e:
                 d.msgbox("Error in pithos+ client: %s" % e.message)
@@ -232,8 +245,8 @@ def register_image(session):
     finally:
         gauge.cleanup()
 
-    d.msgbox("Image `%s' was successfully registered with Cyclades as `%s'" %
-             (session['upload'], name), width=SMALL_WIDTH)
+    d.msgbox("%s image `%s' was successfully registered with Cyclades as `%s'"
+             % (img_type.title(), session['upload'], name), width=SMALL_WIDTH)
     return True
 
 
@@ -286,7 +299,7 @@ def kamaki_menu(session):
                 else:
                     del session['account']
                     d.msgbox("The token you provided is not valid!",
-                        width=SMALL_WIDTH)
+                             width=SMALL_WIDTH)
         elif choice == "Upload":
             if upload_image(session):
                 default_item = "Register"
index ee62963..61e7ecd 100644 (file)
@@ -103,12 +103,12 @@ class WizardRadioListPage(WizardPage):
 
         choices = []
         for i in range(len(self.choices)):
-            default = 1 if i == self.default else 0
+            default = 1 if self.choices[i][0] == self.default else 0
             choices.append((self.choices[i][0], self.choices[i][1], default))
 
         while True:
             (code, answer) = \
-                d.radiolist(self.message, width=PAGE_WIDTH,
+                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)
                             )
@@ -116,11 +116,8 @@ class WizardRadioListPage(WizardPage):
             if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
                 return self.PREV
 
-            for i in range(len(choices)):
-                if self.choices[i] == answer:
-                    self.default = i
-                    w[name] = i
-                    break
+            w[self.name] = answer
+            self.default = answer
 
             return self.NEXT
 
@@ -190,11 +187,15 @@ def wizard(session):
 
     name = WizardInputPage("ImageName", "Please provide a name for the image:",
                            title="Image Name", init=session['device'].distro)
-    descr = WizardInputPage("ImageDescription",
-                            "Please provide a description for the image:",
-                            title="Image Description",
-                            init=session['metadata']['DESCRIPTION'] if
-                            'DESCRIPTION' in session['metadata'] else '')
+    descr = WizardInputPage(
+        "ImageDescription", "Please provide a description for the image:",
+        title="Image Description", init=session['metadata']['DESCRIPTION'] if
+        'DESCRIPTION' in session['metadata'] else '')
+    registration = WizardRadioListPage(
+        "ImageRegistration", "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):
         if len(token) == 0:
@@ -204,15 +205,14 @@ def wizard(session):
         account = Kamaki.get_account(token)
         if account is None:
             session['dialog'].msgbox("The token you provided in not valid!",
-                width=PAGE_WIDTH)
+                                     width=PAGE_WIDTH)
             raise WizardInvalidData
 
         return account
 
-    account = WizardInputPage("account",
-        "Please provide your ~okeanos authentication token:",
-        title="~okeanos account token", init=init_token,
-        validate=validate_account)
+    account = WizardInputPage(
+        "account", "Please provide your ~okeanos authentication token:",
+        title="~okeanos account", init=init_token, validate=validate_account)
 
     msg = "All necessary information has been gathered. Confirm and Proceed."
     proceed = WizardYesNoPage(msg, title="Confirmation")
@@ -221,6 +221,7 @@ def wizard(session):
 
     w.add_page(name)
     w.add_page(descr)
+    w.add_page(registration)
     w.add_page(account)
     w.add_page(proceed)
 
@@ -296,8 +297,11 @@ def create_image(session):
             out.success('done')
             out.output()
 
-            out.output('Registering image with ~okeanos ...', False)
-            kamaki.register(wizard['ImageName'], pithos_file, metadata)
+            is_public = True if w['ImageRegistration'] == "Public" else False
+            out.output('Registering %s image with ~okeanos ...' %
+                       w['ImageRegistration'].lower(), False)
+            kamaki.register(wizard['ImageName'], pithos_file, metadata,
+                            is_public)
             out.success('done')
             out.output()
 
@@ -306,8 +310,9 @@ def create_image(session):
     finally:
         out.remove(with_progress)
 
-    msg = "The image was successfully uploaded and registered with " \
-          "~okeanos. Would you like to keep a local copy of the image?"
+    msg = "The %s image was successfully uploaded and registered with " \
+          "~okeanos. Would you like to keep a local copy of the image?" \
+          % w['ImageRegistration'].lower()
     if not d.yesno(msg, width=PAGE_WIDTH):
         extract_image(session)
 
index dafea32..5963990 100644 (file)
@@ -77,8 +77,9 @@ class Kamaki(object):
         config = Config()
 
         pithos_url = config.get('store', 'url')
-        self.pithos_client = PithosClient(pithos_url,
-            self.account['auth_token'], self.account['uuid'], self.CONTAINER)
+        self.pithos_client = PithosClient(
+            pithos_url, self.account['auth_token'], self.account['uuid'],
+            self.CONTAINER)
 
         image_url = config.get('image', 'url')
         self.image_client = ImageClient(image_url, self.account['auth_token'])
@@ -103,15 +104,15 @@ class Kamaki(object):
         return "pithos://%s/%s/%s" % (self.account['uuid'], self.CONTAINER,
                                       path)
 
-    def register(self, name, location, metadata):
+    def register(self, name, location, metadata, public=False):
         """Register an image to ~okeanos"""
 
         # Convert all metadata to strings
         str_metadata = {}
         for (key, value) in metadata.iteritems():
             str_metadata[str(key)] = str(value)
-
-        params = {'is_public': 'true', 'disk_format': 'diskdump'}
+        is_public = 'true' if public else 'false'
+        params = {'is_public': is_public, 'disk_format': 'diskdump'}
         self.image_client.register(name, location, params, str_metadata)
 
 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :
index afdf1a5..6d46ae3 100644 (file)
@@ -118,6 +118,10 @@ def parse_options(input_args):
     parser.add_option("--no-shrink", dest="shrink", default=True,
                       help="don't shrink any partition", action="store_false")
 
+    parser.add_option("--public", dest="public", default=False,
+                      help="register image to cyclades as public",
+                      action="store_true")
+
     parser.add_option("--tmpdir", dest="tmp", type="string", default=None,
                       help="create large temporary image files under DIR",
                       metavar="DIR")
@@ -135,7 +139,8 @@ def parse_options(input_args):
         raise FatalError("You also need to set -u when -r option is set")
 
     if options.upload and options.token is None:
-        raise FatalError("Image uploading cannot be performed. "
+        raise FatalError(
+            "Image uploading cannot be performed. "
             "No authentication token is specified. Use -t to set a token")
 
     if options.tmp is not None and not os.path.isdir(options.tmp):
@@ -270,7 +275,8 @@ def image_creator():
                 out.output("Uploading image to pithos:")
                 kamaki = Kamaki(account, out)
                 with open(snapshot, 'rb') as f:
-                    uploaded_obj = kamaki.upload(f, size, options.upload,
+                    uploaded_obj = kamaki.upload(
+                        f, size, options.upload,
                         "(1/4)  Calculating block hashes",
                         "(2/4)  Uploading missing blocks")
 
@@ -289,8 +295,11 @@ def image_creator():
                 out.output()
 
             if options.register:
-                out.output('Registering image with ~okeanos ...', False)
-                kamaki.register(options.register, uploaded_obj, metadata)
+                img_type = 'public' if options.public else 'private'
+                out.output('Registering %s image with ~okeanos ...' % img_type,
+                           False)
+                kamaki.register(options.register, uploaded_obj, metadata,
+                                options.public)
                 out.success('done')
                 out.output()
         except ClientError as e: