Add code to deregister nodes and services from freeipa

A module has been added to delete hosts and clean up all the sub hosts
and services associated with those hosts in IPA.  It can be called
for a single host - as in the case of a scale_down operation, or for
multiple hosts - as in the case of a stack delete.  Ultimately, we
would like to add this functionality to ansible-freeipa.

Co-Authored-By: Grzegorz Grasza <xek@redhat.com>
Change-Id: Ib08f038773f8ca28d85a95921cdb522b5cd4de04
This commit is contained in:
Ade Lee 2019-12-20 13:44:38 -05:00 committed by Grzegorz Grasza
parent c9441fc968
commit c4b5452672
12 changed files with 966 additions and 1 deletions

37
bindep.txt Normal file
View File

@ -0,0 +1,37 @@
# This file facilitates OpenStack-CI package installation
# before the execution of any tests.
#
# See the following for details:
# - https://docs.openstack.org/infra/bindep/
# - https://opendev.org/opendev/bindep/
#
# Even if the role does not make use of this facility, it
# is better to have this file empty, otherwise OpenStack-CI
# will fall back to installing its default packages which
# will potentially be detrimental to the tests executed.
# The gcc compiler
gcc
# Base requirements for RPM distros
gcc-c++ [platform:rpm]
git [platform:rpm]
libffi-devel [platform:rpm]
openssl-devel [platform:rpm]
python-devel [platform:rpm !platform:rhel-8 !platform:centos-8]
python3-devel [platform:rpm !platform:rhel-7 !platform:centos-7]
PyYAML [platform:rpm !platform:rhel-8 !platform:centos-8]
python3-pyyaml [platform:rpm !platform:rhel-7 !platform:centos-7]
python3-dnf [platform:rpm !platform:rhel-7 !platform:centos-7]
# For SELinux
libselinux-python [platform:rpm !platform:rhel-8 !platform:centos-8]
libsemanage-python [platform:redhat !platform:rhel-8 !platform:centos-8]
libselinux-python3 [platform:rpm !platform:rhel-7 !platform:centos-7]
libsemanage-python3 [platform:redhat !platform:rhel-7 !platform:centos-7]
# Required for compressing collected log files in CI
gzip
# Required to build language docs
gettext

2
requirements.txt Normal file
View File

@ -0,0 +1,2 @@
six>=1.10.0 # MIT
PyYAML>=3.12 # MIT

View File

@ -10,6 +10,7 @@ install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/
passenv = *
sitepackages = True
deps =
-r {toxinidir}/requirements.txt
-r {toxinidir}/ansible-requirements.txt
-r {toxinidir}/test-requirements.txt
commands = stestr run {posargs}
@ -19,6 +20,7 @@ whitelist_externals =
[testenv:molecule]
setenv =
ANSIBLE_FILTER_PLUGINS={toxinidir}/tripleo_ipa/ansible_plugins/filter
ANSIBLE_LIBRARY={toxinidir}/tripleo_ipa/roles.galaxy/config_template/library:{toxinidir}/tripleo_ipa/ansible_plugins/modules
ANSIBLE_ROLES_PATH={toxinidir}/tripleo_ipa/roles.galaxy:{toxinidir}/tripleo_ipa/roles
deps =
-r {toxinidir}/molecule-requirements.txt

View File

