Add novajoin class

In this commit, we add the puppet module for a new
nova metadata micro-service (novajoin) that allows nova
instances to be registered to a FreeIPA server as IPA clients.

Implements: blueprint novajoin
Change-Id: I5ffa45bdc400e123079c79e15776ebacdcb24de9
This commit is contained in:
Ade Lee 2016-11-11 11:10:14 -05:00
parent 4cb2476197
commit 57e3f661ff
7 changed files with 559 additions and 0 deletions

View File

@ -0,0 +1,10 @@
Puppet::Type.type(:novajoin_config).provide(
:ini_setting,
:parent => Puppet::Type.type(:openstack_config).provider(:ini_setting)
) do
def self.file_path
'/etc/nova/join.conf'
end
end

View File

@ -0,0 +1,53 @@
Puppet::Type.newtype(:novajoin_config) do
ensurable
newparam(:name, :namevar => true) do
desc 'Section/setting name to manage from join.conf'
newvalues(/\S+\/\S+/)
end
newproperty(:value) do
desc 'The value of the setting to be defined.'
munge do |value|
value = value.to_s.strip
value.capitalize! if value =~ /^(true|false)$/i
value
end
newvalues(/^[\S ]*$/)
def is_to_s( currentvalue )
if resource.secret?
return '[old secret redacted]'
else
return currentvalue
end
end
def should_to_s( newvalue )
if resource.secret?
return '[new secret redacted]'
else
return newvalue
end
end
end
newparam(:secret, :boolean => true) do
desc 'Whether to hide the value from Puppet logs. Defaults to `false`.'
newvalues(:true, :false)
defaultto false
end
newparam(:ensure_absent_val) do
desc 'A value that is specified as the value property will behave as if ensure => absent was specified'
defaultto('<SERVICE DEFAULT>')
end
autorequire(:package) do
'novajoin'
end
end

View File

