commit ac6a221f4ba3ea45fb10c9a404c0c0ad638a7f4f
Author: Christos Stavrakakis <cstavr@grnet.gr>
Date:   Fri Oct 18 16:52:12 2013 +0300

    cyclades: Add management commands for volumes

diff --git a/snf-cyclades-app/synnefo/logic/reconciliation.py b/snf-cyclades-app/synnefo/logic/reconciliation.py
index 597bef7..e2d1330 100644
--- a/snf-cyclades-app/synnefo/logic/reconciliation.py
+++ b/snf-cyclades-app/synnefo/logic/reconciliation.py
@@ -479,16 +479,21 @@ def nics_from_instance(i):
     return nics
 
 
+def disks_from_instance(i):
+    sizes = zip(itertools.repeat('size'), i['disk.sizes'])
+    names = zip(itertools.repeat('name'), i['disk.names'])
+    uuids = zip(itertools.repeat('uuid'), i['disk.uuids'])
+    disks = zip(sizes, names, uuids)
+    disks = map(lambda x: dict(x), disks)
+    #disks = dict(enumerate(disks))
+    return disks
+
+
 def get_ganeti_jobs(backend):
     gnt_jobs = backend_mod.get_jobs(backend)
     return dict([(int(j["id"]), j) for j in gnt_jobs])
 
 
-def disks_from_instance(i):
-    return dict([(index, {"size": size})
-                 for index, size in enumerate(i["disk.sizes"])])
-
-
 class NetworkReconciler(object):
     def __init__(self, logger, fix=False):
         self.log = logger
diff --git a/snf-cyclades-app/synnefo/management/common.py b/snf-cyclades-app/synnefo/management/common.py
index 87021e3..81d85c8 100644
--- a/snf-cyclades-app/synnefo/management/common.py
+++ b/snf-cyclades-app/synnefo/management/common.py
@@ -35,7 +35,7 @@ from django.core.management import CommandError
 from synnefo.db.models import (Backend, VirtualMachine, Network,
                                Flavor, IPAddress, Subnet,
                                BridgePoolTable, MacPrefixPoolTable,
-                               NetworkInterface, IPAddressLog)
+                               NetworkInterface, IPAddressLog, Volume)
 from functools import wraps
 
 from snf_django.lib.api import faults
@@ -177,7 +177,7 @@ def get_flavor(flavor_id, for_update=False):
             objs = objs.select_for_update()
         return objs.get(id=flavor_id)
     except ValueError:
