Test OpenStack server instance enrollment
A basic test to check that a spawned instance will be added to and than deleted from FreeIPA. This also fixes the novajoin-install script to work by default on devstack. Change-Id: Id7e940360ade74d605fef9004c6a5454790c55a4
This commit is contained in:
parent
47ce2f7136
commit
fe72231faa
25
README.rst
25
README.rst
|
@ -107,10 +107,16 @@ novajoin REST service and enable notifications in
|
|||
vendordata_dynamic_read_timeout = 30
|
||||
vendordata_jsonfile_path = /etc/novajoin/cloud-config-novajoin.json
|
||||
|
||||
[oslo_messaging_notifications]
|
||||
notification_driver = messaging
|
||||
notification_topic = notifications,novajoin_notifications
|
||||
[notifications]
|
||||
notify_on_state_change = vm_state
|
||||
notification_format = unversioned
|
||||
|
||||
[oslo_messaging_notifications]
|
||||
...
|
||||
topics=notifications,novajoin_notifications
|
||||
|
||||
.. note::
|
||||
Notifications have to be also enabled and configured on nova computes!
|
||||
|
||||
Novajoin enables keystone authentication by default, as seen in
|
||||
**/etc/novajoin/join-api-paste.ini**. So credentials need to be set for
|
||||
|
@ -203,9 +209,18 @@ Notification listener Configuration
|
|||
===================================
|
||||
|
||||
The only special configuration needed here is to configure nova to
|
||||
send notifications to the novajoin topic in /etc/nova/nova.conf:
|
||||
send notifications to the novajoin topic in /etc/nova/nova.conf::
|
||||
|
||||
notification_topic = notifications,novajoin_notifications
|
||||
[notifications]
|
||||
notify_on_state_change = vm_state
|
||||
notification_format = unversioned
|
||||
|
||||
[oslo_messaging_notifications]
|
||||
...
|
||||
topics=notifications,novajoin_notifications
|
||||
|
||||
.. note::
|
||||
Notifications have to be also enabled and configured on nova computes!
|
||||
|
||||
If you simply use notifications and ceilometer is running then the
|
||||
notifications will be roughly split between the two services in a
|
||||
|
|
|
@ -17,6 +17,7 @@ import os
|
|||
import time
|
||||
import uuid
|
||||
|
||||
import six
|
||||
from six.moves import http_client
|
||||
|
||||
|
||||
|
@ -481,6 +482,14 @@ class IPAClient(IPANovaJoinBase):
|
|||
result = self._call_ipa('service_find', *params, **service_args)
|
||||
return result['count'] > 0
|
||||
|
||||
def find_host(self, hostname):
|
||||
"""Return True if this host exists"""
|
||||
LOG.debug('Checking if host ' + hostname + ' exists')
|
||||
params = []
|
||||
service_args = {'fqdn': six.text_type(hostname)}
|
||||
result = self._call_ipa('host_find', *params, **service_args)
|
||||
return result['count'] > 0
|
||||
|
||||
def delete_service(self, principal, batch=True):
|
||||
LOG.debug('Deleting service: ' + principal)
|
||||
params = [principal]
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
# Copyright 2018 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.
|
||||
|
||||
"""
|
||||
Tests enrollment of new OpenStack VMs in FreeIPA.
|
||||
|
||||
The test uses the default demo project and credentials and assumes there is a
|
||||
centos-image present in Glance.
|
||||
"""
|
||||
|
||||
import os
|
||||
import testtools
|
||||
import uuid
|
||||
|
||||
import openstack
|
||||
from oslo_service import loopingcall
|
||||
|
||||
from novajoin import config
|
||||
from novajoin.ipa import IPAClient
|
||||
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
EXAMPLE_DOMAIN = '.example.test'
|
||||
TEST_IMAGE = 'centos-image'
|
||||
TEST_IMAGE_USER = 'centos'
|
||||
TEST_INSTANCE = str(uuid.uuid4())
|
||||
TEST_KEY = str(uuid.uuid4())
|
||||
|
||||
|
||||
class TestEnrollment(testtools.TestCase):
|
||||
"""Do a live test against a Devstack installation.
|
||||
|
||||
This requires:
|
||||
- Devstack running on localhost
|
||||
- novajoin configured and running
|
||||
- centos-image present in Glance
|
||||
|
||||
This will add and remove server instances.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestEnrollment, self).setUp()
|
||||
CONF.keytab = '/tmp/test.keytab'
|
||||
if not os.path.isfile(CONF.keytab):
|
||||
CONF.keytab = '/etc/novajoin/krb5.keytab'
|
||||
self.ipaclient = IPAClient()
|
||||
self.conn = openstack.connect(
|
||||
auth_url='http://127.0.0.1/identity', project_name='demo',
|
||||
username='demo', password='secretadmin', region_name='RegionOne',
|
||||
user_domain_id='default', project_domain_id='default',
|
||||
app_name='functional-tests', app_version='1.0')
|
||||
self._key = self.conn.compute.create_keypair(name=TEST_KEY)
|
||||
group = self.conn.network.find_security_group('default')
|
||||
self._rules = []
|
||||
for protocol, port in [('icmp', None), ('tcp', 22), ('tcp', 443)]:
|
||||
try:
|
||||
self._rules.append(
|
||||
self.conn.network.create_security_group_rule(
|
||||
security_group_id=group.id, direction='ingress',
|
||||
remote_ip_prefix='0.0.0.0/0', protocol=protocol,
|
||||
port_range_max=port, port_range_min=port,
|
||||
ethertype='IPv4'))
|
||||
except openstack.exceptions.ConflictException:
|
||||
pass
|
||||
network = self.conn.network.find_network('public')
|
||||
self._ip = self.conn.network.create_ip(floating_network_id=network.id)
|
||||
self._server = None
|
||||
|
||||
def tearDown(self):
|
||||
super(TestEnrollment, self).tearDown()
|
||||
self.conn.compute.delete_keypair(self._key)
|
||||
for rule in self._rules:
|
||||
self.conn.network.delete_security_group_rule(rule)
|
||||
self._delete_server()
|
||||
self.conn.network.delete_ip(self._ip)
|
||||
|
||||
def _create_server(self):
|
||||
image = self.conn.compute.find_image(TEST_IMAGE)
|
||||
flavor = self.conn.compute.find_flavor('m1.small')
|
||||
network = self.conn.network.find_network('private')
|
||||
|
||||
self._server = self.conn.compute.create_server(
|
||||
name=TEST_INSTANCE, image_id=image.id, flavor_id=flavor.id,
|
||||
networks=[{"uuid": network.id}], key_name=self._key.name,
|
||||
metadata = {"ipa_enroll": "True"})
|
||||
|
||||
server = self.conn.compute.wait_for_server(self._server)
|
||||
self.conn.compute.add_floating_ip_to_server(
|
||||
server, self._ip.floating_ip_address)
|
||||
return server
|
||||
|
||||
def _delete_server(self):
|
||||
if self._server:
|
||||
self.conn.compute.delete_server(self._server)
|
||||
self._server = None
|
||||
|
||||
@loopingcall.RetryDecorator(200, 5, 5, (AssertionError,))
|
||||
def _check_ipa_client_created(self):
|
||||
self.assertTrue(
|
||||
self.ipaclient.find_host(TEST_INSTANCE + EXAMPLE_DOMAIN))
|
||||
|
||||
@loopingcall.RetryDecorator(50, 5, 5, (AssertionError,))
|
||||
def _check_ipa_client_deleted(self):
|
||||
self.assertFalse(
|
||||
self.ipaclient.find_host(TEST_INSTANCE + EXAMPLE_DOMAIN))
|
||||
|
||||
def test_enroll_server(self):
|
||||
self._create_server()
|
||||
self._check_ipa_client_created()
|
||||
self._delete_server()
|
||||
self._check_ipa_client_deleted()
|
|
@ -18,35 +18,21 @@ import logging
|
|||
import os
|
||||
import pwd
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import copy
|
||||
import tempfile
|
||||
import getpass
|
||||
from ipapython.ipautil import run, user_input
|
||||
from ipalib import api
|
||||
from ipalib import errors
|
||||
from novajoin.errors import ConfigurationError
|
||||
from novajoin import configure_ipa
|
||||
from six.moves import input
|
||||
from six.moves.configparser import SafeConfigParser, NoOptionError
|
||||
from subprocess import CalledProcessError
|
||||
from string import Template
|
||||
from urllib3.util import parse_url
|
||||
|
||||
try:
|
||||
from ipapython.ipautil import kinit_password
|
||||
except ImportError:
|
||||
# The import moved in freeIPA 4.5.0
|
||||
from ipalib.install.kinit import kinit_password
|
||||
|
||||
|
||||
DATADIR = '/usr/share/novajoin'
|
||||
NOVADIR = '/etc/nova'
|
||||
IPACONF = '/etc/ipa/default.conf'
|
||||
NOVACONF = '/etc/nova/nova.conf'
|
||||
NOVACPUCONF = '/etc/nova/nova-cpu.conf'
|
||||
JOINCONF = '/etc/novajoin/join.conf'
|
||||
|
||||
|
||||
|
@ -84,7 +70,7 @@ def openlogs():
|
|||
def install(opts):
|
||||
logger.info('Installation initiated')
|
||||
|
||||
if not os.path.exists(IPACONF):
|
||||
if not os.path.exists(opts.ipa_conf):
|
||||
raise ConfigurationError('Must be enrolled in IPA')
|
||||
|
||||
try:
|
||||
|
@ -96,7 +82,7 @@ def install(opts):
|
|||
% e.message)
|
||||
|
||||
try:
|
||||
user = pwd.getpwnam(opts.user)
|
||||
pwd.getpwnam(opts.user)
|
||||
except KeyError:
|
||||
raise ConfigurationError('User: %s not found on the system' %
|
||||
opts.user)
|
||||
|
@ -118,7 +104,7 @@ def install(opts):
|
|||
keystone_url = parse_url(opts.keystone_auth_url)
|
||||
|
||||
config = SafeConfigParser()
|
||||
config.read(JOINCONF)
|
||||
config.read(opts.novajoin_conf)
|
||||
config.set('DEFAULT', 'domain', api.env.domain) # pylint: disable=no-member
|
||||
config.set('DEFAULT',
|
||||
'api_paste_config',
|
||||
|
@ -152,11 +138,11 @@ def install(opts):
|
|||
config.set('keystone_authtoken', 'user_domain_id', 'default')
|
||||
|
||||
|
||||
with open(JOINCONF, 'w') as f:
|
||||
with open(opts.novajoin_conf, 'w') as f:
|
||||
config.write(f)
|
||||
|
||||
config = SafeConfigParser()
|
||||
config.read(NOVACONF)
|
||||
config.read(opts.nova_conf)
|
||||
config.set('DEFAULT',
|
||||
'vendordata_jsonfile_path',
|
||||
'/etc/novajoin/cloud-config-novajoin.json')
|
||||
|
@ -176,15 +162,6 @@ def install(opts):
|
|||
'vendordata_dynamic_targets',
|
||||
'join@http://127.0.0.1:9090/v1/')
|
||||
|
||||
# Notifications
|
||||
config.set('DEFAULT',
|
||||
'notification_topic',
|
||||
'notifications')
|
||||
|
||||
config.set('DEFAULT',
|
||||
'notify_on_state_change',
|
||||
'vm_state')
|
||||
|
||||
if not config.has_section('vendordata_dynamic_auth'):
|
||||
config.add_section('vendordata_dynamic_auth')
|
||||
|
||||
|
@ -201,14 +178,36 @@ def install(opts):
|
|||
except NoOptionError:
|
||||
transport_url = None
|
||||
|
||||
with open(NOVACONF, 'w') as f:
|
||||
with open(opts.nova_conf, 'w') as f:
|
||||
config.write(f)
|
||||
|
||||
# Notifications
|
||||
for conf in set([opts.nova_conf, opts.nova_cpu_conf]):
|
||||
config = SafeConfigParser()
|
||||
config.read(conf)
|
||||
if not config.has_section('notifications'):
|
||||
config.add_section('notifications')
|
||||
|
||||
config.set('notifications',
|
||||
'notify_on_state_change',
|
||||
'vm_state')
|
||||
|
||||
config.set('notifications',
|
||||
'notification_format',
|
||||
'unversioned')
|
||||
|
||||
config.set('oslo_messaging_notifications',
|
||||
'topics',
|
||||
'notifications,novajoin_notifications')
|
||||
with open(conf, 'w') as f:
|
||||
config.write(f)
|
||||
|
||||
|
||||
if transport_url:
|
||||
join_config = SafeConfigParser()
|
||||
join_config.read(JOINCONF)
|
||||
join_config.read(opts.novajoin_conf)
|
||||
join_config.set('DEFAULT', 'transport_url', transport_url)
|
||||
with open(JOINCONF, 'w') as f:
|
||||
with open(opts.novajoin_conf, 'w') as f:
|
||||
join_config.write(f)
|
||||
|
||||
logger.info('Importing IPA metadata')
|
||||
|
@ -235,6 +234,16 @@ def parse_args():
|
|||
help='Nova service user password', default=None)
|
||||
parser.add_argument('--project', dest='project_name',
|
||||
help='Keystone project', default='service')
|
||||
parser.add_argument('--ipa-conf', dest='ipa_conf',
|
||||
help='IPA configuration file', default=IPACONF)
|
||||
parser.add_argument('--nova-conf', dest='nova_conf',
|
||||
help='nova configuration file', default=NOVACONF)
|
||||
parser.add_argument('--nova-cpu-conf', dest='nova_cpu_conf',
|
||||
help='nova compute configuration file',
|
||||
default=NOVACPUCONF)
|
||||
parser.add_argument('--novajoin-conf', dest='novajoin_conf',
|
||||
help='novajoin configuration file',
|
||||
default=JOINCONF)
|
||||
parser = configure_ipa.ipa_options(parser)
|
||||
|
||||
opts = parser.parse_args()
|
||||
|
|
Loading…
Reference in New Issue