Merge lp:~openstack-charmers/charms/trusty/keystone/keystonev3 into lp:~openstack-charmers-archive/charms/trusty/keystone/next

Proposed by Liam Young
Status: Work in progress
Proposed branch: lp:~openstack-charmers/charms/trusty/keystone/keystonev3
Merge into: lp:~openstack-charmers-archive/charms/trusty/keystone/next
Diff against target: 3551 lines (+2443/-211) (has conflicts)
20 files modified
charm-helpers-tests.yaml (+2/-0)
charmhelpers/contrib/openstack/neutron.py (+13/-0)
charmhelpers/contrib/storage/linux/ceph.py (+38/-12)
config.yaml (+6/-0)
hooks/keystone_context.py (+8/-2)
hooks/keystone_hooks.py (+6/-0)
hooks/keystone_utils.py (+254/-98)
hooks/manager.py (+183/-3)
templates/liberty/policy.json (+382/-0)
templates/liberty/policy.json.v2 (+184/-0)
tests/basic_deployment.py (+208/-44)
tests/charmhelpers/contrib/openstack/amulet/utils.py (+34/-11)
tests/charmhelpers/core/__init__.py (+15/-0)
tests/charmhelpers/core/decorators.py (+57/-0)
tests/charmhelpers/core/hookenv.py (+978/-0)
unit_tests/test_actions.py (+14/-7)
unit_tests/test_actions_git_reinstall.py (+2/-1)
unit_tests/test_actions_openstack_upgrade.py (+9/-4)
unit_tests/test_keystone_hooks.py (+14/-5)
unit_tests/test_keystone_utils.py (+36/-24)
Text conflict in charmhelpers/contrib/openstack/neutron.py
Text conflict in hooks/keystone_utils.py
To merge this branch: bzr merge lp:~openstack-charmers/charms/trusty/keystone/keystonev3
Reviewer Review Type Date Requested Status
OpenStack Charmers Pending
Review via email: mp+285694@code.launchpad.net
To post a comment you must log in.
188. By Liam Young

Update unit tests to mock new method used for accessing keystone manager

189. By Liam Young

Lint fixes and unit tests for manager

190. By Liam Young

Fix charm-helper src

191. By Liam Young

Fix readme

192. By Liam Young

Update charm to render policy.json which is needed to enforce keystone v3 features like domains

193. By Liam Young

Fix getting domain info when keystone maybe offline

194. By Liam Young

Setup cloud_admin user

195. By Liam Young

v2 fixes

196. By Liam Young

Fix endpoint generation

197. By Liam Young

Transitioning to dynamic keystone ep api version discovery

198. By Liam Young

Lint tidy

199. By Liam Young

Update amulet tests

200. By Liam Young

More amulet updates

201. By Liam Young

Get v3 tests on openstack release > liberty

202. By Liam Young

POLICY_JSON is only managed for liberty and above so don't try and write it for earlier releases

203. By Liam Young

More amulet fixes

204. By Liam Young

Fixes for pure v3 deploy

205. By Liam Young

More fixes

206. By Liam Young

More fixes

207. By Liam Young

Catch exception when keystone is down

208. By Liam Young

Import problems

209. By Liam Young

Fix api v2

210. By Liam Young

Fix lint

211. By Liam Young

Fix typo

212. By Liam Young

Remove cloud_admin user

213. By Liam Young

Remove cloud_admin user from tests

214. By Liam Young

Need to specifiy domain for user lookups with v3

215. By Liam Young

Role needs to created after user or grant fails

216. By Liam Young

Fix unit_tests

Unmerged revisions

216. By Liam Young

Fix unit_tests

215. By Liam Young

Role needs to created after user or grant fails

214. By Liam Young

Need to specifiy domain for user lookups with v3

213. By Liam Young

Remove cloud_admin user from tests

212. By Liam Young

Remove cloud_admin user

211. By Liam Young

Fix typo

210. By Liam Young

Fix lint

209. By Liam Young

Fix api v2

208. By Liam Young

Import problems

207. By Liam Young

