Add the availability_zone to the volume.usage notifications

The volume.usage notifications are used to enable traffic based billing
on volumes. The availability_zone of the instance is an important piece of
information that can enable more specialized billing of customers.

This makes the volume.usage events consistent with the volume.create.start/end
snapshot.create.start/end, etc. emitted from cinder, which contain the
availability_zone field.

Change-Id: I9b6b03174cd03235d0e6cf01e34a6da13bd9bb70
This commit is contained in:
Michael Kerrin
2013-03-29 10:19:31 +00:00
parent 83f6fc8469
commit 2cf1f39d9e
10 changed files with 105 additions and 15 deletions

View File

@@ -297,6 +297,7 @@ def usage_volume_info(vol_usage):
volume_id=vol_usage['volume_id'], volume_id=vol_usage['volume_id'],
tenant_id=vol_usage['project_id'], tenant_id=vol_usage['project_id'],
user_id=vol_usage['user_id'], user_id=vol_usage['user_id'],
availability_zone=vol_usage['availability_zone'],
instance_id=vol_usage['instance_uuid'], instance_id=vol_usage['instance_uuid'],
last_refreshed=null_safe_str(last_refreshed_time), last_refreshed=null_safe_str(last_refreshed_time),
reads=vol_usage['tot_reads'] + vol_usage['curr_reads'], reads=vol_usage['tot_reads'] + vol_usage['curr_reads'],

View File