@ -0,0 +1,196 @@
# == Class: nova::metadata::novajoin::api
#
# The nova::metadata::novajoin::api class encapsulates an
# IPA Nova Join API service.
#
# === Parameters
#
# [*nova_password*]
# (required) Password for the nova service user.
#
# [*api_paste_config*]
# (optional) Filename for the paste deploy file.
# Defaults to '/etc/nova/join-api-paste.ini'.
#
# [*auth_strategy*]
# (optional) Strategy to use for authentication.
# Defaults to 'keystone'.
#
# [*auth_type*]
# (optional) Authentication type.
# Defaults to 'password'.
#
# [*cacert*]
# (optional) CA cert file.
# Defaults to '/etc/ipa/ca.crt'.
#
# [*connect_retries*]
# (optional) Number of connection retries to IPA.
# Defaults to 1.
#
# [*debug*]
# (optional) Set log level to debug.
# Defaults to false.
#
# [*enabled*]
# (optional) Whether to enable services.
# Defaults to true.
#
# [*enable_ipa_client_install*]
# (optional) whether to perform ipa_client_install
# Defaults to true.
#
# [*ensure_package*]
# (optional) The state of novajoin packages.
# Defaults to 'present'
#
# [*ipa_domain*]
# (optional) IPA domain
# Reads the value from /etc/ipa/default.conf if not defined.
#
# [*join_listen_port*]
# (optional) Port for novajoin service to listen on.
# Defaults to 9090
#
# [*keystone_auth_url*]
# (optional) auth_url for the keystone instance.
# Defaults to 'http:://127.0.0.1:35357'
#
# [*keytab*]
# (optional) Kerberos client keytab file.
# Defaults to '/etc/nova/krb5.keytab'
#
# [*log_dir*]
# (optional) log directory.
# Defaults to '/var/log/novajoin'
#
# [*manage_service*]
# (optional) If Puppet should manage service startup / shutdown.
# Defaults to true.
#
# [*nova_user*]
# (optional) User that nova services run as.
# Defaults to 'nova'
#
# [*project_domain_name*]
# (optional) Domain name containing project (for nova auth).
# Defaults to 'default'
#
# [*project_name*]
# (optional) Project name (for nova auth).
# Defaults to 'service'
#
# [*user_domain_id*]
# (optional) Domain for nova user.
# Defaults to 'default'
#
class nova::metadata::novajoin::api (
$nova_password,
$api_paste_config = '/etc/nova/join-api-paste.ini',
$auth_strategy = $::os_service_default,
$auth_type = 'password',
$cacert = '/etc/ipa/ca.crt',
$connect_retries = $::os_service_default,
$debug = $::os_service_default,
$enabled = true,
$enable_ipa_client_install = true,
$ensure_package = 'present',
$ipa_domain = undef,
$join_listen_port = $::os_service_default,
$keystone_auth_url = 'http://127.0.0.1:35357/',
$keytab = $::os_service_default,
$log_dir = '/var/log/novajoin',
$manage_service = true,
$nova_user = 'nova',
$project_domain_name = 'default',
$project_name = 'service',
$user_domain_id = 'default',
) {
case $::osfamily {
'RedHat': {
$package_name = 'python-novajoin'
$service_name = 'novajoin-server'
$notify_service_name = 'novajoin-notify'
}
default: {
fail("Unsupported osfamily: ${::osfamily} operatingsystem")
}
} # Case $::osfamily
# Keep this commented out until puppet-ipaclient is available
# This will invoke ipa-client-install
#
# if $enable_ipa_client_install {
# require ::ipaclient
# }
package { 'python-novajoin':
ensure => $ensure_package,
name => $package_name,
tag => ['openstack', 'novajoin-package'],
}
if $ipa_domain != undef {
novajoin_config {
'DEFAULT/domain': value => $ipa_domain;
}
}
novajoin_config {
'DEFAULT/api_paste_config': value => $api_paste_config;
'DEFAULT/auth_strategy': value => $auth_strategy;
'DEFAULT/cacert': value => $cacert;
'DEFAULT/connect_retries': value => $connect_retries;
'DEFAULT/debug': value => $debug;
'DEFAULT/join_listen_port': value => $join_listen_port;
'DEFAULT/keytab': value => $keytab;
'DEFAULT/log_dir': value => $log_dir;
'service_credentials/auth_type': value => $auth_type;
'service_credentials/auth_url': value => $keystone_auth_url;
'service_credentials/password': value => $nova_password;
'service_credentials/username': value => $nova_user;
'service_credentials/project_name': value => $project_name;
'service_credentials/user_domain_id': value => $user_domain_id;
'service_credentials/project_domain_name':
value => $project_domain_name;
}
if $manage_service {
if $enabled {
$service_ensure = 'running'
} else {
$service_ensure = 'stopped'
}
}
service { 'novajoin-server':
ensure => $service_ensure,
name => $service_name,
enable => $enabled,
hasstatus => true,
hasrestart => true,
tag => 'openstack',
}
service { 'novajoin-notify':
ensure => $service_ensure,
name => $notify_service_name,
enable => $enabled,
hasstatus => true,
hasrestart => true,
tag => 'openstack',
}
exec { 'get-service-user-keytab':
command => "/usr/bin/kinit -kt /etc/krb5.keytab && ipa-getkeytab -s `grep xmlrpc_uri /etc/ipa/default.conf | cut -d/ -f3` \
-p nova/${::fqdn} -k /etc/nova/krb5.keytab",
creates => '/etc/nova/krb5.keytab',
require => Package['python-novajoin']
}
Novajoin_config<||> ~> Service<| title == 'nova-api'|>
Exec['get-service-user-keytab'] ~> Service['novajoin-server']
Exec['get-service-user-keytab'] ~> Service['novajoin-notify']
Exec['get-service-user-keytab'] ~> Service<| title == 'nova-api'|>
}

View File

@ -0,0 +1,6 @@
---
features:
- Adds support to configure a vendordata plugin called
novajoin, which is a micro-service that registers
instances in FreeIPA, and listens to nofications to
unregister the instance when the instance is removed.

View File

