Merge "Implement swauth"

This commit is contained in:
Jenkins 2016-09-23 13:56:23 +00:00 committed by Gerrit Code Review
commit 95ff2df40b
14 changed files with 247 additions and 14 deletions

View File

@ -1,3 +1,21 @@
add-user:
description: |
Add a user to swauth.
This adds a given user / pass to swauth. Auth-type must be set to swauth.
params:
account:
type: string
description: account to add this user to
username:
type: string
description: username for the newly created user
password:
type: string
description: password for the newly created user
required:
- account
- username
- password
pause: pause:
description: | description: |
Pause swift-proxy services. Pause swift-proxy services.

1
actions/add-user Symbolic link
View File

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

70
actions/add_user.py Executable file
View File

@ -0,0 +1,70 @@
#!/usr/bin/python
#
# Copyright 2016 Canonical Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from subprocess import (
check_call,
CalledProcessError
)
from charmhelpers.core.hookenv import (
action_get,
config,
action_set,
action_fail,
leader_get,
log,
)
from lib.swift_utils import (
try_initialize_swauth,
)
from charmhelpers.contrib.hahelpers.cluster import (
determine_api_port,
)
def add_user():
"""Add a swauth user to swift."""
if config('auth-type') == 'swauth':
try_initialize_swauth()
account = action_get('account')
username = action_get('username')
password = action_get('password')
bind_port = config('bind-port')
bind_port = determine_api_port(bind_port, singlenode_mode=True)
success = True
try:
check_call([
"swauth-add-user",
"-A", "http://localhost:{}/auth/".format(bind_port),
"-K", leader_get('swauth-admin-key'),
"-a", account, username, password])
except CalledProcessError as e:
success = False
log("Has a problem adding user: {}".format(e.output))
action_fail(
"Adding user {} failed with: \"{}\""
.format(username, e.message))
if success:
message = "Successfully added the user {}".format(username)
action_set({
'add-user.result': 'Success',
'add-user.message': message,
})
if __name__ == '__main__':
add_user()

View File

@ -110,7 +110,11 @@ options:
auth-type: auth-type:
default: tempauth default: tempauth
type: string type: string
description: Auth method to use, tempauth or keystone description: Auth method to use, tempauth, swauth or keystone
swauth-admin-key:
default:
type: string
description: The secret key to use to authenticate as an swauth admin
delay-auth-decision: delay-auth-decision:
default: true default: true
type: boolean type: boolean

View File

