merged trunk

This commit is contained in:
Trey Morris
2011-06-29 16:44:42 -05:00
9 changed files with 265 additions and 7 deletions

116
bin/instance-usage-audit Executable file
View File

@@ -0,0 +1,116 @@
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2011 Openstack, LLC.
# 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.
"""Cron script to generate usage notifications for instances neither created
nor destroyed in a given time period.
Together with the notifications generated by compute on instance
create/delete/resize, over that ime period, this allows an external
system consuming usage notification feeds to calculate instance usage
for each tenant.
Time periods are specified like so:
<number>[mdy]
1m = previous month. If the script is run April 1, it will generate usages
for March 1 thry March 31.
3m = 3 previous months.
90d = previous 90 days.
1y = previous year. If run on Jan 1, it generates usages for
Jan 1 thru Dec 31 of the previous year.
"""
import datetime
import gettext
import os
import sys
import time
# If ../nova/__init__.py exists, add ../ to Python search path, so that
# it will override what happens to be installed in /usr/(local/)lib/python...
POSSIBLE_TOPDIR = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
os.pardir,
os.pardir))
if os.path.exists(os.path.join(POSSIBLE_TOPDIR, 'nova', '__init__.py')):
sys.path.insert(0, POSSIBLE_TOPDIR)
gettext.install('nova', unicode=1)
from nova import context
from nova import db
from nova import exception
from nova import flags
from nova import log as logging
from nova import utils
from nova.notifier import api as notifier_api
FLAGS = flags.FLAGS
flags.DEFINE_string('instance_usage_audit_period', '1m',
'time period to generate instance usages for.')
def time_period(period):
today = datetime.date.today()
unit = period[-1]
if unit not in 'mdy':
raise ValueError('Time period must be m, d, or y')
n = int(period[:-1])
if unit == 'm':
year = today.year - (n // 12)
n = n % 12
if n >= today.month:
year -= 1
month = 12 + (today.month - n)
else:
month = today.month - n
begin = datetime.datetime(day=1, month=month, year=year)
end = datetime.datetime(day=1, month=today.month, year=today.year)
elif unit == 'y':
begin = datetime.datetime(day=1, month=1, year=today.year - n)
end = datetime.datetime(day=1, month=1, year=today.year)
elif unit == 'd':
b = today - datetime.timedelta(days=n)
begin = datetime.datetime(day=b.day, month=b.month, year=b.year)
end = datetime.datetime(day=today.day,
month=today.month,
year=today.year)
return (begin, end)
if __name__ == '__main__':
utils.default_flagfile()
flags.FLAGS(sys.argv)
logging.setup()
begin, end = time_period(FLAGS.instance_usage_audit_period)
print "Creating usages for %s until %s" % (str(begin), str(end))
instances = db.instance_get_active_by_window(context.get_admin_context(),
begin,
end)
print "%s instances" % len(instances)
for instance_ref in instances:
usage_info = utils.usage_from_instance(instance_ref,
audit_period_begining=str(begin),
audit_period_ending=str(end))
notifier_api.notify('compute.%s' % FLAGS.host,
'compute.instance.exists',
notifier_api.INFO,
usage_info)

View File

@@ -23,8 +23,14 @@ Starts both the EC2 and OpenStack APIs in separate processes.
""" """
import os
import sys import sys
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(
sys.argv[0]), os.pardir, os.pardir))
if os.path.exists(os.path.join(possible_topdir, "nova", "__init__.py")):
sys.path.insert(0, possible_topdir)
import nova.service import nova.service
import nova.utils import nova.utils

View File