@ -0,0 +1,403 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright 2020 Red Hat, Inc.
#
# 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 __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import logging
import os
import time
import uuid
import yaml
import six
from six.moves import http_client
from gssapi.exceptions import GSSError
from ipalib import api
from ipalib import errors
try:
from ipapython.ipautil import kinit_keytab
except ImportError:
# The import moved in freeIPA 4.5.0
from ipalib.install.kinit import kinit_keytab
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.openstack import openstack_full_argument_spec
from ansible.module_utils.openstack import openstack_module_kwargs
ANSIBLE_METADATA = {
'metadata_version': '1.0',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = '''
---
module: cleanup_ipa_services
short_description: Cleanup IPA Services and Hosts
version_added: "2.8"
description:
- When hosts are deleted, delete the hosts, subhosts and services
associated with the hosts in the FreeIPA server.
- If the services are managed exclusively by the hosts, then
delete the subhost for that service and the service itself.
- If the service is managed by other hosts (not being deleted),
then simply remove the host(s) being deleted from the managed_by
attribute.
options:
principal:
description:
- Principal to use when authenticating to FreeIPA.
type: str
keytab:
description:
- Keytab to use when authenticating to FreeIPA
type: str
hosts:
description:
- Hosts to be deleted (list of FQDNs)
type: list
author:
- Ade Lee (@vakwetu)
'''
EXAMPLES = '''
- name: Cleanup IPA hosts and services
cleanup_ipa_services:
principal: user/my_host@REALM
keytab: /etc/krb5.keytab
hosts:
- test-server-0.exmaple.com
- test-server-1.example.com
- test-server-2.example.com
'''
class IPAClient(object):
def __init__(self, keytab, principal):
self.ntries = 5
self.retry_delay = 2
self.keytab = keytab
self.principal = principal
if self._ipa_client_configured() and not api.isdone('finalize'):
self.ccache = "MEMORY:" + str(uuid.uuid4())
os.environ['KRB5CCNAME'] = self.ccache
kinit_keytab(self.principal, self.keytab, self.ccache)
api.bootstrap(context='cleanup')
api.finalize()
else:
self.ccache = os.environ['KRB5CCNAME']
self.batch_args = list()
def split_principal(self, principal):
"""Split a principal into its components. Copied from IPA 4.0.0"""
service = hostname = realm = None
# Break down the principal into its component parts, which may or
# may not include the realm.
sp = principal.split('/')
if len(sp) != 2:
raise errors.MalformedServicePrincipal(reason='missing service')
service = sp[0]
if len(service) == 0:
raise errors.MalformedServicePrincipal(reason='blank service')
sr = sp[1].split('@')
if len(sr) > 2:
raise errors.MalformedServicePrincipal(
reason='unable to determine realm')
hostname = sr[0].lower()
if len(sr) == 2:
realm = sr[1].upper()
# At some point we'll support multiple realms
if realm != api.env.realm:
raise errors.RealmMismatch()
else:
realm = api.env.realm
# Note that realm may be None.
return (service, hostname, realm)
def split_hostname(self, hostname):
"""Split a hostname into its host and domain parts"""
parts = hostname.split('.')
domain = six.text_type('.'.join(parts[1:]) + '.')
return (parts[0], domain)
def __get_connection(self):
"""Make a connection to IPA or raise an error."""
tries = 0
while (tries <= self.ntries):
logging.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:
tries += 1
# pylint: disable=no-member
logging.debug("kinit new ccache in get_connection: %s", e)
try:
kinit_keytab(str('nova/%s@%s' %
(api.env.host, api.env.realm)),
self.keytab,
self.ccache)
except GSSError as e:
logging.debug("kinit failed: %s", e)
except errors.NetworkError:
tries += 1
except http_client.ResponseNotReady:
# NOTE(xek): This means that the server closed the socket,
# so keep-alive ended and we can't use that connection.
api.Backend.rpcclient.disconnect()
tries += 1
else:
# successful connection
return
logging.debug("Waiting %s seconds before next retry.",
self.retry_delay)
time.sleep(self.retry_delay)
logging.error(" Failed to connect to IPA after %d attempts",
self.ntries)
raise Exception("Failed to connect to IPA")
def start_batch_operation(self):
"""Start a batch operation.
IPA method calls will be collected in a batch job
and submitted to IPA once all the operations have collected
by a call to _flush_batch_operation().
"""
logging.debug("start batch operation")
self.batch_args = list()
def _add_batch_operation(self, command, *args, **kw):
"""Add an IPA call to the batch operation"""
self.batch_args.append({
"method": six.text_type(command),
"params": [args, kw],
})
def flush_batch_operation(self):
"""Make an IPA batch call."""
logging.debug("flush_batch_operation")
if not self.batch_args:
return None
kw = {}
logging.debug(" %s", self.batch_args)
return self._call_ipa('batch', *self.batch_args, **kw)
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)
logging.debug(result)
return result
except (errors.CCacheError,
errors.TicketExpired,
errors.KerberosError):
logging.debug("Refresh authentication")
self.__get_connection()
except errors.NetworkError:
raise
except http_client.ResponseNotReady:
# NOTE(xek): This means that the server closed the socket,
# so keep-alive ended and we can't use that connection.
api.Backend.rpcclient.disconnect()
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')
def delete_host(self, hostname, batch=True):
"""Delete a host from IPA.
Servers can have multiple network interfaces, and therefore can
have multiple aliases. Moreover, they can part of a service using
a virtual host (VIP). These aliases are denoted 'subhosts',
"""
logging.debug("Deleting subhost: %s", hostname)
host_params = [hostname]
(hn, domain) = self.split_hostname(hostname)
dns_params = [domain, hn]
# If there is no DNS entry, this operation fails
host_kw = {'updatedns': False, }
dns_kw = {'del_all': True, }
if batch:
self._add_batch_operation('host_del', *host_params, **host_kw)
self._add_batch_operation('dnsrecord_del', *dns_params,
**dns_kw)
else:
self._call_ipa('host_del', *host_params, **host_kw)
try:
self._call_ipa('dnsrecord_del',
*dns_params, **dns_kw)
except (errors.NotFound, errors.ACIError):
# Ignore DNS deletion errors
pass
def host_get_services(self, service_host):
"""Return list of services this host manages"""
logging.debug("Checking host %s services", service_host)
params = []
service_args = {'man_by_host': six.text_type(service_host)}
result = self._call_ipa('service_find',
*params, **service_args)
return [service['krbprincipalname'][0] for service in result['result']]
def service_managed_by_other_hosts(self, service_principal,
hosts_to_be_deleted):
"""Return True if hosts other than parent manages this service"""
logging.debug("Checking if principal %s has hosts", service_principal)
params = [service_principal]
service_args = {}
try:
result = self._call_ipa('service_show',
*params, **service_args)
except errors.NotFound:
raise KeyError
serviceresult = result['result']
try:
(service, hostname, realm) = self.split_principal(
service_principal
)
except errors.MalformedServicePrincipal as e:
logging.error("Unable to split principal %s: %s",
service_principal, e)
raise
for candidate in serviceresult.get('managedby_host', []):
if candidate != hostname:
if candidate not in hosts_to_be_deleted:
return True
return False
def find_host(self, hostname):
"""Return True if this host exists"""
logging.debug("Checking if host %s exists", hostname)
params = []
service_args = {'fqdn': six.text_type(hostname)}
result = self._call_ipa('host_find',
*params, **service_args)
return result['count'] > 0
def cleanup_ipa_services(keytab, principal, hosts):
ipa = IPAClient(keytab, principal)
hosts_to_delete = set()
for host in hosts:
hostname = host.decode('UTF-8')
if ipa.find_host(hostname):
hosts_to_delete.add(hostname)
# get a list of all the services associated with a given hosts
principals = set()
for host in hosts_to_delete:
principals.update(ipa.host_get_services(host))
# Check the managed_by attribute of each service identified with
# the given host. If it is managed by a host other than the
# parent or the hosts to be deleted, then it is likely a VIP and it
# is not ready to be removed.
subhosts_to_delete = set()
for principal in principals:
(service, subhost, domain) = ipa.split_principal(principal)
if ipa.service_managed_by_other_hosts(principal, hosts_to_delete):
# this service still has other hosts
continue
subhosts_to_delete.add(subhost)
# delete the subhosts. Referential integrity should take care of the
# services associated with these hosts.
ipa.start_batch_operation()
for host in hosts_to_delete:
ipa.delete_host(host)
for subhost in subhosts_to_delete:
ipa.delete_host(subhost)
ipa.flush_batch_operation()
def run_module():
argument_spec = openstack_full_argument_spec(
**yaml.safe_load(DOCUMENTATION)['options']
)
module = AnsibleModule(
argument_spec,
supports_check_mode=True,
**openstack_module_kwargs()
)
try:
keytab = module.params.get('keytab')
principal = module.params.get('principal')
hosts = module.params.get('hosts')
cleanup_ipa_services(keytab, principal, hosts)
module.exit_json(changed=True)
except Exception as err:
module.fail_json(msg=str(err))
def main():
run_module()
if __name__ == '__main__':
main()