-        raise CommandError("Invalid flavor ID: %s", flavor_id)
+        raise CommandError("Invalid flavor ID: %s" % flavor_id)
     except Flavor.DoesNotExist:
         raise CommandError("Flavor with ID %s not found in DB."
                            " Use snf-manage flavor-list to find out"
@@ -218,6 +218,21 @@ def get_floating_ip_by_id(floating_ip_id, for_update=False):
         raise CommandError("Floating IP %s does not exist." % floating_ip_id)
 
 
+def get_volume(volume_id, for_update=False):
+    try:
+        volume_id = int(volume_id)
+        objs = Volume.objects
+        if for_update:
+            objs = objs.select_for_update()
+        return objs.get(id=volume_id)
+    except ValueError:
+        raise CommandError("Invalid volume ID: %s" % volume_id)
+    except Volume.DoesNotExist:
+        raise CommandError("Volume with ID %s not found in DB."
+                           " Use snf-manage volume-list to find out"
+                           " available volume IDs." % volume_id)
+
+
 def check_backend_credentials(clustername, port, username, password):
     try:
         client = GanetiRapiClient(clustername, port, username, password)
diff --git a/snf-cyclades-app/synnefo/management/pprint.py b/snf-cyclades-app/synnefo/management/pprint.py
index cf5571c..e94dc2c 100644
--- a/snf-cyclades-app/synnefo/management/pprint.py
+++ b/snf-cyclades-app/synnefo/management/pprint.py
@@ -41,7 +41,8 @@ from synnefo.db.models import Backend, pooled_rapi_client
 from synnefo.db.pools import bitarray_to_map
 
 from synnefo.logic.rapi import GanetiApiError
-from synnefo.logic.reconciliation import nics_from_instance
+from synnefo.logic.reconciliation import (nics_from_instance,
+                                          disks_from_instance)
 from synnefo.management.common import get_image
 
 
@@ -259,16 +260,16 @@ def pprint_port_in_ganeti(port, stdout=None, title=None):
             return
         raise e
 
-    nics = nics_from_instance(vm_info)
+    disks = disks_from_instance(vm_info)
     try:
-        gnt_nic = filter(lambda nic: nic.get("name") == port.backend_uuid,
-                         nics)[0]
-        gnt_nic["instance"] = vm_info["name"]
+        gnt_disk = filter(lambda disk: disk.get("name") == port.backend_uuid,
+                          disks)[0]
+        gnt_disk["instance"] = vm_info["name"]
     except IndexError:
         stdout.write("Port %s is not attached to instance %s\n" %
                      (port.id, vm.id))
         return
-    pprint_table(stdout, gnt_nic.items(), None, separator=" | ",
+    pprint_table(stdout, gnt_disk.items(), None, separator=" | ",
                  title=title)
 
     vm.put_client(client)
@@ -382,3 +383,68 @@ def pprint_server_in_ganeti(server, print_jobs=False, stdout=None, title=None):
                      separator=" | ",
                      title="Ganeti Job %s" % server_job["id"])
     server.put_client(client)
+
+
+def pprint_volume(volume, display_mails=False, stdout=None, title=None):
+    if stdout is None:
+        stdout = sys.stdout
+    if title is None:
+        title = "State of volume %s in DB" % volume.id
+
+    ucache = UserCache(ASTAKOS_AUTH_URL, ASTAKOS_TOKEN)
+    userid = volume.userid
+
+    volume_dict = OrderedDict([
+        ("id", volume.id),
+        ("size", volume.size),
+        ("disk_template", volume.disk_template),
+        ("disk_provider", volume.disk_provider),
+        ("server_id", volume.machine_id),
+        ("userid", volume.userid),
+        ("username", ucache.get_name(userid) if display_mails else None),
+        ("name", volume.name),
+        ("state", volume.status),
+        ("deleted", volume.deleted),
+        ("backendjobid", volume.backendjobid),
+        ])
+
+    pprint_table(stdout, volume_dict.items(), None, separator=" | ",
+                 title=title)
+
+
+def pprint_volume_in_ganeti(volume, stdout=None, title=None):
+    if stdout is None:
+        stdout = sys.stdout
+    if title is None:
+        title = "State of volume %s in Ganeti" % volume.id
+
+    vm = volume.machine
+    if vm is None:
+        stdout.write("volume is not attached to any instance.\n")
+        return
+
+    client = vm.get_client()
+    try:
+        vm_info = client.GetInstance(vm.backend_vm_id)
+    except GanetiApiError as e:
+        if e.code == 404:
+            stdout.write("Volume seems attached to server %s, but"
+                         " server does not exist in backend.\n"
+                         % vm)
+            return
+        raise e
+
+    disks = disks_from_instance(vm_info)
+    try:
+        gnt_disk = filter(lambda disk:
+                          disk.get("name") == volume.backend_volume_uuid,
+                          disks)[0]
+        gnt_disk["instance"] = vm_info["name"]
+    except IndexError:
+        stdout.write("Volume %s is not attached to instance %s\n" % (volume.id,
+                                                                     vm.id))
+        return
+    pprint_table(stdout, gnt_disk.items(), None, separator=" | ",
+                 title=title)
+
+    vm.put_client(client)
diff --git a/snf-cyclades-app/synnefo/volume/management/__init__.py b/snf-cyclades-app/synnefo/volume/management/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/snf-cyclades-app/synnefo/volume/management/commands/__init__.py b/snf-cyclades-app/synnefo/volume/management/commands/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/snf-cyclades-app/synnefo/volume/management/commands/snapshot-create.py b/snf-cyclades-app/synnefo/volume/management/commands/snapshot-create.py
new file mode 100644
index 0000000..7132b12
--- /dev/null
+++ b/snf-cyclades-app/synnefo/volume/management/commands/snapshot-create.py
@@ -0,0 +1,89 @@
+# Copyright 2013 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+#   1. Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials
+#      provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+from optparse import make_option
+
+from django.core.management.base import BaseCommand, CommandError
+from synnefo.management import common
+#from snf_django.management.utils import parse_bool
+from synnefo.volume import snapshots
+
+
+class Command(BaseCommand):
+    args = "<volume ID>"
+    help = "Create a snapshot from the specified volume"
+
+    option_list = BaseCommand.option_list + (
+        make_option(
+            '--wait',
+            dest='wait',
+            default="True",
+            choices=["True", "False"],
+            metavar="True|False",
+            help="Wait for Ganeti job to complete."),
+        make_option(
+            "--name",
+            dest="name",
+            default=None,
+            help="Display name of the snapshot"),
+        make_option(
+            "--description",
+            dest="description",
+            default=None,
+            help="Display description of the snapshot"),
+    )
+
+    @common.convert_api_faults
+    def handle(self, *args, **options):
+        if len(args) != 1:
+            raise CommandError("Please provide a volume ID")
+
+        volume = common.get_volume(args[0])
+
+        name = options.get("name")
+        if name is None:
+            raise CommandError("'name' option is required")
+
+        description = options.get("description")
+        if description is None:
+            description = "Snapshot of Volume '%s" % volume.id
+
+        snapshot = snapshots.create(volume.userid,
+                                    volume,
+                                    name=name,
+                                    description=description,
+                                    metadata={})
+
+        msg = ("Created snapshot of volume '%s' with ID %s\n"
+               % (volume.id, snapshot["uuid"]))
+        self.stdout.write(msg)
diff --git a/snf-cyclades-app/synnefo/volume/management/commands/snapshot-list.py b/snf-cyclades-app/synnefo/volume/management/commands/snapshot-list.py
new file mode 100644
index 0000000..d4991ff
--- /dev/null
+++ b/snf-cyclades-app/synnefo/volume/management/commands/snapshot-list.py
@@ -0,0 +1,62 @@
+# Copyright 2011-2012 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+#   1. Redistributions of source code must retain the above copyright
+#      notice, this list of conditions and the following disclaimer.
+#
+#  2. Redistributions in binary form must reproduce the above copyright
+#     notice, this list of conditions and the following disclaimer in the
+#     documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and documentation are
+# those of the authors and should not be interpreted as representing official
+# policies, either expressed or implied, of GRNET S.A.
+#
+
+from django.core.management.base import BaseCommand
+from optparse import make_option
+
+from snf_django.management.utils import pprint_table
+from synnefo.plankton.utils import image_backend
+
+
+class Command(BaseCommand):
+    help = "List public snapshots or snapshots available to a user."
+    option_list = BaseCommand.option_list + (
+        make_option(
+            '--user-id',
+            dest='userid',
+            default=None,
+            help="List all snapshots available to that user."
+                 " If no user is specified, only public snapshots"
+                 " are displayed."),
+    )
+
+    def handle(self, **options):
+        user = options['userid']
+
+        with image_backend(user) as backend:
+            snapshots = backend.list_snapshots(user)
+
+        headers = ("id", "name", "volume_id", "size", "map")
+        table = []
+        for snap in snapshots:
+            fields = (snap["uuid"], snap["name"], snap["volume_id"],
+                      snap["size"], snap["map"])
+            table.append(fields)
+        pprint_table(self.stdout, table, headers)
diff --git a/snf-cyclades-app/synnefo/volume/management/commands/snapshot-remove.py b/snf-cyclades-app/synnefo/volume/management/commands/snapshot-remove.py
new file mode 100644
index 0000000..87080a7
--- /dev/null
+++ b/snf-cyclades-app/synnefo/volume/management/commands/snapshot-remove.py
@@ -0,0 +1,67 @@
+# Copyright 2011-2013 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+#   1. Redistributions of source code must retain the above copyright
+#      notice, this list of conditions and the following disclaimer.
+#
+#  2. Redistributions in binary form must reproduce the above copyright
+#     notice, this list of conditions and the following disclaimer in the
+#     documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and documentation are
+# those of the authors and should not be interpreted as representing official
+# policies, either expressed or implied, of GRNET S.A.
+#
+from optparse import make_option
+from django.core.management.base import CommandError
+from synnefo.volume import snapshots, util
+from synnefo.management import common
+from snf_django.management.commands import RemoveCommand
+
+
+class Command(RemoveCommand):
+    args = "<Snapshot ID> [<Snapshot ID> ...]"
+    help = "Remove a snapshot"
+    option_list = RemoveCommand.option_list + (
+        make_option(
+            "--user_id",
+            dest="user_id",
+            default=None,
+            help="UUID of the owner of the snapshot"),
+    )
+
+    @common.convert_api_faults
+    def handle(self, *args, **options):
+        if not args:
+            raise CommandError("Please provide a snapshot ID")
+
+        force = options['force']
+        message = "snapshots" if len(args) > 1 else "snapshot"
+        self.confirm_deletion(force, message, args)
+        user_id = self.options["user_id"]
+
+        for snapshot_id in args:
+            self.stdout.write("\n")
+            try:
+                snapshot = util.get_snapshot(user_id, snapshot_id)
+
+                snapshots.delete(snapshot)
+                self.stdout.write("Successfully removed snapshot %s\n"
+                                  % snapshot)
+            except CommandError as e:
+                self.stdout.write("Error -- %s\n" % e.message)
diff --git a/snf-cyclades-app/synnefo/volume/management/commands/volume-create.py b/snf-cyclades-app/synnefo/volume/management/commands/volume-create.py
new file mode 100644
index 0000000..3a231cb
--- /dev/null
+++ b/snf-cyclades-app/synnefo/volume/management/commands/volume-create.py
@@ -0,0 +1,123 @@
+# Copyright 2013 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+#   1. Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials
+#      provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+from optparse import make_option
+
+from django.core.management.base import BaseCommand, CommandError
+
+from snf_django.management.utils import parse_bool
+from synnefo.management import common, pprint
+from synnefo.volume import volumes
+
+HELP_MSG = """Create a new volume."""
+
+
+class Command(BaseCommand):
+    help = HELP_MSG
+
+    option_list = BaseCommand.option_list + (
+        make_option(
+            "--name",
+            dest="name",
+            default=None,
+            help="Display name of the volume."),
+        make_option(
+            "--description",
+            dest="description",
+            default=None,
+            help="Display description of the volume."),
+        make_option(
+            "--owner",
+            dest="user_id",
+            default=None,
+            help="UUID of the owner of the volume."),
+        make_option(
+            "-s", "--size",
+            dest="size",
+            default=None,
+            help="Size of the new volume in GB"),
+        make_option(
+            "--server",
+            dest="server_id",
+            default=None,
+            help="The ID of the server that the volume will be connected to."),
+        make_option(
+            "--wait",
+            dest="wait",
+            default="True",
+            choices=["True", "False"],
+            metavar="True|False",
+            help="Wait for Ganeti jobs to complete."),
+    )
+
+    @common.convert_api_faults
+    def handle(self, *args, **options):
+        if args:
+            raise CommandError("Command doesn't accept any arguments")
+
+        size = options.get("size")
+        user_id = options.get("user_id")
+        server_id = options.get("server_id")
+        wait = parse_bool(options["wait"])
+
+        display_name = options.get("name", "")
+        display_description = options.get("description", "")
+
+        if size is None:
+            raise CommandError("Please specify the size of the volume")
+
+        if server_id is None:
+            raise CommandError("Please specify the server to attach the"
+                               " volume.")
+
+        vm = common.get_vm(server_id)
+
+        if user_id is None:
+            user_id = vm.userid
+
+        source_image_id = source_volume_id = source_snapshot_id = None
+        volume = volumes.create(user_id, size, server_id,
+                                name=display_name,
+                                description=display_description,
+                                source_image_id=source_image_id,
+                                source_snapshot_id=source_snapshot_id,
+                                source_volume_id=source_volume_id,
+                                metadata={})
+
+        self.stdout.write("Created volume '%s' in DB:\n" % volume.id)
+
+        pprint.pprint_volume(volume, stdout=self.stdout)
+        self.stdout.write("\n")
+        if volume.machine is not None:
+            volume.machine.task_job_id = volume.backendjobid
+            common.wait_server_task(volume.machine, wait, stdout=self.stdout)
diff --git a/snf-cyclades-app/synnefo/volume/management/commands/volume-inspect.py b/snf-cyclades-app/synnefo/volume/management/commands/volume-inspect.py
new file mode 100644
index 0000000..f417f70
--- /dev/null
+++ b/snf-cyclades-app/synnefo/volume/management/commands/volume-inspect.py
@@ -0,0 +1,64 @@
+# Copyright 2013 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+#   1. Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials
+#      provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+from optparse import make_option
+from django.core.management.base import BaseCommand, CommandError
+
+from synnefo.management.common import convert_api_faults
+from synnefo.management import pprint, common
+
+
+class Command(BaseCommand):
+    help = "Inspect a Volume on DB and Ganeti"
+    args = "<volume ID>"
+
+    option_list = BaseCommand.option_list + (
+        make_option(
+            '--displayname',
+            action='store_true',
+            dest='displayname',
+            default=False,
+            help="Display both uuid and display name"),
+    )
+
+    @convert_api_faults
+    def handle(self, *args, **options):
+        if len(args) != 1:
+            raise CommandError("Please provide a volume ID")
+
+        volume = common.get_volume(args[0])
+
+        pprint.pprint_volume(volume, stdout=self.stdout)
+        self.stdout.write('\n\n')
+
+        pprint.pprint_volume_in_ganeti(volume, stdout=self.stdout)
diff --git a/snf-cyclades-app/synnefo/volume/management/commands/volume-list.py b/snf-cyclades-app/synnefo/volume/management/commands/volume-list.py
new file mode 100644
index 0000000..7d575d7
--- /dev/null
+++ b/snf-cyclades-app/synnefo/volume/management/commands/volume-list.py
@@ -0,0 +1,66 @@
+# Copyright 2012-2013 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+#   1. Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials
+#      provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+#from optparse import make_option
+
+from snf_django.management.commands import ListCommand
+from synnefo.db.models import Volume
+from synnefo.settings import (CYCLADES_SERVICE_TOKEN as ASTAKOS_TOKEN,
+                              ASTAKOS_AUTH_URL)
+from logging import getLogger
+log = getLogger(__name__)
+
+
+class Command(ListCommand):
+    help = "List Volumes"
+
+    option_list = ListCommand.option_list
+
+    object_class = Volume
+    deleted_field = "deleted"
+    user_uuid_field = "userid"
+    astakos_auth_url = ASTAKOS_AUTH_URL
+    astakos_token = ASTAKOS_TOKEN
+
+    FIELDS = {
+        "id": ("id", "ID of the server"),
+        "name": ("name", "Name of the server"),
+        "user.uuid": ("userid", "The UUID of the server's owner"),
+        "server_id": ("machine_id", ""),
+        "source": ("source", ""),
+        "status": ("status", ""),
+        "created": ("created", "The date the server was created"),
+        "deleted": ("deleted", "Whether the server is deleted or not"),
+    }
+
+    fields = ["id", "name", "user.uuid", "status", "source", "server_id"]
diff --git a/snf-cyclades-app/synnefo/volume/management/commands/volume-remove.py b/snf-cyclades-app/synnefo/volume/management/commands/volume-remove.py
new file mode 100644
index 0000000..5fc1d4d
--- /dev/null
+++ b/snf-cyclades-app/synnefo/volume/management/commands/volume-remove.py
@@ -0,0 +1,78 @@
+# Copyright 2011-2013 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+#   1. Redistributions of source code must retain the above copyright
+#      notice, this list of conditions and the following disclaimer.
+#
+#  2. Redistributions in binary form must reproduce the above copyright
+#     notice, this list of conditions and the following disclaimer in the
+#     documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and documentation are
+# those of the authors and should not be interpreted as representing official
+# policies, either expressed or implied, of GRNET S.A.
+#
+
+from optparse import make_option
+from django.core.management.base import CommandError
+from synnefo.volume import volumes
+from synnefo.management import common
+from snf_django.management.utils import parse_bool
+from snf_django.management.commands import RemoveCommand
+
+
+class Command(RemoveCommand):
+    can_import_settings = True
+    args = "<Volume ID> [<Volume ID> ...]"
+    help = "Remove a volume from the Database and from the VM attached to"
+    option_list = RemoveCommand.option_list + (
+        make_option(
+            "--wait",
+            dest="wait",
+            default="True",
+            choices=["True", "False"],
+            metavar="True|False",
+            help="Wait for Ganeti jobs to complete."),
+    )
+
+    @common.convert_api_faults
+    def handle(self, *args, **options):
+        if not args:
+            raise CommandError("Please provide a volume ID")
+
+        force = options['force']
+        message = "volumes" if len(args) > 1 else "volume"
+        self.confirm_deletion(force, message, args)
+
+        for volume_id in args:
+            self.stdout.write("\n")
+            try:
+                volume = common.get_volume(volume_id, for_update=True)
+
+                volumes.delete(volume)
+
+                wait = parse_bool(options["wait"])
+                if volume.machine is not None:
+                    volume.machine.task_job_id = volume.backendjobid
+                    common.wait_server_task(volume.machine, wait,
+                                            stdout=self.stdout)
+                else:
+                    self.stdout.write("Successfully removed volume %s\n"
+                                      % volume)
+            except CommandError as e:
+                self.stdout.write("Error -- %s\n" % e.message)
diff --git a/snf-cyclades-app/synnefo/volume/management/commands/volume-show.py b/snf-cyclades-app/synnefo/volume/management/commands/volume-show.py
new file mode 100644
index 0000000..3aea7b6
--- /dev/null
+++ b/snf-cyclades-app/synnefo/volume/management/commands/volume-show.py
@@ -0,0 +1,62 @@
+# Copyright 2013 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+#   1. Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials
+#      provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+from optparse import make_option
+from django.core.management.base import BaseCommand, CommandError
+
+from synnefo.management.common import convert_api_faults
+from synnefo.management import pprint, common
+
+
+class Command(BaseCommand):
+    help = "Show Volume information"
+    args = "<volume ID>"
+
+    option_list = BaseCommand.option_list + (
+        make_option(
+            '--displayname',
+            action='store_true',
+            dest='displayname',
+            default=False,
+            help="Display both uuid and display name"),
+    )
+
+    @convert_api_faults
+    def handle(self, *args, **options):
+        if len(args) != 1:
+            raise CommandError("Please provide a volume ID")
+
+        volume = common.get_volume(args[0])
+
+        pprint.pprint_volume(volume, stdout=self.stdout)
+        self.stdout.write('\n\n')