Catch exception when keystone is down

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'charm-helpers-tests.yaml'
--- charm-helpers-tests.yaml 2016-02-19 14:47:52 +0000
+++ charm-helpers-tests.yaml 2016-03-05 15:43:48 +0000
@@ -3,3 +3,5 @@
3include:3include:
4 - contrib.amulet4 - contrib.amulet
5 - contrib.openstack.amulet5 - contrib.openstack.amulet
6 - core.hookenv
7 - core.decorators
68
=== modified file 'charmhelpers/contrib/openstack/neutron.py'
--- charmhelpers/contrib/openstack/neutron.py 2016-02-19 14:49:59 +0000
+++ charmhelpers/contrib/openstack/neutron.py 2016-03-05 15:43:48 +0000
@@ -233,6 +233,7 @@
233 'neutron-plugin-ml2']233 'neutron-plugin-ml2']
234 # NOTE: patch in vmware renames nvp->nsx for icehouse onwards234 # NOTE: patch in vmware renames nvp->nsx for icehouse onwards
235 plugins['nvp'] = plugins['nsx']235 plugins['nvp'] = plugins['nsx']
236<<<<<<< TREE
236 if release >= 'kilo':237 if release >= 'kilo':
237 plugins['midonet']['driver'] = (238 plugins['midonet']['driver'] = (
238 'neutron.plugins.midonet.plugin.MidonetPluginV2')239 'neutron.plugins.midonet.plugin.MidonetPluginV2')
@@ -245,6 +246,18 @@
245 'python-neutron-plugin-midonet')246 'python-neutron-plugin-midonet')
246 plugins['midonet']['server_packages'].append(247 plugins['midonet']['server_packages'].append(
247 'python-networking-midonet')248 'python-networking-midonet')
249=======
250 if release >= 'kilo':
251 plugins['midonet']['driver'] = (
252 'neutron.plugins.midonet.plugin.MidonetPluginV2')
253 if release >= 'liberty':
254 plugins['midonet']['driver'] = (
255 'midonet.neutron.plugin_v1.MidonetPluginV2')
256 plugins['midonet']['server_packages'].remove(
257 'python-neutron-plugin-midonet')
258 plugins['midonet']['server_packages'].append(
259 'python-networking-midonet')
260>>>>>>> MERGE-SOURCE
248 return plugins261 return plugins
249262
250263
251264
=== modified file 'charmhelpers/contrib/storage/linux/ceph.py'
--- charmhelpers/contrib/storage/linux/ceph.py 2016-01-04 21:27:51 +0000
+++ charmhelpers/contrib/storage/linux/ceph.py 2016-03-05 15:43:48 +0000
@@ -120,6 +120,7 @@
120 """120 """
121 A custom error to inform the caller that a pool creation failed. Provides an error message121 A custom error to inform the caller that a pool creation failed. Provides an error message
122 """122 """
123
123 def __init__(self, message):124 def __init__(self, message):
124 super(PoolCreationError, self).__init__(message)125 super(PoolCreationError, self).__init__(message)
125126
@@ -129,6 +130,7 @@
129 An object oriented approach to Ceph pool creation. This base class is inherited by ReplicatedPool and ErasurePool.130 An object oriented approach to Ceph pool creation. This base class is inherited by ReplicatedPool and ErasurePool.
130 Do not call create() on this base class as it will not do anything. Instantiate a child class and call create().131 Do not call create() on this base class as it will not do anything. Instantiate a child class and call create().
131 """132 """
133
132 def __init__(self, service, name):134 def __init__(self, service, name):
133 self.service = service135 self.service = service
134 self.name = name136 self.name = name
@@ -180,36 +182,41 @@
180 :return: int. The number of pgs to use.182 :return: int. The number of pgs to use.
181 """183 """
182 validator(value=pool_size, valid_type=int)184 validator(value=pool_size, valid_type=int)
183 osds = get_osds(self.service)185 osd_list = get_osds(self.service)
184 if not osds:186 if not osd_list:
185 # NOTE(james-page): Default to 200 for older ceph versions187 # NOTE(james-page): Default to 200 for older ceph versions
186 # which don't support OSD query from cli188 # which don't support OSD query from cli
187 return 200189 return 200
188190
191 osd_list_length = len(osd_list)
189 # Calculate based on Ceph best practices192 # Calculate based on Ceph best practices
190 if osds < 5:193 if osd_list_length < 5:
191 return 128194 return 128
192 elif 5 < osds < 10:195 elif 5 < osd_list_length < 10:
193 return 512196 return 512
194 elif 10 < osds < 50:197 elif 10 < osd_list_length < 50:
195 return 4096198 return 4096
196 else:199 else:
197 estimate = (osds * 100) / pool_size200 estimate = (osd_list_length * 100) / pool_size
198 # Return the next nearest power of 2201 # Return the next nearest power of 2
199 index = bisect.bisect_right(powers_of_two, estimate)202 index = bisect.bisect_right(powers_of_two, estimate)
200 return powers_of_two[index]203 return powers_of_two[index]
201204
202205
203class ReplicatedPool(Pool):206class ReplicatedPool(Pool):
204 def __init__(self, service, name, replicas=2):207 def __init__(self, service, name, pg_num=None, replicas=2):
205 super(ReplicatedPool, self).__init__(service=service, name=name)208 super(ReplicatedPool, self).__init__(service=service, name=name)
206 self.replicas = replicas209 self.replicas = replicas
210 if pg_num is None:
211 self.pg_num = self.get_pgs(self.replicas)
212 else:
213 self.pg_num = pg_num
207214
208 def create(self):215 def create(self):
209 if not pool_exists(self.service, self.name):216 if not pool_exists(self.service, self.name):
210 # Create it217 # Create it
211 pgs = self.get_pgs(self.replicas)218 cmd = ['ceph', '--id', self.service, 'osd', 'pool', 'create',
212 cmd = ['ceph', '--id', self.service, 'osd', 'pool', 'create', self.name, str(pgs)]219 self.name, str(self.pg_num)]
213 try:220 try:
214 check_call(cmd)221 check_call(cmd)
215 except CalledProcessError:222 except CalledProcessError:
@@ -241,7 +248,7 @@
241248
242 pgs = self.get_pgs(int(erasure_profile['k']) + int(erasure_profile['m']))249 pgs = self.get_pgs(int(erasure_profile['k']) + int(erasure_profile['m']))
243 # Create it250 # Create it
244 cmd = ['ceph', '--id', self.service, 'osd', 'pool', 'create', self.name, str(pgs),251 cmd = ['ceph', '--id', self.service, 'osd', 'pool', 'create', self.name, str(pgs), str(pgs),
245 'erasure', self.erasure_code_profile]252 'erasure', self.erasure_code_profile]
246 try:253 try:
247 check_call(cmd)254 check_call(cmd)
@@ -322,7 +329,8 @@
322 :return: None. Can raise CalledProcessError329 :return: None. Can raise CalledProcessError
323 """330 """
324 # Set a byte quota on a RADOS pool in ceph.331 # Set a byte quota on a RADOS pool in ceph.
325 cmd = ['ceph', '--id', service, 'osd', 'pool', 'set-quota', pool_name, 'max_bytes', max_bytes]332 cmd = ['ceph', '--id', service, 'osd', 'pool', 'set-quota', pool_name,
333 'max_bytes', str(max_bytes)]
326 try:334 try:
327 check_call(cmd)335 check_call(cmd)
328 except CalledProcessError:336 except CalledProcessError:
@@ -343,7 +351,25 @@
343 raise351 raise
344352
345353
346def create_erasure_profile(service, profile_name, erasure_plugin_name='jerasure', failure_domain='host',354def remove_erasure_profile(service, profile_name):
355 """
356 Create a new erasure code profile if one does not already exist for it. Updates
357 the profile if it exists. Please see http://docs.ceph.com/docs/master/rados/operations/erasure-code-profile/
358 for more details
359 :param service: six.string_types. The Ceph user name to run the command under
360 :param profile_name: six.string_types
361 :return: None. Can raise CalledProcessError
362 """
363 cmd = ['ceph', '--id', service, 'osd', 'erasure-code-profile', 'rm',
364 profile_name]
365 try:
366 check_call(cmd)
367 except CalledProcessError:
368 raise
369
370
371def create_erasure_profile(service, profile_name, erasure_plugin_name='jerasure',
372 failure_domain='host',
347 data_chunks=2, coding_chunks=1,373 data_chunks=2, coding_chunks=1,
348 locality=None, durability_estimator=None):374 locality=None, durability_estimator=None):
349 """375 """
350376
=== modified file 'config.yaml'
--- config.yaml 2016-02-18 09:59:57 +0000
+++ config.yaml 2016-03-05 15:43:48 +0000
@@ -298,6 +298,12 @@
298 description: |298 description: |
299 A comma-separated list of nagios servicegroups.299 A comma-separated list of nagios servicegroups.
300 If left empty, the nagios_context will be used as the servicegroup300 If left empty, the nagios_context will be used as the servicegroup
301 preferred-api-version:
302 default: 2
303 type: int
304 description: |
305 Use this keystone api version for keystone endpoints and advertise this
306 version to identity client charms
301 action-managed-upgrade:307 action-managed-upgrade:
302 type: boolean308 type: boolean
303 default: False309 default: False
304310
=== modified file 'hooks/keystone_context.py'
--- hooks/keystone_context.py 2016-02-18 09:59:57 +0000
+++ hooks/keystone_context.py 2016-03-05 15:43:48 +0000
@@ -190,9 +190,15 @@
190 from keystone_utils import (190 from keystone_utils import (
191 api_port, set_admin_token, endpoint_url, resolve_address,191 api_port, set_admin_token, endpoint_url, resolve_address,
192 PUBLIC, ADMIN, PKI_CERTS_DIR, ensure_pki_cert_paths,192 PUBLIC, ADMIN, PKI_CERTS_DIR, ensure_pki_cert_paths,
193 get_admin_domain_id
193 )194 )
194 ctxt = {}195 ctxt = {}
195 ctxt['token'] = set_admin_token(config('admin-token'))196 ctxt['token'] = set_admin_token(config('admin-token'))
197 ctxt['api_version'] = int(config('preferred-api-version'))
198 ctxt['admin_role'] = config('admin-role')
199 if ctxt['api_version'] > 2:
200 ctxt['admin_domain_id'] = \
201 get_admin_domain_id() or 'admin_domain_id'
196 ctxt['admin_port'] = determine_api_port(api_port('keystone-admin'),202 ctxt['admin_port'] = determine_api_port(api_port('keystone-admin'),
197 singlenode_mode=True)203 singlenode_mode=True)
198 ctxt['public_port'] = determine_api_port(api_port('keystone-public'),204 ctxt['public_port'] = determine_api_port(api_port('keystone-public'),
@@ -233,10 +239,10 @@
233 # correct auth URL.239 # correct auth URL.
234 ctxt['public_endpoint'] = endpoint_url(240 ctxt['public_endpoint'] = endpoint_url(
235 resolve_address(PUBLIC),241 resolve_address(PUBLIC),
236 api_port('keystone-public')).rstrip('v2.0')242 api_port('keystone-public')).replace('v2.0', '')
237 ctxt['admin_endpoint'] = endpoint_url(243 ctxt['admin_endpoint'] = endpoint_url(
238 resolve_address(ADMIN),244 resolve_address(ADMIN),
239 api_port('keystone-admin')).rstrip('v2.0')245 api_port('keystone-admin')).replace('v2.0', '')
240246
241 return ctxt247 return ctxt
242248
243249
=== modified file 'hooks/keystone_hooks.py'
--- hooks/keystone_hooks.py 2016-01-19 16:54:03 +0000
+++ hooks/keystone_hooks.py 2016-03-05 15:43:48 +0000
@@ -47,6 +47,7 @@
47 git_install_requested,47 git_install_requested,
48 openstack_upgrade_available,48 openstack_upgrade_available,
49 sync_db_with_multi_ipv6_addresses,49 sync_db_with_multi_ipv6_addresses,
50 os_release,
50)51)
5152
52from keystone_utils import (53from keystone_utils import (
@@ -64,6 +65,7 @@
64 services,65 services,
65 CLUSTER_RES,66 CLUSTER_RES,
66 KEYSTONE_CONF,67 KEYSTONE_CONF,
68 POLICY_JSON,
67 SSH_USER,69 SSH_USER,
68 setup_ipv6,70 setup_ipv6,
69 send_notifications,71 send_notifications,
@@ -309,6 +311,8 @@
309 else:311 else:
310 CONFIGS.write(KEYSTONE_CONF)312 CONFIGS.write(KEYSTONE_CONF)
311 leader_init_db_if_ready(use_current_context=True)313 leader_init_db_if_ready(use_current_context=True)
314 if os_release('keystone-common') >= 'liberty':
315 CONFIGS.write(POLICY_JSON)
312316
313317
314@hooks.hook('pgsql-db-relation-changed')318@hooks.hook('pgsql-db-relation-changed')
@@ -320,6 +324,8 @@
320 else:324 else:
321 CONFIGS.write(KEYSTONE_CONF)325 CONFIGS.write(KEYSTONE_CONF)
322 leader_init_db_if_ready(use_current_context=True)326 leader_init_db_if_ready(use_current_context=True)
327 if os_release('keystone-common') >= 'liberty':
328 CONFIGS.write(POLICY_JSON)
323329
324330
325@hooks.hook('identity-service-relation-changed')331@hooks.hook('identity-service-relation-changed')
326332
=== modified file 'hooks/keystone_utils.py'
--- hooks/keystone_utils.py 2016-02-19 14:49:59 +0000
+++ hooks/keystone_utils.py 2016-03-05 15:43:48 +0000
@@ -166,6 +166,7 @@
166KEYSTONE_CONF_DIR = os.path.dirname(KEYSTONE_CONF)166KEYSTONE_CONF_DIR = os.path.dirname(KEYSTONE_CONF)
167STORED_PASSWD = "/var/lib/keystone/keystone.passwd"167STORED_PASSWD = "/var/lib/keystone/keystone.passwd"
168STORED_TOKEN = "/var/lib/keystone/keystone.token"168STORED_TOKEN = "/var/lib/keystone/keystone.token"
169STORED_ADMIN_DOMAIN_ID = "/var/lib/keystone/keystone.admin_domain_id"
169SERVICE_PASSWD_PATH = '/var/lib/keystone/services.passwd'170SERVICE_PASSWD_PATH = '/var/lib/keystone/services.passwd'
170171
171HAPROXY_CONF = '/etc/haproxy/haproxy.cfg'172HAPROXY_CONF = '/etc/haproxy/haproxy.cfg'
@@ -184,6 +185,10 @@
184CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'185CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
185SSL_SYNC_SEMAPHORE = threading.Semaphore()186SSL_SYNC_SEMAPHORE = threading.Semaphore()
186SSL_DIRS = [SSL_DIR, APACHE_SSL_DIR, CA_CERT_PATH]187SSL_DIRS = [SSL_DIR, APACHE_SSL_DIR, CA_CERT_PATH]
188ADMIN_DOMAIN = 'admin_domain'
189DEFAULT_DOMAIN = 'Default'
190POLICY_JSON = '/etc/keystone/policy.json'
191
187BASE_RESOURCE_MAP = OrderedDict([192BASE_RESOURCE_MAP = OrderedDict([
188 (KEYSTONE_CONF, {193 (KEYSTONE_CONF, {
189 'services': BASE_SERVICES,194 'services': BASE_SERVICES,
@@ -212,6 +217,10 @@
212 'contexts': [keystone_context.ApacheSSLContext()],217 'contexts': [keystone_context.ApacheSSLContext()],
213 'services': ['apache2'],218 'services': ['apache2'],
214 }),219 }),
220 (POLICY_JSON, {
221 'contexts': [keystone_context.KeystoneContext()],
222 'services': BASE_SERVICES,
223 }),
215])224])
216225
217valid_services = {226valid_services = {
@@ -329,6 +338,8 @@
329 """338 """
330 resource_map = deepcopy(BASE_RESOURCE_MAP)339 resource_map = deepcopy(BASE_RESOURCE_MAP)
331340
341 if os_release('keystone') < 'liberty':
342 resource_map.pop(POLICY_JSON)
332 if os.path.exists('/etc/apache2/conf-available'):343 if os.path.exists('/etc/apache2/conf-available'):
333 resource_map.pop(APACHE_CONF)344 resource_map.pop(APACHE_CONF)
334 else:345 else:
@@ -452,18 +463,30 @@
452# OLD463# OLD
453464
454465
455def get_local_endpoint():466def get_api_suffix():
467 if get_api_version() == 2:
468 api_suffix = 'v2.0'
469 else:
470 api_suffix = 'v3'
471 return api_suffix
472
473
474def get_local_endpoint(api_suffix=None):
456 """Returns the URL for the local end-point bypassing haproxy/ssl"""475 """Returns the URL for the local end-point bypassing haproxy/ssl"""
476 if not api_suffix:
477 api_suffix = get_api_suffix()
457 if config('prefer-ipv6'):478 if config('prefer-ipv6'):
458 ipv6_addr = get_ipv6_addr(exc_list=[config('vip')])[0]479 ipv6_addr = get_ipv6_addr(exc_list=[config('vip')])[0]
459 endpoint_url = 'http://[%s]:{}/v2.0/' % ipv6_addr480 endpoint_url = 'http://[%s]:{}/{}/' % ipv6_addr
460 local_endpoint = endpoint_url.format(481 local_endpoint = endpoint_url.format(
461 determine_api_port(api_port('keystone-admin'),482 determine_api_port(api_port('keystone-admin'),
462 singlenode_mode=True))483 singlenode_mode=True),
484 api_suffix)
463 else:485 else:
464 local_endpoint = 'http://localhost:{}/v2.0/'.format(486 local_endpoint = 'http://localhost:{}/{}/'.format(
465 determine_api_port(api_port('keystone-admin'),487 determine_api_port(api_port('keystone-admin'),
466 singlenode_mode=True))488 singlenode_mode=True),
489 api_suffix)
467490
468 return local_endpoint491 return local_endpoint
469492
@@ -506,18 +529,14 @@
506529
507530
508def is_service_present(service_name, service_type):531def is_service_present(service_name, service_type):
509 import manager532 manager = get_manager()
510 manager = manager.KeystoneManager(endpoint=get_local_endpoint(),
511 token=get_admin_token())
512 service_id = manager.resolve_service_id(service_name, service_type)533 service_id = manager.resolve_service_id(service_name, service_type)
513 return service_id is not None534 return service_id is not None
514535
515536
516def delete_service_entry(service_name, service_type):537def delete_service_entry(service_name, service_type):
517 """ Delete a service from keystone"""538 """ Delete a service from keystone"""
518 import manager539 manager = get_manager()
519 manager = manager.KeystoneManager(endpoint=get_local_endpoint(),
520 token=get_admin_token())
521 service_id = manager.resolve_service_id(service_name, service_type)540 service_id = manager.resolve_service_id(service_name, service_type)
522 if service_id:541 if service_id:
523 manager.api.services.delete(service_id)542 manager.api.services.delete(service_id)
@@ -526,28 +545,34 @@
526545
527def create_service_entry(service_name, service_type, service_desc, owner=None):546def create_service_entry(service_name, service_type, service_desc, owner=None):
528 """ Add a new service entry to keystone if one does not already exist """547 """ Add a new service entry to keystone if one does not already exist """
529 import manager548 manager = get_manager()
530 manager = manager.KeystoneManager(endpoint=get_local_endpoint(),
531 token=get_admin_token())
532 for service in [s._info for s in manager.api.services.list()]:549 for service in [s._info for s in manager.api.services.list()]:
533 if service['name'] == service_name:550 if service['name'] == service_name:
534 log("Service entry for '%s' already exists." % service_name,551 log("Service entry for '%s' already exists." % service_name,
535 level=DEBUG)552 level=DEBUG)
536 return553 return
537554
538 manager.api.services.create(name=service_name,555 manager.api.services.create(service_name,
539 service_type=service_type,556 service_type,
540 description=service_desc)557 description=service_desc)
541 log("Created new service entry '%s'" % service_name, level=DEBUG)558 log("Created new service entry '%s'" % service_name, level=DEBUG)
542559
543560
544def create_endpoint_template(region, service, publicurl, adminurl,561def create_endpoint_template(region, service, publicurl, adminurl,
545 internalurl):562 internalurl):
563 manager = get_manager()
564 if manager.api_version == 2:
565 create_endpoint_template_v2(manager, region, service, publicurl,
566 adminurl, internalurl)
567 else:
568 create_endpoint_template_v3(manager, region, service, publicurl,
569 adminurl, internalurl)
570
571
572def create_endpoint_template_v2(manager, region, service, publicurl, adminurl,
573 internalurl):
546 """ Create a new endpoint template for service if one does not already574 """ Create a new endpoint template for service if one does not already
547 exist matching name *and* region """575 exist matching name *and* region """
548 import manager
549 manager = manager.KeystoneManager(endpoint=get_local_endpoint(),
550 token=get_admin_token())
551 service_id = manager.resolve_service_id(service)576 service_id = manager.resolve_service_id(service)
552 for ep in [e._info for e in manager.api.endpoints.list()]:577 for ep in [e._info for e in manager.api.endpoints.list()]:
553 if ep['service_id'] == service_id and ep['region'] == region:578 if ep['service_id'] == service_id and ep['region'] == region:
@@ -566,34 +591,91 @@
566 log("Updating endpoint template with new endpoint urls.")591 log("Updating endpoint template with new endpoint urls.")
567 manager.api.endpoints.delete(ep['id'])592 manager.api.endpoints.delete(ep['id'])
568593
569 manager.api.endpoints.create(region=region,594 manager.create_endpoints(region=region,
570 service_id=service_id,595 service_id=service_id,
571 publicurl=publicurl,596 publicurl=publicurl,
572 adminurl=adminurl,597 adminurl=adminurl,
573 internalurl=internalurl)598 internalurl=internalurl)
574 log("Created new endpoint template for '%s' in '%s'" % (region, service),599 log("Created new endpoint template for '%s' in '%s'" % (region, service),
575 level=DEBUG)600 level=DEBUG)
576601
577602
603def create_endpoint_template_v3(manager, region, service, publicurl, adminurl,
604 internalurl):
605 service_id = manager.resolve_service_id(service)
606 endpoints = {
607 'public': publicurl,
608 'admin': adminurl,
609 'internal': internalurl,
610 }
611 for ep_type in endpoints.keys():
612 # Delete endpoint if its has changed
613 ep_deleted = manager.delete_old_endpoint_v3(
614 ep_type,
615 service_id,
616 region,
617 endpoints[ep_type]
618 )
619 ep_exists = manager.find_endpoint_v3(
620 ep_type,
621 service_id,
622 region
623 )
624 if ep_deleted or not ep_exists:
625 manager.api.endpoints.create(
626 service_id,
627 endpoints[ep_type],
628 interface=ep_type,
629 region=region
630 )
631
632
578def create_tenant(name):633def create_tenant(name):
579 """Creates a tenant if it does not already exist"""634 """Creates a tenant if it does not already exist"""
580 import manager635 manager = get_manager()
581 manager = manager.KeystoneManager(endpoint=get_local_endpoint(),636 tenants = manager.resolve_tenant_id(name)
582 token=get_admin_token())637 if not tenants:
583 tenants = [t._info for t in manager.api.tenants.list()]638 manager.create_tenant(tenant_name=name,
584 if not tenants or name not in [t['name'] for t in tenants]:639 description='Created by Juju')
585 manager.api.tenants.create(tenant_name=name,
586 description='Created by Juju')
587 log("Created new tenant: %s" % name, level=DEBUG)640 log("Created new tenant: %s" % name, level=DEBUG)
588 return641 return
589642
590 log("Tenant '%s' already exists." % name, level=DEBUG)643 log("Tenant '%s' already exists." % name, level=DEBUG)
591644
592645
593def user_exists(name):646def create_or_show_domain(name):
594 import manager647 """Creates a tenant if it does not already exist"""
595 manager = manager.KeystoneManager(endpoint=get_local_endpoint(),648 manager = get_manager()
596 token=get_admin_token())649 domain_id = manager.resolve_domain_id(name)
650 if domain_id:
651 log("Domain '%s' already exists." % name, level=DEBUG)
652 else:
653 manager.create_domain(domain_name=name,
654 description='Created by Juju')
655 log("Created new domain: %s" % name, level=DEBUG)
656 domain_id = manager.resolve_domain_id(name)
657 return domain_id
658
659
660def user_exists(name, domain=None):
661 manager = get_manager()
662 if domain:
663 domain_id = manager.resolve_domain_id(domain)
664 for user in manager.api.users.list():
665 if user.name == name:
666 # In v3 Domains are seperate user namespaces so need to check that
667 # the domain matched if provided
668 if domain:
669 if domain_id == user.domain_id:
670 return True
671 else:
672 return True
673
674 return False
675
676
677def old_user_exists(name):
678 manager = get_manager()
597 users = [u._info for u in manager.api.users.list()]679 users = [u._info for u in manager.api.users.list()]
598 if not users or name not in [u['name'] for u in users]:680 if not users or name not in [u['name'] for u in users]:
599 return False681 return False
@@ -601,32 +683,44 @@
601 return True683 return True
602684
603685
604def create_user(name, password, tenant):686def create_user(name, password, tenant=None, domain=None):
605 """Creates a user if it doesn't already exist, as a member of tenant"""687 """Creates a user if it doesn't already exist, as a member of tenant"""
606 import manager688 manager = get_manager()
607 manager = manager.KeystoneManager(endpoint=get_local_endpoint(),689 if user_exists(name, domain=domain):
608 token=get_admin_token())
609 if user_exists(name):
610 log("A user named '%s' already exists" % name, level=DEBUG)690 log("A user named '%s' already exists" % name, level=DEBUG)
611 return691 return
612692
613 tenant_id = manager.resolve_tenant_id(tenant)693 tenant_id = None
614 if not tenant_id:694 if tenant:
615 error_out('Could not resolve tenant_id for tenant %s' % tenant)695 tenant_id = manager.resolve_tenant_id(tenant)
616696 if not tenant_id:
617 manager.api.users.create(name=name,697 error_out('Could not resolve tenant_id for tenant %s' % tenant)
618 password=password,698
619 email='juju@localhost',699 domain_id = None
620 tenant_id=tenant_id)700 if domain:
701 domain_id = manager.resolve_domain_id(domain)
702 if not domain_id:
703 error_out('Could not resolve domain_id for domain %s' % domain)
704
705 manager.create_user(name=name,
706 password=password,
707 email='juju@localhost',
708 tenant_id=tenant_id,
709 domain_id=domain_id)
621 log("Created new user '%s' tenant: %s" % (name, tenant_id),710 log("Created new user '%s' tenant: %s" % (name, tenant_id),
622 level=DEBUG)711 level=DEBUG)
623712
624713
625def create_role(name, user=None, tenant=None):714def get_manager(api_version=None):
715 """Return a keystonemanager for the correct API version"""
716 from manager import get_keystone_manager
717 return get_keystone_manager(get_local_endpoint(), get_admin_token(),
718 api_version)
719
720
721def create_role(name, user=None, tenant=None, domain=None):
626 """Creates a role if it doesn't already exist. grants role to user"""722 """Creates a role if it doesn't already exist. grants role to user"""
627 import manager723 manager = get_manager()
628 manager = manager.KeystoneManager(endpoint=get_local_endpoint(),
629 token=get_admin_token())
630 roles = [r._info for r in manager.api.roles.list()]724 roles = [r._info for r in manager.api.roles.list()]
631 if not roles or name not in [r['name'] for r in roles]:725 if not roles or name not in [r['name'] for r in roles]:
632 manager.api.roles.create(name=name)726 manager.api.roles.create(name=name)
@@ -640,31 +734,36 @@
640 # NOTE(adam_g): Keystone client requires id's for add_user_role, not names734 # NOTE(adam_g): Keystone client requires id's for add_user_role, not names
641 user_id = manager.resolve_user_id(user)735 user_id = manager.resolve_user_id(user)
642 role_id = manager.resolve_role_id(name)736 role_id = manager.resolve_role_id(name)
643 tenant_id = manager.resolve_tenant_id(tenant)737
644738 if None in [user_id, role_id]:
645 if None in [user_id, role_id, tenant_id]:739 error_out("Could not resolve [%s, %s]" %
646 error_out("Could not resolve [%s, %s, %s]" %740 (user_id, role_id))
647 (user_id, role_id, tenant_id))741
648742 grant_role(user, name, tenant, domain)
649 grant_role(user, name, tenant)743
650744
651745def grant_role(user, role, tenant=None, domain=None):
652def grant_role(user, role, tenant):
653 """Grant user and tenant a specific role"""746 """Grant user and tenant a specific role"""
654 import manager747 manager = get_manager()
655 manager = manager.KeystoneManager(endpoint=get_local_endpoint(),
656 token=get_admin_token())
657 log("Granting user '%s' role '%s' on tenant '%s'" %748 log("Granting user '%s' role '%s' on tenant '%s'" %
658 (user, role, tenant))749 (user, role, tenant))
659 user_id = manager.resolve_user_id(user)750 user_id = manager.resolve_user_id(user)
660 role_id = manager.resolve_role_id(role)751 role_id = manager.resolve_role_id(role)
661 tenant_id = manager.resolve_tenant_id(tenant)752 tenant_id = None
662753 if tenant:
663 cur_roles = manager.api.roles.roles_for_user(user_id, tenant_id)754 tenant_id = manager.resolve_tenant_id(tenant)
755
756 domain_id = None
757 if domain:
758 domain_id = manager.resolve_domain_id(domain)
759
760 cur_roles = manager.roles_for_user(user_id, tenant_id=tenant_id,
761 domain_id=domain_id)
664 if not cur_roles or role_id not in [r.id for r in cur_roles]:762 if not cur_roles or role_id not in [r.id for r in cur_roles]:
665 manager.api.roles.add_user_role(user=user_id,763 manager.add_user_role(user=user_id,
666 role=role_id,764 role=role_id,
667 tenant=tenant_id)765 tenant=tenant_id,
766 domain=domain_id)
668 log("Granted user '%s' role '%s' on tenant '%s'" %767 log("Granted user '%s' role '%s' on tenant '%s'" %
669 (user, role, tenant), level=DEBUG)768 (user, role, tenant), level=DEBUG)
670 else:769 else:
@@ -677,6 +776,11 @@
677 fd.writelines("%s\n" % passwd)776 fd.writelines("%s\n" % passwd)
678777
679778
779def store_admin_domain_id(domain_id):
780 with open(STORED_ADMIN_DOMAIN_ID, 'w+') as fd:
781 fd.writelines("%s\n" % domain_id)
782
783
680def get_admin_passwd():784def get_admin_passwd():
681 passwd = config("admin-password")785 passwd = config("admin-password")
682 if passwd and passwd.lower() != "none":786 if passwd and passwd.lower() != "none":
@@ -708,6 +812,13 @@
708 return passwd812 return passwd
709813
710814
815def get_api_version():
816 api_version = config('preferred-api-version')
817 if api_version not in [2, 3]:
818 raise ValueError('Bad preferred-api-version')
819 return api_version
820
821
711def ensure_initial_admin(config):822def ensure_initial_admin(config):
712 # Allow retry on fail since leader may not be ready yet.823 # Allow retry on fail since leader may not be ready yet.
713 # NOTE(hopem): ks client may not be installed at module import time so we824 # NOTE(hopem): ks client may not be installed at module import time so we
@@ -734,13 +845,27 @@
734 """845 """
735 create_tenant("admin")846 create_tenant("admin")
736 create_tenant(config("service-tenant"))847 create_tenant(config("service-tenant"))
848 if get_api_version() > 2:
849 domain_id = create_or_show_domain(ADMIN_DOMAIN)
850 store_admin_domain_id(domain_id)
737 # User is managed by ldap backend when using ldap identity851 # User is managed by ldap backend when using ldap identity
738 if not (config('identity-backend') ==852 if not (config('identity-backend') ==
739 'ldap' and config('ldap-readonly')):853 'ldap' and config('ldap-readonly')):
740 passwd = get_admin_passwd()854 passwd = get_admin_passwd()
741 if passwd:855 if passwd:
742 create_user_credentials(config('admin-user'), 'admin', passwd,856 if get_api_version() > 2:
743 new_roles=[config('admin-role')])857 create_user_credentials(config('admin-user'), passwd,
858 domain=ADMIN_DOMAIN)
859 create_role(config('admin-role'), config('admin-user'),
860 domain=ADMIN_DOMAIN)
861 grant_role(config('admin-user'), config('admin-role'),
862 tenant='admin')
863 grant_role(config('admin-user'), config('admin-role'),
864 domain=ADMIN_DOMAIN)
865 else:
866 create_user_credentials(config('admin-user'), passwd,
867 tenant='admin',
868 new_roles=[config('admin-role')])
744869
745 create_service_entry("keystone", "identity",870 create_service_entry("keystone", "identity",
746 "Keystone Identity Service")871 "Keystone Identity Service")
@@ -751,39 +876,49 @@
751 internal_ip=resolve_address(INTERNAL),876 internal_ip=resolve_address(INTERNAL),
752 admin_ip=resolve_address(ADMIN),877 admin_ip=resolve_address(ADMIN),
753 auth_port=config("admin-port"),878 auth_port=config("admin-port"),
754 region=region)879 region=region,
880 api_version=get_api_version())
755881
756 return _ensure_initial_admin(config)882 return _ensure_initial_admin(config)
757883
758884
759def endpoint_url(ip, port):885def endpoint_url(ip, port, suffix=None):
760 proto = 'http'886 proto = 'http'
761 if https():887 if https():
762 proto = 'https'888 proto = 'https'
763 if is_ipv6(ip):889 if is_ipv6(ip):
764 ip = "[{}]".format(ip)890 ip = "[{}]".format(ip)
765 return "%s://%s:%s/v2.0" % (proto, ip, port)891 if suffix:
892 ep = "%s://%s:%s/%s" % (proto, ip, port, suffix)
893 else:
894 ep = "%s://%s:%s" % (proto, ip, port)
895 return ep
766896
767897
768def create_keystone_endpoint(public_ip, service_port,898def create_keystone_endpoint(public_ip, service_port,
769 internal_ip, admin_ip, auth_port, region):899 internal_ip, admin_ip, auth_port, region,
770 create_endpoint_template(region, "keystone",900 api_version):
771 endpoint_url(public_ip, service_port),901 api_suffix = ''
772 endpoint_url(admin_ip, auth_port),902 if api_version == 2:
773 endpoint_url(internal_ip, service_port))903 api_suffix = 'v2.0'
904 api_suffix = get_api_suffix()
905 create_endpoint_template(
906 region, "keystone",
907 endpoint_url(public_ip, service_port, suffix=api_suffix),
908 endpoint_url(admin_ip, auth_port, suffix=api_suffix),
909 endpoint_url(internal_ip, service_port, suffix=api_suffix),
910 )
774911
775912
776def update_user_password(username, password):913def update_user_password(username, password):
777 import manager914 manager = get_manager()
778 manager = manager.KeystoneManager(endpoint=get_local_endpoint(),
779 token=get_admin_token())
780 log("Updating password for user '%s'" % username)915 log("Updating password for user '%s'" % username)
781916
782 user_id = manager.resolve_user_id(username)917 user_id = manager.resolve_user_id(username)
783 if user_id is None:918 if user_id is None:
784 error_out("Could not resolve user id for '%s'" % username)919 error_out("Could not resolve user id for '%s'" % username)
785920
786 manager.api.users.update_password(user=user_id, password=password)921 manager.update_password(user=user_id, password=password)
787 log("Successfully updated password for user '%s'" %922 log("Successfully updated password for user '%s'" %
788 username)923 username)
789924
@@ -1361,22 +1496,23 @@
1361 return result1496 return result
13621497
13631498
1364def create_user_credentials(user, tenant, passwd, new_roles=None, grants=None):1499def create_user_credentials(user, passwd, tenant=None, new_roles=None,
1500 grants=None, domain=None):
1365 """Create user credentials.1501 """Create user credentials.
13661502
1367 Optionally adds role grants to user and/or creates new roles.1503 Optionally adds role grants to user and/or creates new roles.
1368 """1504 """
1369 log("Creating service credentials for '%s'" % user, level=DEBUG)1505 log("Creating service credentials for '%s'" % user, level=DEBUG)
1370 if user_exists(user):1506 if user_exists(user, domain=domain):
1371 log("User '%s' already exists - updating password" % (user),1507 log("User '%s' already exists - updating password" % (user),
1372 level=DEBUG)1508 level=DEBUG)
1373 update_user_password(user, passwd)1509 update_user_password(user, passwd)
1374 else:1510 else:
1375 create_user(user, passwd, tenant)1511 create_user(user, passwd, tenant, domain)
13761512
1377 if grants:1513 if grants:
1378 for role in grants:1514 for role in grants:
1379 grant_role(user, role, tenant)1515 grant_role(user, role, tenant, domain)
1380 else:1516 else:
1381 log("No role grants requested for user '%s'" % (user), level=DEBUG)1517 log("No role grants requested for user '%s'" % (user), level=DEBUG)
13821518
@@ -1385,7 +1521,7 @@
1385 # Currently used by Swift and Ceilometer.1521 # Currently used by Swift and Ceilometer.
1386 for role in new_roles:1522 for role in new_roles:
1387 log("Creating requested role '%s'" % role, level=DEBUG)1523 log("Creating requested role '%s'" % role, level=DEBUG)
1388 create_role(role, user, tenant)1524 create_role(role, user, tenant, domain)
13891525
1390 return passwd1526 return passwd
13911527
@@ -1400,15 +1536,20 @@
1400 if not tenant:1536 if not tenant:
1401 raise Exception("No service tenant provided in config")1537 raise Exception("No service tenant provided in config")
14021538
1403 return create_user_credentials(user, tenant, get_service_password(user),1539 if get_api_version() == 2:
1404 new_roles=new_roles,1540 domain = None
1405 grants=[config('admin-role')])1541 else:
14061542 domain = DEFAULT_DOMAIN
1543 return create_user_credentials(user, get_service_password(user),
1544 tenant=tenant, new_roles=new_roles,
1545 grants=[config('admin-role')],
1546 domain=domain)
1547
1548
1549# @retry_on_exception(5, base_delay=3)
14071550
1408def add_service_to_keystone(relation_id=None, remote_unit=None):1551def add_service_to_keystone(relation_id=None, remote_unit=None):
1409 import manager1552 manager = get_manager()
1410 manager = manager.KeystoneManager(endpoint=get_local_endpoint(),
1411 token=get_admin_token())
1412 settings = relation_get(rid=relation_id, unit=remote_unit)1553 settings = relation_get(rid=relation_id, unit=remote_unit)
1413 # the minimum settings needed per endpoint1554 # the minimum settings needed per endpoint
1414 single = set(['service', 'region', 'public_url', 'admin_url',1555 single = set(['service', 'region', 'public_url', 'admin_url',
@@ -1546,6 +1687,7 @@
1546 # we return a token, information about our API endpoints, and the generated1687 # we return a token, information about our API endpoints, and the generated
1547 # service credentials1688 # service credentials
1548 service_tenant = config('service-tenant')1689 service_tenant = config('service-tenant')
1690 grant_role(service_username, 'Admin', service_tenant, 'default')
15491691
1550 # NOTE(dosaboy): we use __null__ to represent settings that are to be1692 # NOTE(dosaboy): we use __null__ to represent settings that are to be
1551 # routed to relations via the cluster relation and set to None.1693 # routed to relations via the cluster relation and set to None.
@@ -1565,6 +1707,7 @@
1565 "ca_cert": '__null__',1707 "ca_cert": '__null__',
1566 "auth_protocol": protocol,1708 "auth_protocol": protocol,
1567 "service_protocol": protocol,1709 "service_protocol": protocol,
1710 "api_version": get_api_version(),
1568 }1711 }
15691712
1570 # generate or get a new cert/key for service if set to manage certs.1713 # generate or get a new cert/key for service if set to manage certs.
@@ -1863,7 +2006,6 @@
18632006
1864 @param configs: a templating.OSConfigRenderer() object2007 @param configs: a templating.OSConfigRenderer() object
1865 """2008 """
1866
1867 if is_paused():2009 if is_paused():
1868 status_set("maintenance",2010 status_set("maintenance",
1869 "Paused. Use 'resume' action to resume normal service.")2011 "Paused. Use 'resume' action to resume normal service.")
@@ -1871,5 +2013,19 @@
18712013
1872 # set the status according to the current state of the contexts2014 # set the status according to the current state of the contexts
1873 set_os_workload_status(2015 set_os_workload_status(
2016<<<<<<< TREE
1874 configs, REQUIRED_INTERFACES, charm_func=check_optional_relations,2017 configs, REQUIRED_INTERFACES, charm_func=check_optional_relations,
1875 services=services(), ports=determine_ports())2018 services=services(), ports=determine_ports())
2019=======
2020 configs, REQUIRED_INTERFACES, charm_func=check_optional_relations)
2021
2022
2023def get_admin_domain_id():
2024 domain_id = None
2025 if os.path.isfile(STORED_ADMIN_DOMAIN_ID):
2026 log("Loading stored domain id from %s" % STORED_ADMIN_DOMAIN_ID,
2027 level=INFO)
2028 with open(STORED_ADMIN_DOMAIN_ID, 'r') as fd:
2029 domain_id = fd.readline().strip('\n')
2030 return domain_id
2031>>>>>>> MERGE-SOURCE
18762032
=== modified file 'hooks/manager.py'
--- hooks/manager.py 2016-01-12 11:09:46 +0000
+++ hooks/manager.py 2016-03-05 15:43:48 +0000
@@ -1,12 +1,57 @@
1#!/usr/bin/python1#!/usr/bin/python
2from keystoneclient.v2_0 import client2from keystoneclient.v2_0 import client
3from keystoneclient.v3 import client as keystoneclient_v3
4from keystoneclient.auth import token_endpoint
5from keystoneclient import session
6
7
8def _get_keystone_manager_class(endpoint, token, api_version):
9 """Return KeystoneManager class for the given API version"""
10 if api_version == 2:
11 return KeystoneManager2(endpoint, token)
12 if api_version == 3:
13 return KeystoneManager3(endpoint, token)
14 raise ValueError('No manager found for api version {}'.format(api_version))
15
16
17def get_keystone_manager(endpoint, token, api_version):
18 """Return a keystonemanager for the correct API version"""
19 if api_version:
20 return _get_keystone_manager_class(endpoint, token, api_version)
21 else:
22 # If api_version has not been set then use the manager we have to query
23 # the catalogue and determine which api version should be being used.
24 # Then return the correct client based on that
25 # XXX I think the keystone client should be able to do version
26 # detection automatically so the code below could be greatly
27 # simplified
28 if 'v2.0' in endpoint.split('/'):
29 manager = _get_keystone_manager_class(endpoint, token, 2)
30 else:
31 manager = _get_keystone_manager_class(endpoint, token, 3)
32 if endpoint.endswith('/'):
33 base_ep = endpoint.rsplit('/', 2)[0]
34 else:
35 base_ep = endpoint.rsplit('/', 1)[0]
36 for svc in manager.api.services.list():
37 if svc.type == 'identity':
38 svc_id = svc.id
39 version = None
40 for ep in manager.api.endpoints.list():
41 if ep.service_id == svc_id and hasattr(ep, 'adminurl'):
42 version = ep.adminurl.split('/')[-1]
43 if version and version == 'v2.0':
44 new_ep = base_ep + "/" + 'v2.0'
45 return _get_keystone_manager_class(new_ep, token, 2)
46 elif version and version == 'v3':
47 new_ep = base_ep + "/" + 'v3'
48 return _get_keystone_manager_class(new_ep, token, 3)
49 else:
50 return manager
351
452
5class KeystoneManager(object):53class KeystoneManager(object):
654
7 def __init__(self, endpoint, token):
8 self.api = client.Client(endpoint=endpoint, token=token)
9
10 def resolve_tenant_id(self, name):55 def resolve_tenant_id(self, name):
11 """Find the tenant_id of a given tenant"""56 """Find the tenant_id of a given tenant"""
12 tenants = [t._info for t in self.api.tenants.list()]57 tenants = [t._info for t in self.api.tenants.list()]
@@ -14,6 +59,9 @@
14 if name == t['name']:59 if name == t['name']:
15 return t['id']60 return t['id']
1661
62 def resolve_domain_id(self, name):
63 pass
64
17 def resolve_role_id(self, name):65 def resolve_role_id(self, name):
18 """Find the role_id of a given role"""66 """Find the role_id of a given role"""
19 roles = [r._info for r in self.api.roles.list()]67 roles = [r._info for r in self.api.roles.list()]
@@ -45,3 +93,135 @@
45 for s in services:93 for s in services:
46 if type == s['type']:94 if type == s['type']:
47 return s['id']95 return s['id']
96
97
98class KeystoneManager2(KeystoneManager):
99
100 def __init__(self, endpoint, token):
101 self.api_version = 2
102 self.api = client.Client(endpoint=endpoint, token=token)
103
104 def create_endpoints(self, region, service_id, publicurl, adminurl,
105 internalurl):
106 self.api.endpoints.create(region=region, service_id=service_id,
107 publicurl=publicurl, adminurl=adminurl,
108 internalurl=internalurl)
109
110 def tenants_list(self):
111 return self.api.tenants.list()
112
113 def create_tenant(self, tenant_name, description, domain='default'):
114 self.api.tenants.create(tenant_name=tenant_name,
115 description=description)
116
117 def delete_tenant(self, tenant_id):
118 self.api.tenants.delete(tenant_id)
119
120 def create_user(self, name, password, email, tenant_id=None,
121 domain_id=None):
122 self.api.users.create(name=name,
123 password=password,
124 email=email,
125 tenant_id=tenant_id)
126
127 def update_password(self, user, password):
128 self.api.users.update_password(user=user, password=password)
129
130 def roles_for_user(self, user_id, tenant_id=None, domain_id=None):
131 return self.api.roles.roles_for_user(user_id, tenant_id)
132
133 def add_user_role(self, user, role, tenant, domain):
134 self.api.roles.add_user_role(user=user, role=role, tenant=tenant)
135
136
137class KeystoneManager3(KeystoneManager):
138
139 def __init__(self, endpoint, token):
140 self.api_version = 3
141 keystone_auth_v3 = token_endpoint.Token(endpoint=endpoint, token=token)
142 keystone_session_v3 = session.Session(auth=keystone_auth_v3)
143 self.api = keystoneclient_v3.Client(session=keystone_session_v3)
144
145 def resolve_tenant_id(self, name):
146 """Find the tenant_id of a given tenant"""
147 tenants = [t._info for t in self.api.projects.list()]
148 for t in tenants:
149 if name == t['name']:
150 return t['id']
151
152 def resolve_domain_id(self, name):
153 """Find the domain_id of a given domain"""
154 domains = [d._info for d in self.api.domains.list()]
155 for d in domains:
156 if name == d['name']:
157 return d['id']
158
159 def create_endpoints(self, region, service_id, publicurl, adminurl,
160 internalurl):
161 self.api.endpoints.create(service_id, publicurl, interface='public',
162 region=region)
163 self.api.endpoints.create(service_id, adminurl, interface='admin',
164 region=region)
165 self.api.endpoints.create(service_id, internalurl,
166 interface='internal', region=region)
167
168 def tenants_list(self):
169 return self.api.projects.list()
170
171 def create_domain(self, domain_name, description):
172 self.api.domains.create(domain_name, description=description)
173
174 def create_tenant(self, tenant_name, description, domain='default'):
175 self.api.projects.create(tenant_name, domain, description=description)
176
177 def delete_tenant(self, tenant_id):
178 self.api.projects.delete(tenant_id)
179
180 def create_user(self, name, password, email, tenant_id=None,
181 domain_id=None):
182 if not domain_id:
183 domain_id = self.resolve_domain_id('default')
184 if tenant_id:
185 self.api.users.create(name,
186 domain=domain_id,
187 password=password,
188 email=email,
189 project=tenant_id)
190 else:
191 self.api.users.create(name,
192 domain=domain_id,
193 password=password,
194 email=email)
195
196 def update_password(self, user, password):
197 self.api.users.update(user, password=password)
198
199 def roles_for_user(self, user_id, tenant_id=None, domain_id=None):
200 # Specify either a domain or project, not both
201 if domain_id:
202 return self.api.roles.list(user_id, domain=domain_id)
203 else:
204 return self.api.roles.list(user_id, project=tenant_id)
205
206 def add_user_role(self, user, role, tenant, domain):
207 # Specify either a domain or project, not both
208 if domain:
209 self.api.roles.grant(role, user=user, domain=domain)
210 if tenant:
211 self.api.roles.grant(role, user=user, project=tenant)
212
213 def find_endpoint_v3(self, interface, service_id, region):
214 found_eps = []
215 for ep in self.api.endpoints.list():
216 if ep.service_id == service_id and ep.region == region and \
217 ep.interface == interface:
218 found_eps.append(ep)
219 return found_eps
220
221 def delete_old_endpoint_v3(self, interface, service_id, region, url):
222 eps = self.find_endpoint_v3(interface, service_id, region)
223 for ep in eps:
224 if getattr(ep, 'url') != url:
225 self.api.endpoints.delete(ep.id)
226 return True
227 return False
48228
=== added directory 'templates/liberty'
=== added file 'templates/liberty/policy.json'
--- templates/liberty/policy.json 1970-01-01 00:00:00 +0000
+++ templates/liberty/policy.json 2016-03-05 15:43:48 +0000
@@ -0,0 +1,382 @@
1{% if api_version == 3 -%}
2{
3 "admin_required": "role:{{ admin_role }}",
4 "cloud_admin": "rule:admin_required and domain_id:{{ admin_domain_id }}",
5 "service_role": "role:service",
6 "service_or_admin": "rule:admin_required or rule:service_role",
7 "owner" : "user_id:%(user_id)s or user_id:%(target.token.user_id)s",
8 "admin_or_owner": "(rule:admin_required and domain_id:%(target.token.user.domain.id)s) or rule:owner",
9 "admin_or_cloud_admin": "rule:admin_required or rule:cloud_admin",
10 "admin_and_matching_domain_id": "rule:admin_required and domain_id:%(domain_id)s",
11 "service_admin_or_owner": "rule:service_or_admin or rule:owner",
12
13 "default": "rule:admin_required",
14
15 "identity:get_region": "",
16 "identity:list_regions": "",
17 "identity:create_region": "rule:cloud_admin",
18 "identity:update_region": "rule:cloud_admin",
19 "identity:delete_region": "rule:cloud_admin",
20
21 "identity:get_service": "rule:admin_or_cloud_admin",
22 "identity:list_services": "rule:admin_or_cloud_admin",
23 "identity:create_service": "rule:cloud_admin",
24 "identity:update_service": "rule:cloud_admin",
25 "identity:delete_service": "rule:cloud_admin",
26
27 "identity:get_endpoint": "rule:admin_or_cloud_admin",
28 "identity:list_endpoints": "rule:admin_or_cloud_admin",
29 "identity:create_endpoint": "rule:cloud_admin",
30 "identity:update_endpoint": "rule:cloud_admin",
31 "identity:delete_endpoint": "rule:cloud_admin",
32
33 "identity:get_domain": "rule:cloud_admin or rule:admin_and_matching_domain_id",
34 "identity:list_domains": "rule:cloud_admin",
35 "identity:create_domain": "rule:cloud_admin",
36 "identity:update_domain": "rule:cloud_admin",
37 "identity:delete_domain": "rule:cloud_admin",
38
39 "admin_and_matching_target_project_domain_id": "rule:admin_required and domain_id:%(target.project.domain_id)s",
40 "admin_and_matching_project_domain_id": "rule:admin_required and domain_id:%(project.domain_id)s",
41 "identity:get_project": "rule:cloud_admin or rule:admin_and_matching_target_project_domain_id",
42 "identity:list_projects": "rule:cloud_admin or rule:admin_and_matching_domain_id",
43 "identity:list_user_projects": "rule:owner or rule:admin_and_matching_domain_id",
44 "identity:create_project": "rule:cloud_admin or rule:admin_and_matching_project_domain_id",
45 "identity:update_project": "rule:cloud_admin or rule:admin_and_matching_target_project_domain_id",
46 "identity:delete_project": "rule:cloud_admin or rule:admin_and_matching_target_project_domain_id",
47
48 "admin_and_matching_target_user_domain_id": "rule:admin_required and domain_id:%(target.user.domain_id)s",
49 "admin_and_matching_user_domain_id": "rule:admin_required and domain_id:%(user.domain_id)s",
50 "identity:get_user": "rule:cloud_admin or rule:admin_and_matching_target_user_domain_id",
51 "identity:list_users": "rule:cloud_admin or rule:admin_and_matching_domain_id",
52 "identity:create_user": "rule:cloud_admin or rule:admin_and_matching_user_domain_id",
53 "identity:update_user": "rule:cloud_admin or rule:admin_and_matching_target_user_domain_id",
54 "identity:delete_user": "rule:cloud_admin or rule:admin_and_matching_target_user_domain_id",
55
56 "admin_and_matching_target_group_domain_id": "rule:admin_required and domain_id:%(target.group.domain_id)s",
57 "admin_and_matching_group_domain_id": "rule:admin_required and domain_id:%(group.domain_id)s",
58 "identity:get_group": "rule:cloud_admin or rule:admin_and_matching_target_group_domain_id",
59 "identity:list_groups": "rule:cloud_admin or rule:admin_and_matching_domain_id",
60 "identity:list_groups_for_user": "rule:owner or rule:admin_and_matching_domain_id",
61 "identity:create_group": "rule:cloud_admin or rule:admin_and_matching_group_domain_id",
62 "identity:update_group": "rule:cloud_admin or rule:admin_and_matching_target_group_domain_id",
63 "identity:delete_group": "rule:cloud_admin or rule:admin_and_matching_target_group_domain_id",
64 "identity:list_users_in_group": "rule:cloud_admin or rule:admin_and_matching_target_group_domain_id",
65 "identity:remove_user_from_group": "rule:cloud_admin or rule:admin_and_matching_target_group_domain_id",
66 "identity:check_user_in_group": "rule:cloud_admin or rule:admin_and_matching_target_group_domain_id",
67 "identity:add_user_to_group": "rule:cloud_admin or rule:admin_and_matching_target_group_domain_id",
68
69 "identity:get_credential": "rule:admin_required",
70 "identity:list_credentials": "rule:admin_required or user_id:%(user_id)s",
71 "identity:create_credential": "rule:admin_required",
72 "identity:update_credential": "rule:admin_required",
73 "identity:delete_credential": "rule:admin_required",
74
75 "identity:ec2_get_credential": "rule:admin_or_cloud_admin or (rule:owner and user_id:%(target.credential.user_id)s)",
76 "identity:ec2_list_credentials": "rule:admin_or_cloud_admin or rule:owner",
77 "identity:ec2_create_credential": "rule:admin_or_cloud_admin or rule:owner",
78 "identity:ec2_delete_credential": "rule:admin_or_cloud_admin or (rule:owner and user_id:%(target.credential.user_id)s)",
79
80 "identity:get_role": "rule:admin_or_cloud_admin",
81 "identity:list_roles": "rule:admin_or_cloud_admin",
82 "identity:create_role": "rule:cloud_admin",
83 "identity:update_role": "rule:cloud_admin",
84 "identity:delete_role": "rule:cloud_admin",
85
86 "domain_admin_for_grants": "rule:admin_required and (domain_id:%(domain_id)s or domain_id:%(target.project.domain_id)s)",
87 "project_admin_for_grants": "rule:admin_required and project_id:%(project_id)s",
88 "identity:check_grant": "rule:cloud_admin or rule:domain_admin_for_grants or rule:project_admin_for_grants",
89 "identity:list_grants": "rule:cloud_admin or rule:domain_admin_for_grants or rule:project_admin_for_grants",
90 "identity:create_grant": "rule:cloud_admin or rule:domain_admin_for_grants or rule:project_admin_for_grants",
91 "identity:revoke_grant": "rule:cloud_admin or rule:domain_admin_for_grants or rule:project_admin_for_grants",
92
93 "admin_on_domain_filter" : "rule:admin_required and domain_id:%(scope.domain.id)s",
94 "admin_on_project_filter" : "rule:admin_required and project_id:%(scope.project.id)s",
95 "identity:list_role_assignments": "rule:cloud_admin or rule:admin_on_domain_filter or rule:admin_on_project_filter",
96
97 "identity:get_policy": "rule:cloud_admin",
98 "identity:list_policies": "rule:cloud_admin",
99 "identity:create_policy": "rule:cloud_admin",
100 "identity:update_policy": "rule:cloud_admin",
101 "identity:delete_policy": "rule:cloud_admin",
102
103 "identity:change_password": "rule:owner",
104 "identity:check_token": "rule:admin_or_owner",
105 "identity:validate_token": "rule:service_admin_or_owner",
106 "identity:validate_token_head": "rule:service_or_admin",
107 "identity:revocation_list": "rule:service_or_admin",
108 "identity:revoke_token": "rule:admin_or_owner",
109
110 "identity:create_trust": "user_id:%(trust.trustor_user_id)s",
111 "identity:list_trusts": "",
112 "identity:list_roles_for_trust": "",
113 "identity:get_role_for_trust": "",
114 "identity:delete_trust": "",
115
116 "identity:create_consumer": "rule:admin_required",
117 "identity:get_consumer": "rule:admin_required",
118 "identity:list_consumers": "rule:admin_required",
119 "identity:delete_consumer": "rule:admin_required",
120 "identity:update_consumer": "rule:admin_required",
121
122 "identity:authorize_request_token": "rule:admin_required",
123 "identity:list_access_token_roles": "rule:admin_required",
124 "identity:get_access_token_role": "rule:admin_required",
125 "identity:list_access_tokens": "rule:admin_required",
126 "identity:get_access_token": "rule:admin_required",
127 "identity:delete_access_token": "rule:admin_required",
128
129 "identity:list_projects_for_endpoint": "rule:admin_required",
130 "identity:add_endpoint_to_project": "rule:admin_required",
131 "identity:check_endpoint_in_project": "rule:admin_required",
132 "identity:list_endpoints_for_project": "rule:admin_required",
133 "identity:remove_endpoint_from_project": "rule:admin_required",
134
135 "identity:create_endpoint_group": "rule:admin_required",
136 "identity:list_endpoint_groups": "rule:admin_required",
137 "identity:get_endpoint_group": "rule:admin_required",
138 "identity:update_endpoint_group": "rule:admin_required",
139 "identity:delete_endpoint_group": "rule:admin_required",
140 "identity:list_projects_associated_with_endpoint_group": "rule:admin_required",
141 "identity:list_endpoints_associated_with_endpoint_group": "rule:admin_required",
142 "identity:get_endpoint_group_in_project": "rule:admin_required",
143 "identity:list_endpoint_groups_for_project": "rule:admin_required",
144 "identity:add_endpoint_group_to_project": "rule:admin_required",
145 "identity:remove_endpoint_group_from_project": "rule:admin_required",
146
147 "identity:create_identity_provider": "rule:cloud_admin",
148 "identity:list_identity_providers": "rule:cloud_admin",
149 "identity:get_identity_providers": "rule:cloud_admin",
150 "identity:update_identity_provider": "rule:cloud_admin",
151 "identity:delete_identity_provider": "rule:cloud_admin",
152
153 "identity:create_protocol": "rule:cloud_admin",
154 "identity:update_protocol": "rule:cloud_admin",
155 "identity:get_protocol": "rule:cloud_admin",
156 "identity:list_protocols": "rule:cloud_admin",
157 "identity:delete_protocol": "rule:cloud_admin",
158
159 "identity:create_mapping": "rule:cloud_admin",
160 "identity:get_mapping": "rule:cloud_admin",
161 "identity:list_mappings": "rule:cloud_admin",
162 "identity:delete_mapping": "rule:cloud_admin",
163 "identity:update_mapping": "rule:cloud_admin",
164
165 "identity:create_service_provider": "rule:cloud_admin",
166 "identity:list_service_providers": "rule:cloud_admin",
167 "identity:get_service_provider": "rule:cloud_admin",
168 "identity:update_service_provider": "rule:cloud_admin",
169 "identity:delete_service_provider": "rule:cloud_admin",
170
171 "identity:get_auth_catalog": "",
172 "identity:get_auth_projects": "",
173 "identity:get_auth_domains": "",
174
175 "identity:list_projects_for_groups": "",
176 "identity:list_domains_for_groups": "",
177
178 "identity:list_revoke_events": "",
179
180 "identity:create_policy_association_for_endpoint": "rule:cloud_admin",
181 "identity:check_policy_association_for_endpoint": "rule:cloud_admin",
182 "identity:delete_policy_association_for_endpoint": "rule:cloud_admin",
183 "identity:create_policy_association_for_service": "rule:cloud_admin",
184 "identity:check_policy_association_for_service": "rule:cloud_admin",
185 "identity:delete_policy_association_for_service": "rule:cloud_admin",
186 "identity:create_policy_association_for_region_and_service": "rule:cloud_admin",
187 "identity:check_policy_association_for_region_and_service": "rule:cloud_admin",
188 "identity:delete_policy_association_for_region_and_service": "rule:cloud_admin",
189 "identity:get_policy_for_endpoint": "rule:cloud_admin",
190 "identity:list_endpoints_for_policy": "rule:cloud_admin",
191
192 "identity:create_domain_config": "rule:cloud_admin",
193 "identity:get_domain_config": "rule:cloud_admin",
194 "identity:update_domain_config": "rule:cloud_admin",
195 "identity:delete_domain_config": "rule:cloud_admin"
196}
197{% else -%}
198{
199 "admin_required": "role:admin or is_admin:1",
200 "service_role": "role:service",
201 "service_or_admin": "rule:admin_required or rule:service_role",
202 "owner" : "user_id:%(user_id)s",
203 "admin_or_owner": "rule:admin_required or rule:owner",
204 "token_subject": "user_id:%(target.token.user_id)s",
205 "admin_or_token_subject": "rule:admin_required or rule:token_subject",
206 "service_admin_or_token_subject": "rule:service_or_admin or rule:token_subject",
207
208 "default": "rule:admin_required",
209
210 "identity:get_region": "",
211 "identity:list_regions": "",
212 "identity:create_region": "rule:admin_required",
213 "identity:update_region": "rule:admin_required",
214 "identity:delete_region": "rule:admin_required",
215
216 "identity:get_service": "rule:admin_required",
217 "identity:list_services": "rule:admin_required",
218 "identity:create_service": "rule:admin_required",
219 "identity:update_service": "rule:admin_required",
220 "identity:delete_service": "rule:admin_required",
221
222 "identity:get_endpoint": "rule:admin_required",
223 "identity:list_endpoints": "rule:admin_required",
224 "identity:create_endpoint": "rule:admin_required",
225 "identity:update_endpoint": "rule:admin_required",
226 "identity:delete_endpoint": "rule:admin_required",
227
228 "identity:get_domain": "rule:admin_required",
229 "identity:list_domains": "rule:admin_required",
230 "identity:create_domain": "rule:admin_required",
231 "identity:update_domain": "rule:admin_required",
232 "identity:delete_domain": "rule:admin_required",
233
234 "identity:get_project": "rule:admin_required",
235 "identity:list_projects": "rule:admin_required",
236 "identity:list_user_projects": "rule:admin_or_owner",
237 "identity:create_project": "rule:admin_required",
238 "identity:update_project": "rule:admin_required",
239 "identity:delete_project": "rule:admin_required",
240
241 "identity:get_user": "rule:admin_required",
242 "identity:list_users": "rule:admin_required",
243 "identity:create_user": "rule:admin_required",
244 "identity:update_user": "rule:admin_required",
245 "identity:delete_user": "rule:admin_required",
246 "identity:change_password": "rule:admin_or_owner",
247
248 "identity:get_group": "rule:admin_required",
249 "identity:list_groups": "rule:admin_required",
250 "identity:list_groups_for_user": "rule:admin_or_owner",
251 "identity:create_group": "rule:admin_required",
252 "identity:update_group": "rule:admin_required",
253 "identity:delete_group": "rule:admin_required",
254 "identity:list_users_in_group": "rule:admin_required",
255 "identity:remove_user_from_group": "rule:admin_required",
256 "identity:check_user_in_group": "rule:admin_required",
257 "identity:add_user_to_group": "rule:admin_required",
258
259 "identity:get_credential": "rule:admin_required",
260 "identity:list_credentials": "rule:admin_required",
261 "identity:create_credential": "rule:admin_required",
262 "identity:update_credential": "rule:admin_required",
263 "identity:delete_credential": "rule:admin_required",
264
265 "identity:ec2_get_credential": "rule:admin_required or (rule:owner and user_id:%(target.credential.user_id)s)",
266 "identity:ec2_list_credentials": "rule:admin_or_owner",
267 "identity:ec2_create_credential": "rule:admin_or_owner",
268 "identity:ec2_delete_credential": "rule:admin_required or (rule:owner and user_id:%(target.credential.user_id)s)",
269
270 "identity:get_role": "rule:admin_required",
271 "identity:list_roles": "rule:admin_required",
272 "identity:create_role": "rule:admin_required",
273 "identity:update_role": "rule:admin_required",
274 "identity:delete_role": "rule:admin_required",
275
276 "identity:check_grant": "rule:admin_required",
277 "identity:list_grants": "rule:admin_required",
278 "identity:create_grant": "rule:admin_required",
279 "identity:revoke_grant": "rule:admin_required",
280
281 "identity:list_role_assignments": "rule:admin_required",
282
283 "identity:get_policy": "rule:admin_required",
284 "identity:list_policies": "rule:admin_required",
285 "identity:create_policy": "rule:admin_required",
286 "identity:update_policy": "rule:admin_required",
287 "identity:delete_policy": "rule:admin_required",
288
289 "identity:check_token": "rule:admin_or_token_subject",
290 "identity:validate_token": "rule:service_admin_or_token_subject",
291 "identity:validate_token_head": "rule:service_or_admin",
292 "identity:revocation_list": "rule:service_or_admin",
293 "identity:revoke_token": "rule:admin_or_token_subject",
294
295 "identity:create_trust": "user_id:%(trust.trustor_user_id)s",
296 "identity:list_trusts": "",
297 "identity:list_roles_for_trust": "",
298 "identity:get_role_for_trust": "",
299 "identity:delete_trust": "",
300
301 "identity:create_consumer": "rule:admin_required",
302 "identity:get_consumer": "rule:admin_required",
303 "identity:list_consumers": "rule:admin_required",
304 "identity:delete_consumer": "rule:admin_required",
305 "identity:update_consumer": "rule:admin_required",
306
307 "identity:authorize_request_token": "rule:admin_required",
308 "identity:list_access_token_roles": "rule:admin_required",
309 "identity:get_access_token_role": "rule:admin_required",
310 "identity:list_access_tokens": "rule:admin_required",
311 "identity:get_access_token": "rule:admin_required",
312 "identity:delete_access_token": "rule:admin_required",
313
314 "identity:list_projects_for_endpoint": "rule:admin_required",
315 "identity:add_endpoint_to_project": "rule:admin_required",
316 "identity:check_endpoint_in_project": "rule:admin_required",
317 "identity:list_endpoints_for_project": "rule:admin_required",
318 "identity:remove_endpoint_from_project": "rule:admin_required",
319
320 "identity:create_endpoint_group": "rule:admin_required",
321 "identity:list_endpoint_groups": "rule:admin_required",
322 "identity:get_endpoint_group": "rule:admin_required",
323 "identity:update_endpoint_group": "rule:admin_required",
324 "identity:delete_endpoint_group": "rule:admin_required",
325 "identity:list_projects_associated_with_endpoint_group": "rule:admin_required",
326 "identity:list_endpoints_associated_with_endpoint_group": "rule:admin_required",
327 "identity:get_endpoint_group_in_project": "rule:admin_required",
328 "identity:list_endpoint_groups_for_project": "rule:admin_required",
329 "identity:add_endpoint_group_to_project": "rule:admin_required",
330 "identity:remove_endpoint_group_from_project": "rule:admin_required",
331
332 "identity:create_identity_provider": "rule:admin_required",
333 "identity:list_identity_providers": "rule:admin_required",
334 "identity:get_identity_providers": "rule:admin_required",
335 "identity:update_identity_provider": "rule:admin_required",
336 "identity:delete_identity_provider": "rule:admin_required",
337
338 "identity:create_protocol": "rule:admin_required",
339 "identity:update_protocol": "rule:admin_required",
340 "identity:get_protocol": "rule:admin_required",
341 "identity:list_protocols": "rule:admin_required",
342 "identity:delete_protocol": "rule:admin_required",
343
344 "identity:create_mapping": "rule:admin_required",
345 "identity:get_mapping": "rule:admin_required",
346 "identity:list_mappings": "rule:admin_required",
347 "identity:delete_mapping": "rule:admin_required",
348 "identity:update_mapping": "rule:admin_required",
349
350 "identity:create_service_provider": "rule:admin_required",
351 "identity:list_service_providers": "rule:admin_required",
352 "identity:get_service_provider": "rule:admin_required",
353 "identity:update_service_provider": "rule:admin_required",
354 "identity:delete_service_provider": "rule:admin_required",
355
356 "identity:get_auth_catalog": "",
357 "identity:get_auth_projects": "",
358 "identity:get_auth_domains": "",
359
360 "identity:list_projects_for_groups": "",
361 "identity:list_domains_for_groups": "",
362
363 "identity:list_revoke_events": "",
364
365 "identity:create_policy_association_for_endpoint": "rule:admin_required",
366 "identity:check_policy_association_for_endpoint": "rule:admin_required",
367 "identity:delete_policy_association_for_endpoint": "rule:admin_required",
368 "identity:create_policy_association_for_service": "rule:admin_required",
369 "identity:check_policy_association_for_service": "rule:admin_required",
370 "identity:delete_policy_association_for_service": "rule:admin_required",
371 "identity:create_policy_association_for_region_and_service": "rule:admin_required",
372 "identity:check_policy_association_for_region_and_service": "rule:admin_required",
373 "identity:delete_policy_association_for_region_and_service": "rule:admin_required",
374 "identity:get_policy_for_endpoint": "rule:admin_required",
375 "identity:list_endpoints_for_policy": "rule:admin_required",
376
377 "identity:create_domain_config": "rule:admin_required",
378 "identity:get_domain_config": "rule:admin_required",
379 "identity:update_domain_config": "rule:admin_required",
380 "identity:delete_domain_config": "rule:admin_required"
381}
382{% endif -%}
0383
=== added file 'templates/liberty/policy.json.v2'
--- templates/liberty/policy.json.v2 1970-01-01 00:00:00 +0000
+++ templates/liberty/policy.json.v2 2016-03-05 15:43:48 +0000
@@ -0,0 +1,184 @@
1{
2 "admin_required": "role:admin or is_admin:1",
3 "service_role": "role:service",
4 "service_or_admin": "rule:admin_required or rule:service_role",
5 "owner" : "user_id:%(user_id)s",
6 "admin_or_owner": "rule:admin_required or rule:owner",
7 "token_subject": "user_id:%(target.token.user_id)s",
8 "admin_or_token_subject": "rule:admin_required or rule:token_subject",
9 "service_admin_or_token_subject": "rule:service_or_admin or rule:token_subject",
10
11 "default": "rule:admin_required",
12
13 "identity:get_region": "",
14 "identity:list_regions": "",
15 "identity:create_region": "rule:admin_required",
16 "identity:update_region": "rule:admin_required",
17 "identity:delete_region": "rule:admin_required",
18
19 "identity:get_service": "rule:admin_required",
20 "identity:list_services": "rule:admin_required",
21 "identity:create_service": "rule:admin_required",
22 "identity:update_service": "rule:admin_required",
23 "identity:delete_service": "rule:admin_required",
24
25 "identity:get_endpoint": "rule:admin_required",
26 "identity:list_endpoints": "rule:admin_required",
27 "identity:create_endpoint": "rule:admin_required",
28 "identity:update_endpoint": "rule:admin_required",
29 "identity:delete_endpoint": "rule:admin_required",
30
31 "identity:get_domain": "rule:admin_required",
32 "identity:list_domains": "rule:admin_required",
33 "identity:create_domain": "rule:admin_required",
34 "identity:update_domain": "rule:admin_required",
35 "identity:delete_domain": "rule:admin_required",
36
37 "identity:get_project": "rule:admin_required",
38 "identity:list_projects": "rule:admin_required",
39 "identity:list_user_projects": "rule:admin_or_owner",
40 "identity:create_project": "rule:admin_required",
41 "identity:update_project": "rule:admin_required",
42 "identity:delete_project": "rule:admin_required",
43
44 "identity:get_user": "rule:admin_required",
45 "identity:list_users": "rule:admin_required",
46 "identity:create_user": "rule:admin_required",
47 "identity:update_user": "rule:admin_required",
48 "identity:delete_user": "rule:admin_required",
49 "identity:change_password": "rule:admin_or_owner",
50
51 "identity:get_group": "rule:admin_required",
52 "identity:list_groups": "rule:admin_required",
53 "identity:list_groups_for_user": "rule:admin_or_owner",
54 "identity:create_group": "rule:admin_required",
55 "identity:update_group": "rule:admin_required",
56 "identity:delete_group": "rule:admin_required",
57 "identity:list_users_in_group": "rule:admin_required",
58 "identity:remove_user_from_group": "rule:admin_required",
59 "identity:check_user_in_group": "rule:admin_required",
60 "identity:add_user_to_group": "rule:admin_required",
61
62 "identity:get_credential": "rule:admin_required",
63 "identity:list_credentials": "rule:admin_required",
64 "identity:create_credential": "rule:admin_required",
65 "identity:update_credential": "rule:admin_required",
66 "identity:delete_credential": "rule:admin_required",
67
68 "identity:ec2_get_credential": "rule:admin_required or (rule:owner and user_id:%(target.credential.user_id)s)",
69 "identity:ec2_list_credentials": "rule:admin_or_owner",
70 "identity:ec2_create_credential": "rule:admin_or_owner",
71 "identity:ec2_delete_credential": "rule:admin_required or (rule:owner and user_id:%(target.credential.user_id)s)",
72
73 "identity:get_role": "rule:admin_required",
74 "identity:list_roles": "rule:admin_required",
75 "identity:create_role": "rule:admin_required",
76 "identity:update_role": "rule:admin_required",
77 "identity:delete_role": "rule:admin_required",
78
79 "identity:check_grant": "rule:admin_required",
80 "identity:list_grants": "rule:admin_required",
81 "identity:create_grant": "rule:admin_required",
82 "identity:revoke_grant": "rule:admin_required",
83
84 "identity:list_role_assignments": "rule:admin_required",
85
86 "identity:get_policy": "rule:admin_required",
87 "identity:list_policies": "rule:admin_required",
88 "identity:create_policy": "rule:admin_required",
89 "identity:update_policy": "rule:admin_required",
90 "identity:delete_policy": "rule:admin_required",
91
92 "identity:check_token": "rule:admin_or_token_subject",
93 "identity:validate_token": "rule:service_admin_or_token_subject",
94 "identity:validate_token_head": "rule:service_or_admin",
95 "identity:revocation_list": "rule:service_or_admin",
96 "identity:revoke_token": "rule:admin_or_token_subject",
97
98 "identity:create_trust": "user_id:%(trust.trustor_user_id)s",
99 "identity:list_trusts": "",
100 "identity:list_roles_for_trust": "",
101 "identity:get_role_for_trust": "",
102 "identity:delete_trust": "",
103
104 "identity:create_consumer": "rule:admin_required",
105 "identity:get_consumer": "rule:admin_required",
106 "identity:list_consumers": "rule:admin_required",
107 "identity:delete_consumer": "rule:admin_required",
108 "identity:update_consumer": "rule:admin_required",
109
110 "identity:authorize_request_token": "rule:admin_required",
111 "identity:list_access_token_roles": "rule:admin_required",
112 "identity:get_access_token_role": "rule:admin_required",
113 "identity:list_access_tokens": "rule:admin_required",
114 "identity:get_access_token": "rule:admin_required",
115 "identity:delete_access_token": "rule:admin_required",
116
117 "identity:list_projects_for_endpoint": "rule:admin_required",
118 "identity:add_endpoint_to_project": "rule:admin_required",
119 "identity:check_endpoint_in_project": "rule:admin_required",
120 "identity:list_endpoints_for_project": "rule:admin_required",
121 "identity:remove_endpoint_from_project": "rule:admin_required",
122
123 "identity:create_endpoint_group": "rule:admin_required",
124 "identity:list_endpoint_groups": "rule:admin_required",
125 "identity:get_endpoint_group": "rule:admin_required",
126 "identity:update_endpoint_group": "rule:admin_required",
127 "identity:delete_endpoint_group": "rule:admin_required",
128 "identity:list_projects_associated_with_endpoint_group": "rule:admin_required",
129 "identity:list_endpoints_associated_with_endpoint_group": "rule:admin_required",
130 "identity:get_endpoint_group_in_project": "rule:admin_required",
131 "identity:list_endpoint_groups_for_project": "rule:admin_required",
132 "identity:add_endpoint_group_to_project": "rule:admin_required",
133 "identity:remove_endpoint_group_from_project": "rule:admin_required",
134
135 "identity:create_identity_provider": "rule:admin_required",
136 "identity:list_identity_providers": "rule:admin_required",
137 "identity:get_identity_providers": "rule:admin_required",
138 "identity:update_identity_provider": "rule:admin_required",
139 "identity:delete_identity_provider": "rule:admin_required",
140
141 "identity:create_protocol": "rule:admin_required",
142 "identity:update_protocol": "rule:admin_required",
143 "identity:get_protocol": "rule:admin_required",
144 "identity:list_protocols": "rule:admin_required",
145 "identity:delete_protocol": "rule:admin_required",
146
147 "identity:create_mapping": "rule:admin_required",
148 "identity:get_mapping": "rule:admin_required",
149 "identity:list_mappings": "rule:admin_required",
150 "identity:delete_mapping": "rule:admin_required",
151 "identity:update_mapping": "rule:admin_required",
152
153 "identity:create_service_provider": "rule:admin_required",
154 "identity:list_service_providers": "rule:admin_required",
155 "identity:get_service_provider": "rule:admin_required",
156 "identity:update_service_provider": "rule:admin_required",
157 "identity:delete_service_provider": "rule:admin_required",
158
159 "identity:get_auth_catalog": "",
160 "identity:get_auth_projects": "",
161 "identity:get_auth_domains": "",
162
163 "identity:list_projects_for_groups": "",
164 "identity:list_domains_for_groups": "",
165
166 "identity:list_revoke_events": "",
167
168 "identity:create_policy_association_for_endpoint": "rule:admin_required",
169 "identity:check_policy_association_for_endpoint": "rule:admin_required",
170 "identity:delete_policy_association_for_endpoint": "rule:admin_required",
171 "identity:create_policy_association_for_service": "rule:admin_required",
172 "identity:check_policy_association_for_service": "rule:admin_required",
173 "identity:delete_policy_association_for_service": "rule:admin_required",
174 "identity:create_policy_association_for_region_and_service": "rule:admin_required",
175 "identity:check_policy_association_for_region_and_service": "rule:admin_required",
176 "identity:delete_policy_association_for_region_and_service": "rule:admin_required",
177 "identity:get_policy_for_endpoint": "rule:admin_required",
178 "identity:list_endpoints_for_policy": "rule:admin_required",
179
180 "identity:create_domain_config": "rule:admin_required",
181 "identity:get_domain_config": "rule:admin_required",
182 "identity:update_domain_config": "rule:admin_required",
183 "identity:delete_domain_config": "rule:admin_required"
184}
0185
=== modified file 'tests/basic_deployment.py'
--- tests/basic_deployment.py 2016-01-13 21:33:59 +0000
+++ tests/basic_deployment.py 2016-03-05 15:43:48 +0000
@@ -17,6 +17,8 @@
17 DEBUG,17 DEBUG,
18 # ERROR18 # ERROR
19)19)
20import keystoneclient
21from charmhelpers.core.decorators import retry_on_exception
2022
21# Use DEBUG to turn on debug logging23# Use DEBUG to turn on debug logging
22u = OpenStackAmuletUtils(DEBUG)24u = OpenStackAmuletUtils(DEBUG)
@@ -30,6 +32,7 @@
30 """Deploy the entire test environment."""32 """Deploy the entire test environment."""
31 super(KeystoneBasicDeployment, self).__init__(series, openstack,33 super(KeystoneBasicDeployment, self).__init__(series, openstack,
32 source, stable)34 source, stable)
35 self.keystone_api_version = 2
33 self.git = git36 self.git = git
34 self._add_services()37 self._add_services()
35 self._add_relations()38 self._add_relations()
@@ -37,8 +40,8 @@
37 self._deploy()40 self._deploy()
3841
39 u.log.info('Waiting on extended status checks...')42 u.log.info('Waiting on extended status checks...')
40 exclude_services = ['mysql']43 self.exclude_services = ['mysql']
41 self._auto_wait_for_status(exclude_services=exclude_services)44 self._auto_wait_for_status(exclude_services=self.exclude_services)
4245
43 self._initialize_tests()46 self._initialize_tests()
4447
@@ -72,7 +75,8 @@
72 def _configure_services(self):75 def _configure_services(self):
73 """Configure all of the services."""76 """Configure all of the services."""
74 keystone_config = {'admin-password': 'openstack',77 keystone_config = {'admin-password': 'openstack',
75 'admin-token': 'ubuntutesting'}78 'admin-token': 'ubuntutesting',
79 'preferred-api-version': self.keystone_api_version}
76 if self.git:80 if self.git:
77 amulet_http_proxy = os.environ.get('AMULET_HTTP_PROXY')81 amulet_http_proxy = os.environ.get('AMULET_HTTP_PROXY')
7882
@@ -109,6 +113,103 @@
109 }113 }
110 super(KeystoneBasicDeployment, self)._configure_services(configs)114 super(KeystoneBasicDeployment, self)._configure_services(configs)
111115
116 @retry_on_exception(5, base_delay=10)
117 def set_api_version(self, api_version):
118 set_alternate = {'preferred-api-version': api_version}
119
120 # Make config change, check for service restarts
121 u.log.debug('Setting preferred-api-version={}'.format(api_version))
122 self.d.configure('keystone', set_alternate)
123 self.keystone_api_version = api_version
124 client = self.get_keystone_client(api_version=api_version)
125 # List an artefact that needs authorisation to check admin user
126 # has been setup. If that is still in progess
127 # keystoneclient.exceptions.Unauthorized will be thrown and caught by
128 # @retry_on_exception
129 if api_version == 2:
130 client.tenants.list()
131 self.keystone_v2 = self.get_keystone_client(api_version=2)
132 else:
133 client.projects.list()
134 self.keystone_v3 = self.get_keystone_client(api_version=3)
135
136 def get_keystone_client(self, api_version=None):
137 if api_version == 2:
138 return u.authenticate_keystone_admin(self.keystone_sentry,
139 user='admin',
140 password='openstack',
141 tenant='admin',
142 api_version=api_version,
143 keystone_ip=self.keystone_ip)
144 else:
145 return u.authenticate_keystone_admin(self.keystone_sentry,
146 user='admin',
147 password='openstack',
148 api_version=api_version,
149 keystone_ip=self.keystone_ip)
150
151 def create_users_v2(self):
152 # Create a demo tenant/role/user
153 self.demo_tenant = 'demoTenant'
154 self.demo_role = 'demoRole'
155 self.demo_user = 'demoUser'
156 if not u.tenant_exists(self.keystone_v2, self.demo_tenant):
157 tenant = self.keystone_v2.tenants.create(
158 tenant_name=self.demo_tenant,
159 description='demo tenant',
160 enabled=True)
161 self.keystone_v2.roles.create(name=self.demo_role)
162 self.keystone_v2.users.create(name=self.demo_user,
163 password='password',
164 tenant_id=tenant.id,
165 email='demo@demo.com')
166
167 # Authenticate keystone demo
168 self.keystone_demo = u.authenticate_keystone_user(
169 self.keystone_v2, user=self.demo_user,
170 password='password', tenant=self.demo_tenant)
171
172 def create_users_v3(self):
173 # Create a demo tenant/role/user
174 self.demo_project = 'demoProject'
175 self.demo_user_v3 = 'demoUserV3'
176 self.demo_domain = 'demoDomain'
177 try:
178 domain = self.keystone_v3.domains.find(name=self.demo_domain)
179 except keystoneclient.exceptions.NotFound:
180 domain = self.keystone_v3.domains.create(
181 self.demo_domain,
182 description='Demo Domain',
183 enabled=True
184 )
185
186 try:
187 self.keystone_v3.projects.find(name=self.demo_project)
188 except keystoneclient.exceptions.NotFound:
189 self.keystone_v3.projects.create(
190 self.demo_project,
191 domain,
192 description='Demo Project',
193 enabled=True,
194 )
195
196 try:
197 self.keystone_v3.roles.find(name=self.demo_role)
198 except keystoneclient.exceptions.NotFound:
199 self.keystone_v3.roles.create(name=self.demo_role)
200
201 try:
202 self.keystone_v3.users.find(name=self.demo_user_v3)
203 except keystoneclient.exceptions.NotFound:
204 self.keystone_v3.users.create(
205 self.demo_user_v3,
206 domain=domain.id,
207 project=self.demo_project,
208 password='password',
209 email='demov3@demo.com',
210 description='Demo',
211 enabled=True)
212
112 def _initialize_tests(self):213 def _initialize_tests(self):
113 """Perform final initialization before tests get run."""214 """Perform final initialization before tests get run."""
114 # Access the sentries for inspecting service units215 # Access the sentries for inspecting service units
@@ -119,31 +220,14 @@
119 self._get_openstack_release()))220 self._get_openstack_release()))
120 u.log.debug('openstack release str: {}'.format(221 u.log.debug('openstack release str: {}'.format(
121 self._get_openstack_release_string()))222 self._get_openstack_release_string()))
122223 self.keystone_ip = self.keystone_sentry.relation(
224 'shared-db',
225 'mysql:shared-db')['private-address']
226 self.set_api_version(2)
123 # Authenticate keystone admin227 # Authenticate keystone admin
124 self.keystone = u.authenticate_keystone_admin(self.keystone_sentry,228 self.keystone_v2 = self.get_keystone_client(api_version=2)
125 user='admin',229 self.keystone_v3 = self.get_keystone_client(api_version=3)
126 password='openstack',230 self.create_users_v2()
127 tenant='admin')
128
129 # Create a demo tenant/role/user
130 self.demo_tenant = 'demoTenant'
131 self.demo_role = 'demoRole'
132 self.demo_user = 'demoUser'
133 if not u.tenant_exists(self.keystone, self.demo_tenant):
134 tenant = self.keystone.tenants.create(tenant_name=self.demo_tenant,
135 description='demo tenant',
136 enabled=True)
137 self.keystone.roles.create(name=self.demo_role)
138 self.keystone.users.create(name=self.demo_user,
139 password='password',
140 tenant_id=tenant.id,
141 email='demo@demo.com')
142
143 # Authenticate keystone demo
144 self.keystone_demo = u.authenticate_keystone_user(
145 self.keystone, user=self.demo_user,
146 password='password', tenant=self.demo_tenant)
147231
148 def test_100_services(self):232 def test_100_services(self):
149 """Verify the expected services are running on the corresponding233 """Verify the expected services are running on the corresponding
@@ -159,7 +243,7 @@
159 if ret:243 if ret:
160 amulet.raise_status(amulet.FAIL, msg=ret)244 amulet.raise_status(amulet.FAIL, msg=ret)
161245
162 def test_102_keystone_tenants(self):246 def validate_keystone_tenants(self, client):
163 """Verify all existing tenants."""247 """Verify all existing tenants."""
164 u.log.debug('Checking keystone tenants...')248 u.log.debug('Checking keystone tenants...')
165 expected = [249 expected = [
@@ -176,13 +260,20 @@
176 'description': 'Created by Juju',260 'description': 'Created by Juju',
177 'id': u.not_null}261 'id': u.not_null}
178 ]262 ]
179 actual = self.keystone.tenants.list()263 if self.keystone_api_version == 2:
264 actual = client.tenants.list()
265 else:
266 actual = client.projects.list()
180267
181 ret = u.validate_tenant_data(expected, actual)268 ret = u.validate_tenant_data(expected, actual)
182 if ret:269 if ret:
183 amulet.raise_status(amulet.FAIL, msg=ret)270 amulet.raise_status(amulet.FAIL, msg=ret)
184271
185 def test_104_keystone_roles(self):272 def test_102_keystone_tenants(self):
273 self.set_api_version(2)
274 self.validate_keystone_tenants(self.keystone_v2)
275
276 def validate_keystone_roles(self, client):
186 """Verify all existing roles."""277 """Verify all existing roles."""
187 u.log.debug('Checking keystone roles...')278 u.log.debug('Checking keystone roles...')
188 expected = [279 expected = [
@@ -191,40 +282,113 @@
191 {'name': 'Admin',282 {'name': 'Admin',
192 'id': u.not_null}283 'id': u.not_null}
193 ]284 ]
194 actual = self.keystone.roles.list()285 actual = client.roles.list()
195286
196 ret = u.validate_role_data(expected, actual)287 ret = u.validate_role_data(expected, actual)
197 if ret:288 if ret:
198 amulet.raise_status(amulet.FAIL, msg=ret)289 amulet.raise_status(amulet.FAIL, msg=ret)
199290
200 def test_106_keystone_users(self):291 def test_104_keystone_roles(self):
292 self.set_api_version(2)
293 self.validate_keystone_roles(self.keystone_v2)
294
295 def validate_keystone_users(self, client):
201 """Verify all existing roles."""296 """Verify all existing roles."""
202 u.log.debug('Checking keystone users...')297 u.log.debug('Checking keystone users...')
203 expected = [298 base = [
204 {'name': 'demoUser',299 {'name': 'demoUser',
205 'enabled': True,300 'enabled': True,
206 'tenantId': u.not_null,
207 'id': u.not_null,301 'id': u.not_null,
208 'email': 'demo@demo.com'},302 'email': 'demo@demo.com'},
209 {'name': 'admin',303 {'name': 'admin',
210 'enabled': True,304 'enabled': True,
211 'tenantId': u.not_null,
212 'id': u.not_null,305 'id': u.not_null,
213 'email': 'juju@localhost'},306 'email': 'juju@localhost'},
214 {'name': 'cinder_cinderv2',307 {'name': 'cinder_cinderv2',
215 'enabled': True,308 'enabled': True,
216 'tenantId': u.not_null,
217 'id': u.not_null,309 'id': u.not_null,
218 'email': u'juju@localhost'}310 'email': u'juju@localhost'}
219 ]311 ]
220 actual = self.keystone.users.list()312 expected = []
221 ret = u.validate_user_data(expected, actual)313 for user_info in base:
314 if self.keystone_api_version == 2:
315 user_info['tenantId'] = u.not_null
316 else:
317 user_info['default_project_id'] = u.not_null
318 expected.append(user_info)
319 actual = client.users.list()
320 ret = u.validate_user_data(expected, actual,
321 api_version=self.keystone_api_version)
222 if ret:322 if ret:
223 amulet.raise_status(amulet.FAIL, msg=ret)323 amulet.raise_status(amulet.FAIL, msg=ret)
224324
225 def test_108_service_catalog(self):325 def test_106_keystone_users(self):
326 self.set_api_version(2)
327 self.validate_keystone_users(self.keystone_v2)
328
329 def is_liberty_or_newer(self):
330 os_release = self._get_openstack_release_string()
331 if os_release >= 'liberty':
332 return True
333 else:
334 u.log.info('Skipping test, {} < liberty'.format(os_release))
335 return False
336
337 def test_112_keystone_tenants(self):
338 if self.is_liberty_or_newer():
339 self.set_api_version(3)
340 self.validate_keystone_tenants(self.keystone_v3)
341
342 def test_114_keystone_tenants(self):
343 if self.is_liberty_or_newer():
344 self.set_api_version(3)
345 self.validate_keystone_roles(self.keystone_v3)
346
347 def test_116_keystone_users(self):
348 if self.is_liberty_or_newer():
349 self.set_api_version(3)
350 self.validate_keystone_users(self.keystone_v3)
351
352 def test_118_keystone_users(self):
353 if self.is_liberty_or_newer():
354 self.set_api_version(3)
355 self.create_users_v3()
356 actual_user = self.keystone_v3.users.find(name=self.demo_user_v3)
357 expect = {
358 'default_project_id': self.demo_project,
359 'email': 'demov3@demo.com',
360 'name': self.demo_user_v3,
361 }
362 for key in expect.keys():
363 u.log.debug('Checking user {} {} is {}'.format(
364 self.demo_user_v3,
365 key,
366 expect[key])
367 )
368 assert expect[key] == getattr(actual_user, key)
369
370 def test_120_keystone_domains(self):
371 if self.is_liberty_or_newer():
372 self.set_api_version(3)
373 self.create_users_v3()
374 actual_domain = self.keystone_v3.domains.find(
375 name=self.demo_domain
376 )
377 expect = {
378 'name': self.demo_domain,
379 }
380 for key in expect.keys():
381 u.log.debug('Checking domain {} {} is {}'.format(
382 self.demo_domain,
383 key,
384 expect[key])
385 )
386 assert expect[key] == getattr(actual_domain, key)
387
388 def test_138_service_catalog(self):
226 """Verify that the service catalog endpoint data is valid."""389 """Verify that the service catalog endpoint data is valid."""
227 u.log.debug('Checking keystone service catalog...')390 u.log.debug('Checking keystone service catalog...')
391 self.set_api_version(2)
228 endpoint_check = {392 endpoint_check = {
229 'adminURL': u.valid_url,393 'adminURL': u.valid_url,
230 'id': u.not_null,394 'id': u.not_null,
@@ -236,16 +400,16 @@
236 'volume': [endpoint_check],400 'volume': [endpoint_check],
237 'identity': [endpoint_check]401 'identity': [endpoint_check]
238 }402 }
239 actual = self.keystone.service_catalog.get_endpoints()403 actual = self.keystone_v2.service_catalog.get_endpoints()
240404
241 ret = u.validate_svc_catalog_endpoint_data(expected, actual)405 ret = u.validate_svc_catalog_endpoint_data(expected, actual)
242 if ret:406 if ret:
243 amulet.raise_status(amulet.FAIL, msg=ret)407 amulet.raise_status(amulet.FAIL, msg=ret)
244408
245 def test_110_keystone_endpoint(self):409 def test_140_keystone_endpoint(self):
246 """Verify the keystone endpoint data."""410 """Verify the keystone endpoint data."""
247 u.log.debug('Checking keystone api endpoint data...')411 u.log.debug('Checking keystone api endpoint data...')
248 endpoints = self.keystone.endpoints.list()412 endpoints = self.keystone_v2.endpoints.list()
249 admin_port = '35357'413 admin_port = '35357'
250 internal_port = public_port = '5000'414 internal_port = public_port = '5000'
251 expected = {415 expected = {
@@ -262,10 +426,10 @@
262 amulet.raise_status(amulet.FAIL,426 amulet.raise_status(amulet.FAIL,
263 msg='keystone endpoint: {}'.format(ret))427 msg='keystone endpoint: {}'.format(ret))
264428
265 def test_112_cinder_endpoint(self):429 def test_142_cinder_endpoint(self):
266 """Verify the cinder endpoint data."""430 """Verify the cinder endpoint data."""
267 u.log.debug('Checking cinder endpoint...')431 u.log.debug('Checking cinder endpoint...')
268 endpoints = self.keystone.endpoints.list()432 endpoints = self.keystone_v2.endpoints.list()
269 admin_port = internal_port = public_port = '8776'433 admin_port = internal_port = public_port = '8776'
270 expected = {434 expected = {
271 'id': u.not_null,435 'id': u.not_null,
272436
=== modified file 'tests/charmhelpers/contrib/openstack/amulet/utils.py'
--- tests/charmhelpers/contrib/openstack/amulet/utils.py 2016-01-04 21:27:51 +0000
+++ tests/charmhelpers/contrib/openstack/amulet/utils.py 2016-03-05 15:43:48 +0000
@@ -27,6 +27,10 @@
27import glanceclient.v1.client as glance_client27import glanceclient.v1.client as glance_client
28import heatclient.v1.client as heat_client28import heatclient.v1.client as heat_client
29import keystoneclient.v2_0 as keystone_client29import keystoneclient.v2_0 as keystone_client
30from keystoneclient.auth.identity import v3 as keystone_id_v3
31from keystoneclient import session as keystone_session
32from keystoneclient.v3 import client as keystone_client_v3
33
30import novaclient.v1_1.client as nova_client34import novaclient.v1_1.client as nova_client
31import pika35import pika
32import swiftclient36import swiftclient
@@ -139,7 +143,7 @@
139 return "role {} does not exist".format(e['name'])143 return "role {} does not exist".format(e['name'])
140 return ret144 return ret
141145
142 def validate_user_data(self, expected, actual):146 def validate_user_data(self, expected, actual, api_version=None):
143 """Validate user data.147 """Validate user data.
144148
145 Validate a list of actual user data vs a list of expected user149 Validate a list of actual user data vs a list of expected user
@@ -150,10 +154,14 @@
150 for e in expected:154 for e in expected:
151 found = False155 found = False
152 for act in actual:156 for act in actual:
153 a = {'enabled': act.enabled, 'name': act.name,157 if e['name'] == act.name:
154 'email': act.email, 'tenantId': act.tenantId,158 a = {'enabled': act.enabled, 'name': act.name,
155 'id': act.id}159 'email': act.email, 'id': act.id}
156 if e['name'] == a['name']:160 if api_version == 2:
161 a['tenantId'] = act.tenantId
162 else:
163 a['default_project_id'] = getattr(act,
164 'default_project_id', 'none')
157 found = True165 found = True
158 ret = self._validate_dict_data(e, a)166 ret = self._validate_dict_data(e, a)
159 if ret:167 if ret:
@@ -188,15 +196,30 @@
188 return cinder_client.Client(username, password, tenant, ept)196 return cinder_client.Client(username, password, tenant, ept)
189197
190 def authenticate_keystone_admin(self, keystone_sentry, user, password,198 def authenticate_keystone_admin(self, keystone_sentry, user, password,
191 tenant):199 tenant=None, api_version=None,
200 keystone_ip=None):
192 """Authenticates admin user with the keystone admin endpoint."""201 """Authenticates admin user with the keystone admin endpoint."""
193 self.log.debug('Authenticating keystone admin...')202 self.log.debug('Authenticating keystone admin...')
194 unit = keystone_sentry203 unit = keystone_sentry
195 service_ip = unit.relation('shared-db',204 if not keystone_ip:
196 'mysql:shared-db')['private-address']205 keystone_ip = unit.relation('shared-db',
197 ep = "http://{}:35357/v2.0".format(service_ip.strip().decode('utf-8'))206 'mysql:shared-db')['private-address']
198 return keystone_client.Client(username=user, password=password,207 base_ep = "http://{}:35357".format(keystone_ip.strip().decode('utf-8'))
199 tenant_name=tenant, auth_url=ep)208 if not api_version or api_version == 2:
209 ep = base_ep + "/v2.0"
210 return keystone_client.Client(username=user, password=password,
211 tenant_name=tenant, auth_url=ep)
212 else:
213 ep = base_ep + "/v3"
214 auth = keystone_id_v3.Password(
215 user_domain_name='admin_domain',
216 username=user,
217 password=password,
218 domain_name='admin_domain',
219 auth_url=ep,
220 )
221 sess = keystone_session.Session(auth=auth)
222 return keystone_client_v3.Client(session=sess)
200223
201 def authenticate_keystone_user(self, keystone, user, password, tenant):224 def authenticate_keystone_user(self, keystone, user, password, tenant):
202 """Authenticates a regular user with the keystone public endpoint."""225 """Authenticates a regular user with the keystone public endpoint."""
203226
=== added directory 'tests/charmhelpers/core'
=== added file 'tests/charmhelpers/core/__init__.py'
--- tests/charmhelpers/core/__init__.py 1970-01-01 00:00:00 +0000
+++ tests/charmhelpers/core/__init__.py 2016-03-05 15:43:48 +0000
@@ -0,0 +1,15 @@
1# Copyright 2014-2015 Canonical Limited.
2#
3# This file is part of charm-helpers.
4#
5# charm-helpers is free software: you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License version 3 as
7# published by the Free Software Foundation.
8#
9# charm-helpers is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
016
=== added file 'tests/charmhelpers/core/decorators.py'
--- tests/charmhelpers/core/decorators.py 1970-01-01 00:00:00 +0000
+++ tests/charmhelpers/core/decorators.py 2016-03-05 15:43:48 +0000
@@ -0,0 +1,57 @@
1# Copyright 2014-2015 Canonical Limited.
2#
3# This file is part of charm-helpers.
4#
5# charm-helpers is free software: you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License version 3 as
7# published by the Free Software Foundation.
8#
9# charm-helpers is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
16
17#
18# Copyright 2014 Canonical Ltd.
19#
20# Authors:
21# Edward Hope-Morley <opentastic@gmail.com>
22#
23
24import time
25
26from charmhelpers.core.hookenv import (
27 log,
28 INFO,
29)
30
31
32def retry_on_exception(num_retries, base_delay=0, exc_type=Exception):
33 """If the decorated function raises exception exc_type, allow num_retries
34 retry attempts before raise the exception.
35 """
36 def _retry_on_exception_inner_1(f):
37 def _retry_on_exception_inner_2(*args, **kwargs):
38 retries = num_retries
39 multiplier = 1
40 while True:
41 try:
42 return f(*args, **kwargs)
43 except exc_type:
44 if not retries:
45 raise
46
47 delay = base_delay * multiplier
48 multiplier += 1
49 log("Retrying '%s' %d more times (delay=%s)" %
50 (f.__name__, retries, delay), level=INFO)
51 retries -= 1
52 if delay:
53 time.sleep(delay)
54
55 return _retry_on_exception_inner_2
56
57 return _retry_on_exception_inner_1
058
=== added file 'tests/charmhelpers/core/hookenv.py'
--- tests/charmhelpers/core/hookenv.py 1970-01-01 00:00:00 +0000
+++ tests/charmhelpers/core/hookenv.py 2016-03-05 15:43:48 +0000
@@ -0,0 +1,978 @@
1# Copyright 2014-2015 Canonical Limited.
2#
3# This file is part of charm-helpers.
4#
5# charm-helpers is free software: you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License version 3 as
7# published by the Free Software Foundation.
8#
9# charm-helpers is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
16
17"Interactions with the Juju environment"
18# Copyright 2013 Canonical Ltd.
19#
20# Authors:
21# Charm Helpers Developers <juju@lists.ubuntu.com>
22
23from __future__ import print_function
24import copy
25from distutils.version import LooseVersion
26from functools import wraps
27import glob
28import os
29import json
30import yaml
31import subprocess
32import sys
33import errno
34import tempfile
35from subprocess import CalledProcessError
36
37import six
38if not six.PY3:
39 from UserDict import UserDict
40else:
41 from collections import UserDict
42
43CRITICAL = "CRITICAL"
44ERROR = "ERROR"
45WARNING = "WARNING"
46INFO = "INFO"
47DEBUG = "DEBUG"
48MARKER = object()
49
50cache = {}
51
52
53def cached(func):
54 """Cache return values for multiple executions of func + args
55
56 For example::
57
58 @cached
59 def unit_get(attribute):
60 pass
61
62 unit_get('test')
63
64 will cache the result of unit_get + 'test' for future calls.
65 """
66 @wraps(func)
67 def wrapper(*args, **kwargs):
68 global cache
69 key = str((func, args, kwargs))
70 try:
71 return cache[key]
72 except KeyError:
73 pass # Drop out of the exception handler scope.
74 res = func(*args, **kwargs)
75 cache[key] = res
76 return res
77 wrapper._wrapped = func
78 return wrapper
79
80
81def flush(key):
82 """Flushes any entries from function cache where the
83 key is found in the function+args """
84 flush_list = []
85 for item in cache:
86 if key in item:
87 flush_list.append(item)
88 for item in flush_list:
89 del cache[item]
90
91
92def log(message, level=None):
93 """Write a message to the juju log"""
94 command = ['juju-log']
95 if level:
96 command += ['-l', level]
97 if not isinstance(message, six.string_types):
98 message = repr(message)
99 command += [message]
100 # Missing juju-log should not cause failures in unit tests
101 # Send log output to stderr
102 try:
103 subprocess.call(command)
104 except OSError as e:
105 if e.errno == errno.ENOENT:
106 if level:
107 message = "{}: {}".format(level, message)
108 message = "juju-log: {}".format(message)
109 print(message, file=sys.stderr)
110 else:
111 raise
112
113
114class Serializable(UserDict):
115 """Wrapper, an object that can be serialized to yaml or json"""
116
117 def __init__(self, obj):
118 # wrap the object
119 UserDict.__init__(self)
120 self.data = obj
121
122 def __getattr__(self, attr):
123 # See if this object has attribute.
124 if attr in ("json", "yaml", "data"):
125 return self.__dict__[attr]
126 # Check for attribute in wrapped object.
127 got = getattr(self.data, attr, MARKER)
128 if got is not MARKER:
129 return got
130 # Proxy to the wrapped object via dict interface.
131 try:
132 return self.data[attr]
133 except KeyError:
134 raise AttributeError(attr)
135
136 def __getstate__(self):
137 # Pickle as a standard dictionary.
138 return self.data
139
140 def __setstate__(self, state):
141 # Unpickle into our wrapper.
142 self.data = state
143
144 def json(self):
145 """Serialize the object to json"""
146 return json.dumps(self.data)
147
148 def yaml(self):
149 """Serialize the object to yaml"""
150 return yaml.dump(self.data)
151
152
153def execution_environment():
154 """A convenient bundling of the current execution context"""
155 context = {}
156 context['conf'] = config()
157 if relation_id():
158 context['reltype'] = relation_type()
159 context['relid'] = relation_id()
160 context['rel'] = relation_get()
161 context['unit'] = local_unit()
162 context['rels'] = relations()
163 context['env'] = os.environ
164 return context
165
166
167def in_relation_hook():
168 """Determine whether we're running in a relation hook"""
169 return 'JUJU_RELATION' in os.environ
170
171
172def relation_type():
173 """The scope for the current relation hook"""
174 return os.environ.get('JUJU_RELATION', None)
175
176
177@cached
178def relation_id(relation_name=None, service_or_unit=None):
179 """The relation ID for the current or a specified relation"""
180 if not relation_name and not service_or_unit:
181 return os.environ.get('JUJU_RELATION_ID', None)
182 elif relation_name and service_or_unit:
183 service_name = service_or_unit.split('/')[0]
184 for relid in relation_ids(relation_name):
185 remote_service = remote_service_name(relid)
186 if remote_service == service_name:
187 return relid
188 else:
189 raise ValueError('Must specify neither or both of relation_name and service_or_unit')
190
191
192def local_unit():
193 """Local unit ID"""
194 return os.environ['JUJU_UNIT_NAME']
195
196
197def remote_unit():
198 """The remote unit for the current relation hook"""
199 return os.environ.get('JUJU_REMOTE_UNIT', None)
200
201
202def service_name():
203 """The name service group this unit belongs to"""
204 return local_unit().split('/')[0]
205
206
207@cached
208def remote_service_name(relid=None):
209 """The remote service name for a given relation-id (or the current relation)"""
210 if relid is None:
211 unit = remote_unit()
212 else:
213 units = related_units(relid)
214 unit = units[0] if units else None
215 return unit.split('/')[0] if unit else None
216
217
218def hook_name():
219 """The name of the currently executing hook"""
220 return os.environ.get('JUJU_HOOK_NAME', os.path.basename(sys.argv[0]))
221
222
223class Config(dict):
224 """A dictionary representation of the charm's config.yaml, with some
225 extra features:
226
227 - See which values in the dictionary have changed since the previous hook.
228 - For values that have changed, see what the previous value was.
229 - Store arbitrary data for use in a later hook.
230
231 NOTE: Do not instantiate this object directly - instead call
232 ``hookenv.config()``, which will return an instance of :class:`Config`.
233
234 Example usage::
235
236 >>> # inside a hook
237 >>> from charmhelpers.core import hookenv
238 >>> config = hookenv.config()
239 >>> config['foo']
240 'bar'
241 >>> # store a new key/value for later use
242 >>> config['mykey'] = 'myval'
243
244
245 >>> # user runs `juju set mycharm foo=baz`
246 >>> # now we're inside subsequent config-changed hook
247 >>> config = hookenv.config()
248 >>> config['foo']
249 'baz'
250 >>> # test to see if this val has changed since last hook
251 >>> config.changed('foo')
252 True
253 >>> # what was the previous value?
254 >>> config.previous('foo')
255 'bar'
256 >>> # keys/values that we add are preserved across hooks
257 >>> config['mykey']
258 'myval'
259
260 """
261 CONFIG_FILE_NAME = '.juju-persistent-config'
262
263 def __init__(self, *args, **kw):
264 super(Config, self).__init__(*args, **kw)
265 self.implicit_save = True
266 self._prev_dict = None
267 self.path = os.path.join(charm_dir(), Config.CONFIG_FILE_NAME)
268 if os.path.exists(self.path):
269 self.load_previous()
270 atexit(self._implicit_save)
271
272 def load_previous(self, path=None):
273 """Load previous copy of config from disk.
274
275 In normal usage you don't need to call this method directly - it
276 is called automatically at object initialization.
277
278 :param path:
279
280 File path from which to load the previous config. If `None`,
281 config is loaded from the default location. If `path` is
282 specified, subsequent `save()` calls will write to the same
283 path.
284
285 """
286 self.path = path or self.path
287 with open(self.path) as f:
288 self._prev_dict = json.load(f)
289 for k, v in copy.deepcopy(self._prev_dict).items():
290 if k not in self:
291 self[k] = v
292
293 def changed(self, key):
294 """Return True if the current value for this key is different from
295 the previous value.
296
297 """
298 if self._prev_dict is None:
299 return True
300 return self.previous(key) != self.get(key)
301
302 def previous(self, key):
303 """Return previous value for this key, or None if there
304 is no previous value.
305
306 """
307 if self._prev_dict:
308 return self._prev_dict.get(key)
309 return None
310
311 def save(self):
312 """Save this config to disk.
313
314 If the charm is using the :mod:`Services Framework <services.base>`
315 or :meth:'@hook <Hooks.hook>' decorator, this
316 is called automatically at the end of successful hook execution.
317 Otherwise, it should be called directly by user code.
318
319 To disable automatic saves, set ``implicit_save=False`` on this
320 instance.
321
322 """
323 with open(self.path, 'w') as f:
324 json.dump(self, f)
325
326 def _implicit_save(self):
327 if self.implicit_save:
328 self.save()
329
330
331@cached
332def config(scope=None):
333 """Juju charm configuration"""
334 config_cmd_line = ['config-get']
335 if scope is not None:
336 config_cmd_line.append(scope)
337 config_cmd_line.append('--format=json')
338 try:
339 config_data = json.loads(
340 subprocess.check_output(config_cmd_line).decode('UTF-8'))
341 if scope is not None:
342 return config_data
343 return Config(config_data)
344 except ValueError:
345 return None
346
347
348@cached
349def relation_get(attribute=None, unit=None, rid=None):
350 """Get relation information"""
351 _args = ['relation-get', '--format=json']
352 if rid:
353 _args.append('-r')
354 _args.append(rid)
355 _args.append(attribute or '-')
356 if unit:
357 _args.append(unit)
358 try:
359 return json.loads(subprocess.check_output(_args).decode('UTF-8'))
360 except ValueError:
361 return None
362 except CalledProcessError as e:
363 if e.returncode == 2:
364 return None
365 raise
366
367
368def relation_set(relation_id=None, relation_settings=None, **kwargs):
369 """Set relation information for the current unit"""
370 relation_settings = relation_settings if relation_settings else {}
371 relation_cmd_line = ['relation-set']
372 accepts_file = "--file" in subprocess.check_output(
373 relation_cmd_line + ["--help"], universal_newlines=True)
374 if relation_id is not None:
375 relation_cmd_line.extend(('-r', relation_id))
376 settings = relation_settings.copy()
377 settings.update(kwargs)
378 for key, value in settings.items():
379 # Force value to be a string: it always should, but some call
380 # sites pass in things like dicts or numbers.
381 if value is not None:
382 settings[key] = "{}".format(value)
383 if accepts_file:
384 # --file was introduced in Juju 1.23.2. Use it by default if
385 # available, since otherwise we'll break if the relation data is
386 # too big. Ideally we should tell relation-set to read the data from
387 # stdin, but that feature is broken in 1.23.2: Bug #1454678.
388 with tempfile.NamedTemporaryFile(delete=False) as settings_file:
389 settings_file.write(yaml.safe_dump(settings).encode("utf-8"))
390 subprocess.check_call(
391 relation_cmd_line + ["--file", settings_file.name])
392 os.remove(settings_file.name)
393 else:
394 for key, value in settings.items():
395 if value is None:
396 relation_cmd_line.append('{}='.format(key))
397 else:
398 relation_cmd_line.append('{}={}'.format(key, value))
399 subprocess.check_call(relation_cmd_line)
400 # Flush cache of any relation-gets for local unit
401 flush(local_unit())
402
403
404def relation_clear(r_id=None):
405 ''' Clears any relation data already set on relation r_id '''
406 settings = relation_get(rid=r_id,
407 unit=local_unit())
408 for setting in settings:
409 if setting not in ['public-address', 'private-address']:
410 settings[setting] = None
411 relation_set(relation_id=r_id,
412 **settings)
413
414
415@cached
416def relation_ids(reltype=None):
417 """A list of relation_ids"""
418 reltype = reltype or relation_type()
419 relid_cmd_line = ['relation-ids', '--format=json']
420 if reltype is not None:
421 relid_cmd_line.append(reltype)
422 return json.loads(
423 subprocess.check_output(relid_cmd_line).decode('UTF-8')) or []
424 return []
425
426
427@cached
428def related_units(relid=None):
429 """A list of related units"""
430 relid = relid or relation_id()
431 units_cmd_line = ['relation-list', '--format=json']
432 if relid is not None:
433 units_cmd_line.extend(('-r', relid))
434 return json.loads(
435 subprocess.check_output(units_cmd_line).decode('UTF-8')) or []
436
437
438@cached
439def relation_for_unit(unit=None, rid=None):
440 """Get the json represenation of a unit's relation"""
441 unit = unit or remote_unit()
442 relation = relation_get(unit=unit, rid=rid)
443 for key in relation:
444 if key.endswith('-list'):
445 relation[key] = relation[key].split()
446 relation['__unit__'] = unit
447 return relation
448
449
450@cached
451def relations_for_id(relid=None):
452 """Get relations of a specific relation ID"""
453 relation_data = []
454 relid = relid or relation_ids()
455 for unit in related_units(relid):
456 unit_data = relation_for_unit(unit, relid)
457 unit_data['__relid__'] = relid
458 relation_data.append(unit_data)
459 return relation_data
460
461
462@cached
463def relations_of_type(reltype=None):
464 """Get relations of a specific type"""
465 relation_data = []
466 reltype = reltype or relation_type()
467 for relid in relation_ids(reltype):
468 for relation in relations_for_id(relid):
469 relation['__relid__'] = relid
470 relation_data.append(relation)
471 return relation_data
472
473
474@cached
475def metadata():
476 """Get the current charm metadata.yaml contents as a python object"""
477 with open(os.path.join(charm_dir(), 'metadata.yaml')) as md:
478 return yaml.safe_load(md)
479
480
481@cached
482def relation_types():
483 """Get a list of relation types supported by this charm"""
484 rel_types = []
485 md = metadata()
486 for key in ('provides', 'requires', 'peers'):
487 section = md.get(key)
488 if section:
489 rel_types.extend(section.keys())
490 return rel_types
491
492
493@cached
494def peer_relation_id():
495 '''Get the peers relation id if a peers relation has been joined, else None.'''
496 md = metadata()
497 section = md.get('peers')
498 if section:
499 for key in section:
500 relids = relation_ids(key)
501 if relids:
502 return relids[0]
503 return None
504
505
506@cached
507def relation_to_interface(relation_name):
508 """
509 Given the name of a relation, return the interface that relation uses.
510
511 :returns: The interface name, or ``None``.
512 """
513 return relation_to_role_and_interface(relation_name)[1]
514
515
516@cached
517def relation_to_role_and_interface(relation_name):
518 """
519 Given the name of a relation, return the role and the name of the interface
520 that relation uses (where role is one of ``provides``, ``requires``, or ``peers``).
521
522 :returns: A tuple containing ``(role, interface)``, or ``(None, None)``.
523 """
524 _metadata = metadata()
525 for role in ('provides', 'requires', 'peers'):
526 interface = _metadata.get(role, {}).get(relation_name, {}).get('interface')
527 if interface:
528 return role, interface
529 return None, None
530
531
532@cached
533def role_and_interface_to_relations(role, interface_name):
534 """
535 Given a role and interface name, return a list of relation names for the
536 current charm that use that interface under that role (where role is one
537 of ``provides``, ``requires``, or ``peers``).
538
539 :returns: A list of relation names.
540 """
541 _metadata = metadata()
542 results = []
543 for relation_name, relation in _metadata.get(role, {}).items():
544 if relation['interface'] == interface_name:
545 results.append(relation_name)
546 return results
547
548
549@cached
550def interface_to_relations(interface_name):
551 """
552 Given an interface, return a list of relation names for the current
553 charm that use that interface.
554
555 :returns: A list of relation names.
556 """
557 results = []
558 for role in ('provides', 'requires', 'peers'):
559 results.extend(role_and_interface_to_relations(role, interface_name))
560 return results
561
562
563@cached
564def charm_name():
565 """Get the name of the current charm as is specified on metadata.yaml"""
566 return metadata().get('name')
567
568
569@cached
570def relations():
571 """Get a nested dictionary of relation data for all related units"""
572 rels = {}
573 for reltype in relation_types():
574 relids = {}
575 for relid in relation_ids(reltype):
576 units = {local_unit(): relation_get(unit=local_unit(), rid=relid)}
577 for unit in related_units(relid):
578 reldata = relation_get(unit=unit, rid=relid)
579 units[unit] = reldata
580 relids[relid] = units
581 rels[reltype] = relids
582 return rels
583
584
585@cached
586def is_relation_made(relation, keys='private-address'):
587 '''
588 Determine whether a relation is established by checking for
589 presence of key(s). If a list of keys is provided, they
590 must all be present for the relation to be identified as made
591 '''
592 if isinstance(keys, str):
593 keys = [keys]
594 for r_id in relation_ids(relation):
595 for unit in related_units(r_id):
596 context = {}
597 for k in keys:
598 context[k] = relation_get(k, rid=r_id,
599 unit=unit)
600 if None not in context.values():
601 return True
602 return False
603
604
605def open_port(port, protocol="TCP"):
606 """Open a service network port"""
607 _args = ['open-port']
608 _args.append('{}/{}'.format(port, protocol))
609 subprocess.check_call(_args)
610
611
612def close_port(port, protocol="TCP"):
613 """Close a service network port"""
614 _args = ['close-port']
615 _args.append('{}/{}'.format(port, protocol))
616 subprocess.check_call(_args)
617
618
619@cached
620def unit_get(attribute):
621 """Get the unit ID for the remote unit"""
622 _args = ['unit-get', '--format=json', attribute]
623 try:
624 return json.loads(subprocess.check_output(_args).decode('UTF-8'))
625 except ValueError:
626 return None
627
628
629def unit_public_ip():
630 """Get this unit's public IP address"""
631 return unit_get('public-address')
632
633
634def unit_private_ip():
635 """Get this unit's private IP address"""
636 return unit_get('private-address')
637
638
639@cached
640def storage_get(attribute=None, storage_id=None):
641 """Get storage attributes"""
642 _args = ['storage-get', '--format=json']
643 if storage_id:
644 _args.extend(('-s', storage_id))
645 if attribute:
646 _args.append(attribute)
647 try:
648 return json.loads(subprocess.check_output(_args).decode('UTF-8'))
649 except ValueError:
650 return None
651
652
653@cached
654def storage_list(storage_name=None):
655 """List the storage IDs for the unit"""
656 _args = ['storage-list', '--format=json']
657 if storage_name:
658 _args.append(storage_name)
659 try:
660 return json.loads(subprocess.check_output(_args).decode('UTF-8'))
661 except ValueError:
662 return None
663 except OSError as e:
664 import errno
665 if e.errno == errno.ENOENT:
666 # storage-list does not exist
667 return []
668 raise
669
670
671class UnregisteredHookError(Exception):
672 """Raised when an undefined hook is called"""
673 pass
674
675
676class Hooks(object):
677 """A convenient handler for hook functions.
678
679 Example::
680
681 hooks = Hooks()
682
683 # register a hook, taking its name from the function name
684 @hooks.hook()
685 def install():
686 pass # your code here
687
688 # register a hook, providing a custom hook name
689 @hooks.hook("config-changed")
690 def config_changed():
691 pass # your code here
692
693 if __name__ == "__main__":
694 # execute a hook based on the name the program is called by
695 hooks.execute(sys.argv)
696 """
697
698 def __init__(self, config_save=None):
699 super(Hooks, self).__init__()
700 self._hooks = {}
701
702 # For unknown reasons, we allow the Hooks constructor to override
703 # config().implicit_save.
704 if config_save is not None:
705 config().implicit_save = config_save
706
707 def register(self, name, function):
708 """Register a hook"""
709 self._hooks[name] = function
710
711 def execute(self, args):
712 """Execute a registered hook based on args[0]"""
713 _run_atstart()
714 hook_name = os.path.basename(args[0])
715 if hook_name in self._hooks:
716 try:
717 self._hooks[hook_name]()
718 except SystemExit as x:
719 if x.code is None or x.code == 0:
720 _run_atexit()
721 raise
722 _run_atexit()
723 else:
724 raise UnregisteredHookError(hook_name)
725
726 def hook(self, *hook_names):
727 """Decorator, registering them as hooks"""
728 def wrapper(decorated):
729 for hook_name in hook_names:
730 self.register(hook_name, decorated)
731 else:
732 self.register(decorated.__name__, decorated)
733 if '_' in decorated.__name__:
734 self.register(
735 decorated.__name__.replace('_', '-'), decorated)
736 return decorated
737 return wrapper
738
739
740def charm_dir():
741 """Return the root directory of the current charm"""
742 return os.environ.get('CHARM_DIR')
743
744
745@cached
746def action_get(key=None):
747 """Gets the value of an action parameter, or all key/value param pairs"""
748 cmd = ['action-get']
749 if key is not None:
750 cmd.append(key)
751 cmd.append('--format=json')
752 action_data = json.loads(subprocess.check_output(cmd).decode('UTF-8'))
753 return action_data
754
755
756def action_set(values):
757 """Sets the values to be returned after the action finishes"""
758 cmd = ['action-set']
759 for k, v in list(values.items()):
760 cmd.append('{}={}'.format(k, v))
761 subprocess.check_call(cmd)
762
763
764def action_fail(message):
765 """Sets the action status to failed and sets the error message.
766
767 The results set by action_set are preserved."""
768 subprocess.check_call(['action-fail', message])
769
770
771def action_name():
772 """Get the name of the currently executing action."""
773 return os.environ.get('JUJU_ACTION_NAME')
774
775
776def action_uuid():
777 """Get the UUID of the currently executing action."""
778 return os.environ.get('JUJU_ACTION_UUID')
779
780
781def action_tag():
782 """Get the tag for the currently executing action."""
783 return os.environ.get('JUJU_ACTION_TAG')
784
785
786def status_set(workload_state, message):
787 """Set the workload state with a message
788
789 Use status-set to set the workload state with a message which is visible
790 to the user via juju status. If the status-set command is not found then
791 assume this is juju < 1.23 and juju-log the message unstead.
792
793 workload_state -- valid juju workload state.
794 message -- status update message
795 """
796 valid_states = ['maintenance', 'blocked', 'waiting', 'active']
797 if workload_state not in valid_states:
798 raise ValueError(
799 '{!r} is not a valid workload state'.format(workload_state)
800 )
801 cmd = ['status-set', workload_state, message]
802 try:
803 ret = subprocess.call(cmd)
804 if ret == 0:
805 return
806 except OSError as e:
807 if e.errno != errno.ENOENT:
808 raise
809 log_message = 'status-set failed: {} {}'.format(workload_state,
810 message)
811 log(log_message, level='INFO')
812
813
814def status_get():
815 """Retrieve the previously set juju workload state and message
816
817 If the status-get command is not found then assume this is juju < 1.23 and
818 return 'unknown', ""
819
820 """
821 cmd = ['status-get', "--format=json", "--include-data"]
822 try:
823 raw_status = subprocess.check_output(cmd)
824 except OSError as e:
825 if e.errno == errno.ENOENT:
826 return ('unknown', "")
827 else:
828 raise
829 else:
830 status = json.loads(raw_status.decode("UTF-8"))
831 return (status["status"], status["message"])
832
833
834def translate_exc(from_exc, to_exc):
835 def inner_translate_exc1(f):
836 @wraps(f)
837 def inner_translate_exc2(*args, **kwargs):
838 try:
839 return f(*args, **kwargs)
840 except from_exc:
841 raise to_exc
842
843 return inner_translate_exc2
844
845 return inner_translate_exc1
846
847
848@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
849def is_leader():
850 """Does the current unit hold the juju leadership
851
852 Uses juju to determine whether the current unit is the leader of its peers
853 """
854 cmd = ['is-leader', '--format=json']
855 return json.loads(subprocess.check_output(cmd).decode('UTF-8'))
856
857
858@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
859def leader_get(attribute=None):
860 """Juju leader get value(s)"""
861 cmd = ['leader-get', '--format=json'] + [attribute or '-']
862 return json.loads(subprocess.check_output(cmd).decode('UTF-8'))
863
864
865@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
866def leader_set(settings=None, **kwargs):
867 """Juju leader set value(s)"""
868 # Don't log secrets.
869 # log("Juju leader-set '%s'" % (settings), level=DEBUG)
870 cmd = ['leader-set']
871 settings = settings or {}
872 settings.update(kwargs)
873 for k, v in settings.items():
874 if v is None:
875 cmd.append('{}='.format(k))
876 else:
877 cmd.append('{}={}'.format(k, v))
878 subprocess.check_call(cmd)
879
880
881@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
882def payload_register(ptype, klass, pid):
883 """ is used while a hook is running to let Juju know that a
884 payload has been started."""
885 cmd = ['payload-register']
886 for x in [ptype, klass, pid]:
887 cmd.append(x)
888 subprocess.check_call(cmd)
889
890
891@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
892def payload_unregister(klass, pid):
893 """ is used while a hook is running to let Juju know
894 that a payload has been manually stopped. The <class> and <id> provided
895 must match a payload that has been previously registered with juju using
896 payload-register."""
897 cmd = ['payload-unregister']
898 for x in [klass, pid]:
899 cmd.append(x)
900 subprocess.check_call(cmd)
901
902
903@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
904def payload_status_set(klass, pid, status):
905 """is used to update the current status of a registered payload.
906 The <class> and <id> provided must match a payload that has been previously
907 registered with juju using payload-register. The <status> must be one of the
908 follow: starting, started, stopping, stopped"""
909 cmd = ['payload-status-set']
910 for x in [klass, pid, status]:
911 cmd.append(x)
912 subprocess.check_call(cmd)
913
914
915@cached
916def juju_version():
917 """Full version string (eg. '1.23.3.1-trusty-amd64')"""
918 # Per https://bugs.launchpad.net/juju-core/+bug/1455368/comments/1
919 jujud = glob.glob('/var/lib/juju/tools/machine-*/jujud')[0]
920 return subprocess.check_output([jujud, 'version'],
921 universal_newlines=True).strip()
922
923
924@cached
925def has_juju_version(minimum_version):
926 """Return True if the Juju version is at least the provided version"""
927 return LooseVersion(juju_version()) >= LooseVersion(minimum_version)
928
929
930_atexit = []
931_atstart = []
932
933
934def atstart(callback, *args, **kwargs):
935 '''Schedule a callback to run before the main hook.
936
937 Callbacks are run in the order they were added.
938
939 This is useful for modules and classes to perform initialization
940 and inject behavior. In particular:
941
942 - Run common code before all of your hooks, such as logging
943 the hook name or interesting relation data.
944 - Defer object or module initialization that requires a hook
945 context until we know there actually is a hook context,
946 making testing easier.
947 - Rather than requiring charm authors to include boilerplate to
948 invoke your helper's behavior, have it run automatically if
949 your object is instantiated or module imported.
950
951 This is not at all useful after your hook framework as been launched.
952 '''
953 global _atstart
954 _atstart.append((callback, args, kwargs))
955
956
957def atexit(callback, *args, **kwargs):
958 '''Schedule a callback to run on successful hook completion.
959
960 Callbacks are run in the reverse order that they were added.'''
961 _atexit.append((callback, args, kwargs))
962
963
964def _run_atstart():
965 '''Hook frameworks must invoke this before running the main hook body.'''
966 global _atstart
967 for callback, args, kwargs in _atstart:
968 callback(*args, **kwargs)
969 del _atstart[:]
970
971
972def _run_atexit():
973 '''Hook frameworks must invoke this after the main hook body has
974 successfully completed. Do not invoke it if the hook fails.'''
975 global _atexit
976 for callback, args, kwargs in reversed(_atexit):
977 callback(*args, **kwargs)
978 del _atexit[:]
0979
=== modified file 'unit_tests/test_actions.py'
--- unit_tests/test_actions.py 2016-01-13 15:13:10 +0000
+++ unit_tests/test_actions.py 2016-03-05 15:43:48 +0000
@@ -5,7 +5,8 @@
55
6with patch('actions.hooks.keystone_utils.is_paused') as is_paused:6with patch('actions.hooks.keystone_utils.is_paused') as is_paused:
7 with patch('actions.hooks.keystone_utils.register_configs') as configs:7 with patch('actions.hooks.keystone_utils.register_configs') as configs:
8 import actions.actions8 with patch('actions.hooks.keystone_utils.os_release') as os_release:
9 import actions.actions
910
1011
11class PauseTestCase(CharmTestCase):12class PauseTestCase(CharmTestCase):
@@ -15,7 +16,8 @@
15 actions.actions, ["service_pause", "HookData", "kv",16 actions.actions, ["service_pause", "HookData", "kv",
16 "assess_status"])17 "assess_status"])
1718
18 def test_pauses_services(self):19 @patch('actions.hooks.keystone_utils.os_release')
20 def test_pauses_services(self, os_release):
19 """Pause action pauses all Keystone services."""21 """Pause action pauses all Keystone services."""
20 pause_calls = []22 pause_calls = []
2123
@@ -29,7 +31,8 @@
29 self.assertItemsEqual(31 self.assertItemsEqual(
30 pause_calls, ['haproxy', 'keystone', 'apache2'])32 pause_calls, ['haproxy', 'keystone', 'apache2'])
3133
32 def test_bails_out_early_on_error(self):34 @patch('actions.hooks.keystone_utils.os_release')
35 def test_bails_out_early_on_error(self, os_release):
33 """Pause action fails early if there are errors stopping a service."""36 """Pause action fails early if there are errors stopping a service."""
34 pause_calls = []37 pause_calls = []
3538
@@ -46,7 +49,8 @@
46 actions.actions.pause, [])49 actions.actions.pause, [])
47 self.assertEqual(pause_calls, ['haproxy'])50 self.assertEqual(pause_calls, ['haproxy'])
4851
49 def test_pause_sets_value(self):52 @patch('actions.hooks.keystone_utils.os_release')
53 def test_pause_sets_value(self, os_release):
50 """Pause action sets the unit-paused value to True."""54 """Pause action sets the unit-paused value to True."""
51 self.HookData()().return_value = True55 self.HookData()().return_value = True
5256
@@ -61,7 +65,8 @@
61 actions.actions, ["service_resume", "HookData", "kv",65 actions.actions, ["service_resume", "HookData", "kv",
62 "assess_status"])66 "assess_status"])
6367
64 def test_resumes_services(self):68 @patch('actions.hooks.keystone_utils.os_release')
69 def test_resumes_services(self, os_release):
65 """Resume action resumes all Keystone services."""70 """Resume action resumes all Keystone services."""
66 resume_calls = []71 resume_calls = []
6772
@@ -73,7 +78,8 @@
73 actions.actions.resume([])78 actions.actions.resume([])
74 self.assertEqual(resume_calls, ['haproxy', 'keystone', 'apache2'])79 self.assertEqual(resume_calls, ['haproxy', 'keystone', 'apache2'])
7580
76 def test_bails_out_early_on_error(self):81 @patch('actions.hooks.keystone_utils.os_release')
82 def test_bails_out_early_on_error(self, os_release):
77 """Resume action fails early if there are errors starting a service."""83 """Resume action fails early if there are errors starting a service."""
78 resume_calls = []84 resume_calls = []
7985
@@ -90,7 +96,8 @@
90 actions.actions.resume, [])96 actions.actions.resume, [])
91 self.assertEqual(resume_calls, ['haproxy'])97 self.assertEqual(resume_calls, ['haproxy'])
9298
93 def test_resume_sets_value(self):99 @patch('actions.hooks.keystone_utils.os_release')
100 def test_resume_sets_value(self, os_release):
94 """Resume action sets the unit-paused value to False."""101 """Resume action sets the unit-paused value to False."""
95 self.HookData()().return_value = True102 self.HookData()().return_value = True
96103
97104
=== modified file 'unit_tests/test_actions_git_reinstall.py'
--- unit_tests/test_actions_git_reinstall.py 2015-10-30 23:30:09 +0000
+++ unit_tests/test_actions_git_reinstall.py 2016-03-05 15:43:48 +0000
@@ -1,7 +1,8 @@
1from mock import patch1from mock import patch
22
3with patch('hooks.keystone_utils.register_configs') as register_configs:3with patch('hooks.keystone_utils.register_configs') as register_configs:
4 import git_reinstall4 with patch('hooks.keystone_utils.os_release') as os_release:
5 import git_reinstall
56
6from test_utils import (7from test_utils import (
7 CharmTestCase8 CharmTestCase
89
=== modified file 'unit_tests/test_actions_openstack_upgrade.py'
--- unit_tests/test_actions_openstack_upgrade.py 2015-10-19 13:33:33 +0000
+++ unit_tests/test_actions_openstack_upgrade.py 2016-03-05 15:43:48 +0000
@@ -3,9 +3,12 @@
33
4os.environ['JUJU_UNIT_NAME'] = 'keystone'4os.environ['JUJU_UNIT_NAME'] = 'keystone'
55
6# with patch('charmhelpers.contrib.openstack.utils.os_release') as os_release:
7# with patch('keystone_hooks.os_release') as os_release:
6with patch('keystone_utils.register_configs') as register_configs:8with patch('keystone_utils.register_configs') as register_configs:
7 import openstack_upgrade9 with patch('keystone_utils.os_release') as os_release:
8 import keystone_hooks as hooks10 import openstack_upgrade
11 import keystone_hooks as hooks
912
10from test_utils import (13from test_utils import (
11 CharmTestCase14 CharmTestCase
@@ -23,13 +26,14 @@
23 super(TestKeystoneUpgradeActions, self).setUp(openstack_upgrade,26 super(TestKeystoneUpgradeActions, self).setUp(openstack_upgrade,
24 TO_PATCH)27 TO_PATCH)
2528
29 @patch.object(hooks, 'os_release')
26 @patch.object(hooks, 'register_configs')30 @patch.object(hooks, 'register_configs')
27 @patch('charmhelpers.contrib.openstack.utils.config')31 @patch('charmhelpers.contrib.openstack.utils.config')
28 @patch('charmhelpers.contrib.openstack.utils.action_set')32 @patch('charmhelpers.contrib.openstack.utils.action_set')
29 @patch('charmhelpers.contrib.openstack.utils.git_install_requested')33 @patch('charmhelpers.contrib.openstack.utils.git_install_requested')
30 @patch('charmhelpers.contrib.openstack.utils.openstack_upgrade_available')34 @patch('charmhelpers.contrib.openstack.utils.openstack_upgrade_available')
31 def test_openstack_upgrade_true(self, upgrade_avail, git_requested,35 def test_openstack_upgrade_true(self, upgrade_avail, git_requested,
32 action_set, config, reg_configs):36 action_set, config, reg_configs, os_rel):
33 git_requested.return_value = False37 git_requested.return_value = False
34 upgrade_avail.return_value = True38 upgrade_avail.return_value = True
35 config.return_value = True39 config.return_value = True
@@ -40,13 +44,14 @@
40 self.os.execl.assert_called_with('./hooks/config-changed-postupgrade',44 self.os.execl.assert_called_with('./hooks/config-changed-postupgrade',
41 '')45 '')
4246
47 @patch.object(hooks, 'os_release')
43 @patch.object(hooks, 'register_configs')48 @patch.object(hooks, 'register_configs')
44 @patch('charmhelpers.contrib.openstack.utils.config')49 @patch('charmhelpers.contrib.openstack.utils.config')
45 @patch('charmhelpers.contrib.openstack.utils.action_set')50 @patch('charmhelpers.contrib.openstack.utils.action_set')
46 @patch('charmhelpers.contrib.openstack.utils.git_install_requested')51 @patch('charmhelpers.contrib.openstack.utils.git_install_requested')
47 @patch('charmhelpers.contrib.openstack.utils.openstack_upgrade_available')52 @patch('charmhelpers.contrib.openstack.utils.openstack_upgrade_available')
48 def test_openstack_upgrade_false(self, upgrade_avail, git_requested,53 def test_openstack_upgrade_false(self, upgrade_avail, git_requested,
49 action_set, config, reg_configs):54 action_set, config, reg_configs, os_rel):
50 git_requested.return_value = False55 git_requested.return_value = False
51 upgrade_avail.return_value = True56 upgrade_avail.return_value = True
52 config.return_value = False57 config.return_value = False
5358
=== modified file 'unit_tests/test_keystone_hooks.py'
--- unit_tests/test_keystone_hooks.py 2016-01-12 11:09:46 +0000
+++ unit_tests/test_keystone_hooks.py 2016-03-05 15:43:48 +0000
@@ -73,6 +73,7 @@
73 'git_install',73 'git_install',
74 'is_service_present',74 'is_service_present',
75 'delete_service_entry',75 'delete_service_entry',
76 'os_release',
76]77]
7778
7879
@@ -83,9 +84,10 @@
83 self.config.side_effect = self.test_config.get84 self.config.side_effect = self.test_config.get
84 self.ssh_user = 'juju_keystone'85 self.ssh_user = 'juju_keystone'
8586
87 @patch.object(utils, 'os_release')
86 @patch.object(utils, 'git_install_requested')88 @patch.object(utils, 'git_install_requested')
87 @patch.object(unison, 'ensure_user')89 @patch.object(unison, 'ensure_user')
88 def test_install_hook(self, ensure_user, git_requested):90 def test_install_hook(self, ensure_user, git_requested, os_release):
89 git_requested.return_value = False91 git_requested.return_value = False
90 repo = 'cloud:precise-grizzly'92 repo = 'cloud:precise-grizzly'
91 self.test_config.set('openstack-origin', repo)93 self.test_config.set('openstack-origin', repo)
@@ -100,9 +102,10 @@
100 'python-six', 'unison', 'uuid'], fatal=True)102 'python-six', 'unison', 'uuid'], fatal=True)
101 self.git_install.assert_called_with(None)103 self.git_install.assert_called_with(None)
102104
105 @patch.object(utils, 'os_release')
103 @patch.object(utils, 'git_install_requested')106 @patch.object(utils, 'git_install_requested')
104 @patch.object(unison, 'ensure_user')107 @patch.object(unison, 'ensure_user')
105 def test_install_hook_git(self, ensure_user, git_requested):108 def test_install_hook_git(self, ensure_user, git_requested, os_release):
106 git_requested.return_value = True109 git_requested.return_value = True
107 repo = 'cloud:trusty-juno'110 repo = 'cloud:trusty-juno'
108 openstack_origin_git = {111 openstack_origin_git = {
@@ -135,6 +138,7 @@
135138
136 mod_ch_openstack_utils = 'charmhelpers.contrib.openstack.utils'139 mod_ch_openstack_utils = 'charmhelpers.contrib.openstack.utils'
137140
141 @patch.object(utils, 'os_release')
138 @patch.object(hooks, 'config')142 @patch.object(hooks, 'config')
139 @patch('%s.config' % (mod_ch_openstack_utils))143 @patch('%s.config' % (mod_ch_openstack_utils))
140 @patch('%s.relation_set' % (mod_ch_openstack_utils))144 @patch('%s.relation_set' % (mod_ch_openstack_utils))
@@ -143,7 +147,7 @@
143 @patch('%s.sync_db_with_multi_ipv6_addresses' % (mod_ch_openstack_utils))147 @patch('%s.sync_db_with_multi_ipv6_addresses' % (mod_ch_openstack_utils))
144 def test_db_joined(self, mock_sync_db_with_multi, mock_get_ipv6_addr,148 def test_db_joined(self, mock_sync_db_with_multi, mock_get_ipv6_addr,
145 mock_relation_ids, mock_relation_set, mock_config,149 mock_relation_ids, mock_relation_set, mock_config,
146 mock_hooks_config):150 mock_hooks_config, os_release):
147151
148 cfg_dict = {'prefer-ipv6': False,152 cfg_dict = {'prefer-ipv6': False,
149 'database': 'keystone',153 'database': 'keystone',
@@ -317,6 +321,7 @@
317 mock_ensure_ssl_cert_master, mock_log,321 mock_ensure_ssl_cert_master, mock_log,
318 mock_peer_store, mock_peer_retrieve,322 mock_peer_store, mock_peer_retrieve,
319 mock_relation_ids):323 mock_relation_ids):
324 self.os_release.return_value = 'kilo'
320 mock_relation_ids.return_value = ['peer/0']325 mock_relation_ids.return_value = ['peer/0']
321326
322 peer_settings = {}327 peer_settings = {}
@@ -907,6 +912,7 @@
907 cmd = ['a2dissite', 'openstack_https_frontend']912 cmd = ['a2dissite', 'openstack_https_frontend']
908 self.check_call.assert_called_with(cmd)913 self.check_call.assert_called_with(cmd)
909914
915 @patch.object(utils, 'os_release')
910 @patch.object(utils, 'git_install_requested')916 @patch.object(utils, 'git_install_requested')
911 @patch.object(hooks, 'is_db_ready')917 @patch.object(hooks, 'is_db_ready')
912 @patch.object(hooks, 'is_db_initialised')918 @patch.object(hooks, 'is_db_initialised')
@@ -926,7 +932,8 @@
926 mock_log,932 mock_log,
927 mock_is_db_initialised,933 mock_is_db_initialised,
928 mock_is_db_ready,934 mock_is_db_ready,
929 git_requested):935 git_requested,
936 os_release):
930 mock_is_db_initialised.return_value = True937 mock_is_db_initialised.return_value = True
931 mock_is_db_ready.return_value = True938 mock_is_db_ready.return_value = True
932 mock_is_elected_leader.return_value = False939 mock_is_elected_leader.return_value = False
@@ -949,6 +956,7 @@
949 'Firing identity_changed hook for all related services.')956 'Firing identity_changed hook for all related services.')
950 self.assertTrue(self.ensure_initial_admin.called)957 self.assertTrue(self.ensure_initial_admin.called)
951958
959 @patch.object(utils, 'os_release')
952 @patch.object(utils, 'git_install_requested')960 @patch.object(utils, 'git_install_requested')
953 @patch('keystone_utils.log')961 @patch('keystone_utils.log')
954 @patch('keystone_utils.relation_ids')962 @patch('keystone_utils.relation_ids')
@@ -959,7 +967,8 @@
959 mock_update_hash_from_path,967 mock_update_hash_from_path,
960 mock_ensure_ssl_cert_master,968 mock_ensure_ssl_cert_master,
961 mock_relation_ids,969 mock_relation_ids,
962 mock_log, git_requested):970 mock_log, git_requested,
971 os_release):
963 mock_relation_ids.return_value = []972 mock_relation_ids.return_value = []
964 mock_ensure_ssl_cert_master.return_value = False973 mock_ensure_ssl_cert_master.return_value = False
965 # Ensure always returns diff974 # Ensure always returns diff
966975
=== modified file 'unit_tests/test_keystone_utils.py'
--- unit_tests/test_keystone_utils.py 2016-02-19 14:49:59 +0000
+++ unit_tests/test_keystone_utils.py 2016-03-05 15:43:48 +0000
@@ -1,7 +1,6 @@
1from mock import patch, call, MagicMock, Mock1from mock import patch, call, MagicMock, Mock
2from test_utils import CharmTestCase2from test_utils import CharmTestCase
3import os3import os
4import manager
54
6os.environ['JUJU_UNIT_NAME'] = 'keystone'5os.environ['JUJU_UNIT_NAME'] = 'keystone'
7with patch('charmhelpers.core.hookenv.config') as config:6with patch('charmhelpers.core.hookenv.config') as config:
@@ -172,10 +171,11 @@
172 self.subprocess.check_output.assert_called_with(cmd)171 self.subprocess.check_output.assert_called_with(cmd)
173 self.service_start.assert_called_with('keystone')172 self.service_start.assert_called_with('keystone')
174173
174 @patch.object(utils, 'get_manager')
175 @patch.object(utils, 'resolve_address')175 @patch.object(utils, 'resolve_address')
176 @patch.object(utils, 'b64encode')176 @patch.object(utils, 'b64encode')
177 def test_add_service_to_keystone_clustered_https_none_values(177 def test_add_service_to_keystone_clustered_https_none_values(
178 self, b64encode, _resolve_address):178 self, b64encode, _resolve_address, _get_manager):
179 relation_id = 'identity-service:0'179 relation_id = 'identity-service:0'
180 remote_unit = 'unit/0'180 remote_unit = 'unit/0'
181 _resolve_address.return_value = '10.10.10.10'181 _resolve_address.return_value = '10.10.10.10'
@@ -214,7 +214,7 @@
214 @patch.object(utils, 'resolve_address')214 @patch.object(utils, 'resolve_address')
215 @patch.object(utils, 'ensure_valid_service')215 @patch.object(utils, 'ensure_valid_service')
216 @patch.object(utils, 'add_endpoint')216 @patch.object(utils, 'add_endpoint')
217 @patch.object(manager, 'KeystoneManager')217 @patch.object(utils, 'get_manager')
218 def test_add_service_to_keystone_no_clustered_no_https_complete_values(218 def test_add_service_to_keystone_no_clustered_no_https_complete_values(
219 self, KeystoneManager, add_endpoint, ensure_valid_service,219 self, KeystoneManager, add_endpoint, ensure_valid_service,
220 _resolve_address):220 _resolve_address):
@@ -253,9 +253,12 @@
253 internalurl='192.168.1.2')253 internalurl='192.168.1.2')
254 self.assertTrue(self.get_admin_token.called)254 self.assertTrue(self.get_admin_token.called)
255 self.get_service_password.assert_called_with('keystone')255 self.get_service_password.assert_called_with('keystone')
256 self.create_user.assert_called_with('keystone', 'password', 'tenant')256 self.create_user.assert_called_with('keystone', 'password', 'tenant',
257 self.grant_role.assert_called_with('keystone', 'admin', 'tenant')257 None)
258 self.create_role.assert_called_with('role1', 'keystone', 'tenant')258 self.grant_role.assert_called_with('keystone', 'Admin', 'tenant',
259 'default')
260 self.create_role.assert_called_with('role1', 'keystone', 'tenant',
261 None)
259262
260 relation_data = {'auth_host': '10.0.0.3', 'service_host': '10.0.0.3',263 relation_data = {'auth_host': '10.0.0.3', 'service_host': '10.0.0.3',
261 'admin_token': 'token', 'service_port': 81,264 'admin_token': 'token', 'service_port': 81,
@@ -266,7 +269,7 @@
266 'ssl_cert': '__null__', 'ssl_key': '__null__',269 'ssl_cert': '__null__', 'ssl_key': '__null__',
267 'ca_cert': '__null__',270 'ca_cert': '__null__',
268 'auth_protocol': 'http', 'service_protocol': 'http',271 'auth_protocol': 'http', 'service_protocol': 'http',
269 'service_tenant_id': 'tenant_id'}272 'service_tenant_id': 'tenant_id', 'api_version': 2}
270273
271 filtered = {}274 filtered = {}
272 for k, v in relation_data.iteritems():275 for k, v in relation_data.iteritems():
@@ -284,7 +287,7 @@
284 @patch('charmhelpers.contrib.openstack.ip.config')287 @patch('charmhelpers.contrib.openstack.ip.config')
285 @patch.object(utils, 'ensure_valid_service')288 @patch.object(utils, 'ensure_valid_service')
286 @patch.object(utils, 'add_endpoint')289 @patch.object(utils, 'add_endpoint')
287 @patch.object(manager, 'KeystoneManager')290 @patch.object(utils, 'get_manager')
288 def test_add_service_to_keystone_nosubset(291 def test_add_service_to_keystone_nosubset(
289 self, KeystoneManager, add_endpoint, ensure_valid_service,292 self, KeystoneManager, add_endpoint, ensure_valid_service,
290 ip_config):293 ip_config):
@@ -317,8 +320,9 @@
317 mock_grant_role,320 mock_grant_role,
318 mock_user_exists):321 mock_user_exists):
319 mock_user_exists.return_value = False322 mock_user_exists.return_value = False
320 utils.create_user_credentials('userA', 'tenantA', 'passA')323 utils.create_user_credentials('userA', 'passA', tenant='tenantA')
321 mock_create_user.assert_has_calls([call('userA', 'passA', 'tenantA')])324 mock_create_user.assert_has_calls([call('userA', 'passA', 'tenantA',
325 None)])
322 mock_create_role.assert_has_calls([])326 mock_create_role.assert_has_calls([])
323 mock_grant_role.assert_has_calls([])327 mock_grant_role.assert_has_calls([])
324328
@@ -329,11 +333,14 @@
329 def test_create_user_credentials(self, mock_create_user, mock_create_role,333 def test_create_user_credentials(self, mock_create_user, mock_create_role,
330 mock_grant_role, mock_user_exists):334 mock_grant_role, mock_user_exists):
331 mock_user_exists.return_value = False335 mock_user_exists.return_value = False
332 utils.create_user_credentials('userA', 'tenantA', 'passA',336 utils.create_user_credentials('userA', 'passA', tenant='tenantA',
333 grants=['roleA'], new_roles=['roleB'])337 grants=['roleA'], new_roles=['roleB'])
334 mock_create_user.assert_has_calls([call('userA', 'passA', 'tenantA')])338 mock_create_user.assert_has_calls([call('userA', 'passA', 'tenantA',
335 mock_create_role.assert_has_calls([call('roleB', 'userA', 'tenantA')])339 None)])
336 mock_grant_role.assert_has_calls([call('userA', 'roleA', 'tenantA')])340 mock_create_role.assert_has_calls([call('roleB', 'userA', 'tenantA',
341 None)])
342 mock_grant_role.assert_has_calls([call('userA', 'roleA', 'tenantA',
343 None)])
337344
338 @patch.object(utils, 'update_user_password')345 @patch.object(utils, 'update_user_password')
339 @patch.object(utils, 'user_exists')346 @patch.object(utils, 'user_exists')
@@ -346,11 +353,13 @@
346 mock_user_exists,353 mock_user_exists,
347 mock_update_user_password):354 mock_update_user_password):
348 mock_user_exists.return_value = True355 mock_user_exists.return_value = True
349 utils.create_user_credentials('userA', 'tenantA', 'passA',356 utils.create_user_credentials('userA', 'passA', tenant='tenantA',
350 grants=['roleA'], new_roles=['roleB'])357 grants=['roleA'], new_roles=['roleB'])
351 mock_create_user.assert_has_calls([])358 mock_create_user.assert_has_calls([])
352 mock_create_role.assert_has_calls([call('roleB', 'userA', 'tenantA')])359 mock_create_role.assert_has_calls([call('roleB', 'userA', 'tenantA',
353 mock_grant_role.assert_has_calls([call('userA', 'roleA', 'tenantA')])360 None)])
361 mock_grant_role.assert_has_calls([call('userA', 'roleA', 'tenantA',
362 None)])
354 mock_update_user_password.assert_has_calls([call('userA', 'passA')])363 mock_update_user_password.assert_has_calls([call('userA', 'passA')])
355364
356 @patch.object(utils, 'get_service_password')365 @patch.object(utils, 'get_service_password')
@@ -358,10 +367,12 @@
358 def test_create_service_credentials(self, mock_create_user_credentials,367 def test_create_service_credentials(self, mock_create_user_credentials,
359 mock_get_service_password):368 mock_get_service_password):
360 mock_get_service_password.return_value = 'passA'369 mock_get_service_password.return_value = 'passA'
361 cfg = {'service-tenant': 'tenantA', 'admin-role': 'Admin'}370 cfg = {'service-tenant': 'tenantA', 'admin-role': 'Admin',
371 'preferred-api-version': 2}
362 self.config.side_effect = lambda key: cfg.get(key, None)372 self.config.side_effect = lambda key: cfg.get(key, None)
363 calls = [call('serviceA', 'tenantA', 'passA', grants=['Admin'],373 calls = [call('serviceA', 'passA', domain=None, grants=['Admin'],
364 new_roles=None)]374 new_roles=None, tenant='tenantA')]
375
365 utils.create_service_credentials('serviceA')376 utils.create_service_credentials('serviceA')
366 mock_create_user_credentials.assert_has_calls(calls)377 mock_create_user_credentials.assert_has_calls(calls)
367378
@@ -594,7 +605,8 @@
594 internal_ip='10.0.0.1',605 internal_ip='10.0.0.1',
595 admin_ip='10.0.0.1',606 admin_ip='10.0.0.1',
596 auth_port=35357,607 auth_port=35357,
597 region='RegionOne'608 region='RegionOne',
609 api_version=2,
598 )610 )
599611
600 @patch.object(utils, 'peer_units')612 @patch.object(utils, 'peer_units')
@@ -704,21 +716,21 @@
704 self.assertEquals(render.call_args_list, expected)716 self.assertEquals(render.call_args_list, expected)
705 service_restart.assert_called_with('keystone')717 service_restart.assert_called_with('keystone')
706718
707 @patch.object(manager, 'KeystoneManager')719 @patch.object(utils, 'get_manager')
708 def test_is_service_present(self, KeystoneManager):720 def test_is_service_present(self, KeystoneManager):
709 mock_keystone = MagicMock()721 mock_keystone = MagicMock()
710 mock_keystone.resolve_service_id.return_value = 'sid1'722 mock_keystone.resolve_service_id.return_value = 'sid1'
711 KeystoneManager.return_value = mock_keystone723 KeystoneManager.return_value = mock_keystone
712 self.assertTrue(utils.is_service_present('bob', 'bill'))724 self.assertTrue(utils.is_service_present('bob', 'bill'))
713725
714 @patch.object(manager, 'KeystoneManager')726 @patch.object(utils, 'get_manager')
715 def test_is_service_present_false(self, KeystoneManager):727 def test_is_service_present_false(self, KeystoneManager):
716 mock_keystone = MagicMock()728 mock_keystone = MagicMock()
717 mock_keystone.resolve_service_id.return_value = None729 mock_keystone.resolve_service_id.return_value = None
718 KeystoneManager.return_value = mock_keystone730 KeystoneManager.return_value = mock_keystone
719 self.assertFalse(utils.is_service_present('bob', 'bill'))731 self.assertFalse(utils.is_service_present('bob', 'bill'))
720732
721 @patch.object(manager, 'KeystoneManager')733 @patch.object(utils, 'get_manager')
722 def test_delete_service_entry(self, KeystoneManager):734 def test_delete_service_entry(self, KeystoneManager):
723 mock_keystone = MagicMock()735 mock_keystone = MagicMock()
724 mock_keystone.resolve_service_id.return_value = 'sid1'736 mock_keystone.resolve_service_id.return_value = 'sid1'

Subscribers

People subscribed via source and target branches