summaryrefslogtreecommitdiffstats
path: root/nova/volume
diff options
context:
space:
mode:
authorMORITA Kazutaka <morita.kazutaka@gmail.com>2011-01-13 21:02:14 +0900
committerMORITA Kazutaka <morita.kazutaka@gmail.com>2011-01-13 21:02:14 +0900
commit40e13153d3a8ceef80fa40a59145672df796baa8 (patch)
tree9887fa6698a0cbae2da6211921df62308fbb1f79 /nova/volume
parent1c694e9093c627bd50b35e9fb0ae11adf315a154 (diff)
parent621cf8e156582b3ff4dd44409324cc3a5f9aecf4 (diff)
Merge trunk
Diffstat (limited to 'nova/volume')
-rw-r--r--nova/volume/__init__.py14
-rw-r--r--nova/volume/api.py103
-rw-r--r--nova/volume/driver.py73
-rw-r--r--nova/volume/manager.py21
4 files changed, 181 insertions, 30 deletions
diff --git a/nova/volume/__init__.py b/nova/volume/__init__.py
index d6e944fc0..56ef9332e 100644
--- a/nova/volume/__init__.py
+++ b/nova/volume/__init__.py
@@ -16,16 +16,4 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-:mod:`nova.volume` -- Nova Block Storage
-=====================================================
-
-.. automodule:: nova.volume
- :platform: Unix
-.. moduleauthor:: Jesse Andrews <jesse@ansolabs.com>
-.. moduleauthor:: Devin Carlen <devin.carlen@gmail.com>
-.. moduleauthor:: Vishvananda Ishaya <vishvananda@yahoo.com>
-.. moduleauthor:: Joshua McKenty <joshua@cognition.ca>
-.. moduleauthor:: Manish Singh <yosh@gimp.org>
-.. moduleauthor:: Andy Smith <andy@anarkystic.com>
-"""
+from nova.volume.api import API
diff --git a/nova/volume/api.py b/nova/volume/api.py
new file mode 100644
index 000000000..ce4831cc3
--- /dev/null
+++ b/nova/volume/api.py
@@ -0,0 +1,103 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+Handles all requests relating to volumes.
+"""
+
+import datetime
+
+from nova import db
+from nova import exception
+from nova import flags
+from nova import log as logging
+from nova import quota
+from nova import rpc
+from nova.db import base
+
+FLAGS = flags.FLAGS
+flags.DECLARE('storage_availability_zone', 'nova.volume.manager')
+
+LOG = logging.getLogger('nova.volume')
+
+
+class API(base.Base):
+ """API for interacting with the volume manager."""
+
+ def create(self, context, size, name, description):
+ if quota.allowed_volumes(context, 1, size) < 1:
+ LOG.warn(_("Quota exceeeded for %s, tried to create %sG volume"),
+ context.project_id, size)
+ raise quota.QuotaError(_("Volume quota exceeded. You cannot "
+ "create a volume of size %s") % size)
+
+ options = {
+ 'size': size,
+ 'user_id': context.user.id,
+ 'project_id': context.project_id,
+ 'availability_zone': FLAGS.storage_availability_zone,
+ 'status': "creating",
+ 'attach_status': "detached",
+ 'display_name': name,
+ 'display_description': description}
+
+ volume = self.db.volume_create(context, options)
+ rpc.cast(context,
+ FLAGS.scheduler_topic,
+ {"method": "create_volume",
+ "args": {"topic": FLAGS.volume_topic,
+ "volume_id": volume['id']}})
+ return volume
+
+ def delete(self, context, volume_id):
+ volume = self.get(context, volume_id)
+ if volume['status'] != "available":
+ raise exception.ApiError(_("Volume status must be available"))
+ now = datetime.datetime.utcnow()
+ self.db.volume_update(context, volume_id, {'status': 'deleting',
+ 'terminated_at': now})
+ host = volume['host']
+ rpc.cast(context,
+ self.db.queue_get_for(context, FLAGS.volume_topic, host),
+ {"method": "delete_volume",
+ "args": {"volume_id": volume_id}})
+
+ def update(self, context, volume_id, fields):
+ self.db.volume_update(context, volume_id, fields)
+
+ def get(self, context, volume_id):
+ return self.db.volume_get(context, volume_id)
+
+ def get_all(self, context):
+ if context.user.is_admin():
+ return self.db.volume_get_all(context)
+ return self.db.volume_get_all_by_project(context, context.project_id)
+
+ def check_attach(self, context, volume_id):
+ volume = self.get(context, volume_id)
+ # TODO(vish): abstract status checking?
+ if volume['status'] != "available":
+ raise exception.ApiError(_("Volume status must be available"))
+ if volume['attach_status'] == "attached":
+ raise exception.ApiError(_("Volume is already attached"))
+
+ def check_detach(self, context, volume_id):
+ volume = self.get(context, volume_id)
+ # TODO(vish): abstract status checking?
+ if volume['status'] == "available":
+ raise exception.ApiError(_("Volume is already detached"))
diff --git a/nova/volume/driver.py b/nova/volume/driver.py
index 2e8a3c816..71fe18a40 100644
--- a/nova/volume/driver.py
+++ b/nova/volume/driver.py
@@ -20,15 +20,15 @@ Drivers for volumes.
"""
-import logging
-import os
import time
from nova import exception
from nova import flags
+from nova import log as logging
from nova import utils
+LOG = logging.getLogger("nova.volume.driver")
FLAGS = flags.FLAGS
flags.DEFINE_string('volume_group', 'nova-volumes',
'Name for the VG that will contain exported volumes')
@@ -49,6 +49,8 @@ flags.DEFINE_string('iscsi_target_prefix', 'iqn.2010-10.org.openstack:',
'prefix for iscsi volumes')
flags.DEFINE_string('iscsi_ip_prefix', '127.0',
'discover volumes on the ip that starts with this prefix')
+flags.DEFINE_string('rbd_pool', 'rbd',
+ 'the rbd pool in which volumes are stored')
class VolumeDriver(object):
@@ -73,13 +75,15 @@ class VolumeDriver(object):
tries = tries + 1
if tries >= FLAGS.num_shell_tries:
raise
- logging.exception(_("Recovering from a failed execute."
- "Try number %s"), tries)
+ LOG.exception(_("Recovering from a failed execute. "
+ "Try number %s"), tries)
time.sleep(tries ** 2)
def check_for_setup_error(self):
"""Returns an error if prerequisites aren't met"""
- if not os.path.isdir("/dev/%s" % FLAGS.volume_group):
+ out, err = self._execute("sudo vgs --noheadings -o name")
+ volume_groups = out.split()
+ if not FLAGS.volume_group in volume_groups:
raise exception.Error(_("volume group %s doesn't exist")
% FLAGS.volume_group)
@@ -205,7 +209,7 @@ class FakeAOEDriver(AOEDriver):
@staticmethod
def fake_execute(cmd, *_args, **_kwargs):
"""Execute that simply logs the command."""
- logging.debug(_("FAKE AOE: %s"), cmd)
+ LOG.debug(_("FAKE AOE: %s"), cmd)
return (None, None)
@@ -310,10 +314,65 @@ class FakeISCSIDriver(ISCSIDriver):
@staticmethod
def fake_execute(cmd, *_args, **_kwargs):
"""Execute that simply logs the command."""
- logging.debug(_("FAKE ISCSI: %s"), cmd)
+ LOG.debug(_("FAKE ISCSI: %s"), cmd)
return (None, None)
+class RBDDriver(VolumeDriver):
+ """Implements RADOS block device (RBD) volume commands"""
+
+ def check_for_setup_error(self):
+ """Returns an error if prerequisites aren't met"""
+ (stdout, stderr) = self._execute("rados lspools")
+ pools = stdout.split("\n")
+ if not FLAGS.rbd_pool in pools:
+ raise exception.Error(_("rbd has no pool %s") %
+ FLAGS.rbd_pool)
+
+ def create_volume(self, volume):
+ """Creates a logical volume."""
+ if int(volume['size']) == 0:
+ size = 100
+ else:
+ size = int(volume['size']) * 1024
+ self._try_execute("rbd --pool %s --size %d create %s" %
+ (FLAGS.rbd_pool,
+ size,
+ volume['name']))
+
+ def delete_volume(self, volume):
+ """Deletes a logical volume."""
+ self._try_execute("rbd --pool %s rm %s" %
+ (FLAGS.rbd_pool,
+ volume['name']))
+
+ def local_path(self, volume):
+ """Returns the path of the rbd volume."""
+ # This is the same as the remote path
+ # since qemu accesses it directly.
+ return self.discover_volume(volume)
+
+ def ensure_export(self, context, volume):
+ """Synchronously recreates an export for a logical volume."""
+ pass
+
+ def create_export(self, context, volume):
+ """Exports the volume"""
+ pass
+
+ def remove_export(self, context, volume):
+ """Removes an export for a logical volume"""
+ pass
+
+ def discover_volume(self, volume):
+ """Discover volume on a remote host"""
+ return "rbd:%s/%s" % (FLAGS.rbd_pool, volume['name'])
+
+ def undiscover_volume(self, volume):
+ """Undiscover volume on a remote host"""
+ pass
+
+
class SheepdogDriver(VolumeDriver):
"""Executes commands relating to Sheepdog Volumes"""
diff --git a/nova/volume/manager.py b/nova/volume/manager.py
index 966334c50..6348539c5 100644
--- a/nova/volume/manager.py
+++ b/nova/volume/manager.py
@@ -42,17 +42,18 @@ intact.
"""
-import logging
import datetime
from nova import context
from nova import exception
from nova import flags
+from nova import log as logging
from nova import manager
from nova import utils
+LOG = logging.getLogger('nova.volume.manager')
FLAGS = flags.FLAGS
flags.DEFINE_string('storage_availability_zone',
'nova',
@@ -81,7 +82,7 @@ class VolumeManager(manager.Manager):
self.driver.check_for_setup_error()
ctxt = context.get_admin_context()
volumes = self.db.volume_get_all_by_host(ctxt, self.host)
- logging.debug(_("Re-exporting %s volumes"), len(volumes))
+ LOG.debug(_("Re-exporting %s volumes"), len(volumes))
for volume in volumes:
self.driver.ensure_export(ctxt, volume)
@@ -89,7 +90,7 @@ class VolumeManager(manager.Manager):
"""Creates and exports the volume."""
context = context.elevated()
volume_ref = self.db.volume_get(context, volume_id)
- logging.info(_("volume %s: creating"), volume_ref['name'])
+ LOG.info(_("volume %s: creating"), volume_ref['name'])
self.db.volume_update(context,
volume_id,
@@ -98,18 +99,18 @@ class VolumeManager(manager.Manager):
# before passing it to the driver.
volume_ref['host'] = self.host
- logging.debug(_("volume %s: creating lv of size %sG"),
- volume_ref['name'], volume_ref['size'])
+ LOG.debug(_("volume %s: creating lv of size %sG"), volume_ref['name'],
+ volume_ref['size'])
self.driver.create_volume(volume_ref)
- logging.debug(_("volume %s: creating export"), volume_ref['name'])
+ LOG.debug(_("volume %s: creating export"), volume_ref['name'])
self.driver.create_export(context, volume_ref)
now = datetime.datetime.utcnow()
self.db.volume_update(context,
volume_ref['id'], {'status': 'available',
'launched_at': now})
- logging.debug(_("volume %s: created successfully"), volume_ref['name'])
+ LOG.debug(_("volume %s: created successfully"), volume_ref['name'])
return volume_id
def delete_volume(self, context, volume_id):
@@ -120,12 +121,12 @@ class VolumeManager(manager.Manager):
raise exception.Error(_("Volume is still attached"))
if volume_ref['host'] != self.host:
raise exception.Error(_("Volume is not local to this node"))
- logging.debug(_("volume %s: removing export"), volume_ref['name'])
+ LOG.debug(_("volume %s: removing export"), volume_ref['name'])
self.driver.remove_export(context, volume_ref)
- logging.debug(_("volume %s: deleting"), volume_ref['name'])
+ LOG.debug(_("volume %s: deleting"), volume_ref['name'])
self.driver.delete_volume(volume_ref)
self.db.volume_destroy(context, volume_id)
- logging.debug(_("volume %s: deleted successfully"), volume_ref['name'])
+ LOG.debug(_("volume %s: deleted successfully"), volume_ref['name'])
return True
def setup_compute_volume(self, context, volume_id):