@@ -314,6 +314,7 @@ class ConductorManager(manager.Manager):
self.db.vol_usage_update(context, vol_id, rd_req, rd_bytes, wr_req, self.db.vol_usage_update(context, vol_id, rd_req, rd_bytes, wr_req,
wr_bytes, instance['uuid'], wr_bytes, instance['uuid'],
instance['project_id'], instance['user_id'], instance['project_id'], instance['user_id'],
instance['availability_zone'],
last_refreshed, update_totals) last_refreshed, update_totals)
@rpc_common.client_exceptions(exception.ComputeHostNotFound, @rpc_common.client_exceptions(exception.ComputeHostNotFound,

View File

@@ -1461,12 +1461,13 @@ def vol_get_usage_by_time(context, begin):
def vol_usage_update(context, id, rd_req, rd_bytes, wr_req, wr_bytes, def vol_usage_update(context, id, rd_req, rd_bytes, wr_req, wr_bytes,
instance_id, project_id, user_id, instance_id, project_id, user_id, availability_zone,
last_refreshed=None, update_totals=False): last_refreshed=None, update_totals=False):
"""Update cached volume usage for a volume """Update cached volume usage for a volume
Creates new record if needed.""" Creates new record if needed."""
return IMPL.vol_usage_update(context, id, rd_req, rd_bytes, wr_req, return IMPL.vol_usage_update(context, id, rd_req, rd_bytes, wr_req,
wr_bytes, instance_id, project_id, user_id, wr_bytes, instance_id, project_id, user_id,
availability_zone,
last_refreshed=last_refreshed, last_refreshed=last_refreshed,
update_totals=update_totals) update_totals=update_totals)

View File

@@ -4288,8 +4288,8 @@ def vol_get_usage_by_time(context, begin):
@require_context @require_context
def vol_usage_update(context, id, rd_req, rd_bytes, wr_req, wr_bytes, def vol_usage_update(context, id, rd_req, rd_bytes, wr_req, wr_bytes,
instance_id, project_id, user_id, last_refreshed=None, instance_id, project_id, user_id, availability_zone,
update_totals=False, session=None): last_refreshed=None, update_totals=False, session=None):
if not session: if not session:
session = get_session() session = get_session()
@@ -4308,7 +4308,8 @@ def vol_usage_update(context, id, rd_req, rd_bytes, wr_req, wr_bytes,
'curr_write_bytes': wr_bytes, 'curr_write_bytes': wr_bytes,
'instance_uuid': instance_id, 'instance_uuid': instance_id,
'project_id': project_id, 'project_id': project_id,
'user_id': user_id} 'user_id': user_id,
'availability_zone': availability_zone}
else: else:
values = {'tot_last_refreshed': last_refreshed, values = {'tot_last_refreshed': last_refreshed,
'tot_reads': models.VolumeUsage.tot_reads + rd_req, 'tot_reads': models.VolumeUsage.tot_reads + rd_req,
@@ -4323,7 +4324,8 @@ def vol_usage_update(context, id, rd_req, rd_bytes, wr_req, wr_bytes,
'curr_write_bytes': 0, 'curr_write_bytes': 0,
'instance_uuid': instance_id, 'instance_uuid': instance_id,
'project_id': project_id, 'project_id': project_id,
'user_id': user_id} 'user_id': user_id,
'availability_zone': availability_zone}
rows = model_query(context, models.VolumeUsage, rows = model_query(context, models.VolumeUsage,
session=session, read_deleted="yes").\ session=session, read_deleted="yes").\
@@ -4340,6 +4342,7 @@ def vol_usage_update(context, id, rd_req, rd_bytes, wr_req, wr_bytes,
vol_usage.instance_uuid = instance_id vol_usage.instance_uuid = instance_id
vol_usage.project_id = project_id vol_usage.project_id = project_id
vol_usage.user_id = user_id vol_usage.user_id = user_id
vol_usage.availability_zone = availability_zone
if not update_totals: if not update_totals:
vol_usage.curr_reads = rd_req vol_usage.curr_reads = rd_req

View File

@@ -0,0 +1,55 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 OpenStack Foundation
#
# 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.
#
# This migration adds the availability_zone to the volume_usage_cache table.
#
# This table keeps a cache of the volume usage. Every minute (or what ever
# is configured for volume_usage_poll_interval value) one row in this table
# gets updated for each attached volume. After this patch is applied, for
# any currently attached volumes, the value will immediately be null, but
# will get updated to the correct value on the next tick of the volume
# usage poll.
#
# The volume usage poll function is the only code to access this table. The
# sequence of operation in that function is to first update the field with
# the correct value and then to pass the updated data to the consumer.
#
# Hence this new column does not need to be pre-populated.
#
from sqlalchemy import MetaData, String, Table, Column
from nova.openstack.common import log as logging
LOG = logging.getLogger(__name__)
def upgrade(migrate_engine):
meta = MetaData()
meta.bind = migrate_engine
volume_usage_cache = Table('volume_usage_cache', meta, autoload=True)
availability_zone = Column('availability_zone', String(255))
volume_usage_cache.create_column(availability_zone)
def downgrade(migrate_engine):
meta = MetaData()
meta.bind = migrate_engine
volume_usage_cache = Table('volume_usage_cache', meta, autoload=True)
volume_usage_cache.drop_column('availability_zone')

View File

@@ -893,6 +893,7 @@ class VolumeUsage(BASE, NovaBase):
instance_uuid = Column(String(36)) instance_uuid = Column(String(36))
project_id = Column(String(36)) project_id = Column(String(36))
user_id = Column(String(36)) user_id = Column(String(36))
availability_zone = Column(String(255))
tot_last_refreshed = Column(DateTime) tot_last_refreshed = Column(DateTime)
tot_reads = Column(BigInteger, default=0) tot_reads = Column(BigInteger, default=0)
tot_read_bytes = Column(BigInteger, default=0) tot_read_bytes = Column(BigInteger, default=0)

View File

@@ -454,6 +454,7 @@ class ComputeVolumeTestCase(BaseTestCase):
'instance_uuid': 'fake_instance_uuid', 'instance_uuid': 'fake_instance_uuid',
'project_id': 'fake_project_id', 'project_id': 'fake_project_id',
'user_id': 'fake_user_id', 'user_id': 'fake_user_id',
'availability_zone': 'fake-az',
'tot_reads': 11, 'tot_reads': 11,
'curr_reads': 22, 'curr_reads': 22,
'tot_read_bytes': 33, 'tot_read_bytes': 33,
@@ -483,6 +484,7 @@ class ComputeVolumeTestCase(BaseTestCase):
self.assertEquals(payload['read_bytes'], 77) self.assertEquals(payload['read_bytes'], 77)
self.assertEquals(payload['writes'], 121) self.assertEquals(payload['writes'], 121)
self.assertEquals(payload['write_bytes'], 165) self.assertEquals(payload['write_bytes'], 165)
self.assertEquals(payload['availability_zone'], 'fake-az')
def test_detach_volume_usage(self): def test_detach_volume_usage(self):
# Test that detach volume update the volume usage cache table correctly # Test that detach volume update the volume usage cache table correctly

View File

@@ -82,6 +82,7 @@ class _BaseTestCase(object):
inst['ephemeral_gb'] = 0 inst['ephemeral_gb'] = 0
inst['architecture'] = 'x86_64' inst['architecture'] = 'x86_64'
inst['os_type'] = 'Linux' inst['os_type'] = 'Linux'
inst['availability_zone'] = 'fake-az'
inst.update(params) inst.update(params)
return db.instance_create(self.context, inst) return db.instance_create(self.context, inst)
@@ -374,17 +375,18 @@ class _BaseTestCase(object):
def test_vol_usage_update(self): def test_vol_usage_update(self):
self.mox.StubOutWithMock(db, 'vol_usage_update') self.mox.StubOutWithMock(db, 'vol_usage_update')
inst = self._create_fake_instance({
'project_id': 'fake-project_id',
'user_id': 'fake-user_id',
})
db.vol_usage_update(self.context, 'fake-vol', 'rd-req', 'rd-bytes', db.vol_usage_update(self.context, 'fake-vol', 'rd-req', 'rd-bytes',
'wr-req', 'wr-bytes', 'fake-id', 'wr-req', 'wr-bytes', inst['uuid'],
'fake-project_id', 'fake-user_id', 'fake-refr', 'fake-project_id', 'fake-user_id', 'fake-az',
'fake-bool') 'fake-refr', 'fake-bool')
self.mox.ReplayAll() self.mox.ReplayAll()
self.conductor.vol_usage_update(self.context, 'fake-vol', 'rd-req', self.conductor.vol_usage_update(self.context, 'fake-vol', 'rd-req',
'rd-bytes', 'wr-req', 'wr-bytes', 'rd-bytes', 'wr-req', 'wr-bytes',
{'uuid': 'fake-id', inst, 'fake-refr', 'fake-bool')
'project_id': 'fake-project_id',
'user_id': 'fake-user_id'},
'fake-refr', 'fake-bool')
def test_compute_node_create(self): def test_compute_node_create(self):
self.mox.StubOutWithMock(db, 'compute_node_create') self.mox.StubOutWithMock(db, 'compute_node_create')

View File

@@ -2695,17 +2695,20 @@ class VolumeUsageDBApiTestCase(test.TestCase):
wr_req=30, wr_bytes=40, wr_req=30, wr_bytes=40,
instance_id='fake-instance-uuid1', instance_id='fake-instance-uuid1',
project_id='fake-project-uuid1', project_id='fake-project-uuid1',
user_id='fake-user-uuid1') user_id='fake-user-uuid1',
availability_zone='fake-az')
vol_usage = db.vol_usage_update(ctxt, 2, rd_req=100, rd_bytes=200, vol_usage = db.vol_usage_update(ctxt, 2, rd_req=100, rd_bytes=200,
wr_req=300, wr_bytes=400, wr_req=300, wr_bytes=400,
instance_id='fake-instance-uuid2', instance_id='fake-instance-uuid2',
project_id='fake-project-uuid2', project_id='fake-project-uuid2',
user_id='fake-user-uuid2') user_id='fake-user-uuid2',
availability_zone='fake-az')
vol_usage = db.vol_usage_update(ctxt, 1, rd_req=1000, rd_bytes=2000, vol_usage = db.vol_usage_update(ctxt, 1, rd_req=1000, rd_bytes=2000,
wr_req=3000, wr_bytes=4000, wr_req=3000, wr_bytes=4000,
instance_id='fake-instance-uuid1', instance_id='fake-instance-uuid1',
project_id='fake-project-uuid1', project_id='fake-project-uuid1',
user_id='fake-user-uuid1', user_id='fake-user-uuid1',
availability_zone='fake-az',
last_refreshed=refreshed_time) last_refreshed=refreshed_time)
vol_usages = db.vol_get_usage_by_time(ctxt, start_time) vol_usages = db.vol_get_usage_by_time(ctxt, start_time)
@@ -2723,6 +2726,7 @@ class VolumeUsageDBApiTestCase(test.TestCase):
'project_id': 'fake-project-uuid', 'project_id': 'fake-project-uuid',
'user_id': 'fake-user-uuid', 'user_id': 'fake-user-uuid',
'instance_uuid': 'fake-instance-uuid', 'instance_uuid': 'fake-instance-uuid',
'availability_zone': 'fake-az',
'tot_reads': 600, 'tot_reads': 600,
'tot_read_bytes': 800, 'tot_read_bytes': 800,
'tot_writes': 1000, 'tot_writes': 1000,
@@ -2736,23 +2740,27 @@ class VolumeUsageDBApiTestCase(test.TestCase):
wr_req=300, wr_bytes=400, wr_req=300, wr_bytes=400,
instance_id='fake-instance-uuid', instance_id='fake-instance-uuid',
project_id='fake-project-uuid', project_id='fake-project-uuid',
user_id='fake-user-uuid') user_id='fake-user-uuid',
availability_zone='fake-az')
vol_usage = db.vol_usage_update(ctxt, 1, rd_req=200, rd_bytes=300, vol_usage = db.vol_usage_update(ctxt, 1, rd_req=200, rd_bytes=300,
wr_req=400, wr_bytes=500, wr_req=400, wr_bytes=500,
instance_id='fake-instance-uuid', instance_id='fake-instance-uuid',
project_id='fake-project-uuid', project_id='fake-project-uuid',
user_id='fake-user-uuid', user_id='fake-user-uuid',
availability_zone='fake-az',
update_totals=True) update_totals=True)
vol_usage = db.vol_usage_update(ctxt, 1, rd_req=300, rd_bytes=400, vol_usage = db.vol_usage_update(ctxt, 1, rd_req=300, rd_bytes=400,
wr_req=500, wr_bytes=600, wr_req=500, wr_bytes=600,
instance_id='fake-instance-uuid', instance_id='fake-instance-uuid',
project_id='fake-project-uuid', project_id='fake-project-uuid',
availability_zone='fake-az',
user_id='fake-user-uuid') user_id='fake-user-uuid')
vol_usage = db.vol_usage_update(ctxt, 1, rd_req=400, rd_bytes=500, vol_usage = db.vol_usage_update(ctxt, 1, rd_req=400, rd_bytes=500,
wr_req=600, wr_bytes=700, wr_req=600, wr_bytes=700,
instance_id='fake-instance-uuid', instance_id='fake-instance-uuid',
project_id='fake-project-uuid', project_id='fake-project-uuid',
user_id='fake-user-uuid', user_id='fake-user-uuid',
availability_zone='fake-az',
update_totals=True) update_totals=True)
vol_usages = db.vol_get_usage_by_time(ctxt, start_time) vol_usages = db.vol_get_usage_by_time(ctxt, start_time)

View File

@@ -1269,6 +1269,22 @@ class TestNovaMigrations(BaseMigrationTestCase, CommonTestsMixIn):
self.assertFalse('user_id' in rows[0]) self.assertFalse('user_id' in rows[0])
self.assertEqual(rows[0]['instance_id'], None) self.assertEqual(rows[0]['instance_id'], None)
def _check_176(self, engine, data):
volume_usage_cache = get_table(engine, 'volume_usage_cache')
# Get the record
rows = volume_usage_cache.select().execute().fetchall()
self.assertEqual(len(rows), 1)
self.assertEqual(rows[0]['availability_zone'], None)
def _post_downgrade_176(self, engine):
volume_usage_cache = get_table(engine, 'volume_usage_cache')
# Get the record
rows = volume_usage_cache.select().execute().fetchall()
self.assertEqual(len(rows), 1)
self.assertFalse('availability_zone' in rows[0])
class TestBaremetalMigrations(BaseMigrationTestCase, CommonTestsMixIn): class TestBaremetalMigrations(BaseMigrationTestCase, CommonTestsMixIn):
"""Test sqlalchemy-migrate migrations.""" """Test sqlalchemy-migrate migrations."""