merged trunk

This commit is contained in:
Vishvananda Ishaya 2010-07-26 11:02:28 -07:00
commit b1bc1e2edc
38 changed files with 171 additions and 147 deletions

View File

@ -17,7 +17,7 @@
# under the License. # under the License.
# ARG is the id of the user # ARG is the id of the user
export SUBJ=/C=US/ST=California/L=Mountain View/O=Anso Labs/OU=Nova Dev/CN=customer-intCA-$3 export SUBJ="/C=US/ST=California/L=MountainView/O=AnsoLabs/OU=NovaDev/CN=customer-intCA-$1"
mkdir INTER/$1 mkdir INTER/$1
cd INTER/$1 cd INTER/$1
cp ../../openssl.cnf.tmpl openssl.cnf cp ../../openssl.cnf.tmpl openssl.cnf

14
README
View File

@ -6,15 +6,19 @@ The Choose Your Own Adventure README for Nova:
To monitor it from a distance: follow @novacc on twitter To monitor it from a distance: follow @novacc on twitter
To tame it for use in your own cloud: read http://docs.novacc.org/getting.started.html To tame it for use in your own cloud: read http://nova.openstack.org/getting.started.html
To study its anatomy: read http://docs.novacc.org/architecture.html To study its anatomy: read http://nova.openstack.org/architecture.html
To disect it in detail: visit http://github.com/nova/cc To disect it in detail: visit http://code.launchpad.net/nova
To taunt it with its weaknesses: use http://github.com/nova/cc/issues To taunt it with its weaknesses: use http://bugs.launchpad.net/nova
To watch it: http://hudson.openstack.org
To hack at it: read HACKING To hack at it: read HACKING
To watch it: http://test.novacc.org/waterfall To laugh at its PEP8 problems: http://hudson.openstack.org/job/nova-pep8/violations
To cry over its pylint problems: http://hudson.openstack.org/job/nova-pylint/violations

View File

@ -33,9 +33,6 @@ NOVA_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'nova')
if os.path.exists(NOVA_PATH): if os.path.exists(NOVA_PATH):
sys.path.insert(0, os.path.dirname(NOVA_PATH)) sys.path.insert(0, os.path.dirname(NOVA_PATH))
from carrot import connection
from carrot import messaging
from twisted.internet import task from twisted.internet import task
from twisted.application import service from twisted.application import service
@ -43,6 +40,7 @@ from nova import flags
from nova import rpc from nova import rpc
from nova import twistd from nova import twistd
from nova.compute import node from nova.compute import node
from nova.objectstore import image # For the images_path flag
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
@ -50,8 +48,8 @@ FLAGS = flags.FLAGS
# context when the twistd.serve() call is made below so any # context when the twistd.serve() call is made below so any
# flags we define here will have to be conditionally defined, # flags we define here will have to be conditionally defined,
# flags defined by imported modules are safe. # flags defined by imported modules are safe.
if 'node_report_state_interval' not in FLAGS: if 'compute_report_state_interval' not in FLAGS:
flags.DEFINE_integer('node_report_state_interval', 10, flags.DEFINE_integer('compute_report_state_interval', 10,
'seconds between nodes reporting state to cloud', 'seconds between nodes reporting state to cloud',
lower_bound=1) lower_bound=1)
logging.getLogger().setLevel(logging.DEBUG) logging.getLogger().setLevel(logging.DEBUG)
@ -75,10 +73,10 @@ def main():
bin_name = os.path.basename(__file__) bin_name = os.path.basename(__file__)
pulse = task.LoopingCall(n.report_state, FLAGS.node_name, bin_name) pulse = task.LoopingCall(n.report_state, FLAGS.node_name, bin_name)
pulse.start(interval=FLAGS.node_report_state_interval, now=False) pulse.start(interval=FLAGS.compute_report_state_interval, now=False)
injected = consumer_all.attach_to_twisted() consumer_all.attach_to_twisted()
injected = consumer_node.attach_to_twisted() consumer_node.attach_to_twisted()
# This is the parent service that twistd will be looking for when it # This is the parent service that twistd will be looking for when it
# parses this file, return it so that we can get it into globals below # parses this file, return it so that we can get it into globals below

View File

