Change to use IPA client instead of cmd line invocations
This will end up using the creds of the nova user - which is probably what we want to end up using in any case.
This commit is contained in:
parent
547b1ead85
commit
4df347c612
|
@ -41,3 +41,14 @@ class NovajoinTempestPlugin(plugins.TempestPlugin):
|
|||
def get_opt_lists(self):
|
||||
return [('service_available', [project_config.service_option]),
|
||||
(project_config.ipa_group.name, project_config.IpaGroup)]
|
||||
|
||||
def get_service_clients(self):
|
||||
params = {
|
||||
'name': 'ipa_v4',
|
||||
'service_version': 'ipa.v4',
|
||||
'module_path': 'novajoin_tempest_plugin.services.ipa',
|
||||
'client_names': [
|
||||
'IPAClient',
|
||||
],
|
||||
}
|
||||
return [params]
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
# Copyright (c) 2016 Red Hat
|
||||
#
|
||||
# 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 novajoin_tempest_plugin.services.ipa.ipa_client import IPAClient
|
||||
|
||||
__all__ = [
|
||||
'IPAClient'
|
||||
]
|
|
@ -0,0 +1,198 @@
|
|||
# Copyright 2017 Red Hat
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import os
|
||||
import time
|
||||
import uuid
|
||||
|
||||
try:
|
||||
from gssapi.exceptions import GSSError
|
||||
from ipalib import api
|
||||
from ipalib import errors
|
||||
from ipapython.ipautil import kinit_keytab
|
||||
ipalib_imported = True
|
||||
except ImportError:
|
||||
# ipalib/ipapython are not available in PyPy yet, don't make it
|
||||
# a showstopper for the tests.
|
||||
ipalib_imported = False
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from six.moves.configparser import SafeConfigParser
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IPABase(object):
|
||||
|
||||
def __init__(self, backoff=0):
|
||||
try:
|
||||
self.ntries = CONF.connect_retries
|
||||
except cfg.NoSuchOptError:
|
||||
self.ntries = 1
|
||||
if not ipalib_imported:
|
||||
return
|
||||
self.ccache = "MEMORY:" + str(uuid.uuid4())
|
||||
os.environ['KRB5CCNAME'] = self.ccache
|
||||
if self._ipa_client_configured() and not api.isdone('finalize'):
|
||||
(hostname, realm) = self.get_host_and_realm()
|
||||
kinit_keytab(str('nova/%s@%s' % (hostname, realm)),
|
||||
CONF.keytab, self.ccache)
|
||||
api.bootstrap(context='novajoin')
|
||||
api.finalize()
|
||||
self.batch_args = list()
|
||||
self.backoff = backoff
|
||||
|
||||
def get_host_and_realm(self):
|
||||
"""Return the hostname and IPA realm name.
|
||||
|
||||
IPA 4.4 introduced the requirement that the schema be
|
||||
fetched when calling finalize(). This is really only used by
|
||||
the ipa command-line tool but for now it is baked in.
|
||||
So we have to get a TGT first but need the hostname and
|
||||
realm. For now directly read the IPA config file which is
|
||||
in INI format and pull those two values out and return as
|
||||
a tuple.
|
||||
"""
|
||||
config = SafeConfigParser()
|
||||
config.read('/etc/ipa/default.conf')
|
||||
hostname = config.get('global', 'host')
|
||||
realm = config.get('global', 'realm')
|
||||
|
||||
return hostname, realm
|
||||
|
||||
def __backoff(self):
|
||||
LOG.debug("Backing off %s seconds", self.backoff)
|
||||
time.sleep(self.backoff)
|
||||
if self.backoff < 1024:
|
||||
self.backoff = self.backoff * 2
|
||||
|
||||
def __get_connection(self):
|
||||
"""Make a connection to IPA or raise an error."""
|
||||
tries = 0
|
||||
|
||||
while (tries <= self.ntries) or (self.backoff > 0):
|
||||
if self.backoff == 0:
|
||||
LOG.debug("Attempt %d of %d", tries, self.ntries)
|
||||
if api.Backend.rpcclient.isconnected():
|
||||
api.Backend.rpcclient.disconnect()
|
||||
try:
|
||||
api.Backend.rpcclient.connect()
|
||||
# ping to force an actual connection in case there is only one
|
||||
# IPA master
|
||||
api.Command[u'ping']()
|
||||
except (errors.CCacheError,
|
||||
errors.TicketExpired,
|
||||
errors.KerberosError) as e:
|
||||
LOG.debug("kinit again: %s", e)
|
||||
# pylint: disable=no-member
|
||||
try:
|
||||
kinit_keytab(str('nova/%s@%s' %
|
||||
(api.env.host, api.env.realm)),
|
||||
CONF.keytab,
|
||||
self.ccache)
|
||||
except GSSError as e:
|
||||
LOG.debug("kinit failed: %s", e)
|
||||
if tries > 0 and self.backoff:
|
||||
self.__backoff()
|
||||
tries += 1
|
||||
except errors.NetworkError:
|
||||
tries += 1
|
||||
if self.backoff:
|
||||
self.__backoff()
|
||||
else:
|
||||
return
|
||||
|
||||
def _call_ipa(self, command, *args, **kw):
|
||||
"""Make an IPA call."""
|
||||
if not api.Backend.rpcclient.isconnected():
|
||||
self.__get_connection()
|
||||
if 'version' not in kw:
|
||||
kw['version'] = u'2.146' # IPA v4.2.0 for compatibility
|
||||
|
||||
while True:
|
||||
try:
|
||||
result = api.Command[command](*args, **kw)
|
||||
LOG.debug(result)
|
||||
return result
|
||||
except (errors.CCacheError,
|
||||
errors.TicketExpired,
|
||||
errors.KerberosError):
|
||||
LOG.debug("Refresh authentication")
|
||||
self.__get_connection()
|
||||
except errors.NetworkError:
|
||||
if self.backoff:
|
||||
self.__backoff()
|
||||
else:
|
||||
raise
|
||||
|
||||
def _ipa_client_configured(self):
|
||||
"""Determine if the machine is an enrolled IPA client.
|
||||
|
||||
Return boolean indicating whether this machine is enrolled
|
||||
in IPA. This is a rather weak detection method but better
|
||||
than nothing.
|
||||
"""
|
||||
return os.path.exists('/etc/ipa/default.conf')
|
||||
|
||||
|
||||
class IPAClient(IPABase):
|
||||
|
||||
def find_host(self, hostname):
|
||||
params = [hostname]
|
||||
return self._call_ipa('host_find', *params)
|
||||
|
||||
def show_host(self, hostname):
|
||||
params = [hostname]
|
||||
return self._call_ipa('host-show', *params)
|
||||
|
||||
def find_service(self, service_principal):
|
||||
params = [service_principal]
|
||||
service_args = {}
|
||||
return self._call_ipa('service_find', *params, **service_args)
|
||||
|
||||
def show_service(self, service_principal):
|
||||
params = [service_principal]
|
||||
service_args = {}
|
||||
return self._call_ipa('service_show', *params, **service_args)
|
||||
|
||||
def service_managed_by_host(self, service_principal, host):
|
||||
"""Return True if service is managed by specified host"""
|
||||
params = [service_principal]
|
||||
service_args = {}
|
||||
try:
|
||||
result = self._call_ipa('service_show', *params, **service_args)
|
||||
except errors.NotFound:
|
||||
raise KeyError
|
||||
serviceresult = result['result']
|
||||
|
||||
for candidate in serviceresult.get('managedby_host', []):
|
||||
if candidate == host:
|
||||
return True
|
||||
return False
|
||||
|
||||
def host_has_services(self, service_host):
|
||||
"""Return True if this host manages any services"""
|
||||
LOG.debug('Checking if host ' + service_host + ' has services')
|
||||
params = []
|
||||
service_args = {'man_by_host': service_host}
|
||||
result = self._call_ipa('service_find', *params, **service_args)
|
||||
return result['count'] > 0
|
||||
|
||||
def show_cert(self, serial_number):
|
||||
params = [serial_number]
|
||||
return self._call_ipa('cert_show', *params)
|
|
@ -12,13 +12,13 @@
|
|||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import os
|
||||
|
||||
from oslo_log import log as logging
|
||||
from tempest import config
|
||||
|
||||
from tempest import clients
|
||||
from tempest.scenario import manager as mgr
|
||||
from tempest.lib.common import ssh
|
||||
|
||||
CONF = config.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
@ -30,55 +30,42 @@ class NovajoinScenarioTest(mgr.ScenarioTest):
|
|||
|
||||
def setUp(self):
|
||||
super(NovajoinScenarioTest, self).setUp()
|
||||
ssh_host = CONF.tripleo.undercloud_hostname
|
||||
ssh_user = CONF.stress.target_ssh_user
|
||||
ssh_key = CONF.stress.target_private_key_path
|
||||
self.ssh_client = ssh.Client(ssh_host, ssh_user, key_filename=ssh_key)
|
||||
|
||||
@classmethod
|
||||
def skip_checks(cls):
|
||||
super(NovajoinScenarioTest, cls).skip_checks()
|
||||
|
||||
cmd = 'source ~/stackrc;openstack service list | grep novajoin'
|
||||
novajoin_enabled = ssh_client.exec_command(cmd)
|
||||
if not novajoin_enabled:
|
||||
if not CONF.service_available.novajoin:
|
||||
raise cls.skipException("Novajoin is not enabled")
|
||||
|
||||
@classmethod
|
||||
def setup_clients(cls):
|
||||
super(NovajoinScenarioTest, cls).setup_clients()
|
||||
# os = getattr(cls, 'os_%s' % cls.credentials[0])
|
||||
# os_adm = getattr(cls, 'os_%s' % cls.credentials[1])
|
||||
# set up ipa client
|
||||
cls.ipa_client = os.ipa_v4.IPAClient
|
||||
|
||||
def verify_host_registered_with_ipa(self, host):
|
||||
# check if specified host is registered with ipa
|
||||
# basically doing a host-show
|
||||
|
||||
cmd = 'ipa host-show {hostname}'.format(hostname=host)
|
||||
result = self.ssh_client.exec_command(cmd)
|
||||
if host in result:
|
||||
result = self.ipa_client.find_host(host)
|
||||
if result['count'] > 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
def verify_host_has_keytab(self, host):
|
||||
# check if specified host entry has a keytab
|
||||
|
||||
cmd = 'ipa host-show {hostname} | grep Keytab'.format(hostname=host)
|
||||
result = self.ssh_client.exec_command(cmd)
|
||||
if 'True' in result:
|
||||
result = self.ipa_client.show_host(host)['result']
|
||||
keytab_present = result['Keytab']
|
||||
if 'True' in keytab_present:
|
||||
return True
|
||||
return False
|
||||
|
||||
def verify_service_exists(self, service, host):
|
||||
# verify service exists for host on ipa server
|
||||
# needed for the triple-O tests
|
||||
|
||||
cmd = 'ipa service-show {servicename}/{hostname}'.format(
|
||||
service_principal = '{servicename}/{hostname}'.format(
|
||||
servicename=service, hostname=host
|
||||
)
|
||||
result = self.ssh_client.exec_command(cmd)
|
||||
if service in result:
|
||||
result = self.ipa_client.find_service(service_principal)
|
||||
if result['count'] > 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
@ -110,10 +97,8 @@ class NovajoinScenarioTest(mgr.ScenarioTest):
|
|||
|
||||
def verify_cert_revoked(self, serial):
|
||||
# verify that the given certificate has been revoked
|
||||
cmd = 'ipa cert-show {serial} |grep Revoked'.format(
|
||||
serial=serial
|
||||
)
|
||||
result = self.ssh_client.exec_command(cmd)
|
||||
if 'True' in result:
|
||||
result = self.ipa_client.show_cert(serial)['result']
|
||||
is_revoked = result['Revoked']
|
||||
if 'True' in is_revoked:
|
||||
return True
|
||||
return False
|
||||
|
|
Loading…
Reference in New Issue