View File

@ -0,0 +1,37 @@
# Molecule managed
# Copyright 2019 Red Hat, Inc.
# 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.
{% if item.registry is defined %}
FROM {{ item.registry.url }}/{{ item.image }}
{% else %}
FROM {{ item.image }}
{% endif %}
RUN if [ $(command -v apt-get) ]; then apt-get update && apt-get install -y python sudo bash ca-certificates && apt-get clean; \
elif [ $(command -v dnf) ]; then dnf makecache && dnf --assumeyes install python sudo python-devel python*-dnf bash {{ item.pkg_extras | default('') }} && dnf clean all; \
elif [ $(command -v yum) ]; then yum makecache fast && yum install -y python sudo yum-plugin-ovl python-setuptools bash {{ item.pkg_extras | default('') }} && sed -i 's/plugins=0/plugins=1/g' /etc/yum.conf && yum clean all; \
elif [ $(command -v zypper) ]; then zypper refresh && zypper install -y python sudo bash python-xml {{ item.pkg_extras | default('') }} && zypper clean -a; \
elif [ $(command -v apk) ]; then apk update && apk add --no-cache python sudo bash ca-certificates {{ item.pkg_extras | default('') }}; \
elif [ $(command -v xbps-install) ]; then xbps-install -Syu && xbps-install -y python sudo bash ca-certificates {{ item.pkg_extras | default('') }} && xbps-remove -O; fi
{% for pkg in item.easy_install | default([]) %}
# install pip for centos where there is no python-pip rpm in default repos
RUN easy_install {{ pkg }}
{% endfor %}
CMD ["/sbin/init"]