@@ -644,7 +644,7 @@ class VmCommands(object):
:param host: show all instance on specified host. :param host: show all instance on specified host.
:param instance: show specificed instance. :param instance: show specificed instance.
""" """
print "%-10s %-15s %-10s %-10s %-19s %-12s %-12s %-12s" \ print "%-10s %-15s %-10s %-10s %-26s %-9s %-9s %-9s" \
" %-10s %-10s %-10s %-5s" % ( " %-10s %-10s %-10s %-5s" % (
_('instance'), _('instance'),
_('node'), _('node'),
@@ -666,14 +666,14 @@ class VmCommands(object):
context.get_admin_context(), host) context.get_admin_context(), host)
for instance in instances: for instance in instances:
print "%-10s %-15s %-10s %-10s %-19s %-12s %-12s %-12s" \ print "%-10s %-15s %-10s %-10s %-26s %-9s %-9s %-9s" \
" %-10s %-10s %-10s %-5d" % ( " %-10s %-10s %-10s %-5d" % (
instance['hostname'], instance['hostname'],
instance['host'], instance['host'],
instance['instance_type'], instance['instance_type'].name,
instance['state_description'], instance['state_description'],
instance['launched_at'], instance['launched_at'],
instance['image_id'], instance['image_ref'],
instance['kernel_id'], instance['kernel_id'],
instance['ramdisk_id'], instance['ramdisk_id'],
instance['project_id'], instance['project_id'],
@@ -905,7 +905,7 @@ class InstanceTypeCommands(object):
try: try:
instance_types.create(name, memory, vcpus, local_gb, instance_types.create(name, memory, vcpus, local_gb,
flavorid, swap, rxtx_quota, rxtx_cap) flavorid, swap, rxtx_quota, rxtx_cap)
except exception.InvalidInput: except exception.InvalidInput, e:
print "Must supply valid parameters to create instance_type" print "Must supply valid parameters to create instance_type"
print e print e
sys.exit(1) sys.exit(1)

View File

@@ -591,6 +591,14 @@ class GlobalRoleNotAllowed(NotAllowed):
message = _("Unable to use global role %(role_id)s") message = _("Unable to use global role %(role_id)s")
class ImageRotationNotAllowed(NovaException):
message = _("Rotation is not allowed for snapshots")
class RotationRequiredForBackup(NovaException):
message = _("Rotation param is required for backup image_type")
#TODO(bcwaldon): EOL this exception! #TODO(bcwaldon): EOL this exception!
class Duplicate(NovaException): class Duplicate(NovaException):
pass pass

View File

@@ -0,0 +1,28 @@
# Copyright 2011 OpenStack LLC.
# 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.
import json
from nova import flags
from nova import log as logging
FLAGS = flags.FLAGS
NOTIFICATIONS = []
def notify(message):
"""Test notifier, stores notifications in memory for unittests."""
NOTIFICATIONS.append(message)

View File

@@ -275,6 +275,11 @@ class FanoutAdapterConsumer(AdapterConsumer):
unique = uuid.uuid4().hex unique = uuid.uuid4().hex
self.queue = '%s_fanout_%s' % (topic, unique) self.queue = '%s_fanout_%s' % (topic, unique)
self.durable = False self.durable = False
# Fanout creates unique queue names, so we should auto-remove
# them when done, so they're not left around on restart.
# Also, we're the only one that should be consuming. exclusive
# implies auto_delete, so we'll just set that..
self.exclusive = True
LOG.info(_('Created "%(exchange)s" fanout exchange ' LOG.info(_('Created "%(exchange)s" fanout exchange '
'with "%(key)s" routing key'), 'with "%(key)s" routing key'),
dict(exchange=self.exchange, key=self.routing_key)) dict(exchange=self.exchange, key=self.routing_key))

View File

@@ -37,6 +37,7 @@ from nova import log as logging
from nova import rpc from nova import rpc
from nova import test from nova import test
from nova import utils from nova import utils
from nova.notifier import test_notifier
LOG = logging.getLogger('nova.tests.compute') LOG = logging.getLogger('nova.tests.compute')
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
@@ -62,6 +63,7 @@ class ComputeTestCase(test.TestCase):
super(ComputeTestCase, self).setUp() super(ComputeTestCase, self).setUp()
self.flags(connection_type='fake', self.flags(connection_type='fake',
stub_network=True, stub_network=True,
notification_driver='nova.notifier.test_notifier',
network_manager='nova.network.manager.FlatManager') network_manager='nova.network.manager.FlatManager')
self.compute = utils.import_object(FLAGS.compute_manager) self.compute = utils.import_object(FLAGS.compute_manager)
self.compute_api = compute.API() self.compute_api = compute.API()
@@ -69,6 +71,7 @@ class ComputeTestCase(test.TestCase):
self.user = self.manager.create_user('fake', 'fake', 'fake') self.user = self.manager.create_user('fake', 'fake', 'fake')
self.project = self.manager.create_project('fake', 'fake', 'fake') self.project = self.manager.create_project('fake', 'fake', 'fake')
self.context = context.RequestContext('fake', 'fake', False) self.context = context.RequestContext('fake', 'fake', False)
test_notifier.NOTIFICATIONS = []
def fake_show(meh, context, id): def fake_show(meh, context, id):
return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1}} return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1}}
@@ -326,6 +329,50 @@ class ComputeTestCase(test.TestCase):
self.assert_(console) self.assert_(console)
self.compute.terminate_instance(self.context, instance_id) self.compute.terminate_instance(self.context, instance_id)
def test_run_instance_usage_notification(self):
"""Ensure run instance generates apropriate usage notification"""
instance_id = self._create_instance()
self.compute.run_instance(self.context, instance_id)
self.assertEquals(len(test_notifier.NOTIFICATIONS), 1)
msg = test_notifier.NOTIFICATIONS[0]
self.assertEquals(msg['priority'], 'INFO')
self.assertEquals(msg['event_type'], 'compute.instance.create')
payload = msg['payload']
self.assertEquals(payload['tenant_id'], self.project.id)
self.assertEquals(payload['user_id'], self.user.id)
self.assertEquals(payload['instance_id'], instance_id)
self.assertEquals(payload['instance_type'], 'm1.tiny')
type_id = instance_types.get_instance_type_by_name('m1.tiny')['id']
self.assertEquals(str(payload['instance_type_id']), str(type_id))
self.assertTrue('display_name' in payload)
self.assertTrue('created_at' in payload)
self.assertTrue('launched_at' in payload)
self.assertEquals(payload['image_ref'], '1')
self.compute.terminate_instance(self.context, instance_id)
def test_terminate_usage_notification(self):
"""Ensure terminate_instance generates apropriate usage notification"""
instance_id = self._create_instance()
self.compute.run_instance(self.context, instance_id)
test_notifier.NOTIFICATIONS = []
self.compute.terminate_instance(self.context, instance_id)
self.assertEquals(len(test_notifier.NOTIFICATIONS), 1)
msg = test_notifier.NOTIFICATIONS[0]
self.assertEquals(msg['priority'], 'INFO')
self.assertEquals(msg['event_type'], 'compute.instance.delete')
payload = msg['payload']
self.assertEquals(payload['tenant_id'], self.project.id)
self.assertEquals(payload['user_id'], self.user.id)
self.assertEquals(payload['instance_id'], instance_id)
self.assertEquals(payload['instance_type'], 'm1.tiny')
type_id = instance_types.get_instance_type_by_name('m1.tiny')['id']
self.assertEquals(str(payload['instance_type_id']), str(type_id))
self.assertTrue('display_name' in payload)
self.assertTrue('created_at' in payload)
self.assertTrue('launched_at' in payload)
self.assertEquals(payload['image_ref'], '1')
def test_run_instance_existing(self): def test_run_instance_existing(self):
"""Ensure failure when running an instance that already exists""" """Ensure failure when running an instance that already exists"""
instance_id = self._create_instance() instance_id = self._create_instance()
@@ -378,6 +425,36 @@ class ComputeTestCase(test.TestCase):
self.compute.terminate_instance(self.context, instance_id) self.compute.terminate_instance(self.context, instance_id)
def test_resize_instance_notification(self):
"""Ensure notifications on instance migrate/resize"""
instance_id = self._create_instance()
context = self.context.elevated()
self.compute.run_instance(self.context, instance_id)
test_notifier.NOTIFICATIONS = []
db.instance_update(self.context, instance_id, {'host': 'foo'})
self.compute.prep_resize(context, instance_id, 1)
migration_ref = db.migration_get_by_instance_and_status(context,
instance_id, 'pre-migrating')
self.assertEquals(len(test_notifier.NOTIFICATIONS), 1)
msg = test_notifier.NOTIFICATIONS[0]
self.assertEquals(msg['priority'], 'INFO')
self.assertEquals(msg['event_type'], 'compute.instance.resize.prep')
payload = msg['payload']
self.assertEquals(payload['tenant_id'], self.project.id)
self.assertEquals(payload['user_id'], self.user.id)
self.assertEquals(payload['instance_id'], instance_id)
self.assertEquals(payload['instance_type'], 'm1.tiny')
type_id = instance_types.get_instance_type_by_name('m1.tiny')['id']
self.assertEquals(str(payload['instance_type_id']), str(type_id))
self.assertTrue('display_name' in payload)
self.assertTrue('created_at' in payload)
self.assertTrue('launched_at' in payload)
self.assertEquals(payload['image_ref'], '1')
self.compute.terminate_instance(context, instance_id)
def test_resize_instance(self): def test_resize_instance(self):
"""Ensure instance can be migrated/resized""" """Ensure instance can be migrated/resized"""
instance_id = self._create_instance() instance_id = self._create_instance()

