from cinder.openstack.common import fileutils
from cinder.openstack.common import importutils
from cinder.openstack.common import jsonutils
+from cinder.openstack.common import timeutils
import cinder.policy
from cinder import quota
from cinder import test
'uuid': 'vR1JU3-FAKE-C4A9-PQFh-Mctm-9FwA-Xwzc1m'}]
+class AvailabilityZoneTestCase(BaseVolumeTestCase):
+ def test_list_availability_zones_cached(self):
+ volume_api = cinder.volume.api.API()
+ with mock.patch.object(volume_api.db,
+ 'service_get_all_by_topic') as get_all:
+ get_all.return_value = [
+ {
+ 'availability_zone': 'a',
+ 'disabled': False,
+ },
+ ]
+ azs = volume_api.list_availability_zones(enable_cache=True)
+ self.assertEqual([{"name": 'a', 'available': True}], list(azs))
+ self.assertIsNotNone(volume_api.availability_zones_last_fetched)
+ self.assertTrue(get_all.called)
+ volume_api.list_availability_zones(enable_cache=True)
+ self.assertEqual(1, get_all.call_count)
+
+ def test_list_availability_zones_no_cached(self):
+ volume_api = cinder.volume.api.API()
+ with mock.patch.object(volume_api.db,
+ 'service_get_all_by_topic') as get_all:
+ get_all.return_value = [
+ {
+ 'availability_zone': 'a',
+ 'disabled': False,
+ },
+ ]
+ azs = volume_api.list_availability_zones(enable_cache=False)
+ self.assertEqual([{"name": 'a', 'available': True}], list(azs))
+ self.assertIsNone(volume_api.availability_zones_last_fetched)
+
+ with mock.patch.object(volume_api.db,
+ 'service_get_all_by_topic') as get_all:
+ get_all.return_value = [
+ {
+ 'availability_zone': 'a',
+ 'disabled': True,
+ },
+ ]
+ azs = volume_api.list_availability_zones(enable_cache=False)
+ self.assertEqual([{"name": 'a', 'available': False}], list(azs))
+ self.assertIsNone(volume_api.availability_zones_last_fetched)
+
+ def test_list_availability_zones_refetched(self):
+ timeutils.set_time_override()
+ volume_api = cinder.volume.api.API()
+ with mock.patch.object(volume_api.db,
+ 'service_get_all_by_topic') as get_all:
+ get_all.return_value = [
+ {
+ 'availability_zone': 'a',
+ 'disabled': False,
+ },
+ ]
+ azs = volume_api.list_availability_zones(enable_cache=True)
+ self.assertEqual([{"name": 'a', 'available': True}], list(azs))
+ self.assertIsNotNone(volume_api.availability_zones_last_fetched)
+ last_fetched = volume_api.availability_zones_last_fetched
+ self.assertTrue(get_all.called)
+ volume_api.list_availability_zones(enable_cache=True)
+ self.assertEqual(1, get_all.call_count)
+
+ # The default cache time is 3600, push past that...
+ timeutils.advance_time_seconds(3800)
+ get_all.return_value = [
+ {
+ 'availability_zone': 'a',
+ 'disabled': False,
+ },
+ {
+ 'availability_zone': 'b',
+ 'disabled': False,
+ },
+ ]
+ azs = volume_api.list_availability_zones(enable_cache=True)
+ azs = sorted([n['name'] for n in azs])
+ self.assertEqual(['a', 'b'], azs)
+ self.assertEqual(2, get_all.call_count)
+ self.assertGreater(volume_api.availability_zones_last_fetched,
+ last_fetched)
+
+
class VolumeTestCase(BaseVolumeTestCase):
def setUp(self):
"""Test setting availability_zone correctly during volume create."""
volume_api = cinder.volume.api.API()
- def fake_list_availability_zones():
+ def fake_list_availability_zones(enable_cache=False):
return ({'name': 'az1', 'available': True},
{'name': 'az2', 'available': True},
{'name': 'default-az', 'available': True})
"""Test volume can't be created from snapshot in a different az."""
volume_api = cinder.volume.api.API()
- def fake_list_availability_zones():
+ def fake_list_availability_zones(enable_cache=False):
return ({'name': 'nova', 'available': True},
{'name': 'az2', 'available': True})
"""Test volume can't be cloned from an other volume in different az."""
volume_api = cinder.volume.api.API()
- def fake_list_availability_zones():
+ def fake_list_availability_zones(enable_cache=False):
return ({'name': 'nova', 'available': True},
{'name': 'az2', 'available': True})
import collections
+import datetime
import functools
from oslo.config import cfg
default=True,
help='Ensure that the new volumes are the '
'same AZ as snapshot or source volume')
+az_cache_time_opt = cfg.IntOpt('az_cache_duration',
+ default=3600,
+ help='Cache volume availability zones in '
+ 'memory for the provided duration in '
+ 'seconds')
CONF = cfg.CONF
CONF.register_opt(volume_host_opt)
CONF.register_opt(volume_same_az_opt)
+CONF.register_opt(az_cache_time_opt)
CONF.import_opt('glance_core_properties', 'cinder.image.glance')
CONF.import_opt('storage_availability_zone', 'cinder.volume.manager')
glance.get_default_image_service())
self.scheduler_rpcapi = scheduler_rpcapi.SchedulerAPI()
self.volume_rpcapi = volume_rpcapi.VolumeAPI()
- self.availability_zone_names = ()
+ self.availability_zones = []
+ self.availability_zones_last_fetched = None
self.key_manager = keymgr.API()
super(API, self).__init__(db_driver)
def _valid_availability_zone(self, availability_zone):
- #NOTE(bcwaldon): This approach to caching fails to handle the case
- # that an availability zone is disabled/removed.
- if availability_zone in self.availability_zone_names:
- return True
- if CONF.storage_availability_zone == availability_zone:
- return True
-
- azs = self.list_availability_zones()
- self.availability_zone_names = [az['name'] for az in azs]
- return availability_zone in self.availability_zone_names
-
- def list_availability_zones(self):
+ azs = self.list_availability_zones(enable_cache=True)
+ names = set([az['name'] for az in azs])
+ if CONF.storage_availability_zone:
+ names.add(CONF.storage_availability_zone)
+ return availability_zone in names
+
+ def list_availability_zones(self, enable_cache=False):
"""Describe the known availability zones
:retval list of dicts, each with a 'name' and 'available' key
"""
- topic = CONF.volume_topic
- ctxt = context.get_admin_context()
- services = self.db.service_get_all_by_topic(ctxt, topic)
- az_data = [(s['availability_zone'], s['disabled']) for s in services]
-
- disabled_map = {}
- for (az_name, disabled) in az_data:
- tracked_disabled = disabled_map.get(az_name, True)
- disabled_map[az_name] = tracked_disabled and disabled
-
- azs = [{'name': name, 'available': not disabled}
- for (name, disabled) in disabled_map.items()]
-
+ refresh_cache = False
+ if enable_cache:
+ if self.availability_zones_last_fetched is None:
+ refresh_cache = True
+ else:
+ cache_age = timeutils.delta_seconds(
+ self.availability_zones_last_fetched,
+ timeutils.utcnow())
+ if cache_age >= CONF.az_cache_duration:
+ refresh_cache = True
+ if refresh_cache or not enable_cache:
+ topic = CONF.volume_topic
+ ctxt = context.get_admin_context()
+ services = self.db.service_get_all_by_topic(ctxt, topic)
+ az_data = [(s['availability_zone'], s['disabled'])
+ for s in services]
+ disabled_map = {}
+ for (az_name, disabled) in az_data:
+ tracked_disabled = disabled_map.get(az_name, True)
+ disabled_map[az_name] = tracked_disabled and disabled
+ azs = [{'name': name, 'available': not disabled}
+ for (name, disabled) in disabled_map.items()]
+ if refresh_cache:
+ now = timeutils.utcnow()
+ self.availability_zones = azs
+ self.availability_zones_last_fetched = now
+ LOG.debug("Availability zone cache updated, next update will"
+ " occur around %s", now + datetime.timedelta(
+ seconds=CONF.az_cache_duration))
+ else:
+ azs = self.availability_zones
return tuple(azs)
def create(self, context, size, name, description, snapshot=None,