View File

@ -0,0 +1,163 @@
---
# Copyright 2019 Red Hat, Inc.
# 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.
- name: Setup server
hosts: all
vars:
ipa_domain: example.test
ipa_server_ip: 172.18.0.22
ipa_server_user: admin
ipa_server_password: password123
ipa_server_hostname: ipa.example.test
undercloud_fqdn: ipa.example.test
tasks:
- name: copy requirements file
copy:
src: "{{playbook_dir}}/../../../requirements.txt"
dest: /tmp/requirements.txt
- name: install requirements
pip:
requirements: /tmp/requirements.txt
- name: install python urllib gssapi
pip:
name: urllib_gssapi
- name: install ipa client
package:
name: ipa-client
state: present
- name: set resolv.conf to point to the ipa server
shell:
cmd: cat > /etc/resolv.conf
stdin: |
search {{ ipa_domain }}
nameserver {{ ipa_server_ip }}
- name: Set fqdn in /etc/hosts
shell:
cmd: cat > /etc/hosts
- name: Set fqdn in /etc/hosts
shell:
cmd: cat > /etc/hosts
stdin: |
127.0.0.1 test-0.example.test test-0 localhost localhost.localdomain
- name: enroll the server as an ipa client using admin creds
shell: |
ipa-client-install -U \
--server "{{ ipa_server_hostname }}" \
--domain "{{ ipa_domain }}" \
--realm "{{ ipa_domain | upper }}" \
--principal "{{ ipa_server_user }}" \
--password "{{ ipa_server_password }}" \
--no-ntp --force-join --no-nisdomain
args:
creates: /etc/ipa/default.conf
# we need this keytab for operations that we cannot do yet with ansible
- name: kinit to get admin creds
command: kinit "{{ ipa_server_user }}"
args:
stdin: "{{ ipa_server_password }}"
- name: ensure "tripleo-admin" group exists
group:
name: tripleo-admin
state: present
- name: create users, perms, get keytab
include_role:
name: tripleo_ipa_setup
apply:
environment:
IPA_USER: "{{ ipa_server_user }}"
IPA_HOST: "{{ ipa_server_hostname }}"
IPA_PASS: "{{ ipa_server_password }}"
- name: Converge - add host and relevant services
hosts: all
vars:
tripleo_ipa_enroll_base_server: true
tripleo_ipa_base_server_fqdn: test-0.example.test
tripleo_ipa_base_server_short_name: test-0
tripleo_ipa_base_server_domain: example.test
tripleo_ipa_delegate_server: localhost
tripleo_ipa_server_metadata: |
{
"compact_service_HTTP": [
"ctlplane",
"storage",
"storagemgmt",
"internalapi",
"external"
],
"compact_service_haproxy": [
"ctlplane",
"storage",
"storagemgmt",
"internalapi"
],
"compact_service_libvirt-vnc": [
"internalapi"
],
"compact_service_mysql": [
"internalapi"
],
"compact_service_neutron_ovn": [
"internalapi"
],
"compact_service_novnc-proxy": [
"internalapi"
],
"compact_service_ovn_controller": [
"internalapi"
],
"compact_service_ovn_dbs": [
"internalapi"
],
"compact_service_rabbitmq": [
"internalapi"
],
"compact_service_redis": [
"internalapi"
],
"managed_service_haproxyctlplane": "haproxy/test-0.ctlplane.example.test",
"managed_service_haproxyexternal": "haproxy/test-0.example.test",
"managed_service_haproxyinternal_api": "haproxy/test-0.internalapi.example.test",
"managed_service_haproxystorage": "haproxy/test-0.storage.example.test",
"managed_service_haproxystorage_mgmt": "haproxy/test-0.storagemgmt.example.test",
"managed_service_mysqlinternal_api": "mysql/test-0.internalapi.example.test",
"managed_service_ovn_dbsinternal_api": "ovn_dbs/test-0.internalapi.example.test",
"managed_service_redisinternal_api": "redis/test-0.internalapi.example.test"
}
roles:
- name: tripleo_ipa_registration
environment:
IPA_USER: admin
IPA_HOST: ipa.example.test
IPA_PASS: password123
- name: Converge - delete host and relevant services
hosts: all
vars:
ipa_server_user: nova/ipa.example.test
ipa_server_hostname: ipa.example.test
tasks:
- include_role:
name: tripleo_ipa_cleanup
vars:
tripleo_ipa_hosts_to_delete: [ 'test-0.example.test' ]
tripleo_ipa_principal: "{{ ipa_server_user }}"
tripleo_ipa_keytab: "/etc/novajoin/krb5.keytab"