@ -22,22 +22,37 @@
""" """
import logging import logging
from tornado import ioloop import os
import sys
# NOTE(termie): kludge so that we can run this from the bin directory in the
# checkout without having to screw with paths
NOVA_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'nova')
if os.path.exists(NOVA_PATH):
sys.path.insert(0, os.path.dirname(NOVA_PATH))
from twisted.internet import task
from twisted.application import service
from nova import flags from nova import flags
from nova import rpc from nova import rpc
from nova import server from nova import twistd
from nova import utils
from nova.volume import storage from nova.volume import storage
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
flags.DEFINE_integer('storage_report_state_interval', 10, # NOTE(termie): This file will necessarily be re-imported under different
'seconds between broadcasting state to cloud', # context when the twistd.serve() call is made below so any
lower_bound=1) # flags we define here will have to be conditionally defined,
# flags defined by imported modules are safe.
if 'volume_report_state_interval' not in FLAGS:
flags.DEFINE_integer('volume_report_state_interval', 10,
'seconds between nodes reporting state to cloud',
lower_bound=1)
def main(argv): def main():
logging.warn('Starting volume node')
bs = storage.BlockStore() bs = storage.BlockStore()
conn = rpc.Connection.instance() conn = rpc.Connection.instance()
@ -51,19 +66,29 @@ def main(argv):
topic='%s.%s' % (FLAGS.storage_topic, FLAGS.node_name), topic='%s.%s' % (FLAGS.storage_topic, FLAGS.node_name),
proxy=bs) proxy=bs)
io_inst = ioloop.IOLoop.instance() bin_name = os.path.basename(__file__)
scheduler = ioloop.PeriodicCallback( pulse = task.LoopingCall(bs.report_state, FLAGS.node_name, bin_name)
lambda: bs.report_state(), pulse.start(interval=FLAGS.volume_report_state_interval, now=False)
FLAGS.storage_report_state_interval * 1000,
io_loop=io_inst)
injected = consumer_all.attachToTornado(io_inst) consumer_all.attach_to_twisted()
injected = consumer_node.attachToTornado(io_inst) consumer_node.attach_to_twisted()
scheduler.start()
io_inst.start() # This is the parent service that twistd will be looking for when it
# parses this file, return it so that we can get it into globals below
application = service.Application(bin_name)
bs.setServiceParent(application)
return application
# NOTE(termie): When this script is executed from the commandline what it will
# actually do is tell the twistd application runner that it
# should run this file as a twistd application (see below).
if __name__ == '__main__': if __name__ == '__main__':
utils.default_flagfile() twistd.serve(__file__)
server.serve('nova-volume', main)
# NOTE(termie): When this script is loaded by the twistd application runner
# this code path will be executed and twistd will expect a
# variable named 'application' to be available, it will then
# handle starting it and stopping it.
if __name__ == '__builtin__':
application = main()

View File

@ -1,4 +1,3 @@
--daemonize=1
--ca_path=/var/lib/nova/CA --ca_path=/var/lib/nova/CA
--keys_path=/var/lib/nova/keys --keys_path=/var/lib/nova/keys
--images_path=/var/lib/nova/images --images_path=/var/lib/nova/images

View File

View File

@ -16,8 +16,7 @@ import sys, os
# If extensions (or modules to document with autodoc) are in another directory, # If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the # add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here. # documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.append(os.path.abspath('/Users/jmckenty/Projects/cc')) sys.path.append([os.path.abspath('../nova'), os.path.abspath('..'), os.path.abspath('../bin')])
sys.path.append([os.path.abspath('../nova'),os.path.abspath('../'),os.path.abspath('../vendor')])
# -- General configuration ----------------------------------------------------- # -- General configuration -----------------------------------------------------
@ -25,7 +24,6 @@ sys.path.append([os.path.abspath('../nova'),os.path.abspath('../'),os.path.abspa
# Add any Sphinx extension module names here, as strings. They can be extensions # Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.pngmath', 'sphinx.ext.ifconfig'] extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.pngmath', 'sphinx.ext.ifconfig']
#sphinx_to_github = False
todo_include_todos = True todo_include_todos = True
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
@ -68,7 +66,7 @@ release = '0.42'
# List of directories, relative to source directory, that shouldn't be searched # List of directories, relative to source directory, that shouldn't be searched
# for source files. # for source files.
exclude_trees = ['_build'] exclude_trees = []
# The reST default role (used for this markup: `text`) to use for all documents. # The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None #default_role = None
@ -176,7 +174,7 @@ htmlhelp_basename = 'novadoc'
# Grouping the document tree into LaTeX files. List of tuples # Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]). # (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [ latex_documents = [
('index', 'nova.tex', u'nova Documentation', ('index', 'Nova.tex', u'Nova Documentation',
u'Anso Labs, LLC', 'manual'), u'Anso Labs, LLC', 'manual'),
] ]
@ -199,4 +197,6 @@ latex_documents = [
# Example configuration for intersphinx: refer to the Python standard library. # Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'http://docs.python.org/': None} intersphinx_mapping = {'python': ('http://docs.python.org/', None),
'swift': ('http://swift.openstack.org', None)}