@ -52,6 +52,7 @@ from lib.swift_utils import (
is_most_recent_timestamp, is_most_recent_timestamp,
timestamps_available, timestamps_available,
assess_status, assess_status,
try_initialize_swauth,
) )
import charmhelpers.contrib.openstack.utils as openstack import charmhelpers.contrib.openstack.utils as openstack
@ -62,11 +63,9 @@ from charmhelpers.contrib.openstack.ha.utils import (
from charmhelpers.contrib.hahelpers.cluster import ( from charmhelpers.contrib.hahelpers.cluster import (
get_hacluster_config, get_hacluster_config,
)
from charmhelpers.contrib.hahelpers.cluster import (
is_elected_leader, is_elected_leader,
) )
from charmhelpers.core.hookenv import ( from charmhelpers.core.hookenv import (
config, config,
local_unit, local_unit,
@ -183,6 +182,7 @@ def config_changed():
for r_id in relation_ids('object-store'): for r_id in relation_ids('object-store'):
object_store_joined(relation_id=r_id) object_store_joined(relation_id=r_id)
try_initialize_swauth()
@hooks.hook('identity-service-relation-joined') @hooks.hook('identity-service-relation-joined')
@ -233,6 +233,7 @@ def storage_joined():
# possibility of storage nodes getting out-of-date rings by deprecating # possibility of storage nodes getting out-of-date rings by deprecating
# any existing ones from the www dir. # any existing ones from the www dir.
mark_www_rings_deleted() mark_www_rings_deleted()
try_initialize_swauth()
def get_host_ip(rid=None, unit=None): def get_host_ip(rid=None, unit=None):

View File

@ -9,6 +9,7 @@ from charmhelpers.core.hookenv import (
relation_get, relation_get,
unit_get, unit_get,
service_name, service_name,
leader_get,
) )
from charmhelpers.contrib.openstack.context import ( from charmhelpers.contrib.openstack.context import (
OSContextGenerator, OSContextGenerator,
@ -107,9 +108,13 @@ class SwiftIdentityContext(OSContextGenerator):
'delay_auth_decision': config('delay-auth-decision'), 'delay_auth_decision': config('delay-auth-decision'),
'node_timeout': config('node-timeout'), 'node_timeout': config('node-timeout'),
'recoverable_node_timeout': config('recoverable-node-timeout'), 'recoverable_node_timeout': config('recoverable-node-timeout'),
'log_headers': config('log-headers') 'log_headers': config('log-headers'),
} }
admin_key = leader_get('swauth-admin-key')
if admin_key is not None:
ctxt['swauth_admin_key'] = admin_key
if config('debug'): if config('debug'):
ctxt['log_level'] = 'DEBUG' ctxt['log_level'] = 'DEBUG'
else: else:
@ -126,6 +131,8 @@ class SwiftIdentityContext(OSContextGenerator):
ctxt['ssl'] = False ctxt['ssl'] = False
auth_type = config('auth-type') auth_type = config('auth-type')
ctxt['auth_type'] = auth_type
auth_host = config('keystone-auth-host') auth_host = config('keystone-auth-host')
admin_user = config('keystone-admin-user') admin_user = config('keystone-admin-user')
admin_password = config('keystone-admin-user') admin_password = config('keystone-admin-user')

View File

@ -34,19 +34,23 @@ from charmhelpers.contrib.openstack.utils import (
from charmhelpers.contrib.hahelpers.cluster import ( from charmhelpers.contrib.hahelpers.cluster import (
is_elected_leader, is_elected_leader,
peer_units, peer_units,
determine_api_port,
) )
from charmhelpers.core.hookenv import ( from charmhelpers.core.hookenv import (
log, log,
DEBUG, DEBUG,
INFO, INFO,
WARNING, WARNING,
config,
local_unit, local_unit,
relation_get, relation_get,
unit_get, unit_get,
relation_set, relation_set,
relation_ids, relation_ids,
related_units, related_units,
config,
is_leader,
leader_set,
leader_get,
) )
from charmhelpers.fetch import ( from charmhelpers.fetch import (
apt_update, apt_update,
@ -116,7 +120,7 @@ BASE_PACKAGES = [
'python-keystone', 'python-keystone',
] ]
# > Folsom specific packages # > Folsom specific packages
FOLSOM_PACKAGES = BASE_PACKAGES + ['swift-plugin-s3'] FOLSOM_PACKAGES = BASE_PACKAGES + ['swift-plugin-s3', 'swauth']
SWIFT_HA_RES = 'grp_swift_vips' SWIFT_HA_RES = 'grp_swift_vips'
TEMPLATES = 'templates/' TEMPLATES = 'templates/'
@ -304,6 +308,29 @@ class SwiftProxyClusterRPC(object):
return rq return rq
def try_initialize_swauth():
if is_leader() and config('auth-type') == 'swauth':
if leader_get('swauth-init') is not True:
try:
admin_key = config('swauth-admin-key')
if admin_key == '' or admin_key is None:
admin_key = leader_get('swauth-admin-key')
if admin_key is None:
admin_key = uuid.uuid4()
leader_set({'swauth-admin-key': admin_key})
bind_port = config('bind-port')
bind_port = determine_api_port(bind_port, singlenode_mode=True)
subprocess.check_call([
'swauth-prep',
'-A',
'http://localhost:{}/auth'.format(bind_port),
'-K', admin_key])
leader_set({'swauth-init': True})
except subprocess.CalledProcessError:
log("had a problem initializing swauth!")
def get_first_available_value(responses, key, default=None): def get_first_available_value(responses, key, default=None):
for r in responses: for r in responses:
if key in r: if key in r:

View File

@ -12,7 +12,7 @@ key_file = {{ ssl_key }}
pipeline = healthcheck cache swift3 s3token authtoken keystone container-quotas account-quotas proxy-server pipeline = healthcheck cache swift3 s3token authtoken keystone container-quotas account-quotas proxy-server
{% else %} {% else %}
[pipeline:main] [pipeline:main]
pipeline = healthcheck cache tempauth container-quotas account-quotas proxy-server pipeline = healthcheck cache {{ auth_type }} container-quotas account-quotas proxy-server
{% endif %} {% endif %}
[app:proxy-server] [app:proxy-server]
@ -70,3 +70,11 @@ admin_token = {{ admin_token }}
[filter:swift3] [filter:swift3]
use = egg:swift3#swift3 use = egg:swift3#swift3
{% endif %} {% endif %}
{% if auth_type == 'swauth' %}
[filter:swauth]
use = egg:swauth#swauth
set log_name = swauth
super_admin_key = {{ swauth_admin_key }}
default_swift_cluster = local#https://{{ proxy_ip }}:8080/v1
{% endif %}

View File

@ -12,7 +12,7 @@ key_file = {{ ssl_key }}
pipeline = healthcheck cache swift3 authtoken keystoneauth container-quotas account-quotas proxy-server pipeline = healthcheck cache swift3 authtoken keystoneauth container-quotas account-quotas proxy-server
{% else %} {% else %}
[pipeline:main] [pipeline:main]
pipeline = healthcheck cache tempauth container-quotas account-quotas proxy-server pipeline = healthcheck cache {{ auth_type }} container-quotas account-quotas proxy-server
{% endif %} {% endif %}
[app:proxy-server] [app:proxy-server]
@ -71,3 +71,11 @@ admin_token = {{ admin_token }}
[filter:swift3] [filter:swift3]
use = egg:swift3#swift3 use = egg:swift3#swift3
{% endif %} {% endif %}
{% if auth_type == 'swauth' %}
[filter:swauth]
use = egg:swauth#swauth
set log_name = swauth
super_admin_key = {{ swauth_admin_key }}
default_swift_cluster = local#https://{{ proxy_ip }}:8080/v1
{% endif %}

View File

@ -19,7 +19,7 @@ key_file = {{ ssl_key }}
pipeline = gatekeeper healthcheck proxy-logging cache swift3 s3token container_sync bulk tempurl slo dlo formpost authtoken keystoneauth staticweb container-quotas account-quotas proxy-logging proxy-server pipeline = gatekeeper healthcheck proxy-logging cache swift3 s3token container_sync bulk tempurl slo dlo formpost authtoken keystoneauth staticweb container-quotas account-quotas proxy-logging proxy-server
{% else %} {% else %}
[pipeline:main] [pipeline:main]
pipeline = gatekeeper healthcheck proxy-logging cache container_sync bulk tempurl slo dlo formpost tempauth staticweb container-quotas account-quotas proxy-logging proxy-server pipeline = gatekeeper healthcheck proxy-logging cache container_sync bulk tempurl slo dlo formpost {{ auth_type }} staticweb container-quotas account-quotas proxy-logging proxy-server
{% endif %} {% endif %}
[app:proxy-server] [app:proxy-server]
@ -104,3 +104,11 @@ admin_token = {{ admin_token }}
[filter:swift3] [filter:swift3]
use = egg:swift3#swift3 use = egg:swift3#swift3
{% endif %} {% endif %}
{% if auth_type == 'swauth' %}
[filter:swauth]
use = egg:swauth#swauth
set log_name = swauth
super_admin_key = {{ swauth_admin_key }}
default_swift_cluster = local#https://{{ proxy_ip }}:8080/v1
{% endif %}

View File

@ -19,7 +19,7 @@ key_file = {{ ssl_key }}
pipeline = gatekeeper healthcheck proxy-logging cache swift3 s3token container_sync bulk tempurl slo dlo formpost authtoken keystoneauth staticweb container-quotas account-quotas proxy-logging proxy-server pipeline = gatekeeper healthcheck proxy-logging cache swift3 s3token container_sync bulk tempurl slo dlo formpost authtoken keystoneauth staticweb container-quotas account-quotas proxy-logging proxy-server
{% else %} {% else %}
[pipeline:main] [pipeline:main]
pipeline = gatekeeper healthcheck proxy-logging cache container_sync bulk tempurl slo dlo formpost tempauth staticweb container-quotas account-quotas proxy-logging proxy-server pipeline = gatekeeper healthcheck proxy-logging cache container_sync bulk tempurl slo dlo formpost {{ auth_type }} staticweb container-quotas account-quotas proxy-logging proxy-server
{% endif %} {% endif %}
[app:proxy-server] [app:proxy-server]
@ -106,3 +106,11 @@ auth_uri = {{ auth_protocol }}://{{ keystone_host }}:{{ auth_port }}
[filter:swift3] [filter:swift3]
use = egg:swift3#swift3 use = egg:swift3#swift3
{% endif %} {% endif %}
{% if auth_type == 'swauth' %}
[filter:swauth]
use = egg:swauth#swauth
set log_name = swauth
super_admin_key = {{ swauth_admin_key }}
default_swift_cluster = local#https://{{ proxy_ip }}:8080/v1
{% endif %}

View File

@ -15,12 +15,13 @@
import argparse import argparse
import sys import sys
import tempfile import tempfile
import subprocess
import mock import mock
import yaml import yaml
import unittest import unittest
from mock import patch, MagicMock from mock import patch, MagicMock, call
# python-apt is not installed as part of test-requirements but is imported by # python-apt is not installed as part of test-requirements but is imported by
# some charmhelpers modules so create a fake import. # some charmhelpers modules so create a fake import.
@ -32,6 +33,7 @@ with patch('charmhelpers.contrib.hardening.harden.harden') as mock_dec, \
mock_dec.side_effect = (lambda *dargs, **dkwargs: lambda f: mock_dec.side_effect = (lambda *dargs, **dkwargs: lambda f:
lambda *args, **kwargs: f(*args, **kwargs)) lambda *args, **kwargs: f(*args, **kwargs))
import actions.actions import actions.actions
import actions.add_user
class CharmTestCase(unittest.TestCase): class CharmTestCase(unittest.TestCase):
@ -202,3 +204,46 @@ class MainTestCase(CharmTestCase):
with mock.patch.dict(actions.actions.ACTIONS, {"foo": dummy_action}): with mock.patch.dict(actions.actions.ACTIONS, {"foo": dummy_action}):
actions.actions.main([]) actions.actions.main([])
self.assertEqual(dummy_calls, ["uh oh"]) self.assertEqual(dummy_calls, ["uh oh"])
class AddUserTestCase(CharmTestCase):
def setUp(self):
super(AddUserTestCase, self).setUp(
actions.add_user, ["action_get", "action_set",
"action_fail", "check_call",
"try_initialize_swauth", "config",
"determine_api_port", "leader_get"])
def test_success(self):
"""Ensure that the action_set is called on succees."""
self.config.return_value = "swauth"
self.action_get.return_value = "test"
self.determine_api_port.return_value = 8070
actions.add_user.add_user()
self.leader_get.assert_called_with("swauth-admin-key")
calls = [call("account"), call("username"), call("password")]
self.action_get.assert_has_calls(calls)
self.action_set.assert_called_once_with({
'add-user.result': 'Success',
'add-user.message': "Successfully added the user test",
})
def test_failure(self):
"""Ensure that action_fail is called on failure."""
self.config.return_value = "swauth"
self.action_get.return_value = "test"
self.determine_api_port.return_value = 8070
self.CalledProcessError = ValueError
self.check_call.side_effect = subprocess.CalledProcessError(0,
"hi",
"no")
actions.add_user.add_user()
self.leader_get.assert_called_with("swauth-admin-key")
calls = [call("account"), call("username"), call("password")]
self.action_get.assert_has_calls(calls)
self.action_set.assert_not_called()
self.action_fail.assert_called_once_with(
'Adding user test failed with: ""')

View File

@ -25,6 +25,7 @@ with mock.patch('charmhelpers.core.hookenv.config'):
class SwiftIdentityContextTest(unittest.TestCase): class SwiftIdentityContextTest(unittest.TestCase):
@mock.patch('lib.swift_context.leader_get')
@mock.patch('lib.swift_context.relation_get') @mock.patch('lib.swift_context.relation_get')
@mock.patch('lib.swift_context.related_units') @mock.patch('lib.swift_context.related_units')
@mock.patch('lib.swift_context.relation_ids') @mock.patch('lib.swift_context.relation_ids')
@ -36,7 +37,8 @@ class SwiftIdentityContextTest(unittest.TestCase):
def test_context_api_v2(self, mock_config, mock_get_host_ip, def test_context_api_v2(self, mock_config, mock_get_host_ip,
mock_unit_get, mock_determine_api_port, mock_unit_get, mock_determine_api_port,
mock_IdentityServiceContext, mock_relation_ids, mock_IdentityServiceContext, mock_relation_ids,
mock_related_units, mock_relation_get): mock_related_units, mock_relation_get,
mock_leader_get):
_relinfo = { _relinfo = {
'auth_protocol': 'http', 'auth_protocol': 'http',
'service_protocol': 'http', 'service_protocol': 'http',
@ -57,6 +59,7 @@ class SwiftIdentityContextTest(unittest.TestCase):
ctxt = swift_context.SwiftIdentityContext() ctxt = swift_context.SwiftIdentityContext()
self.assertEqual(ctxt()['api_version'], '2') self.assertEqual(ctxt()['api_version'], '2')
@mock.patch('lib.swift_context.leader_get')
@mock.patch('lib.swift_context.relation_get') @mock.patch('lib.swift_context.relation_get')
@mock.patch('lib.swift_context.related_units') @mock.patch('lib.swift_context.related_units')
@mock.patch('lib.swift_context.relation_ids') @mock.patch('lib.swift_context.relation_ids')
@ -68,7 +71,8 @@ class SwiftIdentityContextTest(unittest.TestCase):
def test_context_api_v3(self, mock_config, mock_get_host_ip, def test_context_api_v3(self, mock_config, mock_get_host_ip,
mock_unit_get, mock_determine_api_port, mock_unit_get, mock_determine_api_port,
mock_IdentityServiceContext, mock_relation_ids, mock_IdentityServiceContext, mock_relation_ids,
mock_related_units, mock_relation_get): mock_related_units, mock_relation_get,
mock_leader_get):
_relinfo = { _relinfo = {
'auth_protocol': 'http', 'auth_protocol': 'http',
'service_protocol': 'http', 'service_protocol': 'http',

View File

@ -18,6 +18,7 @@ import shutil
import tempfile import tempfile
import uuid import uuid
import unittest import unittest
import subprocess
with mock.patch('charmhelpers.core.hookenv.config'): with mock.patch('charmhelpers.core.hookenv.config'):
import lib.swift_utils as swift_utils import lib.swift_utils as swift_utils
@ -421,3 +422,26 @@ class SwiftUtilsTestCase(unittest.TestCase):
'test-config', required_interfaces, 'test-config', required_interfaces,
charm_func=swift_utils.customer_check_assess_status, charm_func=swift_utils.customer_check_assess_status,
services='s1', ports=None) services='s1', ports=None)
@mock.patch.object(swift_utils, 'leader_set')
@mock.patch.object(swift_utils, 'determine_api_port')
@mock.patch.object(swift_utils, 'is_leader')
@mock.patch.object(swift_utils, 'config')
@mock.patch.object(swift_utils, 'leader_get')
@mock.patch.object(subprocess, 'check_call')
def test_config_and_leader_get(self, check_call, leader_get, config,
is_leader, determine_api_port, leader_set):
"""Ensure that we config_get, and then leader_get."""
config.side_effect = lambda key: {
'auth-type': 'swauth',
'swauth-admin-key': None,
'bind-port': 8080}[key]
determine_api_port.return_value = 8080
is_leader.return_value = True
leader_get.return_value = "Test"
swift_utils.try_initialize_swauth()
check_call.assert_called_with(['swauth-prep',
'-A',
'http://localhost:8080/auth',
'-K',
'Test'])