View File

@ -0,0 +1,46 @@
---
driver:
name: docker
log: true
platforms:
- name: centos7
hostname: test-0.example.test
image: centos:7
security_opts:
- seccomp=unconfined
command: /sbin/init
tmpfs:
- /run
- /tmp
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:ro
dockerfile: Dockerfile
network_mode: host
easy_install:
- pip
environment: &env
http_proxy: "{{ lookup('env', 'http_proxy') }}"
https_proxy: "{{ lookup('env', 'https_proxy') }}"
provisioner:
name: ansible
log: true
env:
ANSIBLE_STDOUT_CALLBACK: yaml
ANSIBLE_ROLES_PATH: "${ANSIBLE_ROLES_PATH:-/usr/share/ansible/roles}:${HOME}/zuul-jobs/roles"
ANSIBLE_LIBRARY: "${ANSIBLE_LIBRARY:-/usr/share/ansible/plugins/modules}"
ANSIBLE_FILTER_PLUGINS: "${ANSIBLE_FILTER_PLUGINS:-/usr/share/ansible/plugins/filter}"
scenario:
test_sequence:
- destroy
- create
- prepare
- converge
- verify
- destroy
verifier:
name: testinfra

View File

@ -0,0 +1,74 @@
---
# Copyright 2020 Red Hat, Inc.
# 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.
- hosts: localhost
connection: local
tasks:
- name: set facts for domains
set_fact:
domain: example.test
ipa_password: password123
- name: Download FreeIPA Container
docker_image:
name: freeipa/freeipa-server:fedora-28
source: pull
- name: Make IPA data dir
file:
path: /tmp/ipa-data
state: directory
- name: Toggle SELinux boolean
seboolean:
name: container_manage_cgroup
state: true
persistent: true
become: true
- name: Remove any old IPA container
docker_container:
name: freeipa-server-container
state: absent
- name: Create network
docker_network:
name: ipa_network
ipam_config:
- subnet: 172.18.0.0/16
- name: Configure FreeIPA
shell: >
docker run --name freeipa-server-container
--sysctl net.ipv6.conf.lo.disable_ipv6=0
--security-opt seccomp=unconfined
--net ipa_network --ip 172.18.0.22
-e IPA_SERVER_IP={{ ansible_default_ipv4.address | default('127.0.0.1') }}
-e PASSWORD={{ ipa_password }}
-h ipa.{{ domain }}
--read-only --tmpfs /run --tmpfs /tmp
-v /sys/fs/cgroup:/sys/fs/cgroup:ro
-v /tmp/ipa-data:/data:Z freeipa/freeipa-server:fedora-28 exit-on-finished
-U -r {{ domain | upper }} --setup-dns --no-reverse --no-ntp
--forwarder={{ unbound_primary_nameserver_v4 | default('1.1.1.1') }}
--forwarder={{ unbound_secondary_nameserver_v4 | default('8.8.8.8') }} &
- name: Wait for FreeIPA server install
wait_for:
path: "/tmp/ipa-data/var/log/ipaserver-install.log"
search_regex: "(INFO The ipa-server-install command was successful|ERROR The ipa-server-install command failed)"
timeout: 900
become: true