View File

@ -43,7 +43,6 @@ Contents:
nova nova
fakes fakes
binaries binaries
todo
modules modules
packages packages

View File

@ -24,7 +24,7 @@ export VPN_IP=`ifconfig | grep 'inet addr:'| grep -v '127.0.0.1' | cut -d: -f2
export BROADCAST=`ifconfig | grep 'inet addr:'| grep -v '127.0.0.1' | cut -d: -f3 | awk '{print $1}'` export BROADCAST=`ifconfig | grep 'inet addr:'| grep -v '127.0.0.1' | cut -d: -f3 | awk '{print $1}'`
export DHCP_MASK=`ifconfig | grep 'inet addr:'| grep -v '127.0.0.1' | cut -d: -f4 | awk '{print $1}'` export DHCP_MASK=`ifconfig | grep 'inet addr:'| grep -v '127.0.0.1' | cut -d: -f4 | awk '{print $1}'`
export GATEWAY=`netstat -r | grep default | cut -d' ' -f10` export GATEWAY=`netstat -r | grep default | cut -d' ' -f10`
export SUBJ=/C=US/ST=California/L=Mountain View/O=Anso Labs/OU=Nova Dev/CN=customer-vpn-$VPN_IP export SUBJ="/C=US/ST=California/L=MountainView/O=AnsoLabs/OU=NovaDev/CN=customer-vpn-$VPN_IP"
DHCP_LOWER=`echo $BROADCAST | awk -F. '{print $1"."$2"."$3"." $4 - 10 }'` DHCP_LOWER=`echo $BROADCAST | awk -F. '{print $1"."$2"."$3"." $4 - 10 }'`
DHCP_UPPER=`echo $BROADCAST | awk -F. '{print $1"."$2"."$3"." $4 - 1 }'` DHCP_UPPER=`echo $BROADCAST | awk -F. '{print $1"."$2"."$3"." $4 - 1 }'`

View File

@ -40,7 +40,8 @@ def partition(infile, outfile, local_bytes=0, local_type='ext2', execute=None):
formatted as ext2. formatted as ext2.
In the diagram below, dashes represent drive sectors. In the diagram below, dashes represent drive sectors.
0 a b c d e +-----+------. . .-------+------. . .------+
| 0 a| b c|d e|
+-----+------. . .-------+------. . .------+ +-----+------. . .-------+------. . .------+
| mbr | primary partiton | local partition | | mbr | primary partiton | local partition |
+-----+------. . .-------+------. . .------+ +-----+------. . .-------+------. . .------+
@ -64,8 +65,8 @@ def partition(infile, outfile, local_bytes=0, local_type='ext2', execute=None):
last_sector = local_last # e last_sector = local_last # e
# create an empty file # create an empty file
execute('dd if=/dev/zero of=%s count=1 seek=%d bs=%d' yield execute('dd if=/dev/zero of=%s count=1 seek=%d bs=%d'
% (outfile, last_sector, sector_size)) % (outfile, last_sector, sector_size))
# make mbr partition # make mbr partition
yield execute('parted --script %s mklabel msdos' % outfile) yield execute('parted --script %s mklabel msdos' % outfile)

View File

@ -223,16 +223,20 @@ class Node(object, service.Service):
volume_id = None, mountpoint = None): volume_id = None, mountpoint = None):
volume = storage.get_volume(volume_id) volume = storage.get_volume(volume_id)
yield self._init_aoe() yield self._init_aoe()
yield utils.runthis("Attached Volume: %s", yield process.simple_execute(
"sudo virsh attach-disk %s /dev/etherd/%s %s" "sudo virsh attach-disk %s /dev/etherd/%s %s" %
% (instance_id, volume['aoe_device'], mountpoint.split("/")[-1])) (instance_id,
volume['aoe_device'],
mountpoint.rpartition('/dev/')[2]))
volume.finish_attach() volume.finish_attach()
defer.returnValue(True) defer.returnValue(True)
@defer.inlineCallbacks
def _init_aoe(self): def _init_aoe(self):
utils.runthis("Doin an AoE discover, returns %s", "sudo aoe-discover") yield process.simple_execute("sudo aoe-discover")
utils.runthis("Doin an AoE stat, returns %s", "sudo aoe-stat") yield process.simple_execute("sudo aoe-stat")
@defer.inlineCallbacks
@exception.wrap_exception @exception.wrap_exception
def detach_volume(self, instance_id, volume_id): def detach_volume(self, instance_id, volume_id):
""" detach a volume from an instance """ """ detach a volume from an instance """
@ -240,10 +244,10 @@ class Node(object, service.Service):
# name without the leading /dev/ # name without the leading /dev/
volume = storage.get_volume(volume_id) volume = storage.get_volume(volume_id)
target = volume['mountpoint'].rpartition('/dev/')[2] target = volume['mountpoint'].rpartition('/dev/')[2]
utils.runthis("Detached Volume: %s", "sudo virsh detach-disk %s %s " yield process.simple_execute(
% (instance_id, target)) "sudo virsh detach-disk %s %s " % (instance_id, target))
volume.finish_detach() volume.finish_detach()
return defer.succeed(True) defer.returnValue(True)
class Group(object): class Group(object):

View File

@ -453,21 +453,21 @@ class CloudController(object):
def format_addresses(self, context): def format_addresses(self, context):
addresses = [] addresses = []
# TODO(vish): move authorization checking into network.py
for address in self.network.host_objs: for address in self.network.host_objs:
#logging.debug(address_record) # TODO(vish): implement a by_project iterator for addresses
address_rv = { if (context.user.is_admin() or
'public_ip': address['address'], address['project_id'] == self.project.id):
'instance_id' : address.get('instance_id', 'free') address_rv = {
} 'public_ip': address['address'],
if context.user.is_admin(): 'instance_id' : address.get('instance_id', 'free')
address_rv['instance_id'] = "%s (%s, %s)" % ( }
address['instance_id'], if context.user.is_admin():
address['user_id'], address_rv['instance_id'] = "%s (%s, %s)" % (
address['project_id'], address['instance_id'],
) address['user_id'],
address['project_id'],
)
addresses.append(address_rv) addresses.append(address_rv)
# logging.debug(addresses)
return {'addressesSet': addresses} return {'addressesSet': addresses}
@rbac.allow('netadmin') @rbac.allow('netadmin')

View File

@ -44,6 +44,9 @@ class Duplicate(Error):
class NotAuthorized(Error): class NotAuthorized(Error):
pass pass
class NotEmpty(Error):
pass
def wrap_exception(f): def wrap_exception(f):
def _wrap(*args, **kw): def _wrap(*args, **kw):
try: try:

View File

@ -107,7 +107,7 @@ class Bucket(object):
try: try:
return context.user.is_admin() or self.owner_id == context.project.id return context.user.is_admin() or self.owner_id == context.project.id
except Exception, e: except Exception, e:
pass return False
def list_keys(self, prefix='', marker=None, max_keys=1000, terse=False): def list_keys(self, prefix='', marker=None, max_keys=1000, terse=False):
object_names = [] object_names = []
@ -161,7 +161,7 @@ class Bucket(object):
def delete(self): def delete(self):
if len(os.listdir(self.path)) > 0: if len(os.listdir(self.path)) > 0:
raise exception.NotAuthorized() raise exception.NotEmpty()
os.rmdir(self.path) os.rmdir(self.path)
os.remove(self.path+'.json') os.remove(self.path+'.json')

View File

@ -277,12 +277,12 @@ class ImageResource(Resource):
def render_POST(self, request): def render_POST(self, request):
""" update image attributes: public/private """ """ update image attributes: public/private """
image_id = self.get_argument('image_id', u'') image_id = get_argument(request, 'image_id', u'')
operation = self.get_argument('operation', u'') operation = get_argument(request, 'operation', u'')
image_object = image.Image(image_id) image_object = image.Image(image_id)
if not image.is_authorized(request.context): if not image_object.is_authorized(request.context):
raise exception.NotAuthorized raise exception.NotAuthorized
image_object.set_public(operation=='add') image_object.set_public(operation=='add')
@ -291,10 +291,10 @@ class ImageResource(Resource):
def render_DELETE(self, request): def render_DELETE(self, request):
""" delete a registered image """ """ delete a registered image """
image_id = self.get_argument("image_id", u"") image_id = get_argument(request, "image_id", u"")
image_object = image.Image(image_id) image_object = image.Image(image_id)
if not image.is_authorized(request.context): if not image_object.is_authorized(request.context):
raise exception.NotAuthorized raise exception.NotAuthorized
image_object.delete() image_object.delete()

View File

@ -151,6 +151,7 @@ class TopicPublisher(Publisher):
def __init__(self, connection=None, topic="broadcast"): def __init__(self, connection=None, topic="broadcast"):
self.routing_key = topic self.routing_key = topic
self.exchange = FLAGS.control_exchange self.exchange = FLAGS.control_exchange
self.durable = False
super(TopicPublisher, self).__init__(connection=connection) super(TopicPublisher, self).__init__(connection=connection)
@ -242,7 +243,7 @@ def send_message(topic, message, wait=True):
consumer.register_callback(generic_response) consumer.register_callback(generic_response)
publisher = messaging.Publisher(connection=Connection.instance(), publisher = messaging.Publisher(connection=Connection.instance(),
exchange="nova", exchange=FLAGS.control_exchange,
exchange_type="topic", exchange_type="topic",
routing_key=topic) routing_key=topic)
publisher.send(message) publisher.send(message)

View File

@ -142,7 +142,7 @@ class NetworkTestCase(test.TrialTestCase):
self.dnsmasq.release_ip(mac3, address3, hostname, net.bridge_name) self.dnsmasq.release_ip(mac3, address3, hostname, net.bridge_name)
net = network.get_project_network(self.projects[0].id, "default") net = network.get_project_network(self.projects[0].id, "default")
rv = network.deallocate_ip(secondaddress) rv = network.deallocate_ip(secondaddress)
self.dnsmasq.release_ip(mac, address, hostname, net.bridge_name) self.dnsmasq.release_ip(mac, secondaddress, hostname, net.bridge_name)
def test_release_before_deallocate(self): def test_release_before_deallocate(self):
pass pass

View File

@ -27,6 +27,7 @@ from nova import flags
from nova import objectstore from nova import objectstore
from nova import test from nova import test
from nova.auth import manager from nova.auth import manager
from nova.exception import NotEmpty, NotFound, NotAuthorized
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
@ -95,49 +96,37 @@ class ObjectStoreTestCase(test.BaseTestCase):
# another user is not authorized # another user is not authorized
self.context.user = self.um.get_user('user2') self.context.user = self.um.get_user('user2')
self.context.project = self.um.get_project('proj2') self.context.project = self.um.get_project('proj2')
self.assert_(bucket.is_authorized(self.context) == False) self.assertFalse(bucket.is_authorized(self.context))
# admin is authorized to use bucket # admin is authorized to use bucket
self.context.user = self.um.get_user('admin_user') self.context.user = self.um.get_user('admin_user')
self.context.project = None self.context.project = None
self.assert_(bucket.is_authorized(self.context)) self.assertTrue(bucket.is_authorized(self.context))
# new buckets are empty # new buckets are empty
self.assert_(bucket.list_keys()['Contents'] == []) self.assertTrue(bucket.list_keys()['Contents'] == [])
# storing keys works # storing keys works
bucket['foo'] = "bar" bucket['foo'] = "bar"
self.assert_(len(bucket.list_keys()['Contents']) == 1) self.assertEquals(len(bucket.list_keys()['Contents']), 1)
self.assert_(bucket['foo'].read() == 'bar') self.assertEquals(bucket['foo'].read(), 'bar')
# md5 of key works # md5 of key works
self.assert_(bucket['foo'].md5 == hashlib.md5('bar').hexdigest()) self.assertEquals(bucket['foo'].md5, hashlib.md5('bar').hexdigest())
# deleting non-empty bucket throws exception # deleting non-empty bucket should throw a NotEmpty exception
exception = False self.assertRaises(NotEmpty, bucket.delete)
try:
bucket.delete()
except:
exception = True
self.assert_(exception)
# deleting key # deleting key
del bucket['foo'] del bucket['foo']
# deleting empty button # deleting empty bucket
bucket.delete() bucket.delete()
# accessing deleted bucket throws exception # accessing deleted bucket throws exception
exception = False self.assertRaises(NotFound, objectstore.bucket.Bucket, 'new_bucket')
try:
objectstore.bucket.Bucket('new_bucket')
except:
exception = True
self.assert_(exception)
def test_images(self): def test_images(self):
self.context.user = self.um.get_user('user1') self.context.user = self.um.get_user('user1')
@ -166,37 +155,4 @@ class ObjectStoreTestCase(test.BaseTestCase):
# verify image permissions # verify image permissions
self.context.user = self.um.get_user('user2') self.context.user = self.um.get_user('user2')
self.context.project = self.um.get_project('proj2') self.context.project = self.um.get_project('proj2')
self.assert_(my_img.is_authorized(self.context) == False) self.assertFalse(my_img.is_authorized(self.context))
# class ApiObjectStoreTestCase(test.BaseTestCase):
# def setUp(self):
# super(ApiObjectStoreTestCase, self).setUp()
# FLAGS.fake_users = True
# FLAGS.buckets_path = os.path.join(tempdir, 'buckets')
# FLAGS.images_path = os.path.join(tempdir, 'images')
# FLAGS.ca_path = os.path.join(os.path.dirname(__file__), 'CA')
#
# self.users = manager.AuthManager()
# self.app = handler.Application(self.users)
#
# self.host = '127.0.0.1'
#
# self.conn = boto.s3.connection.S3Connection(
# aws_access_key_id=user.access,
# aws_secret_access_key=user.secret,
# is_secure=False,
# calling_format=boto.s3.connection.OrdinaryCallingFormat(),
# port=FLAGS.s3_port,
# host=FLAGS.s3_host)
#
# self.mox.StubOutWithMock(self.ec2, 'new_http_connection')
#
# def tearDown(self):
# FLAGS.Reset()
# super(ApiObjectStoreTestCase, self).tearDown()
#
# def test_describe_instances(self):
# self.expect_http()
# self.mox.ReplayAll()
#
# self.assertEqual(self.ec2.get_all_instances(), [])

View File

@ -28,15 +28,17 @@ import os
import shutil import shutil
import socket import socket
import tempfile import tempfile
import time
from tornado import ioloop from twisted.application import service
from twisted.internet import defer from twisted.internet import defer
from nova import datastore from nova import datastore
from nova import exception from nova import exception
from nova import flags from nova import flags
from nova import process
from nova import utils from nova import utils
from nova import validate from nova import validate
from nova.compute import model
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
@ -80,7 +82,7 @@ def get_volume(volume_id):
return volume_class(volume_id=volume_id) return volume_class(volume_id=volume_id)
raise exception.Error("Volume does not exist") raise exception.Error("Volume does not exist")
class BlockStore(object): class BlockStore(object, service.Service):
""" """
There is one BlockStore running on each volume node. There is one BlockStore running on each volume node.
However, each BlockStore can report on the state of However, each BlockStore can report on the state of
@ -102,9 +104,21 @@ class BlockStore(object):
except Exception, err: except Exception, err:
pass pass
def report_state(self): @defer.inlineCallbacks
#TODO: aggregate the state of the system def report_state(self, nodename, daemon):
pass # TODO(termie): make this pattern be more elegant. -todd
try:
record = model.Daemon(nodename, daemon)
record.heartbeat()
if getattr(self, "model_disconnected", False):
self.model_disconnected = False
logging.error("Recovered model server connection!")
except model.ConnectionError, ex:
if not getattr(self, "model_disconnected", False):
self.model_disconnected = True
logging.exception("model server went away")
yield
@validate.rangetest(size=(0, 1000)) @validate.rangetest(size=(0, 1000))
def create_volume(self, size, user_id, project_id): def create_volume(self, size, user_id, project_id):
@ -143,17 +157,24 @@ class BlockStore(object):
datastore.Redis.instance().srem('volumes:%s' % (FLAGS.storage_name), vol['volume_id']) datastore.Redis.instance().srem('volumes:%s' % (FLAGS.storage_name), vol['volume_id'])
return True return True
@defer.inlineCallbacks
def _restart_exports(self): def _restart_exports(self):
if FLAGS.fake_storage: if FLAGS.fake_storage:
return return
utils.runthis("Setting exports to auto: %s", "sudo vblade-persist auto all") yield process.simple_execute(
utils.runthis("Starting all exports: %s", "sudo vblade-persist start all") "sudo vblade-persist auto all")
yield process.simple_execute(
"sudo vblade-persist start all")
@defer.inlineCallbacks
def _init_volume_group(self): def _init_volume_group(self):
if FLAGS.fake_storage: if FLAGS.fake_storage:
return return
utils.runthis("PVCreate returned: %s", "sudo pvcreate %s" % (FLAGS.storage_dev)) yield process.simple_execute(
utils.runthis("VGCreate returned: %s", "sudo vgcreate %s %s" % (FLAGS.volume_group, FLAGS.storage_dev)) "sudo pvcreate %s" % (FLAGS.storage_dev))
yield process.simple_execute(
"sudo vgcreate %s %s" % (FLAGS.volume_group,
FLAGS.storage_dev))
class Volume(datastore.BasicModel): class Volume(datastore.BasicModel):
@ -227,15 +248,22 @@ class Volume(datastore.BasicModel):
self._delete_lv() self._delete_lv()
super(Volume, self).destroy() super(Volume, self).destroy()
@defer.inlineCallbacks
def create_lv(self): def create_lv(self):
if str(self['size']) == '0': if str(self['size']) == '0':
sizestr = '100M' sizestr = '100M'
else: else:
sizestr = '%sG' % self['size'] sizestr = '%sG' % self['size']
utils.runthis("Creating LV: %s", "sudo lvcreate -L %s -n %s %s" % (sizestr, self['volume_id'], FLAGS.volume_group)) yield process.simple_execute(
"sudo lvcreate -L %s -n %s %s" % (sizestr,
self['volume_id'],
FLAGS.volume_group))
@defer.inlineCallbacks
def _delete_lv(self): def _delete_lv(self):
utils.runthis("Removing LV: %s", "sudo lvremove -f %s/%s" % (FLAGS.volume_group, self['volume_id'])) yield process.simple_execute(
"sudo lvremove -f %s/%s" % (FLAGS.volume_group,
self['volume_id']))
def _setup_export(self): def _setup_export(self):
(shelf_id, blade_id) = get_next_aoe_numbers() (shelf_id, blade_id) = get_next_aoe_numbers()
@ -245,8 +273,9 @@ class Volume(datastore.BasicModel):
self.save() self.save()
self._exec_export() self._exec_export()
@defer.inlineCallbacks
def _exec_export(self): def _exec_export(self):
utils.runthis("Creating AOE export: %s", yield process.simple_execute(
"sudo vblade-persist setup %s %s %s /dev/%s/%s" % "sudo vblade-persist setup %s %s %s /dev/%s/%s" %
(self['shelf_id'], (self['shelf_id'],
self['blade_id'], self['blade_id'],
@ -254,9 +283,14 @@ class Volume(datastore.BasicModel):
FLAGS.volume_group, FLAGS.volume_group,
self['volume_id'])) self['volume_id']))
@defer.inlineCallbacks
def _remove_export(self): def _remove_export(self):
utils.runthis("Stopped AOE export: %s", "sudo vblade-persist stop %s %s" % (self['shelf_id'], self['blade_id'])) yield process.simple_execute(
utils.runthis("Destroyed AOE export: %s", "sudo vblade-persist destroy %s %s" % (self['shelf_id'], self['blade_id'])) "sudo vblade-persist stop %s %s" % (self['shelf_id'],
self['blade_id']))
yield process.simple_execute(
"sudo vblade-persist destroy %s %s" % (self['shelf_id'],
self['blade_id']))
class FakeVolume(Volume): class FakeVolume(Volume):

View File

@ -1,4 +1,4 @@
[build_sphinx] [build_sphinx]
source-dir = docs source-dir = doc/source
build-dir = docs/_build build-dir = doc/build
all_files = 1 all_files = 1