Use a named enum for capability values

In Iedf0d4f829e46ca64c3f4fc6a7dfee54d9b0605b we added support for driver
capabilities in glance_store. That same change also used (mutable)
module globals to check driver capabilities. By moving the capability
definitions inside a subclass of IntEnum we retain all of the previous
functionality with the guarantee that the enum values will not change
once defined.

Change-Id: I13c7bd507252e793b2c790cc9b77c7b8db432d02
This commit is contained in:
Ian Cordasco 2015-01-23 12:24:52 -06:00
parent 138875b7c3
commit ac9bd0d83a
16 changed files with 107 additions and 83 deletions

View File

@ -131,7 +131,7 @@ class Store(glance_store.driver.Store):
"""Cinder backend store adapter.""" """Cinder backend store adapter."""
_CAPABILITIES = capabilities.DRIVER_REUSABLE _CAPABILITIES = capabilities.BitMasks.DRIVER_REUSABLE
OPTIONS = _CINDER_OPTS OPTIONS = _CINDER_OPTS
EXAMPLE_URL = "cinder://<VOLUME_ID>" EXAMPLE_URL = "cinder://<VOLUME_ID>"

View File

@ -149,9 +149,9 @@ class ChunkedFile(object):
class Store(glance_store.driver.Store): class Store(glance_store.driver.Store):
_CAPABILITIES = (capabilities.READ_RANDOM | _CAPABILITIES = (capabilities.BitMasks.READ_RANDOM |
capabilities.WRITE_ACCESS | capabilities.BitMasks.WRITE_ACCESS |
capabilities.DRIVER_REUSABLE) capabilities.BitMasks.DRIVER_REUSABLE)
OPTIONS = _FILESYSTEM_CONFIGS OPTIONS = _FILESYSTEM_CONFIGS
READ_CHUNKSIZE = 64 * units.Ki READ_CHUNKSIZE = 64 * units.Ki
WRITE_CHUNKSIZE = READ_CHUNKSIZE WRITE_CHUNKSIZE = READ_CHUNKSIZE

View File

@ -81,7 +81,7 @@ class StoreLocation(glance_store.location.StoreLocation):
class Store(glance_store.driver.Store): class Store(glance_store.driver.Store):
"""GridFS adapter""" """GridFS adapter"""
_CAPABILITIES = capabilities.RW_ACCESS _CAPABILITIES = capabilities.BitMasks.RW_ACCESS
OPTIONS = _GRIDFS_OPTS OPTIONS = _GRIDFS_OPTS
EXAMPLE_URL = "gridfs://<IMAGE_ID>" EXAMPLE_URL = "gridfs://<IMAGE_ID>"

View File

@ -112,8 +112,8 @@ class Store(glance_store.driver.Store):
"""An implementation of the HTTP(S) Backend Adapter""" """An implementation of the HTTP(S) Backend Adapter"""
_CAPABILITIES = (capabilities.READ_ACCESS | _CAPABILITIES = (capabilities.BitMasks.READ_ACCESS |
capabilities.DRIVER_REUSABLE) capabilities.BitMasks.DRIVER_REUSABLE)
@capabilities.check @capabilities.check
def get(self, location, offset=0, chunk_size=None, context=None): def get(self, location, offset=0, chunk_size=None, context=None):

View File

@ -176,7 +176,7 @@ class ImageIterator(object):
class Store(driver.Store): class Store(driver.Store):
"""An implementation of the RBD backend adapter.""" """An implementation of the RBD backend adapter."""
_CAPABILITIES = capabilities.RW_ACCESS _CAPABILITIES = capabilities.BitMasks.RW_ACCESS
OPTIONS = _RBD_OPTS OPTIONS = _RBD_OPTS
EXAMPLE_URL = "rbd://<FSID>/<POOL>/<IMAGE>/<SNAP>" EXAMPLE_URL = "rbd://<FSID>/<POOL>/<IMAGE>/<SNAP>"

View File