View File

@ -0,0 +1,128 @@
import os
import pytest
import testinfra
import testinfra.utils.ansible_runner
inventory = os.environ['MOLECULE_INVENTORY_FILE']
testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
inventory).get_hosts('all')
def setup_module(module):
for host in testinfra_hosts:
testinfra.get_host('ansible://' + host,
ansible_inventory=inventory
).check_output('echo password123 | kinit admin')
def teardown_module(module):
for host in testinfra_hosts:
testinfra.get_host('ansible://' + host,
ansible_inventory=inventory
).check_output('kdestroy')
@pytest.mark.parametrize('perm', [
{'name': 'Modify host password', 'right': "write",
'type': "host", 'attrs': "userpassword"},
{'name': 'Write host certificate', 'right': "write",
'type': "host", 'attrs': "usercertificate"},
{'name': 'Modify host userclass', 'right': "write",
'type': "host", 'attrs': "userclass"},
{'name': 'Modify service managedBy attribute', 'right': "write",
'type': "service", 'attrs': "managedby"},
])
def test_permissions(host, perm):
result = host.check_output('ipa permission-find "{name}"'.format(**perm))
assert '1 permission matched' in result
assert 'Granted rights: {right}'.format(**perm) in result
assert 'Type: {type}'.format(**perm) in result
assert 'Effective attributes: {attrs}'.format(**perm) in result
@pytest.mark.parametrize('pri', [
'Nova Host Management',
])
def test_privilages(host, pri):
result = host.check_output('ipa privilege-find "{}"'.format(pri))
assert '1 privilege matched' in result
assert 'Privilege name: {}'.format(pri) in result
assert 'Description: {}'.format(pri) in result
def test_privilege_permissions(host):
pri = 'Nova Host Management'
perms = [
'System: add hosts',
'System: remove hosts',
'Modify host password',
'Modify host userclass',
'System: Modify hosts',
'Modify service managedBy attribute',
'System: Add krbPrincipalName to a Host',
'System: Add Services',
'System: Remove Services',
'Revoke certificate',
'System: manage host keytab',
'System: Manage host certificates',
'System: modify services',
'System: manage service keytab',
'System: read dns entries',
'System: remove dns entries',
'System: add dns entries',
'System: update dns entries',
'Retrieve Certificates from the CA',
]
result = host.check_output('ipa privilege-show "{}"'.format(pri))
assert 'Privilege name: {}'.format(pri) in result
for perm in perms:
assert perm.lower() in result.lower()
def test_role(host):
role = 'Nova Host Manager'
pri = 'Nova Host Management'
result = host.check_output('ipa role-show "{}"'.format(role))
assert 'Role name: {}'.format(role) in result
assert 'Description: {}'.format(role) in result
assert 'Privileges: {}'.format(pri) in result
assert 'nova/test-0.example.test@EXAMPLE.TEST' not in result
@pytest.mark.parametrize('name', [
'test-0.example.test',
'test-0.ctlplane.example.test',
'test-0.external.example.test',
'test-0.internalapi.example.test',
'test-0.storage.example.test',
'test-0.storagemgmt.example.test',
])
def test_hosts(host, name):
host.run_expect([1], 'ipa host-find {}'.format(name))
@pytest.mark.parametrize('service, subhost', [
('HTTP', 'ctlplane'),
('HTTP', 'external'),
('HTTP', 'internalapi'),
('HTTP', 'storage'),
('HTTP', 'storagemgmt'),
('haproxy', 'ctlplane'),
('haproxy', 'internalapi'),
('haproxy', 'storage'),
('haproxy', 'storagemgmt'),
('libvirt-vnc', 'internalapi'),
('mysql', 'internalapi'),
('neutron_ovn', 'internalapi'),
('novnc-proxy', 'internalapi'),
('ovn_controller', 'internalapi'),
('ovn_dbs', 'internalapi'),
('rabbitmq', 'internalapi'),
('redis', 'internalapi'),
])
def test_services(host, service, subhost):
host.run_expect(
[2],
'ipa service-show {}/test-0.{}.example.test@EXAMPLE.TEST'.format(
service, subhost))