View File

@@ -274,6 +274,22 @@ EASIER_PASSWORD_SYMBOLS = ('23456789' # Removed: 0, 1
'ABCDEFGHJKLMNPQRSTUVWXYZ') # Removed: I, O 'ABCDEFGHJKLMNPQRSTUVWXYZ') # Removed: I, O
def usage_from_instance(instance_ref, **kw):
usage_info = dict(
tenant_id=instance_ref['project_id'],
user_id=instance_ref['user_id'],
instance_id=instance_ref['id'],
instance_type=instance_ref['instance_type']['name'],
instance_type_id=instance_ref['instance_type_id'],
display_name=instance_ref['display_name'],
created_at=str(instance_ref['created_at']),
launched_at=str(instance_ref['launched_at']) \
if instance_ref['launched_at'] else '',
image_ref=instance_ref['image_ref'])
usage_info.update(kw)
return usage_info
def generate_password(length=20, symbols=DEFAULT_PASSWORD_SYMBOLS): def generate_password(length=20, symbols=DEFAULT_PASSWORD_SYMBOLS):
"""Generate a random password from the supplied symbols. """Generate a random password from the supplied symbols.

View File

@@ -6,7 +6,8 @@ function usage {
echo "" echo ""
echo " -V, --virtual-env Always use virtualenv. Install automatically if not present" echo " -V, --virtual-env Always use virtualenv. Install automatically if not present"
echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment" echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment"
echo " -r, --recreate-db Recreate the test database." echo " -r, --recreate-db Recreate the test database (deprecated, as this is now the default)."
echo " -n, --no-recreate-db Don't recreate the test database."
echo " -x, --stop Stop running tests after the first error or failure." echo " -x, --stop Stop running tests after the first error or failure."
echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added." echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added."
echo " -p, --pep8 Just run pep8" echo " -p, --pep8 Just run pep8"
@@ -25,6 +26,7 @@ function process_option {
-V|--virtual-env) let always_venv=1; let never_venv=0;; -V|--virtual-env) let always_venv=1; let never_venv=0;;
-N|--no-virtual-env) let always_venv=0; let never_venv=1;; -N|--no-virtual-env) let always_venv=0; let never_venv=1;;
-r|--recreate-db) let recreate_db=1;; -r|--recreate-db) let recreate_db=1;;
-n|--no-recreate-db) let recreate_db=0;;
-f|--force) let force=1;; -f|--force) let force=1;;
-p|--pep8) let just_pep8=1;; -p|--pep8) let just_pep8=1;;
-*) noseopts="$noseopts $1";; -*) noseopts="$noseopts $1";;
@@ -41,7 +43,7 @@ noseargs=
noseopts= noseopts=
wrapper="" wrapper=""
just_pep8=0 just_pep8=0
recreate_db=0 recreate_db=1
for arg in "$@"; do for arg in "$@"; do
process_option $arg process_option $arg