@ -294,7 +294,7 @@ class ChunkedFile(object):
class Store(glance_store.driver.Store): class Store(glance_store.driver.Store):
"""An implementation of the s3 adapter.""" """An implementation of the s3 adapter."""
_CAPABILITIES = capabilities.RW_ACCESS _CAPABILITIES = capabilities.BitMasks.RW_ACCESS
OPTIONS = _S3_OPTS OPTIONS = _S3_OPTS
EXAMPLE_URL = "s3://<ACCESS_KEY>:<SECRET_KEY>@<S3_URL>/<BUCKET>/<OBJ>" EXAMPLE_URL = "s3://<ACCESS_KEY>:<SECRET_KEY>@<S3_URL>/<BUCKET>/<OBJ>"

View File

@ -174,7 +174,8 @@ class ImageIterator(object):
class Store(glance_store.driver.Store): class Store(glance_store.driver.Store):
"""Sheepdog backend adapter.""" """Sheepdog backend adapter."""
_CAPABILITIES = (capabilities.RW_ACCESS | capabilities.DRIVER_REUSABLE) _CAPABILITIES = (capabilities.BitMasks.RW_ACCESS |
capabilities.BitMasks.DRIVER_REUSABLE)
OPTIONS = _SHEEPDOG_OPTS OPTIONS = _SHEEPDOG_OPTS
EXAMPLE_URL = "sheepdog://image" EXAMPLE_URL = "sheepdog://image"

View File

@ -375,7 +375,7 @@ Store.OPTIONS = _SWIFT_OPTS + sutils.swift_opts
class BaseStore(driver.Store): class BaseStore(driver.Store):
_CAPABILITIES = capabilities.RW_ACCESS _CAPABILITIES = capabilities.BitMasks.RW_ACCESS
CHUNKSIZE = 65536 CHUNKSIZE = 65536
OPTIONS = _SWIFT_OPTS + sutils.swift_opts OPTIONS = _SWIFT_OPTS + sutils.swift_opts

View File

@ -221,7 +221,7 @@ class StoreLocation(location.StoreLocation):
class Store(glance_store.Store): class Store(glance_store.Store):
"""An implementation of the VMware datastore adapter.""" """An implementation of the VMware datastore adapter."""
_CAPABILITIES = capabilities.RW_ACCESS _CAPABILITIES = capabilities.BitMasks.RW_ACCESS
OPTIONS = _VMWARE_OPTS OPTIONS = _VMWARE_OPTS
WRITE_CHUNKSIZE = units.Mi WRITE_CHUNKSIZE = units.Mi
# FIXME(arnaud): re-visit this code once the store API is cleaned up. # FIXME(arnaud): re-visit this code once the store API is cleaned up.

View File

@ -231,7 +231,7 @@ def get_store_from_scheme(scheme):
raise exceptions.UnknownScheme(scheme=scheme) raise exceptions.UnknownScheme(scheme=scheme)
scheme_info = location.SCHEME_TO_CLS_MAP[scheme] scheme_info = location.SCHEME_TO_CLS_MAP[scheme]
store = scheme_info['store'] store = scheme_info['store']
if not store.is_capable(capabilities.DRIVER_REUSABLE): if not store.is_capable(capabilities.BitMasks.DRIVER_REUSABLE):
# Driver instance isn't stateless so it can't # Driver instance isn't stateless so it can't
# be reused safely and need recreation. # be reused safely and need recreation.
store_entry = scheme_info['store_entry'] store_entry = scheme_info['store_entry']

View File