View File

@ -0,0 +1,44 @@
---
# Copyright 2020 Red Hat, Inc.
# 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.
galaxy_info:
author: OpenStack
description: TripleO OpenStack Role -- tripleo_ipa_cleanup
company: Red Hat
license: Apache-2.0
min_ansible_version: 2.7
#
# Provide a list of supported platforms, and for each platform a list of versions.
# If you don't wish to enumerate all versions for a particular platform, use 'all'.
# To view available platforms and versions (or releases), visit:
# https://galaxy.ansible.com/api/v1/platforms/
#
platforms:
- name: Fedora
versions:
- 28
- name: CentOS
versions:
- 7
galaxy_tags:
- tripleo
# List your role dependencies here, one per line. Be sure to remove the '[]' above,
# if you add dependencies to this list.
dependencies: []

View File

@ -0,0 +1,29 @@
---
# Copyright 2020 Red Hat, Inc.
# 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.
#
# This role removes a set of hosts and its required sub-hosts and services
# in FreeIPA
#
# The following variables are required:
# - tripleo_ipa_hosts_to_delete (list of FQDNs of hosts to delete)
# - tripleo_ipa_principal (principal to use when connecting to FreeIPA)
# - tripleo_ipa_keytab (file path to keytab to authenticate to FreeIPA)
- name: delete hosts, subhosts and services from freeIPA
cleanup_ipa_services:
principal: "{{ tripleo_ipa_principal }}"
keytab: "{{ tripleo_ipa_keytab }}"
hosts: "{{ tripleo_ipa_hosts_to_delete }}"

View File

@ -12,5 +12,5 @@
- zuul.d/playbooks/run.yml
post-run:
- zuul.d/playbooks/post.yml
timeout: 1800
timeout: 3600
voting: true