@ -0,0 +1,162 @@
require 'spec_helper'
describe 'nova::metadata::novajoin::api' do
let :facts do
@default_facts.merge(
{
:osfamily => 'RedHat',
:processorcount => '7',
:fqdn => "undercloud.example.com",
}
)
end
let :default_params do
{
:api_paste_config => '/etc/nova/join-api-paste.ini',
:auth_strategy => '<SERVICE DEFAULT>',
:auth_type => 'password',
:cacert => '/etc/ipa/ca.crt',
:connect_retries => '<SERVICE DEFAULT>',
:debug => '<SERVICE DEFAULT>',
:enabled => true,
:enable_ipa_client_install => true,
:ensure_package => 'present',
:join_listen_port => '<SERVICE DEFAULT>',
:keytab => '<SERVICE DEFAULT>',
:log_dir => '/var/log/novajoin',
:manage_service => true,
:nova_user => 'nova',
:project_domain_name => 'default',
:project_name => 'service',
:user_domain_id => 'default',
:ipa_domain => 'EXAMPLE.COM',
:keystone_auth_url => 'https://keystone.example.com:35357',
:nova_password => 'my_secret_password',
}
end
[{},
{
:api_paste_config => '/etc/nova/join-api-paste.ini',
:auth_strategy => 'noauth2',
:auth_type => 'password',
:cacert => '/etc/ipa/ca.crt',
:connect_retries => 2,
:debug => true,
:enabled => false,
:enable_ipa_client_install => false,
:ensure_package => 'present',
:join_listen_port => '9921',
:keytab => '/etc/krb5.conf',
:log_dir => '/var/log/novajoin',
:manage_service => true,
:nova_user => 'nova1',
:project_domain_name => 'default',
:project_name => 'service',
:user_domain_id => 'default',
:ipa_domain => 'EXAMPLE2.COM',
:keystone_auth_url => 'https://keystone2.example.com:35357',
:nova_password => 'my_secret_password2',
}
].each do |param_set|
describe "when #{param_set == {} ? "using default" : "specifying"} class parameters" do
let :param_hash do
default_params.merge(param_set)
end
let :params do
param_hash
end
it { is_expected.to contain_service('novajoin-server').with(
'ensure' => (param_hash[:manage_service] && param_hash[:enabled]) ? 'running': 'stopped',
'enable' => param_hash[:enabled],
'hasstatus' => true,
'hasrestart' => true,
'tag' => 'openstack',
) }
it { is_expected.to contain_service('novajoin-notify').with(
'ensure' => (param_hash[:manage_service] && param_hash[:enabled]) ? 'running': 'stopped',
'enable' => param_hash[:enabled],
'hasstatus' => true,
'hasrestart' => true,
'tag' => 'openstack',
) }
it 'is_expected.to configure default parameters' do
is_expected.to contain_novajoin_config('DEFAULT/api_paste_config').with_value(param_hash[:api_paste_config])
is_expected.to contain_novajoin_config('DEFAULT/auth_strategy').with_value(param_hash[:auth_strategy])
is_expected.to contain_novajoin_config('DEFAULT/cacert').with_value(param_hash[:cacert])
is_expected.to contain_novajoin_config('DEFAULT/connect_retries').with_value(param_hash[:connect_retries])
is_expected.to contain_novajoin_config('DEFAULT/debug').with_value(param_hash[:debug])
is_expected.to contain_novajoin_config('DEFAULT/join_listen_port').with_value(param_hash[:join_listen_port])
is_expected.to contain_novajoin_config('DEFAULT/keytab').with_value(param_hash[:keytab])
is_expected.to contain_novajoin_config('DEFAULT/log_dir').with_value(param_hash[:log_dir])
is_expected.to contain_novajoin_config('DEFAULT/domain').with_value(param_hash[:ipa_domain])
end
it 'is_expected.to configure service credentials' do
is_expected.to contain_novajoin_config('service_credentials/auth_type').with_value(param_hash[:auth_type])
is_expected.to contain_novajoin_config('service_credentials/auth_url').with_value(param_hash[:keystone_auth_url])
is_expected.to contain_novajoin_config('service_credentials/password').with_value(param_hash[:nova_password])
is_expected.to contain_novajoin_config('service_credentials/project_name').with_value(param_hash[:project_name])
is_expected.to contain_novajoin_config('service_credentials/user_domain_id').with_value(param_hash[:user_domain_id])
is_expected.to contain_novajoin_config('service_credentials/project_domain_name').with_value(param_hash[:project_domain_name])
is_expected.to contain_novajoin_config('service_credentials/username').with_value(param_hash[:nova_user])
end
it 'is_expected.to get service user keytab' do
is_expected.to contain_exec('get-service-user-keytab').with(
'command' => "/usr/bin/kinit -kt /etc/krb5.keytab && ipa-getkeytab -s `grep xmlrpc_uri /etc/ipa/default.conf | cut -d/ -f3` \
-p nova/undercloud.example.com -k /etc/nova/krb5.keytab",
)
end
end
end
describe 'with disabled service managing' do
let :params do
{
:manage_service => false,
:enabled => false,
:ipa_domain => 'EXAMPLE.COM',
:nova_password => 'my_secret_password',
}
end
it { is_expected.to contain_service('novajoin-server').with(
'ensure' => nil,
'enable' => false,
'hasstatus' => true,
'hasrestart' => true,
'tag' => 'openstack',
) }
it { is_expected.to contain_service('novajoin-notify').with(
'ensure' => nil,
'enable' => false,
'hasstatus' => true,
'hasrestart' => true,
'tag' => 'openstack',
) }
end
describe 'on RedHat platforms' do
let :facts do
OSDefaults.get_facts({
:osfamily => 'RedHat',
:operatingsystem => 'RedHat',
})
end
let(:params) { default_params }
it { is_expected.to contain_package('python-novajoin').with(
:tag => ['openstack', 'novajoin-package'],
)}
end
end