@ -19,6 +19,7 @@ import logging
import threading import threading
import time import time
import enum
from eventlet import tpool from eventlet import tpool
from glance_store import exceptions from glance_store import exceptions
@ -29,22 +30,34 @@ _STORE_CAPABILITES_UPDATE_SCHEDULING_BOOK = {}
_STORE_CAPABILITES_UPDATE_SCHEDULING_LOCK = threading.Lock() _STORE_CAPABILITES_UPDATE_SCHEDULING_LOCK = threading.Lock()
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
# Store capability constants
class BitMasks(enum.IntEnum):
NONE = 0b00000000 NONE = 0b00000000
ALL = 0b11111111 ALL = 0b11111111
READ_ACCESS = 0b00000001 READ_ACCESS = 0b00000001
READ_OFFSET = 0b00000011 # Included READ_ACCESS # Included READ_ACCESS
READ_CHUNK = 0b00000101 # Included READ_ACCESS READ_OFFSET = 0b00000011
READ_RANDOM = 0b00000111 # READ_OFFSET | READ_CHUNK # Included READ_ACCESS
READ_CHUNK = 0b00000101
# READ_OFFSET | READ_CHUNK
READ_RANDOM = 0b00000111
WRITE_ACCESS = 0b00001000 WRITE_ACCESS = 0b00001000
WRITE_OFFSET = 0b00011000 # Included WRITE_ACCESS # Included WRITE_ACCESS
WRITE_CHUNK = 0b00101000 # Included WRITE_ACCESS WRITE_OFFSET = 0b00011000
WRITE_RANDOM = 0b00111000 # WRITE_OFFSET | WRITE_CHUNK # Included WRITE_ACCESS
RW_ACCESS = 0b00001001 # READ_ACCESS | WRITE_ACCESS WRITE_CHUNK = 0b00101000
RW_OFFSET = 0b00011011 # READ_OFFSET | WRITE_OFFSET # WRITE_OFFSET | WRITE_CHUNK
RW_CHUNK = 0b00101101 # READ_CHUNK | WRITE_CHUNK WRITE_RANDOM = 0b00111000
RW_RANDOM = 0b00111111 # RW_OFFSET | RW_CHUNK # READ_ACCESS | WRITE_ACCESS
DRIVER_REUSABLE = 0b01000000 # driver is stateless and can be reused safely RW_ACCESS = 0b00001001
# READ_OFFSET | WRITE_OFFSET
RW_OFFSET = 0b00011011
# READ_CHUNK | WRITE_CHUNK
RW_CHUNK = 0b00101101
# RW_OFFSET | RW_CHUNK
RW_RANDOM = 0b00111111
# driver is stateless and can be reused safely
DRIVER_REUSABLE = 0b01000000
class StoreCapability(object): class StoreCapability(object):
@ -178,12 +191,16 @@ def check(store_op_fun):
if store.conf.glance_store.store_capabilities_update_min_interval > 0: if store.conf.glance_store.store_capabilities_update_min_interval > 0:
_schedule_capabilities_update(store) _schedule_capabilities_update(store)
get_capabilities = [
BitMasks.READ_ACCESS,
BitMasks.READ_OFFSET if kwargs.get('offset') else BitMasks.NONE,
BitMasks.READ_CHUNK if kwargs.get('chunk_size') else BitMasks.NONE
]
op_cap_map = { op_cap_map = {
'get': [READ_ACCESS, 'get': get_capabilities,
READ_OFFSET if kwargs.get('offset') else NONE, 'add': [BitMasks.WRITE_ACCESS],
READ_CHUNK if kwargs.get('chunk_size') else NONE], 'delete': [BitMasks.WRITE_ACCESS]}
'add': [WRITE_ACCESS],
'delete': [WRITE_ACCESS]}
op_exec_map = { op_exec_map = {
'get': (exceptions.StoreRandomGetNotSupported 'get': (exceptions.StoreRandomGetNotSupported

View File

@ -70,7 +70,7 @@ class Store(capabilities.StoreCapability):
try: try:
self.configure_add() self.configure_add()
except exceptions.BadStoreConfiguration as e: except exceptions.BadStoreConfiguration as e:
self.unset_capabilities(capabilities.WRITE_ACCESS) self.unset_capabilities(capabilities.BitMasks.WRITE_ACCESS)
msg = (_(u"Failed to configure store correctly: %s " msg = (_(u"Failed to configure store correctly: %s "
"Disabling add method.") % utils.exception_to_str(e)) "Disabling add method.") % utils.exception_to_str(e))
LOG.warn(msg) LOG.warn(msg)

View File

@ -4,6 +4,7 @@ oslo.serialization>=1.0.0 # Apache-2.0
oslo.utils>=1.2.0 # Apache-2.0 oslo.utils>=1.2.0 # Apache-2.0
oslo.concurrency>=1.4.1 # Apache-2.0 oslo.concurrency>=1.4.1 # Apache-2.0
stevedore>=0.12 stevedore>=0.12
enum34
python-cinderclient>=1.0.6 python-cinderclient>=1.0.6

View File

@ -489,7 +489,8 @@ class TestStore(base.StoreBaseTest,
self.config(**conf) self.config(**conf)
self.store = s3.Store(self.conf) self.store = s3.Store(self.conf)
self.store.configure() self.store.configure()
return not self.store.is_capable(capabilities.WRITE_ACCESS) return not self.store.is_capable(
capabilities.BitMasks.WRITE_ACCESS)
except Exception: except Exception:
return False return False
return False return False

View File

@ -19,23 +19,24 @@ from glance_store.tests import base
class FakeStoreWithStaticCapabilities(caps.StoreCapability): class FakeStoreWithStaticCapabilities(caps.StoreCapability):
_CAPABILITIES = caps.READ_RANDOM | caps.DRIVER_REUSABLE _CAPABILITIES = caps.BitMasks.READ_RANDOM | caps.BitMasks.DRIVER_REUSABLE
class FakeStoreWithDynamicCapabilities(caps.StoreCapability): class FakeStoreWithDynamicCapabilities(caps.StoreCapability):
def __init__(self, *cap_list): def __init__(self, *cap_list):
super(FakeStoreWithDynamicCapabilities, self).__init__() super(FakeStoreWithDynamicCapabilities, self).__init__()
if not cap_list: if not cap_list:
cap_list = [caps.READ_RANDOM, caps.DRIVER_REUSABLE] cap_list = [caps.BitMasks.READ_RANDOM,
caps.BitMasks.DRIVER_REUSABLE]
self.set_capabilities(*cap_list) self.set_capabilities(*cap_list)
class FakeStoreWithMixedCapabilities(caps.StoreCapability): class FakeStoreWithMixedCapabilities(caps.StoreCapability):
_CAPABILITIES = caps.READ_RANDOM _CAPABILITIES = caps.BitMasks.READ_RANDOM
def __init__(self): def __init__(self):
super(FakeStoreWithMixedCapabilities, self).__init__() super(FakeStoreWithMixedCapabilities, self).__init__()
self.set_capabilities(caps.DRIVER_REUSABLE) self.set_capabilities(caps.BitMasks.DRIVER_REUSABLE)
class TestStoreCapabilitiesChecking(object): class TestStoreCapabilitiesChecking(object):
@ -50,9 +51,9 @@ class TestStoreCapabilities(base.StoreBaseTest):
def _verify_store_capabilities(self, store): def _verify_store_capabilities(self, store):
# This function tested is_capable() as well. # This function tested is_capable() as well.
self.assertTrue(store.is_capable(caps.READ_RANDOM)) self.assertTrue(store.is_capable(caps.BitMasks.READ_RANDOM))
self.assertTrue(store.is_capable(caps.DRIVER_REUSABLE)) self.assertTrue(store.is_capable(caps.BitMasks.DRIVER_REUSABLE))
self.assertFalse(store.is_capable(caps.WRITE_ACCESS)) self.assertFalse(store.is_capable(caps.BitMasks.WRITE_ACCESS))
def test_static_capabilities_setup(self): def test_static_capabilities_setup(self):
self._verify_store_capabilities(FakeStoreWithStaticCapabilities()) self._verify_store_capabilities(FakeStoreWithStaticCapabilities())
@ -65,16 +66,16 @@ class TestStoreCapabilities(base.StoreBaseTest):
def test_set_unset_capabilities(self): def test_set_unset_capabilities(self):
store = FakeStoreWithStaticCapabilities() store = FakeStoreWithStaticCapabilities()
self.assertFalse(store.is_capable(caps.WRITE_ACCESS)) self.assertFalse(store.is_capable(caps.BitMasks.WRITE_ACCESS))
# Set and unset single capability on one time # Set and unset single capability on one time
store.set_capabilities(caps.WRITE_ACCESS) store.set_capabilities(caps.BitMasks.WRITE_ACCESS)
self.assertTrue(store.is_capable(caps.WRITE_ACCESS)) self.assertTrue(store.is_capable(caps.BitMasks.WRITE_ACCESS))
store.unset_capabilities(caps.WRITE_ACCESS) store.unset_capabilities(caps.BitMasks.WRITE_ACCESS)
self.assertFalse(store.is_capable(caps.WRITE_ACCESS)) self.assertFalse(store.is_capable(caps.BitMasks.WRITE_ACCESS))
# Set and unset multiple capabilities on one time # Set and unset multiple capabilities on one time
cap_list = [caps.WRITE_ACCESS, caps.WRITE_OFFSET] cap_list = [caps.BitMasks.WRITE_ACCESS, caps.BitMasks.WRITE_OFFSET]
store.set_capabilities(*cap_list) store.set_capabilities(*cap_list)
self.assertTrue(store.is_capable(*cap_list)) self.assertTrue(store.is_capable(*cap_list))
store.unset_capabilities(*cap_list) store.unset_capabilities(*cap_list)
@ -90,54 +91,54 @@ class TestStoreCapabilities(base.StoreBaseTest):
# Test read capability # Test read capability
store = FakeStoreWithMixedCapabilities() store = FakeStoreWithMixedCapabilities()
self._verify_store_capabilities(store) self._verify_store_capabilities(store)
store.unset_capabilities(caps.READ_ACCESS) store.unset_capabilities(caps.BitMasks.READ_ACCESS)
cap_list = [caps.READ_ACCESS, caps.READ_OFFSET, cap_list = [caps.BitMasks.READ_ACCESS, caps.BitMasks.READ_OFFSET,
caps.READ_CHUNK, caps.READ_RANDOM] caps.BitMasks.READ_CHUNK, caps.BitMasks.READ_RANDOM]
for cap in cap_list: for cap in cap_list:
# To make sure all of them are unsetted. # To make sure all of them are unsetted.
self.assertFalse(store.is_capable(cap)) self.assertFalse(store.is_capable(cap))
self.assertTrue(store.is_capable(caps.DRIVER_REUSABLE)) self.assertTrue(store.is_capable(caps.BitMasks.DRIVER_REUSABLE))
# Test write capability # Test write capability
store = FakeStoreWithDynamicCapabilities(caps.WRITE_RANDOM, store = FakeStoreWithDynamicCapabilities(caps.BitMasks.WRITE_RANDOM,
caps.DRIVER_REUSABLE) caps.BitMasks.DRIVER_REUSABLE)
self.assertTrue(store.is_capable(caps.WRITE_RANDOM)) self.assertTrue(store.is_capable(caps.BitMasks.WRITE_RANDOM))
self.assertTrue(store.is_capable(caps.DRIVER_REUSABLE)) self.assertTrue(store.is_capable(caps.BitMasks.DRIVER_REUSABLE))
store.unset_capabilities(caps.WRITE_ACCESS) store.unset_capabilities(caps.BitMasks.WRITE_ACCESS)
cap_list = [caps.WRITE_ACCESS, caps.WRITE_OFFSET, cap_list = [caps.BitMasks.WRITE_ACCESS, caps.BitMasks.WRITE_OFFSET,
caps.WRITE_CHUNK, caps.WRITE_RANDOM] caps.BitMasks.WRITE_CHUNK, caps.BitMasks.WRITE_RANDOM]
for cap in cap_list: for cap in cap_list:
# To make sure all of them are unsetted. # To make sure all of them are unsetted.
self.assertFalse(store.is_capable(cap)) self.assertFalse(store.is_capable(cap))
self.assertTrue(store.is_capable(caps.DRIVER_REUSABLE)) self.assertTrue(store.is_capable(caps.BitMasks.DRIVER_REUSABLE))
class TestStoreCapabilityConstants(base.StoreBaseTest): class TestStoreCapabilityConstants(base.StoreBaseTest):
def test_one_single_capability_own_one_bit(self): def test_one_single_capability_own_one_bit(self):
cap_list = [ cap_list = [
caps.READ_ACCESS, caps.BitMasks.READ_ACCESS,
caps.WRITE_ACCESS, caps.BitMasks.WRITE_ACCESS,
caps.DRIVER_REUSABLE, caps.BitMasks.DRIVER_REUSABLE,
] ]
for cap in cap_list: for cap in cap_list:
self.assertEqual(1, bin(cap).count('1')) self.assertEqual(1, bin(cap).count('1'))
def test_combined_capability_bits(self): def test_combined_capability_bits(self):
check = caps.StoreCapability.contains check = caps.StoreCapability.contains
check(caps.READ_OFFSET, caps.READ_ACCESS) check(caps.BitMasks.READ_OFFSET, caps.BitMasks.READ_ACCESS)
check(caps.READ_CHUNK, caps.READ_ACCESS) check(caps.BitMasks.READ_CHUNK, caps.BitMasks.READ_ACCESS)
check(caps.READ_RANDOM, caps.READ_CHUNK) check(caps.BitMasks.READ_RANDOM, caps.BitMasks.READ_CHUNK)
check(caps.READ_RANDOM, caps.READ_OFFSET) check(caps.BitMasks.READ_RANDOM, caps.BitMasks.READ_OFFSET)
check(caps.WRITE_OFFSET, caps.WRITE_ACCESS) check(caps.BitMasks.WRITE_OFFSET, caps.BitMasks.WRITE_ACCESS)
check(caps.WRITE_CHUNK, caps.WRITE_ACCESS) check(caps.BitMasks.WRITE_CHUNK, caps.BitMasks.WRITE_ACCESS)
check(caps.WRITE_RANDOM, caps.WRITE_CHUNK) check(caps.BitMasks.WRITE_RANDOM, caps.BitMasks.WRITE_CHUNK)
check(caps.WRITE_RANDOM, caps.WRITE_OFFSET) check(caps.BitMasks.WRITE_RANDOM, caps.BitMasks.WRITE_OFFSET)
check(caps.RW_ACCESS, caps.READ_ACCESS) check(caps.BitMasks.RW_ACCESS, caps.BitMasks.READ_ACCESS)
check(caps.RW_ACCESS, caps.WRITE_ACCESS) check(caps.BitMasks.RW_ACCESS, caps.BitMasks.WRITE_ACCESS)
check(caps.RW_OFFSET, caps.READ_OFFSET) check(caps.BitMasks.RW_OFFSET, caps.BitMasks.READ_OFFSET)
check(caps.RW_OFFSET, caps.WRITE_OFFSET) check(caps.BitMasks.RW_OFFSET, caps.BitMasks.WRITE_OFFSET)
check(caps.RW_CHUNK, caps.READ_CHUNK) check(caps.BitMasks.RW_CHUNK, caps.BitMasks.READ_CHUNK)
check(caps.RW_CHUNK, caps.WRITE_CHUNK) check(caps.BitMasks.RW_CHUNK, caps.BitMasks.WRITE_CHUNK)
check(caps.RW_RANDOM, caps.READ_RANDOM) check(caps.BitMasks.RW_RANDOM, caps.BitMasks.READ_RANDOM)
check(caps.RW_RANDOM, caps.WRITE_RANDOM) check(caps.BitMasks.RW_RANDOM, caps.BitMasks.WRITE_RANDOM)

View File

@ -761,7 +761,8 @@ class SwiftTests(object):
try: try:
self.config(**conf) self.config(**conf)
self.store = Store(self.conf) self.store = Store(self.conf)
return not self.store.is_capable(capabilities.WRITE_ACCESS) return not self.store.is_capable(
capabilities.BitMasks.WRITE_ACCESS)
except Exception: except Exception:
return False return False
return False return False
@ -775,7 +776,8 @@ class SwiftTests(object):
'authurl.com', 'user': '', 'authurl.com', 'user': '',
'key': ''}} 'key': ''}}
self.store.configure() self.store.configure()
self.assertFalse(self.store.is_capable(capabilities.WRITE_ACCESS)) self.assertFalse(self.store.is_capable(
capabilities.BitMasks.WRITE_ACCESS))
def test_no_auth_address(self): def test_no_auth_address(self):
""" """
@ -786,7 +788,8 @@ class SwiftTests(object):
'', 'user': 'user1', '', 'user': 'user1',
'key': 'key1'}} 'key': 'key1'}}
self.store.configure() self.store.configure()
self.assertFalse(self.store.is_capable(capabilities.WRITE_ACCESS)) self.assertFalse(self.store.is_capable(
capabilities.BitMasks.WRITE_ACCESS))
def test_delete(self): def test_delete(self):
""" """