Drop charm supplied paste.ini configurations

The glance-api-paste.ini and glance-registry-paste.ini configuration
files provided as part of the glance packages should be used; drop
charm templates and associated code paths.

This was required in early OpenStack versions where keystone auth
credentials where rendered into the paste ini file.

Existing deployments will go through a upgrade step to replace the
previously provided paste files from the charm with those provided
by the glance-api and glance-registry packages.

Change-Id: I016b8cb7823b98e8dffa91b3b427ed88a7b619bc
This commit is contained in:
James Page 2016-03-04 14:27:12 +00:00
parent 2a8c30bd49
commit a8f380b4cf
13 changed files with 91 additions and 417 deletions

View File

@ -18,14 +18,13 @@ from glance_utils import (
SERVICES,
CHARM,
GLANCE_REGISTRY_CONF,
GLANCE_REGISTRY_PASTE_INI,
GLANCE_API_CONF,
GLANCE_API_PASTE_INI,
HAPROXY_CONF,
ceph_config_file,
setup_ipv6,
swift_temp_url_key,
assess_status,
reinstall_paste_ini,
)
from charmhelpers.core.hookenv import (
config,
@ -318,9 +317,6 @@ def keystone_changed():
CONFIGS.write(GLANCE_API_CONF)
CONFIGS.write(GLANCE_REGISTRY_CONF)
CONFIGS.write(GLANCE_API_PASTE_INI)
CONFIGS.write(GLANCE_REGISTRY_PASTE_INI)
# Configure any object-store / swift relations now that we have an
# identity-service
if relation_ids('object-store'):
@ -391,6 +387,7 @@ def cluster_changed():
@restart_on_change(restart_map(), stopstart=True)
def upgrade_charm():
apt_install(filter_installed_packages(determine_packages()), fatal=True)
reinstall_paste_ini()
configure_https()
update_nrpe_config()
CONFIGS.write_all()

View File

@ -113,9 +113,11 @@ CHARM = "glance"
GLANCE_CONF_DIR = "/etc/glance"
GLANCE_REGISTRY_CONF = "%s/glance-registry.conf" % GLANCE_CONF_DIR
GLANCE_REGISTRY_PASTE_INI = "%s/glance-registry-paste.ini" % GLANCE_CONF_DIR
GLANCE_API_CONF = "%s/glance-api.conf" % GLANCE_CONF_DIR
GLANCE_API_PASTE_INI = "%s/glance-api-paste.ini" % GLANCE_CONF_DIR
GLANCE_REGISTRY_PASTE = os.path.join(GLANCE_CONF_DIR,
'glance-registry-paste.ini')
GLANCE_API_PASTE = os.path.join(GLANCE_CONF_DIR,
'glance-api-paste.ini')
CEPH_CONF = "/etc/ceph/ceph.conf"
CHARM_CEPH_CONF = '/var/lib/charm/{}/ceph.conf'
@ -175,14 +177,6 @@ CONFIG_FILES = OrderedDict([
template_flag='api_config_flags')],
'services': ['glance-api']
}),
(GLANCE_API_PASTE_INI, {
'hook_contexts': [context.IdentityServiceContext()],
'services': ['glance-api']
}),
(GLANCE_REGISTRY_PASTE_INI, {
'hook_contexts': [context.IdentityServiceContext()],
'services': ['glance-registry']
}),
(ceph_config_file(), {
'hook_contexts': [context.CephContext()],
'services': ['glance-api', 'glance-registry']
@ -213,8 +207,6 @@ def register_configs():
confs = [GLANCE_REGISTRY_CONF,
GLANCE_API_CONF,
GLANCE_API_PASTE_INI,
GLANCE_REGISTRY_PASTE_INI,
HAPROXY_CONF]
if relation_ids('ceph'):
@ -537,3 +529,33 @@ def assess_status(configs):
# set the status according to the current state of the contexts
set_os_workload_status(
configs, REQUIRED_INTERFACES, charm_func=check_optional_relations)
PASTE_INI_MARKER = 'paste-ini-marker'
REINSTALL_OPTIONS = [
'--reinstall',
'--option=Dpkg::Options::=--force-confmiss'
]
def reinstall_paste_ini():
'''
Re-install glance-{api,registry}-paste.ini file from packages
Existing glance-{api,registry}-paste.ini file will be removed
and the original files provided by the packages will be
re-installed.
This will only ever be performed once per unit.
'''
db = kv()
if not db.get(PASTE_INI_MARKER):
for paste_file in [GLANCE_REGISTRY_PASTE,
GLANCE_API_PASTE]:
if os.path.exists(paste_file):
os.remove(paste_file)
apt_install(packages=['glance-api', 'glance-registry'],
options=REINSTALL_OPTIONS,
fatal=True)
db.set(PASTE_INI_MARKER, True)
db.flush()

1
hooks/upgrade-charm Symbolic link
View File

@ -0,0 +1 @@
glance_relations.py

View File

@ -1,72 +0,0 @@
# Use this pipeline for no auth or image caching - DEFAULT
[pipeline:glance-api]
pipeline = versionnegotiation unauthenticated-context rootapp
# Use this pipeline for image caching and no auth
[pipeline:glance-api-caching]
pipeline = versionnegotiation unauthenticated-context cache rootapp
# Use this pipeline for caching w/ management interface but no auth
[pipeline:glance-api-cachemanagement]
pipeline = versionnegotiation unauthenticated-context cache cachemanage rootapp
# Use this pipeline for keystone auth
[pipeline:glance-api-keystone]
pipeline = versionnegotiation authtoken context rootapp
# Use this pipeline for keystone auth with image caching
[pipeline:glance-api-keystone+caching]
pipeline = versionnegotiation authtoken context cache rootapp
# Use this pipeline for keystone auth with caching and cache management
[pipeline:glance-api-keystone+cachemanagement]
pipeline = versionnegotiation authtoken context cache cachemanage rootapp
# Use this pipeline for authZ only. This means that the registry will treat a
# user as authenticated without making requests to keystone to reauthenticate
# the user.
[pipeline:glance-api-trusted-auth]
pipeline = versionnegotiation context rootapp
# Use this pipeline for authZ only. This means that the registry will treat a
# user as authenticated without making requests to keystone to reauthenticate
# the user and uses cache management
[pipeline:glance-api-trusted-auth+cachemanagement]
pipeline = versionnegotiation context cache cachemanage rootapp
[composite:rootapp]
paste.composite_factory = glance.api:root_app_factory
/: apiversions
/v1: apiv1app
/v2: apiv2app
[app:apiversions]
paste.app_factory = glance.api.versions:create_resource
[app:apiv1app]
paste.app_factory = glance.api.v1.router:API.factory
[app:apiv2app]
paste.app_factory = glance.api.v2.router:API.factory
[filter:versionnegotiation]
paste.filter_factory = glance.api.middleware.version_negotiation:VersionNegotiationFilter.factory
[filter:cache]
paste.filter_factory = glance.api.middleware.cache:CacheFilter.factory
[filter:cachemanage]
paste.filter_factory = glance.api.middleware.cache_manage:CacheManageFilter.factory
[filter:context]
paste.filter_factory = glance.api.middleware.context:ContextMiddleware.factory
[filter:unauthenticated-context]
paste.filter_factory = glance.api.middleware.context:UnauthenticatedContextMiddleware.factory
[filter:authtoken]
paste.filter_factory = keystoneclient.middleware.auth_token:filter_factory
delay_auth_decision = true
[filter:gzip]
paste.filter_factory = glance.api.middleware.gzip:GzipMiddleware.factory

View File

@ -1,25 +0,0 @@
# Use this pipeline for no auth - DEFAULT
[pipeline:glance-registry]
pipeline = unauthenticated-context registryapp
# Use this pipeline for keystone auth
[pipeline:glance-registry-keystone]
pipeline = authtoken context registryapp
# Use this pipeline for authZ only. This means that the registry will treat a
# user as authenticated without making requests to keystone to reauthenticate
# the user.
[pipeline:glance-registry-trusted-auth]
pipeline = context registryapp
[app:registryapp]
paste.app_factory = glance.registry.api:API.factory
[filter:context]
paste.filter_factory = glance.api.middleware.context:ContextMiddleware.factory
[filter:unauthenticated-context]
paste.filter_factory = glance.api.middleware.context:UnauthenticatedContextMiddleware.factory
[filter:authtoken]
paste.filter_factory = keystoneclient.middleware.auth_token:filter_factory

View File

@ -1,77 +0,0 @@
# Use this pipeline for no auth or image caching - DEFAULT
[pipeline:glance-api]
pipeline = versionnegotiation osprofiler unauthenticated-context rootapp
# Use this pipeline for image caching and no auth
[pipeline:glance-api-caching]
pipeline = versionnegotiation osprofiler unauthenticated-context cache rootapp
# Use this pipeline for caching w/ management interface but no auth
[pipeline:glance-api-cachemanagement]
pipeline = versionnegotiation osprofiler unauthenticated-context cache cachemanage rootapp
# Use this pipeline for keystone auth
[pipeline:glance-api-keystone]
pipeline = versionnegotiation osprofiler authtoken context rootapp
# Use this pipeline for keystone auth with image caching
[pipeline:glance-api-keystone+caching]
pipeline = versionnegotiation osprofiler authtoken context cache rootapp
# Use this pipeline for keystone auth with caching and cache management
[pipeline:glance-api-keystone+cachemanagement]
pipeline = versionnegotiation osprofiler authtoken context cache cachemanage rootapp
# Use this pipeline for authZ only. This means that the registry will treat a
# user as authenticated without making requests to keystone to reauthenticate
# the user.
[pipeline:glance-api-trusted-auth]
pipeline = versionnegotiation osprofiler context rootapp
# Use this pipeline for authZ only. This means that the registry will treat a
# user as authenticated without making requests to keystone to reauthenticate
# the user and uses cache management
[pipeline:glance-api-trusted-auth+cachemanagement]
pipeline = versionnegotiation osprofiler context cache cachemanage rootapp
[composite:rootapp]
paste.composite_factory = glance.api:root_app_factory
/: apiversions
/v1: apiv1app
/v2: apiv2app
[app:apiversions]
paste.app_factory = glance.api.versions:create_resource
[app:apiv1app]
paste.app_factory = glance.api.v1.router:API.factory
[app:apiv2app]
paste.app_factory = glance.api.v2.router:API.factory
[filter:versionnegotiation]
paste.filter_factory = glance.api.middleware.version_negotiation:VersionNegotiationFilter.factory
[filter:cache]
paste.filter_factory = glance.api.middleware.cache:CacheFilter.factory
[filter:cachemanage]
paste.filter_factory = glance.api.middleware.cache_manage:CacheManageFilter.factory
[filter:context]
paste.filter_factory = glance.api.middleware.context:ContextMiddleware.factory
[filter:unauthenticated-context]
paste.filter_factory = glance.api.middleware.context:UnauthenticatedContextMiddleware.factory
[filter:authtoken]
paste.filter_factory = keystonemiddleware.auth_token:filter_factory
delay_auth_decision = true
[filter:gzip]
paste.filter_factory = glance.api.middleware.gzip:GzipMiddleware.factory
[filter:osprofiler]
paste.filter_factory = osprofiler.web:WsgiMiddleware.factory
hmac_keys = SECRET_KEY
enabled = yes

View File

@ -1,30 +0,0 @@
# Use this pipeline for no auth - DEFAULT
[pipeline:glance-registry]
pipeline = osprofiler unauthenticated-context registryapp
# Use this pipeline for keystone auth
[pipeline:glance-registry-keystone]
pipeline = osprofiler authtoken context registryapp
# Use this pipeline for authZ only. This means that the registry will treat a
# user as authenticated without making requests to keystone to reauthenticate
# the user.
[pipeline:glance-registry-trusted-auth]
pipeline = osprofiler context registryapp
[app:registryapp]
paste.app_factory = glance.registry.api:API.factory
[filter:context]
paste.filter_factory = glance.api.middleware.context:ContextMiddleware.factory
[filter:unauthenticated-context]
paste.filter_factory = glance.api.middleware.context:UnauthenticatedContextMiddleware.factory
[filter:authtoken]
paste.filter_factory = keystonemiddleware.auth_token:filter_factory
[filter:osprofiler]
paste.filter_factory = osprofiler.web:WsgiMiddleware.factory
hmac_keys = SECRET_KEY
enabled = yes

View File

@ -1,108 +0,0 @@
# Use this pipeline for no auth or image caching - DEFAULT
[pipeline:glance-api]
pipeline = cors healthcheck versionnegotiation osprofiler unauthenticated-context rootapp
# Use this pipeline for image caching and no auth
[pipeline:glance-api-caching]
pipeline = cors healthcheck versionnegotiation osprofiler unauthenticated-context cache rootapp
# Use this pipeline for caching w/ management interface but no auth
[pipeline:glance-api-cachemanagement]
pipeline = cors healthcheck versionnegotiation osprofiler unauthenticated-context cache cachemanage rootapp
# Use this pipeline for keystone auth
[pipeline:glance-api-keystone]
pipeline = cors healthcheck versionnegotiation osprofiler authtoken context rootapp
# Use this pipeline for keystone auth with image caching
[pipeline:glance-api-keystone+caching]
pipeline = cors healthcheck versionnegotiation osprofiler authtoken context cache rootapp
# Use this pipeline for keystone auth with caching and cache management
[pipeline:glance-api-keystone+cachemanagement]
pipeline = cors healthcheck versionnegotiation osprofiler authtoken context cache cachemanage rootapp
# Use this pipeline for authZ only. This means that the registry will treat a
# user as authenticated without making requests to keystone to reauthenticate
# the user.
[pipeline:glance-api-trusted-auth]
pipeline = cors healthcheck versionnegotiation osprofiler context rootapp
# Use this pipeline for authZ only. This means that the registry will treat a
# user as authenticated without making requests to keystone to reauthenticate
# the user and uses cache management
[pipeline:glance-api-trusted-auth+cachemanagement]
pipeline = cors healthcheck versionnegotiation osprofiler context cache cachemanage rootapp
[composite:rootapp]
paste.composite_factory = glance.api:root_app_factory
/: apiversions
/v1: apiv1app
/v2: apiv2app
/v3: apiv3app
[app:apiversions]
paste.app_factory = glance.api.versions:create_resource
[app:apiv1app]
paste.app_factory = glance.api.v1.router:API.factory
[app:apiv2app]
paste.app_factory = glance.api.v2.router:API.factory
[app:apiv3app]
paste.app_factory = glance.api.v3.router:API.factory
[filter:healthcheck]
paste.filter_factory = oslo_middleware:Healthcheck.factory
backends = disable_by_file
disable_by_file_path = /etc/glance/healthcheck_disable
[filter:versionnegotiation]
paste.filter_factory = glance.api.middleware.version_negotiation:VersionNegotiationFilter.factory
[filter:cache]
paste.filter_factory = glance.api.middleware.cache:CacheFilter.factory
[filter:cachemanage]
paste.filter_factory = glance.api.middleware.cache_manage:CacheManageFilter.factory
[filter:context]
paste.filter_factory = glance.api.middleware.context:ContextMiddleware.factory
[filter:unauthenticated-context]
paste.filter_factory = glance.api.middleware.context:UnauthenticatedContextMiddleware.factory
[filter:authtoken]
paste.filter_factory = keystonemiddleware.auth_token:filter_factory
delay_auth_decision = true
[filter:gzip]
paste.filter_factory = glance.api.middleware.gzip:GzipMiddleware.factory
[filter:osprofiler]
paste.filter_factory = osprofiler.web:WsgiMiddleware.factory
hmac_keys = SECRET_KEY #DEPRECATED
enabled = yes #DEPRECATED
[filter:cors]
paste.filter_factory = oslo_middleware.cors:filter_factory
oslo_config_project = glance
oslo_config_program = glance-api
# Basic Headers (Automatic)
# Accept = Origin, Accept, Accept-Language, Content-Type, Cache-Control, Content-Language, Expires, Last-Modified, Pragma
# Expose = Origin, Accept, Accept-Language, Content-Type, Cache-Control, Content-Language, Expires, Last-Modified, Pragma
# Glance Headers
# Accept = Content-MD5, X-Image-Meta-Checksum, X-Storage-Token, Accept-Encoding
# Expose = X-Image-Meta-Checksum
# Keystone Headers
# Accept = X-Auth-Token, X-Identity-Status, X-Roles, X-Service-Catalog, X-User-Id, X-Tenant-Id
# Expose = X-Auth-Token, X-Subject-Token, X-Service-Token
# Request ID Middleware Headers
# Accept = X-OpenStack-Request-ID
# Expose = X-OpenStack-Request-ID
latent_allow_headers = Content-MD5, X-Image-Meta-Checksum, X-Storage-Token, Accept-Encoding, X-Auth-Token, X-Identity-Status, X-Roles, X-Service-Catalog, X-User-Id, X-Tenant-Id, X-OpenStack-Request-ID
latent_expose_headers = X-Image-Meta-Checksum, X-Auth-Token, X-Subject-Token, X-Service-Token, X-OpenStack-Request-ID

View File

@ -1,35 +0,0 @@
# Use this pipeline for no auth - DEFAULT
[pipeline:glance-registry]
pipeline = healthcheck osprofiler unauthenticated-context registryapp
# Use this pipeline for keystone auth
[pipeline:glance-registry-keystone]
pipeline = healthcheck osprofiler authtoken context registryapp
# Use this pipeline for authZ only. This means that the registry will treat a
# user as authenticated without making requests to keystone to reauthenticate
# the user.
[pipeline:glance-registry-trusted-auth]
pipeline = healthcheck osprofiler context registryapp
[app:registryapp]
paste.app_factory = glance.registry.api:API.factory
[filter:healthcheck]
paste.filter_factory = oslo_middleware:Healthcheck.factory
backends = disable_by_file
disable_by_file_path = /etc/glance/healthcheck_disable
[filter:context]
paste.filter_factory = glance.api.middleware.context:ContextMiddleware.factory
[filter:unauthenticated-context]
paste.filter_factory = glance.api.middleware.context:UnauthenticatedContextMiddleware.factory
[filter:authtoken]
paste.filter_factory = keystonemiddleware.auth_token:filter_factory
[filter:osprofiler]
paste.filter_factory = osprofiler.web:WsgiMiddleware.factory
hmac_keys = SECRET_KEY #DEPRECATED
enabled = yes #DEPRECATED

View File

@ -476,45 +476,6 @@ class GlanceBasicDeployment(OpenStackAmuletDeployment):
message = "glance registry paste config error: {}".format(ret)
amulet.raise_status(amulet.FAIL, msg=message)
def _get_filter_factory_expected_dict(self):
"""Return expected authtoken filter factory dict for OS release"""
if self._get_openstack_release() >= self.trusty_kilo:
# Kilo and later
val = 'keystonemiddleware.auth_token:filter_factory'
else:
# Juno and earlier
val = 'keystoneclient.middleware.auth_token:filter_factory'
return {'filter:authtoken': {'paste.filter_factory': val}}
def test_304_glance_api_paste_auth_config(self):
"""Verify authtoken section config in glance-api-paste.ini using
glance/keystone relation data."""
u.log.debug('Checking glance api paste config file...')
unit = self.glance_sentry
conf = '/etc/glance/glance-api-paste.ini'
expected = self._get_filter_factory_expected_dict()
for section, pairs in expected.iteritems():
ret = u.validate_config_data(unit, conf, section, pairs)
if ret:
message = "glance api paste config error: {}".format(ret)
amulet.raise_status(amulet.FAIL, msg=message)
def test_306_glance_registry_paste_auth_config(self):
"""Verify authtoken section config in glance-registry-paste.ini using
glance/keystone relation data."""
u.log.debug('Checking glance registry paste config file...')
unit = self.glance_sentry
conf = '/etc/glance/glance-registry-paste.ini'
expected = self._get_filter_factory_expected_dict()
for section, pairs in expected.iteritems():
ret = u.validate_config_data(unit, conf, section, pairs)
if ret:
message = "glance registry paste config error: {}".format(ret)
amulet.raise_status(amulet.FAIL, msg=message)
def test_410_glance_image_create_delete(self):
"""Create new cirros image in glance, verify, then delete it."""
u.log.debug('Creating, checking and deleting glance image...')

View File

@ -55,6 +55,7 @@ TO_PATCH = [
'ceph_config_file',
'git_install',
'update_nrpe_config',
'reinstall_paste_ini',
# other
'call',
'check_call',
@ -496,9 +497,7 @@ class GlanceRelationTests(CharmTestCase):
self.relation_ids.return_value = []
relations.keystone_changed()
self.assertEquals([call('/etc/glance/glance-api.conf'),
call('/etc/glance/glance-registry.conf'),
call('/etc/glance/glance-api-paste.ini'),
call('/etc/glance/glance-registry-paste.ini')],
call('/etc/glance/glance-registry.conf')],
configs.write.call_args_list)
self.assertTrue(configure_https.called)
@ -517,9 +516,7 @@ class GlanceRelationTests(CharmTestCase):
self.relation_ids.return_value = ['object-store:0']
relations.keystone_changed()
self.assertEquals([call('/etc/glance/glance-api.conf'),
call('/etc/glance/glance-registry.conf'),
call('/etc/glance/glance-api-paste.ini'),
call('/etc/glance/glance-registry-paste.ini')],
call('/etc/glance/glance-registry.conf')],
configs.write.call_args_list)
object_store_joined.assert_called_with()
self.assertTrue(configure_https.called)
@ -620,6 +617,7 @@ class GlanceRelationTests(CharmTestCase):
relations.upgrade_charm()
self.apt_install.assert_called_with(['test'], fatal=True)
self.assertTrue(configs.write_all.called)
self.assertTrue(self.reinstall_paste_ini.called)
def test_ha_relation_joined(self):
self.get_hacluster_config.return_value = {

View File

@ -8,6 +8,7 @@ os.environ['JUJU_UNIT_NAME'] = 'glance'
import hooks.glance_utils as utils
from test_utils import (
CharmTestCase,
SimpleKV,
)
TO_PATCH = [
@ -66,8 +67,6 @@ class TestGlanceUtils(CharmTestCase):
calls = []
for conf in [utils.GLANCE_REGISTRY_CONF,
utils.GLANCE_API_CONF,
utils.GLANCE_API_PASTE_INI,
utils.GLANCE_REGISTRY_PASTE_INI,
utils.HAPROXY_CONF,
utils.HTTPS_APACHE_CONF]:
calls.append(
@ -85,8 +84,6 @@ class TestGlanceUtils(CharmTestCase):
calls = []
for conf in [utils.GLANCE_REGISTRY_CONF,
utils.GLANCE_API_CONF,
utils.GLANCE_API_PASTE_INI,
utils.GLANCE_REGISTRY_PASTE_INI,
utils.HAPROXY_CONF,
utils.HTTPS_APACHE_24_CONF]:
calls.append(
@ -105,8 +102,6 @@ class TestGlanceUtils(CharmTestCase):
calls = []
for conf in [utils.GLANCE_REGISTRY_CONF,
utils.GLANCE_API_CONF,
utils.GLANCE_API_PASTE_INI,
utils.GLANCE_REGISTRY_PASTE_INI,
utils.HAPROXY_CONF,
utils.ceph_config_file()]:
calls.append(
@ -122,8 +117,6 @@ class TestGlanceUtils(CharmTestCase):
ex_map = OrderedDict([
(utils.GLANCE_REGISTRY_CONF, ['glance-registry']),
(utils.GLANCE_API_CONF, ['glance-api']),
(utils.GLANCE_API_PASTE_INI, ['glance-api']),
(utils.GLANCE_REGISTRY_PASTE_INI, ['glance-registry']),
(utils.ceph_config_file(), ['glance-api', 'glance-registry']),
(utils.HAPROXY_CONF, ['haproxy']),
(utils.HTTPS_APACHE_CONF, ['apache2']),
@ -327,3 +320,36 @@ class TestGlanceUtils(CharmTestCase):
"TEST CONFIG",
utils.REQUIRED_INTERFACES,
charm_func=utils.check_optional_relations)
@patch.object(utils, 'os')
@patch.object(utils, 'kv')
def test_reinstall_paste_ini(self, kv, _os):
"""Ensure that paste.ini files are re-installed"""
_os.path.exists.return_value = True
test_kv = SimpleKV()
kv.return_value = test_kv
utils.reinstall_paste_ini()
self.apt_install.assert_called_with(
packages=['glance-api', 'glance-registry'],
options=utils.REINSTALL_OPTIONS,
fatal=True
)
_os.path.exists.assert_has_calls([
call(utils.GLANCE_REGISTRY_PASTE),
call(utils.GLANCE_API_PASTE),
])
_os.remove.assert_has_calls([
call(utils.GLANCE_REGISTRY_PASTE),
call(utils.GLANCE_API_PASTE),
])
self.assertTrue(test_kv.get(utils.PASTE_INI_MARKER))
self.assertTrue(test_kv.flushed)
@patch.object(utils, 'kv')
def test_reinstall_paste_ini_idempotent(self, kv):
"""Ensure that re-running does not re-install files"""
test_kv = SimpleKV()
test_kv.set(utils.PASTE_INI_MARKER, True)
kv.return_value = test_kv
utils.reinstall_paste_ini()
self.assertFalse(self.apt_install.called)

View File

@ -105,6 +105,22 @@ class TestRelation(object):
return None
class SimpleKV(object):
'''Simple in-memory replacement for unitdata.kv'''
def __init__(self):
self.flushed = False
self.data = {}
def get(self, key, default=None):
return self.data.get(key, default)
def set(self, key, value):
self.data[key] = value
def flush(self):
self.flushed = True
@contextmanager
def patch_open():
'''Patch open() to allow mocking both open() itself and the file that is