View File

@ -0,0 +1,68 @@
#
# these tests are a little concerning b/c they are hacking around the
# modulepath, so these tests will not catch issues that may eventually arise
# related to loading these plugins.
# I could not, for the life of me, figure out how to programatcally set the modulepath
$LOAD_PATH.push(
File.join(
File.dirname(__FILE__),
'..',
'..',
'..',
'fixtures',
'modules',
'inifile',
'lib')
)
$LOAD_PATH.push(
File.join(
File.dirname(__FILE__),
'..',
'..',
'..',
'fixtures',
'modules',
'openstacklib',
'lib')
)
require 'spec_helper'
provider_class = Puppet::Type.type(:novajoin_config).provider(:ini_setting)
describe provider_class do
it 'should default to the default setting when no other one is specified' do
resource = Puppet::Type::Novajoin_config.new(
{:name => 'DEFAULT/foo', :value => 'bar'}
)
provider = provider_class.new(resource)
expect(provider.section).to eq('DEFAULT')
expect(provider.setting).to eq('foo')
end
it 'should allow setting to be set explicitly' do
resource = Puppet::Type::Novajoin_config.new(
{:name => 'dude/foo', :value => 'bar'}
)
provider = provider_class.new(resource)
expect(provider.section).to eq('dude')
expect(provider.setting).to eq('foo')
end
it 'should ensure absent when <SERVICE DEFAULT> is specified as a value' do
resource = Puppet::Type::Novajoin_config.new(
{:name => 'dude/foo', :value => '<SERVICE DEFAULT>'}
)
provider = provider_class.new(resource)
provider.exists?
expect(resource[:ensure]).to eq :absent
end
it 'should ensure absent when value matches ensure_absent_val' do
resource = Puppet::Type::Novajoin_config.new(
{:name => 'dude/foo', :value => 'foo', :ensure_absent_val => 'foo' }
)
provider = provider_class.new(resource)
provider.exists?
expect(resource[:ensure]).to eq :absent
end
end

View File

@ -0,0 +1,64 @@
require 'puppet'
require 'puppet/type/novajoin_config'
describe 'Puppet::Type.type(:novajoin_config)' do
before :each do
@novajoin_config = Puppet::Type.type(:novajoin_config).new(:name => 'DEFAULT/foo', :value => 'bar')
end
it 'should require a name' do
expect {
Puppet::Type.type(:novajoin_config).new({})
}.to raise_error(Puppet::Error, 'Title or name must be provided')
end
it 'should not expect a name with whitespace' do
expect {
Puppet::Type.type(:novajoin_config).new(:name => 'f oo')
}.to raise_error(Puppet::Error, /Parameter name failed/)
end
it 'should fail when there is no section' do
expect {
Puppet::Type.type(:novajoin_config).new(:name => 'foo')
}.to raise_error(Puppet::Error, /Parameter name failed/)
end
it 'should not require a value when ensure is absent' do
Puppet::Type.type(:novajoin_config).new(:name => 'DEFAULT/foo', :ensure => :absent)
end
it 'should accept a valid value' do
@novajoin_config[:value] = 'bar'
expect(@novajoin_config[:value]).to eq('bar')
end
it 'should not accept a value with whitespace' do
@novajoin_config[:value] = 'b ar'
expect(@novajoin_config[:value]).to eq('b ar')
end
it 'should accept valid ensure values' do
@novajoin_config[:ensure] = :present
expect(@novajoin_config[:ensure]).to eq(:present)
@novajoin_config[:ensure] = :absent
expect(@novajoin_config[:ensure]).to eq(:absent)
end
it 'should not accept invalid ensure values' do
expect {
@novajoin_config[:ensure] = :latest
}.to raise_error(Puppet::Error, /Invalid value/)
end
it 'should autorequire the package that install the file' do
catalog = Puppet::Resource::Catalog.new
package = Puppet::Type.type(:package).new(:name => 'novajoin')
catalog.add_resource package, @novajoin_config
dependency = @novajoin_config.autorequire
expect(dependency.size).to eq(1)
expect(dependency[0].target).to eq(@novajoin_config)
expect(dependency[0].source).to eq(package)
end
end