Add openstacklib module

master bcbe7aa35c03c83dd58842fdb0f72a332e660124
  source: https://github.com/stackforge/puppet-openstacklib

Partially Implements: blueprint upgrade-openstack-puppet-modules

Change-Id: I6eea00337c5cce2269c236b49305ba7085f68eb4
This commit is contained in:
vsaienko 2015-06-11 15:24:12 +03:00
parent e494cabc57
commit d3a5608583
41 changed files with 2747 additions and 0 deletions

View File

@ -0,0 +1,12 @@
fixtures:
repositories:
apache: git://github.com/puppetlabs/puppetlabs-apache.git
concat:
repo: 'git://github.com/puppetlabs/puppetlabs-concat.git'
ref: '1.2.1'
mysql: git://github.com/puppetlabs/puppetlabs-mysql.git
postgresql: git://github.com/puppetlabs/puppetlabs-postgresql.git
stdlib: git://github.com/puppetlabs/puppetlabs-stdlib.git
rabbitmq: 'git://github.com/puppetlabs/puppetlabs-rabbitmq'
symlinks:
'openstacklib': "#{source_dir}"

View File

@ -0,0 +1,4 @@
*.swp
spec/fixtures/*
pkg
Gemfile.lock

View File

@ -0,0 +1,31 @@
source 'https://rubygems.org'
group :development, :test do
gem 'puppetlabs_spec_helper', :require => false
gem 'rspec-puppet', '~> 2.1.0', :require => false
gem 'puppet-lint', '~> 1.1.0'
gem 'metadata-json-lint'
gem 'puppet-lint-param-docs'
gem 'puppet-lint-absolute_classname-check'
gem 'puppet-lint-absolute_template_path'
gem 'puppet-lint-trailing_newline-check'
# Puppet 4.x related lint checks
gem 'puppet-lint-unquoted_string-check'
gem 'puppet-lint-leading_zero-check'
gem 'puppet-lint-variable_contains_upcase'
gem 'puppet-lint-numericvariable'
gem 'beaker-rspec', :require => false
gem 'mocha'
gem 'json'
end
if puppetversion = ENV['PUPPET_GEM_VERSION']
gem 'puppet', puppetversion, :require => false
else
gem 'puppet', :require => false
end
# vim:ft=ruby

View File

@ -0,0 +1,13 @@
Copyright 2012 OpenStack Foundation
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.

View File

@ -0,0 +1,341 @@
openstacklib
============
5.1.0 - 2014.2 - Juno
#### Table of Contents
1. [Overview - What is the openstacklib module?](#overview)
2. [Module Description - What does the module do?](#module-description)
3. [Setup - The basics of getting started with openstacklib](#setup)
4. [Implementation - An under-the-hood peek at what the module is doing](#implementation)
5. [Limitations - OS compatibility, etc.](#limitations)
6. [Development - Guide for contributing to the module](#development)
7. [Contributors - Those with commits](#contributors)
8. [Release Notes - Notes on the most recent updates to the module](#release-notes)
Overview
--------
The openstacklib module is a part of [Stackforge](https://github.com/stackforge),
an effort by the Openstack infrastructure team to provide continuous integration
testing and code review for Openstack and Openstack community projects not part
of the core software. The module itself is used to expose common functionality
between Openstack modules as a library that can be utilized to avoid code
duplication.
Module Description
------------------
The openstacklib module is a library module for other Openstack modules to
utilize. A thorough description will be added later.
This module is tested in combination with other modules needed to build and
leverage an entire Openstack software stack. These modules can be found, all
pulled together in the [openstack module](https://github.com/stackforge/puppet-openstack).
Setup
-----
### Installing openstacklib
example% puppet module install puppetlabs/openstacklib
Usage
-----
### Classes and Defined Types
#### Defined type: openstacklib::db::mysql
The db::mysql resource is a library resource that can be used by nova, cinder,
ceilometer, etc., to create a mysql database with configurable privileges for
a user connecting from defined hosts.
Typically this resource will be declared with a notify parameter to configure
the sync command to execute when the database resource is changed.
For example, in heat::db::mysql you might declare:
```
::openstacklib::db::mysql { 'heat':
password_hash => mysql_password($password),
dbname => $dbname,
user => $user,
host => $host,
charset => $charset,
collate => $collate,
allowed_hosts => $allowed_hosts,
notify => Exec['heat-dbsync'],
}
```
Some modules should ensure that the database is created before the service is
set up. For example, in keystone::db::mysql you would have:
```
::openstacklib::db::mysql { 'keystone':
password_hash => mysql_password($password),
dbname => $dbname,
user => $user,
host => $host,
charset => $charset,
collate => $collate,
allowed_hosts => $allowed_hosts,
notify => Exec['keystone-manage db_sync'],
before => Service['keystone'],
}
```
** Parameters for openstacklib::db::mysql: **
#####`password_hash`
Password hash to use for the database user for this service;
string; required
#####`dbname`
The name of the database
string; optional; default to the $title of the resource, i.e. 'nova'
#####`user`
The database user to create;
string; optional; default to the $title of the resource, i.e. 'nova'
#####`host`
The IP address or hostname of the user in mysql_grant;
string; optional; default to '127.0.0.1'
#####`charset`
The charset to use for the database;
string; optional; default to 'utf8'
#####`collate`
The collate to use for the database;
string; optional; default to 'utf8_general_ci'
#####`allowed_hosts`
Additional hosts that are allowed to access this database;
array or string; optional; default to undef
#####`privileges`
Privileges given to the database user;
string or array of strings; optional; default to 'ALL'
#### Defined type: openstacklib::db::postgresql
The db::postgresql resource is a library resource that can be used by nova,
cinder, ceilometer, etc., to create a postgresql database and a user with
configurable privileges.
Typically this resource will be declared with a notify parameter to configure
the sync command to execute when the database resource is changed.
For example, in heat::db::postgresql you might declare:
```
::openstacklib::db::postgresql { $dbname:
password_hash => postgresql_password($user, $password),
dbname => $dbname,
user => $user,
notify => Exec['heat-dbsync'],
}
```
Some modules should ensure that the database is created before the service is
set up. For example, in keystone::db::postgresql you would have:
```
::openstacklib::db::postgresql { $dbname:
password_hash => postgresql_password($user, $password),
dbname => $dbname,
user => $user,
notify => Exec['keystone-manage db_sync'],
before => Service['keystone'],
}
```
** Parameters for openstacklib::db::postgresql: **
#####`password_hash`
Password hash to use for the database user for this service;
string; required
#####`dbname`
The name of the database
string; optional; default to the $title of the resource, i.e. 'nova'
#####`user`
The database user to create;
string; optional; default to the $title of the resource, i.e. 'nova'
#####`encoding`
The encoding use for the database;
string; optional; default to undef
#####`privileges`
Privileges given to the database user;
string or array of strings; optional; default to 'ALL'
#### Defined type: openstacklib::service_validation
The service_validation resource is a library resource that can be used by nova, cinder,
ceilometer, etc., to validate that a resource is actually up and running.
For example, in nova::api you might declare:
```
::openstacklib::service_validation { 'nova-api':
command => 'nova list',
}
```
This defined resource creates an exec-anchor pair where the anchor depends upon
the successful exec run.
** Parameters for openstacklib::service_validation: **
#####`command`
Command to run for validating the service;
string; required
#####`service_name`
The name of the service to validate;
string; optional; default to the $title of the resource, i.e. 'nova-api'
#####`path`
The path of the command to validate the service;
string; optional; default to '/usr/bin:/bin:/usr/sbin:/sbin'
#####`provider`
The provider to use for the exec command;
string; optional; default to 'shell'
#####`tries`
Number of times to retry validation;
string; optional; default to '10'
#####`try_sleep`
Number of seconds between validation attempts;
string; optional; default to '2'
### Types and Providers
#### Aviator
#####`Puppet::add_aviator_params`
The aviator type is not a real type, but it serves to simulate a mixin model,
whereby other types can call out to the Puppet::add\_aviator\_params method in
order to add aviator-specific parameters to themselves. Currently this adds the
auth parameter to the given type. The method must be called after the type is
declared, e.g.:
```puppet
require 'puppet/type/aviator'
Puppet::Type.newtype(:my_type) do
# ...
end
Puppet::add_aviator_params(:my_type)
```
#####`Puppet::Provider::Aviator`
The aviator provider is a parent provider intended to serve as a base for other
providers that need to authenticate against keystone in order to accomplish a
task.
**`Puppet::Provider::Aviator#authenticate`**
Either creates an authenticated session or sets up an unauthenticated session
with instance variables initialized with a token to inject into the next request.
It takes as arguments a set of authentication parameters as a hash and a path
to a log file. Puppet::Provider::Aviator#authencate looks for five different
possible methods of authenticating, in the following order:
1) Username and password credentials in the auth parameters
2) The path to an openrc file containing credentials to read in the auth
parameters
3) A service token in the auth parameters
4) Environment variables set for the environment in which Puppet is running
5) A service token in /etc/keystone/keystone.conf. This option provides
backwards compatibility with earlier keystone providers.
If the provider has password credentials, it can create an authenticated
session. If it only has a service token, it initializes an unauthenciated
session and a hash of session data that can be injected into a future request.
**`Puppet::Provider::Aviator#make_request`**
After creating a session, the make\_request method provides an interface that
providers can use to make requests without worrying about whether they have an
authenticated or unauthenticated session. It takes as arguments the
Aviator::Service it is making a request at (for example, keystone), a symbol for
the request (for example, :list\_tenants), and optionally a block to execute
that will set parameters for an update request.
Implementation
--------------
### openstacklib
openstacklib is a combination of Puppet manifest and ruby code to delivery
configuration and extra functionality through types and providers.
Limitations
-----------
The python-migrate system package for RHEL 6 and below is out of date and may
fail to correctly migrate postgresql databases. While this module does not
handle database migrations, it is common to set up refresh relationships
between openstacklib::db::postgresql resource and the database sync exec
resource. Relying on this behavior may cause errors.
Beaker-Rspec
------------
This module has beaker-rspec tests
To run:
```shell
bundle install
bundle exec rspec spec/acceptance
```
Development
-----------
Developer documentation for the entire puppet-openstack project.
* https://wiki.openstack.org/wiki/Puppet-openstack#Developer_documentation
Contributors
------------
* https://github.com/stackforge/puppet-openstacklib/graphs/contributors
Versioning
----------
This module has been given version 5 to track the puppet-openstack modules. The
versioning for the puppet-openstack modules are as follows:
```
Puppet Module :: OpenStack Version :: OpenStack Codename
2.0.0 -> 2013.1.0 -> Grizzly
3.0.0 -> 2013.2.0 -> Havana
4.0.0 -> 2014.1.0 -> Icehouse
5.0.0 -> 2014.2.0 -> Juno
```
Release Notes
-------------
**5.1.0**
* Update .gitreview file for project rename
* Adding augeas insertion check
* MySQL: change default MySQL collate to utf8_general_ci
* spec: pin rspec-puppet to 1.0.1
**5.0.0**
* This is the initial release of this module.

View File

@ -0,0 +1,7 @@
require 'rubygems'
require 'puppetlabs_spec_helper/rake_tasks'
require 'puppet-lint/tasks/puppet-lint'
PuppetLint.configuration.fail_on_warnings = true
PuppetLint.configuration.send('disable_80chars')
PuppetLint.configuration.ignore_paths = ["spec/**/*.pp", "pkg/**/*.pp"]

View File

@ -0,0 +1,72 @@
require 'puppet/parser/functions'
Puppet::Parser::Functions.newfunction(:os_database_connection,
:type => :rvalue,
:doc => <<-EOS
This function builds a os_database_connection string from various parameters.
EOS
) do |arguments|
require 'uri'
if (arguments.size != 1) then
raise(Puppet::ParseError, "os_database_connection(): Wrong number of arguments " +
"given (#{arguments.size} for 1)")
end
v = arguments[0]
klass = v.class
unless klass == Hash
raise(Puppet::ParseError, "os_database_connection(): Requires an hash, got #{klass}")
end
v.keys.each do |key|
unless (v[key].class == String) or (v[key] == :undef)
raise(Puppet::ParseError, "os_database_connection(): #{key} should be a String")
end
end
parts = {}
unless v.include?('dialect')
raise(Puppet::ParseError, 'os_database_connection(): dialect is required')
end
if v.include?('host')
parts[:host] = v['host']
end
unless v.include?('database')
raise(Puppet::ParseError, 'os_database_connection(): database is required')
end
if v.include?('port')
if v.include?('host')
parts[:port] = v['port'].to_i
else
raise(Puppet::ParseError, 'os_database_connection(): host is required with port')
end
end
if v.include?('username') and (v['username'] != :undef) and (v['username'].to_s != '')
parts[:userinfo] = URI.escape(v['username'])
if v.include?('password') and (v['password'] != :undef) and (v['password'].to_s != '')
parts[:userinfo] += ":#{URI.escape(v['password'])}"
end
end
if v.include?('charset')
parts[:query] = "charset=#{v['charset']}"
end
parts[:scheme] = v['dialect']
if v.include?('host')
parts[:path] = "/#{v['database']}"
else
parts[:path] = "///#{v['database']}"
end
URI::Generic.build(parts).to_s
end

View File

@ -0,0 +1,84 @@
require 'csv'
require 'puppet'
class Puppet::Error::OpenstackAuthInputError < Puppet::Error
end
class Puppet::Error::OpenstackUnauthorizedError < Puppet::Error
end
class Puppet::Provider::Openstack < Puppet::Provider
initvars # so commands will work
commands :openstack => 'openstack'
# Returns an array of hashes, where the keys are the downcased CSV headers
# with underscores instead of spaces
def self.request(service, action, properties, credentials=nil)
env = credentials ? credentials.to_env : {}
Puppet::Util.withenv(env) do
rv = nil
timeout = 10
end_time = Time.now.to_i + timeout
loop do
begin
if(action == 'list')
response = openstack(service, action, '--quiet', '--format', 'csv', properties)
response = parse_csv(response)
keys = response.delete_at(0) # ID,Name,Description,Enabled
rv = response.collect do |line|
hash = {}
keys.each_index do |index|
key = keys[index].downcase.gsub(/ /, '_').to_sym
hash[key] = line[index]
end
hash
end
elsif(action == 'show' || action == 'create')
rv = {}
# shell output is name="value"\nid="value2"\ndescription="value3" etc.
openstack(service, action, '--format', 'shell', properties).split("\n").each do |line|
# key is everything before the first "="
key, val = line.split("=", 2)
next unless val # Ignore warnings
# value is everything after the first "=", with leading and trailing double quotes stripped
val = val.gsub(/\A"|"\Z/, '')
rv[key.downcase.to_sym] = val
end
else
rv = openstack(service, action, properties)
end
break
rescue Puppet::ExecutionFailure => e
if e.message =~ /HTTP 401/
raise(Puppet::Error::OpenstackUnauthorizedError, 'Could not authenticate.')
elsif e.message =~ /Unable to establish connection/
current_time = Time.now.to_i
if current_time > end_time
break
else
wait = end_time - current_time
Puppet::debug("Non-fatal error: \"#{e.message}\"; retrying for #{wait} more seconds.")
if wait > timeout - 2 # Only notice the first time
notice("#{service} service is unavailable. Will retry for up to #{wait} seconds.")
end
end
sleep(2)
else
raise e
end
end
end
return rv
end
end
private
def self.parse_csv(text)
# Ignore warnings - assume legitimate output starts with a double quoted
# string. Errors will be caught and raised prior to this
text = text.split("\n").drop_while { |line| line !~ /^\".*\"/ }.join("\n")
return CSV.parse(text + "\n")
end
end

View File

@ -0,0 +1,49 @@
require 'puppet/provider/openstack/credentials'
module Puppet::Provider::Openstack::Auth
RCFILENAME = "#{ENV['HOME']}/openrc"
def get_os_vars_from_env
env = {}
ENV.each { |k,v| env.merge!(k => v) if k =~ /^OS_/ }
return env
end
def get_os_vars_from_rcfile(filename)
env = {}
if File.exists?(filename)
File.open(filename).readlines.delete_if{|l| l=~ /^#|^$/ }.each do |line|
key, value = line.split('=')
key = key.split(' ').last
value = value.chomp.gsub(/'/, '')
env.merge!(key => value) if key =~ /OS_/
end
end
return env
end
def rc_filename
RCFILENAME
end
def request(service, action, properties=nil)
properties ||= []
set_credentials(@credentials, get_os_vars_from_env)
unless @credentials.set?
@credentials.unset
set_credentials(@credentials, get_os_vars_from_rcfile(rc_filename))
end
unless @credentials.set?
raise(Puppet::Error::OpenstackAuthInputError, 'Insufficient credentials to authenticate')
end
super(service, action, properties, @credentials)
end
def set_credentials(creds, env)
env.each do |key, val|
var = key.sub(/^OS_/,'').downcase
creds.set(var, val)
end
end
end

View File

@ -0,0 +1,89 @@
require 'puppet'
require 'puppet/provider/openstack'
class Puppet::Provider::Openstack::Credentials
KEYS = [
:auth_url, :password, :project_name, :username,
:token, :url,
:identity_api_version
]
KEYS.each { |var| attr_accessor var }
def self.defined?(name)
KEYS.include?(name.to_sym)
end
def set(key, val)
if self.class.defined?(key.to_sym)
self.instance_variable_set("@#{key}".to_sym, val)
end
end
def set?
return true if user_password_set? || service_token_set?
end
def service_token_set?
return true if @token && @url
end
def to_env
env = {}
self.instance_variables.each do |var|
name = var.to_s.sub(/^@/,'OS_').upcase
env.merge!(name => self.instance_variable_get(var))
end
env
end
def user_password_set?
return true if @username && @password && @project_name && @auth_url
end
def unset
list = KEYS.delete_if { |key, val| key == :identity_api_version }
list.each { |key, val| self.set(key, '') if self.class.defined?("@#{key}".to_sym) }
end
def version
self.class.to_s.sub(/.*V/,'').sub('_','.')
end
end
class Puppet::Provider::Openstack::CredentialsV2_0 < Puppet::Provider::Openstack::Credentials
end
class Puppet::Provider::Openstack::CredentialsV3 < Puppet::Provider::Openstack::Credentials
KEYS = [
:cacert,
:cert,
:default_domain,
:domain_id,
:domain_name,
:key,
:project_domain_id,
:project_domain_name,
:project_id,
:trust_id,
:user_domain_id,
:user_domain_name,
:user_id
]
KEYS.each { |var| attr_accessor var }
def self.defined?(name)
KEYS.include?(name.to_sym) || super
end
def user_password_set?
return true if (@username || @user_id) && @password && (@project_name || @project_id) && @auth_url
end
def initialize
set(:identity_api_version, version)
end
end

View File

@ -0,0 +1,68 @@
# == Definition: openstacklib::db::mysql
#
# This resource configures a mysql database for an OpenStack service
#
# == Parameters:
#
# [*password_hash*]
# Password hash to use for the database user for this service;
# string; required
#
# [*dbname*]
# The name of the database
# string; optional; default to the $title of the resource, i.e. 'nova'
#
# [*user*]
# The database user to create;
# string; optional; default to the $title of the resource, i.e. 'nova'
#
# [*host*]
# The IP address or hostname of the user in mysql_grant;
# string; optional; default to '127.0.0.1'
#
# [*charset*]
# The charset to use for the database;
# string; optional; default to 'utf8'
#
# [*collate*]
# The collate to use for the database;
# string; optional; default to 'utf8_general_ci'
#
# [*allowed_hosts*]
# Additional hosts that are allowed to access this database;
# array or string; optional; default to undef
#
# [*privileges*]
# Privileges given to the database user;
# string or array of strings; optional; default to 'ALL'
define openstacklib::db::mysql (
$password_hash,
$dbname = $title,
$user = $title,
$host = '127.0.0.1',
$charset = 'utf8',
$collate = 'utf8_general_ci',
$allowed_hosts = [],
$privileges = 'ALL',
) {
include ::mysql::client
mysql_database { $dbname:
ensure => present,
charset => $charset,
collate => $collate,
require => [ Class['mysql::server'], Class['mysql::client'] ],
}
$allowed_hosts_list = unique(concat(any2array($allowed_hosts), [$host]))
$real_allowed_hosts = prefix($allowed_hosts_list, "${dbname}_")
openstacklib::db::mysql::host_access { $real_allowed_hosts:
user => $user,
password_hash => $password_hash,
database => $dbname,
privileges => $privileges,
}
}

View File

@ -0,0 +1,41 @@
# Allow a user to access the database for the service
#
# == Namevar
# String with the form dbname_host. The host part of the string is the host
# to allow
#
# == Parameters
# [*user*]
# username to allow
#
# [*password_hash*]
# user password hash
#
# [*database*]
# the database name
#
# [*privileges*]
# the privileges to grant to this user
#
define openstacklib::db::mysql::host_access (
$user,
$password_hash,
$database,
$privileges,
) {
validate_re($title, '_', 'Title must be $dbname_$host')
$host = inline_template('<%= @title.split("_").last %>')
mysql_user { "${user}@${host}":
password_hash => $password_hash,
require => Mysql_database[$database],
}
mysql_grant { "${user}@${host}/${database}.*":
privileges => $privileges,
table => "${database}.*",
require => Mysql_user["${user}@${host}"],
user => "${user}@${host}",
}
}

View File

@ -0,0 +1,46 @@
# == Definition: openstacklib::db::postgresql
#
# This resource configures a postgresql database for an OpenStack service
#
# == Parameters:
#
# [*password_hash*]
# Password hash to use for the database user for this service;
# string; required
#
# [*dbname*]
# The name of the database
# string; optional; default to the $title of the resource, i.e. 'nova'
#
# [*user*]
# The database user to create;
# string; optional; default to the $title of the resource, i.e. 'nova'
#
# [*encoding*]
# The charset to use for the database;
# string; optional; default to undef
#
# [*privileges*]
# Privileges given to the database user;
# string or array of strings; optional; default to 'ALL'
define openstacklib::db::postgresql (
$password_hash,
$dbname = $title,
$user = $title,
$encoding = undef,
$privileges = 'ALL',
){
if ((($::operatingsystem == 'RedHat' or $::operatingsystem == 'CentOS') and (versioncmp($::operatingsystemmajrelease, '6') <= 0))
or ($::operatingsystem == 'Fedora' and (versioncmp($::operatingsystemmajrelease, '14') <= 0))) {
warning('The system packages handling the postgresql infrastructure for OpenStack are out of date and should not be relied on for database migrations.')
}
postgresql::server::db { $dbname:
user => $user,
password => $password_hash,
encoding => $encoding,
grant => $privileges,
}
}

View File

@ -0,0 +1,105 @@
#
# Copyright (C) 2014 eNovance SAS <licensing@enovance.com>
#
# Author: Emilien Macchi <emilien.macchi@enovance.com>
#
# 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.
#
# == Definition: openstacklib::messaging::rabbitmq
#
# This resource creates RabbitMQ resources for an OpenStack service.
#
# == Parameters:
#
# [*userid*]
# (optional) The username to use when connecting to Rabbit
# Defaults to 'guest'
#
# [*password*]
# (optional) The password to use when connecting to Rabbit
# Defaults to 'guest'
#
# [*virtual_host*]
# (optional) The virtual host to use when connecting to Rabbit
# Defaults to '/'
#
# [*is_admin*]
# (optional) If the user should be admin or not
# Defaults to false
#
# [*configure_permission*]
# (optional) Define configure permission
# Defaults to '.*'
#
# [*write_permission*]
# (optional) Define write permission
# Defaults to '.*'
#
# [*read_permission*]
# (optional) Define read permission
# Defaults to '.*'
#
# [*manage_user*]
# (optional) Manage or not the user
# Defaults to true
#
# [*manage_user_permissions*]
# (optional) Manage or not user permissions
# Defaults to true
#
# [*manage_vhost*]
# (optional) Manage or not the vhost
# Defaults to true
#
define openstacklib::messaging::rabbitmq(
$userid = 'guest',
$password = 'guest',
$virtual_host = '/',
$is_admin = false,
$configure_permission = '.*',
$write_permission = '.*',
$read_permission = '.*',
$manage_user = true,
$manage_user_permissions = true,
$manage_vhost = true,
) {
if $manage_user {
if $userid == 'guest' {
$is_admin_real = false
} else {
$is_admin_real = $is_admin
}
ensure_resource('rabbitmq_user', $userid, {
'admin' => $is_admin_real,
'password' => $password,
'provider' => 'rabbitmqctl',
})
}
if $manage_user_permissions {
ensure_resource('rabbitmq_user_permissions', "${userid}@${virtual_host}", {
'configure_permission' => $configure_permission,
'write_permission' => $write_permission,
'read_permission' => $read_permission,
'provider' => 'rabbitmqctl',
})
}
if $manage_vhost {
ensure_resource('rabbitmq_vhost', $virtual_host, {
'provider' => 'rabbitmqctl',
})
}
}

View File

@ -0,0 +1,18 @@
# == Class: openstacklib::openstackclient
#
# Installs the openstackclient
#
# == Parameters
#
# [*package_ensure*]
# Ensure state of the openstackclient package.
# Optional. Defaults to 'present'.
#
class openstacklib::openstackclient(
$package_ensure = 'present',
){
package { 'python-openstackclient':
ensure => $package_ensure,
tag => 'openstack',
}
}

View File

@ -0,0 +1,19 @@
# == Class: openstacklib::policies
#
# This resource is an helper to call the policy definition
#
# == Parameters:
#
# [*policies*]
# Hash of policies one would like to set to specific values
# hash; optional
#
class openstacklib::policy (
$policies = {},
) {
validate_hash($policies)
create_resources('openstacklib::policy::base', $policies)
}

View File

@ -0,0 +1,45 @@
# == Definition: openstacklib::policy::base
#
# This resource configures the policy.json file for an OpenStack service
#
# == Parameters:
#
# [*file_path*]
# Path to the policy.json file
# string; required
#
# [*key*]
# The key to replace the value for
# string; required; the key to replace the value for
#
# [*value*]
# The value to set
# string; optional; the value to set
#
define openstacklib::policy::base (
$file_path,
$key,
$value = '',
) {
# Add entry if it doesn't exists
augeas { "${file_path}-${key}-${value}-add":
lens => 'Json.lns',
incl => $file_path,
changes => [
"set dict/entry[last()+1] \"${key}\"",
"set dict/entry[last()]/string \"${value}\"",
],
onlyif => "match dict/entry[*][.=\"${key}\"] size == 0",
}
# Requires that the entry is added before this call or it will fail.
augeas { "${file_path}-${key}-${value}" :
lens => 'Json.lns',
incl => $file_path,
changes => "set dict/entry[*][.=\"${key}\"]/string \"${value}\"",
require => Augeas["${file_path}-${key}-${value}-add"],
}
}

View File

@ -0,0 +1,69 @@
#
# Copyright (C) 2014 eNovance SAS <licensing@enovance.com>
#
# Author: Emilien Macchi <emilien.macchi@enovance.com>
#
# 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.
#
# == Definition: openstacklib::service_validation
#
# This resource does service validation for an OpenStack service.
#
# == Parameters:
#
# [*command*]
# Command to run for validating the service;
# string; required
#
# [*service_name*]
# The name of the service to validate;
# string; optional; default to the $title of the resource, i.e. 'nova-api'
#
# [*path*]
# The path of the command to validate the service;
# string; optional; default to '/usr/bin:/bin:/usr/sbin:/sbin'
#
# [*provider*]
# The provider to use for the exec command;
# string; optional; default to 'shell'
#
# [*tries*]
# Number of times to retry validation;
# string; optional; default to '10'
#
# [*try_sleep*]
# Number of seconds between validation attempts;
# string; optional; default to '2'
#
define openstacklib::service_validation(
$command,
$service_name = $name,
$path = '/usr/bin:/bin:/usr/sbin:/sbin',
$provider = shell,
$tries = '10',
$try_sleep = '2',
) {
exec { "execute ${service_name} validation":
path => $path,
provider => $provider,
command => $command,
tries => $tries,
try_sleep => $try_sleep,
}
anchor { "create ${service_name} anchor":
require => Exec["execute ${service_name} validation"],
}
}

View File

@ -0,0 +1,204 @@
#
# Copyright (C) 2014 eNovance SAS <licensing@enovance.com>
#
# Author: Emilien Macchi <emilien.macchi@enovance.com>
#
# 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.
#
# == Class: openstacklib::wsgi::apache
#
# Serve a service with apache mod_wsgi
# When using this class you should disable your service.
#
# == Parameters
#
# [*service_name*]
# (optional) Name of the service to run.
# Example: nova-api
# Defaults to $name
#
# [*servername*]
# (optional) The servername for the virtualhost.
# Defaults to $::fqdn
#
# [*bind_host*]
# (optional) The host/ip address Apache will listen on.
# Defaults to undef (listen on all ip addresses).
#
# [*bind_port*]
# (optional) The port to listen.
# Defaults to undef
#
# [*group*]
# (optional) Group with permissions on the script
# Defaults to undef
#
# [*path*]
# (optional) The prefix for the endpoint.
# Defaults to '/'
#
# [*priority*]
# (optional) The priority for the vhost.
# Defaults to '10'
#
# [*ssl*]
# (optional) Use ssl ? (boolean)
# Defaults to false
#
# [*ssl_cert*]
# (optional) Path to SSL certificate
# Default to apache::vhost 'ssl_*' defaults.
#
# [*ssl_key*]
# (optional) Path to SSL key
# Default to apache::vhost 'ssl_*' defaults.
#
# [*ssl_chain*]
# (optional) SSL chain
# Default to apache::vhost 'ssl_*' defaults.
#
# [*ssl_ca*]
# (optional) Path to SSL certificate authority
# Default to apache::vhost 'ssl_*' defaults.
#
# [*ssl_crl_path*]
# (optional) Path to SSL certificate revocation list
# Default to apache::vhost 'ssl_*' defaults.
#
# [*ssl_crl*]
# (optional) SSL certificate revocation list name
# Default to apache::vhost 'ssl_*' defaults.
#
# [*ssl_certs_dir*]
# (optional) Path to SSL certificate directory
# Default to apache::vhost 'ssl_*' defaults.
#
# [*threads*]
# (optional) The number of threads for the vhost.
# Defaults to $::processorcount
#
# [*user*]
# (optional) User with permissions on the script
# Defaults to undef
#
# [*workers*]
# (optional) The number of workers for the vhost.
# Defaults to '1'
#
# [*wsgi_daemon_process*]
# (optional) Name of the WSGI daemon process.
# Defaults to $name
#
# [*wsgi_process_group*]
# (optional) Name of the WSGI process group.
# Defaults to $name
#
# [*wsgi_script_dir*]
# (optional) The directory path of the WSGI script.
# Defaults to undef
#
# [*wsgi_script_file*]
# (optional) The file path of the WSGI script.
# Defaults to undef
#
# [*wsgi_script_source*]
# (optional) The source of the WSGI script.
# Defaults to undef
#
define openstacklib::wsgi::apache (
$service_name = $name,
$bind_host = undef,
$bind_port = undef,
$group = undef,
$path = '/',
$priority = '10',
$servername = $::fqdn,
$ssl = false,
$ssl_ca = undef,
$ssl_cert = undef,
$ssl_certs_dir = undef,
$ssl_chain = undef,
$ssl_crl = undef,
$ssl_crl_path = undef,
$ssl_key = undef,
$threads = $::processorcount,
$user = undef,
$workers = 1,
$wsgi_daemon_process = $name,
$wsgi_process_group = $name,
$wsgi_script_dir = undef,
$wsgi_script_file = undef,
$wsgi_script_source = undef,
) {
include ::apache
include ::apache::mod::wsgi
if $ssl {
include ::apache::mod::ssl
}
# Ensure there's no trailing '/' except if this is also the only character
$path_real = regsubst($path, '(^/.*)/$', '\1')
if !defined(File[$wsgi_script_dir]) {
file { $wsgi_script_dir:
ensure => directory,
owner => $user,
group => $group,
require => Package['httpd'],
}
}
file { $service_name:
ensure => file,
path => "${wsgi_script_dir}/${wsgi_script_file}",
source => $wsgi_script_source,
owner => $user,
group => $group,
mode => '0644',
require => File[$wsgi_script_dir],
}
$wsgi_daemon_process_options = {
user => $user,
group => $group,
processes => $workers,
threads => $threads,
}
$wsgi_script_aliases = hash([$path_real,"${wsgi_script_dir}/${wsgi_script_file}"])
::apache::vhost { $service_name:
ensure => 'present',
servername => $servername,
ip => $bind_host,
port => $bind_port,
docroot => $wsgi_script_dir,
docroot_owner => $user,
docroot_group => $group,
priority => $priority,
ssl => $ssl,
ssl_cert => $ssl_cert,
ssl_key => $ssl_key,
ssl_chain => $ssl_chain,
ssl_ca => $ssl_ca,
ssl_crl_path => $ssl_crl_path,
ssl_crl => $ssl_crl,
ssl_certs_dir => $ssl_certs_dir,
wsgi_daemon_process => $wsgi_daemon_process,
wsgi_daemon_process_options => $wsgi_daemon_process_options,
wsgi_process_group => $wsgi_process_group,
wsgi_script_aliases => $wsgi_script_aliases,
require => File[$service_name],
}
}

View File

@ -0,0 +1,40 @@
{
"name": "stackforge-openstacklib",
"version": "5.1.0",
"author": "Puppet Labs and OpenStack Contributors",
"summary": "Puppet OpenStack Libraries",
"license": "Apache-2.0",
"source": "git://github.com/openstack/puppet-openstacklib.git",
"project_page": "https://launchpad.net/puppet-openstacklib",
"issues_url": "https://bugs.launchpad.net/puppet-openstacklib",
"requirements": [
{ "name": "pe","version_requirement": "3.x" },
{ "name": "puppet","version_requirement": "3.x" }
],
"operatingsystem_support": [
{
"operatingsystem": "Debian",
"operatingsystemrelease": ["7"]
},
{
"operatingsystem": "Fedora",
"operatingsystemrelease": ["20"]
},
{
"operatingsystem": "RedHat",
"operatingsystemrelease": ["6.5","7"]
},
{
"operatingsystem": "Ubuntu",
"operatingsystemrelease": ["12.04","14.04"]
}
],
"description": "Puppet module library to expose common functionality between OpenStack modules.",
"dependencies": [
{ "name": "puppetlabs/apache", "version_requirement": ">=1.0.0 <2.0.0" },
{ "name": "puppetlabs/mysql", "version_requirement": ">=3.0.0 <4.0.0" },
{ "name": "puppetlabs/stdlib", "version_requirement": ">=4.0.0 <5.0.0" },
{ "name": "puppetlabs/rabbitmq", "version_requirement": ">=2.0.2 <4.0.0" },
{ "name": "puppetlabs/postgresql", "version_requirement": ">=3.3.0 <4.0.0" }
]
}

View File

@ -0,0 +1,35 @@
require 'spec_helper_acceptance'
describe 'openstacklib mysql' do
context 'default parameters' do
it 'should work with no errors' do
pp= <<-EOS
Exec { logoutput => 'on_failure' }
class { '::mysql::server': }
::openstacklib::db::mysql { 'beaker':
password_hash => mysql_password('keystone'),
allowed_hosts => '127.0.0.1',
}
EOS
# Run it twice and test for idempotency
apply_manifest(pp, :catch_failures => true)
apply_manifest(pp, :catch_changes => true)
end
describe port(3306) do
it { is_expected.to be_listening.with('tcp') }
end
describe 'test database listing' do
it 'should list beaker database' do
expect(shell("mysql -e 'show databases;'|grep -q beaker").exit_code).to be_zero
end
end
end
end

View File

@ -0,0 +1,9 @@
HOSTS:
ubuntu-14.04-amd64:
roles:
- master
platform: ubuntu-14.04-amd64
hypervisor : none
ip: 127.0.0.1
CONFIG:
type: foss

View File

@ -0,0 +1,10 @@
HOSTS:
centos-70-x64:
roles:
- master
platform: el-7-x86_64
hypervisor : none
ip: 127.0.0.1
CONFIG:
type: foss
set_env: false

View File

@ -0,0 +1,10 @@
HOSTS:
ubuntu-14.04-amd64:
roles:
- master
platform: ubuntu-14.04-amd64
hypervisor : none
ip: 127.0.0.1
CONFIG:
type: foss
set_env: false

View File

@ -0,0 +1,66 @@
require 'spec_helper_acceptance'
describe 'openstacklib class' do
context 'default parameters' do
it 'should work with no errors' do
pp= <<-EOS
Exec { logoutput => 'on_failure' }
if $::osfamily == 'RedHat' {
# RabbitMQ is not available in default repo
class { '::openstack_extras::repo::redhat::redhat':
# Kilo is not GA yet, so let's use the testing repo
manage_rdo => false,
repo_hash => {
'rdo-kilo-testing' => {
'baseurl' => 'https://repos.fedorapeople.org/repos/openstack/openstack-kilo/testing/el7/',
# packages are not GA so not signed
'gpgcheck' => '0',
'priority' => 97,
},
},
}
$package_provider = 'yum'
} else {
$package_provider = 'apt'
}
class { '::rabbitmq':
delete_guest_user => true,
erlang_cookie => 'secrete',
package_provider => $package_provider
}
# openstacklib resources
include ::openstacklib::openstackclient
::openstacklib::messaging::rabbitmq { 'beaker':
userid => 'beaker',
is_admin => true,
}
EOS
# Run it twice and test for idempotency
apply_manifest(pp, :catch_failures => true)
apply_manifest(pp, :catch_changes => true)
end
describe 'test rabbitmq resources' do
it 'should list rabbitmq beaker resources' do
shell('rabbitmqctl list_users') do |r|
expect(r.stdout).to match(/^beaker/)
expect(r.stdout).not_to match(/^guest/)
expect(r.exit_code).to eq(0)
end
shell('rabbitmqctl list_permissions') do |r|
expect(r.stdout).to match(/^beaker\t\.\*\t\.\*\t\.\*$/)
expect(r.exit_code).to eq(0)
end
end
end
end
end

View File

@ -0,0 +1 @@
require 'spec_helper'

View File

@ -0,0 +1,25 @@
require 'spec_helper'
describe 'openstacklib::policy' do
let :params do
{
:policies => {
'foo' => {
'file_path' => '/etc/nova/policy.json',
'key' => 'context_is_admin',
'value' => 'foo:bar'
}
}
}
end
it 'configures the proper policy' do
is_expected.to contain_openstacklib__policy__base('foo').with(
:file_path => '/etc/nova/policy.json',
:key => 'context_is_admin',
:value => 'foo:bar'
)
end
end

View File

@ -0,0 +1,50 @@
require 'spec_helper'
describe 'openstacklib::db::mysql::host_access' do
let :pre_condition do
"include mysql::server\n" +
"openstacklib::db::mysql { 'nova':\n" +
" password_hash => 'AA1420F182E88B9E5F874F6FBE7459291E8F4601'}"
end
shared_examples 'openstacklib::db::mysql::host_access examples' do
context 'with required parameters' do
let (:title) { 'nova_10.0.0.1' }
let :params do
{ :user => 'foobar',
:password_hash => 'AA1420F182E88B9E5F874F6FBE7459291E8F4601',
:database => 'nova',
:privileges => 'ALL' }
end
it { is_expected.to contain_mysql_user("#{params[:user]}@10.0.0.1").with(
:password_hash => params[:password_hash]
)}
it { is_expected.to contain_mysql_grant("#{params[:user]}@10.0.0.1/#{params[:database]}.*").with(
:user => "#{params[:user]}@10.0.0.1",
:privileges => 'ALL',
:table => "#{params[:database]}.*"
)}
end
end
context 'on a Debian osfamily' do
let :facts do
{ :osfamily => "Debian" }
end
include_examples 'openstacklib::db::mysql::host_access examples'
end
context 'on a RedHat osfamily' do
let :facts do
{ :osfamily => 'RedHat' }
end
include_examples 'openstacklib::db::mysql::host_access examples'
end
end

View File

@ -0,0 +1,163 @@
require 'spec_helper'
describe 'openstacklib::db::mysql' do
let :pre_condition do
'include mysql::server'
end
let (:title) { 'nova' }
let :required_params do
{ :password_hash => 'AA1420F182E88B9E5F874F6FBE7459291E8F4601' }
end
shared_examples 'openstacklib::db::mysql examples' do
context 'with only required parameters' do
let :params do
required_params
end
it { is_expected.to contain_mysql_database(title).with(
:charset => 'utf8',
:collate => 'utf8_general_ci'
)}
it { is_expected.to contain_openstacklib__db__mysql__host_access("#{title}_127.0.0.1").with(
:user => title,
:database => title,
:privileges => 'ALL'
)}
end
context 'with overriding dbname parameter' do
let :params do
{ :dbname => 'foobar' }.merge(required_params)
end
it { is_expected.to contain_mysql_database(params[:dbname]).with(
:charset => 'utf8',
:collate => 'utf8_general_ci'
)}
it { is_expected.to contain_openstacklib__db__mysql__host_access("#{params[:dbname]}_127.0.0.1").with(
:user => title,
:database => params[:dbname],
:privileges => 'ALL'
)}
end
context 'with overriding user parameter' do
let :params do
{ :user => 'foobar' }.merge(required_params)
end
it { is_expected.to contain_mysql_database(title).with(
:charset => 'utf8',
:collate => 'utf8_general_ci'
)}
it { is_expected.to contain_openstacklib__db__mysql__host_access("#{title}_127.0.0.1").with(
:user => params[:user],
:database => title,
:privileges => 'ALL',
)}
end
context 'when overriding charset parameter' do
let :params do
{ :charset => 'latin1' }.merge(required_params)
end
it { is_expected.to contain_mysql_database(title).with_charset(params[:charset]) }
end
context 'when omitting the required parameter password_hash' do
let :params do
required_params.delete(:password_hash)
end
it { expect { is_expected.to raise_error(Puppet::Error) } }
end
context 'when notifying other resources' do
let :pre_condition do
'exec {"nova-db-sync":}'
end
let :params do
{ :notify => 'Exec[nova-db-sync]'}.merge(required_params)
end
it { is_expected.to contain_exec('nova-db-sync').that_subscribes_to("Openstacklib::Db::Mysql[#{title}]") }
end
context 'when required for other openstack services' do
let :pre_condition do
'service {"keystone":}'
end
let :title do
'keystone'
end
let :params do
{ :before => 'Service[keystone]'}.merge(required_params)
end
it { is_expected.to contain_service('keystone').that_requires("Openstacklib::Db::Mysql[keystone]") }
end
context "overriding allowed_hosts parameter with array value" do
let :params do
{ :allowed_hosts => ['127.0.0.1','%'] }.merge(required_params)
end
it {is_expected.to contain_openstacklib__db__mysql__host_access("#{title}_127.0.0.1").with(
:user => title,
:password_hash => params[:password_hash],
:database => title
)}
it {is_expected.to contain_openstacklib__db__mysql__host_access("#{title}_%").with(
:user => title,
:password_hash => params[:password_hash],
:database => title
)}
end
context "overriding allowed_hosts parameter with string value" do
let :params do
{ :allowed_hosts => '192.168.1.1' }.merge(required_params)
end
it {is_expected.to contain_openstacklib__db__mysql__host_access("#{title}_192.168.1.1").with(
:user => title,
:password_hash => params[:password_hash],
:database => title
)}
end
context "overriding allowed_hosts parameter equals to host param " do
let :params do
{ :allowed_hosts => '127.0.0.1' }.merge(required_params)
end
it {is_expected.to contain_openstacklib__db__mysql__host_access("#{title}_127.0.0.1").with(
:user => title,
:password_hash => params[:password_hash],
:database => title
)}
end
end
context 'on a Debian osfamily' do
let :facts do
{ :osfamily => "Debian" }
end
include_examples 'openstacklib::db::mysql examples'
end
context 'on a RedHat osfamily' do
let :facts do
{ :osfamily => 'RedHat' }
end
include_examples 'openstacklib::db::mysql examples'
end
end

View File

@ -0,0 +1,133 @@
require 'spec_helper'
describe 'openstacklib::db::postgresql' do
password_hash = 'AA1420F182E88B9E5F874F6FBE7459291E8F4601'
title = 'nova'
let (:title) { title }
let :required_params do
{ :password_hash => password_hash }
end
context 'on a RedHat osfamily' do
let :facts do
{
:postgres_default_version => '8.4',
:osfamily => 'RedHat'
}
end
context 'with only required parameters' do
let :params do
required_params
end
it { is_expected.to contain_postgresql__server__db(title).with(
:user => title,
:password => password_hash
)}
end
context 'when overriding encoding' do
let :params do
{ :encoding => 'latin1' }.merge(required_params)
end
it { is_expected.to contain_postgresql__server__db(title).with_encoding(params[:encoding]) }
end
context 'when omitting the required parameter password_hash' do
let :params do
required_params.delete(:password_hash)
end
it { expect { is_expected.to raise_error(Puppet::Error) } }
end
context 'when notifying other resources' do
let :pre_condition do
'exec { "nova-db-sync": }'
end
let :params do
{ :notify => 'Exec[nova-db-sync]'}.merge(required_params)
end
it {is_expected.to contain_exec('nova-db-sync').that_subscribes_to("Openstacklib::Db::Postgresql[#{title}]") }
end
context 'when required for other openstack services' do
let :pre_condition do
'service {"keystone":}'
end
let :title do
'keystone'
end
let :params do
{ :before => 'Service[keystone]'}.merge(required_params)
end
it { is_expected.to contain_service('keystone').that_requires("Openstacklib::Db::Postgresql[keystone]") }
end
end
context 'on a Debian osfamily' do
let :facts do
{
:osfamily => 'Debian'
}
end
context 'with only required parameters' do
let :params do
required_params
end
it { is_expected.to contain_postgresql__server__db(title).with(
:user => title,
:password => password_hash
)}
end
context 'when overriding encoding' do
let :params do
{ :encoding => 'latin1' }.merge(required_params)
end
it { is_expected.to contain_postgresql__server__db(title).with_encoding(params[:encoding]) }
end
context 'when omitting the required parameter password_hash' do
let :params do
required_params.delete(:password_hash)
end
it { expect { is_expected.to raise_error(Puppet::Error) } }
end
context 'when notifying other resources' do
let :pre_condition do
'exec { "nova-db-sync": }'
end
let :params do
{ :notify => 'Exec[nova-db-sync]'}.merge(required_params)
end
it {is_expected.to contain_exec('nova-db-sync').that_subscribes_to("Openstacklib::Db::Postgresql[#{title}]") }
end
context 'when required for other openstack services' do
let :pre_condition do
'service {"keystone":}'
end
let :title do
'keystone'
end
let :params do
{ :before => 'Service[keystone]'}.merge(required_params)
end
it { is_expected.to contain_service('keystone').that_requires("Openstacklib::Db::Postgresql[keystone]") }
end
end
end

View File

@ -0,0 +1,101 @@
#
# Copyright (C) 2014 eNovance SAS <licensing@enovance.com>
#
# Author: Emilien Macchi <emilien.macchi@enovance.com>
#
# 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.
require 'spec_helper'
describe 'openstacklib::messaging::rabbitmq' do
let (:title) { 'nova' }
shared_examples 'openstacklib::messaging::rabbitmq examples' do
let :params do
{}
end
context 'with default parameters' do
it { is_expected.to contain_rabbitmq_user('guest').with(
:admin => false,
:password => 'guest',
:provider => 'rabbitmqctl',
)}
it { is_expected.to contain_rabbitmq_user_permissions('guest@/').with(
:configure_permission => '.*',
:write_permission => '.*',
:read_permission => '.*',
:provider => 'rabbitmqctl',
)}
it { is_expected.to contain_rabbitmq_vhost('/').with(
:provider => 'rabbitmqctl',
)}
end
context 'with custom parameters' do
before :each do
params.merge!(
:userid => 'nova',
:password => 'secrete',
:virtual_host => '/nova',
:is_admin => true,
:configure_permission => '.nova',
:write_permission => '.nova',
:read_permission => '.nova'
)
end
it { is_expected.to contain_rabbitmq_user('nova').with(
:admin => true,
:password => 'secrete',
:provider => 'rabbitmqctl',
)}
it { is_expected.to contain_rabbitmq_user_permissions('nova@/nova').with(
:configure_permission => '.nova',
:write_permission => '.nova',
:read_permission => '.nova',
:provider => 'rabbitmqctl',
)}
it { is_expected.to contain_rabbitmq_vhost('/nova').with(
:provider => 'rabbitmqctl',
)}
end
context 'when disabling vhost management' do
before :each do
params.merge!( :manage_vhost => false )
end
it { is_expected.not_to contain_rabbitmq_vhost }
end
end
context 'on a Debian osfamily' do
let :facts do
{ :osfamily => "Debian" }
end
include_examples 'openstacklib::messaging::rabbitmq examples'
end
context 'on a RedHat osfamily' do
let :facts do
{ :osfamily => 'RedHat' }
end
include_examples 'openstacklib::messaging::rabbitmq examples'
end
end

View File

@ -0,0 +1,37 @@
require 'spec_helper'
describe 'openstacklib::policy::base' do
let :title do
'nova-contest_is_admin'
end
let :params do
{:file_path => '/etc/nova/policy.json',
:key => 'context_is_admin or owner',
:value => 'foo:bar'}
end
it 'configures (modifies) the proper policy' do
is_expected.to contain_augeas('/etc/nova/policy.json-context_is_admin or owner-foo:bar').with(
'lens' => 'Json.lns',
'incl' => '/etc/nova/policy.json',
'changes' => 'set dict/entry[*][.="context_is_admin or owner"]/string "foo:bar"',
'require' => 'Augeas[/etc/nova/policy.json-context_is_admin or owner-foo:bar-add]'
)
end
it 'configures (adds) the proper policy' do
is_expected.to contain_augeas('/etc/nova/policy.json-context_is_admin or owner-foo:bar-add').with(
'lens' => 'Json.lns',
'incl' => '/etc/nova/policy.json',
'changes' => [
'set dict/entry[last()+1] "context_is_admin or owner"',
'set dict/entry[last()]/string "foo:bar"'
],
'onlyif' => 'match dict/entry[*][.="context_is_admin or owner"] size == 0'
)
end
end

View File

@ -0,0 +1,73 @@
#
# Copyright (C) 2014 eNovance SAS <licensing@enovance.com>
#
# Author: Emilien Macchi <emilien.macchi@enovance.com>
#
# 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.
require 'spec_helper'
describe 'openstacklib::service_validation' do
let (:title) { 'nova-api' }
let :required_params do
{ :command => 'nova list' }
end
shared_examples 'openstacklib::service_validation examples' do
context 'with only required parameters' do
let :params do
required_params
end
it { is_expected.to contain_exec("execute #{title} validation").with(
:path => '/usr/bin:/bin:/usr/sbin:/sbin',
:provider => 'shell',
:command => 'nova list',
:tries => '10',
:try_sleep => '2',
)}
it { is_expected.to contain_anchor("create #{title} anchor").with(
:require => "Exec[execute #{title} validation]",
)}
end
context 'when omitting a required parameter command' do
let :params do
required_params.delete(:command)
end
it { expect { is_expected.to raise_error(Puppet::Error) } }
end
end
context 'on a Debian osfamily' do
let :facts do
{ :osfamily => "Debian" }
end
include_examples 'openstacklib::service_validation examples'
end
context 'on a RedHat osfamily' do
let :facts do
{ :osfamily => 'RedHat' }
end
include_examples 'openstacklib::service_validation examples'
end
end

View File

@ -0,0 +1,127 @@
#
# Copyright (C) 2014 eNovance SAS <licensing@enovance.com>
#
# Author: Emilien Macchi <emilien.macchi@enovance.com>
#
# 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.
require 'spec_helper'
describe 'openstacklib::wsgi::apache' do
let (:title) { 'keystone_wsgi' }
let :global_facts do
{
:processorcount => 42,
:concat_basedir => '/var/lib/puppet/concat',
:fqdn => 'some.host.tld'
}
end
let :params do
{
:bind_port => 5000,
:group => 'keystone',
:ssl => true,
:user => 'keystone',
:wsgi_script_dir => '/var/www/cgi-bin/keystone',
:wsgi_script_file => 'main',
:wsgi_script_source => '/usr/share/keystone/keystone.wsgi'
}
end
shared_examples_for 'apache serving a service with mod_wsgi' do
it { is_expected.to contain_service('httpd').with_name(platform_parameters[:httpd_service_name]) }
it { is_expected.to contain_class('apache') }
it { is_expected.to contain_class('apache::mod::wsgi') }
describe 'with default parameters' do
it { is_expected.to contain_file('/var/www/cgi-bin/keystone').with(
'ensure' => 'directory',
'owner' => 'keystone',
'group' => 'keystone',
'require' => 'Package[httpd]'
)}
it { is_expected.to contain_file('keystone_wsgi').with(
'ensure' => 'file',
'path' => '/var/www/cgi-bin/keystone/main',
'source' => '/usr/share/keystone/keystone.wsgi',
'owner' => 'keystone',
'group' => 'keystone',
'mode' => '0644',
)}
it { is_expected.to contain_apache__vhost('keystone_wsgi').with(
'servername' => 'some.host.tld',
'ip' => nil,
'port' => '5000',
'docroot' => '/var/www/cgi-bin/keystone',
'docroot_owner' => 'keystone',
'docroot_group' => 'keystone',
'ssl' => 'true',
'wsgi_daemon_process' => 'keystone_wsgi',
'wsgi_process_group' => 'keystone_wsgi',
'wsgi_script_aliases' => { '/' => "/var/www/cgi-bin/keystone/main" },
'wsgi_daemon_process_options' => {
'user' => 'keystone',
'group' => 'keystone',
'processes' => 1,
'threads' => global_facts[:processorcount],
},
'require' => 'File[keystone_wsgi]'
)}
it { is_expected.to contain_file("#{platform_parameters[:httpd_ports_file]}") }
end
end
context 'on RedHat platforms' do
let :facts do
global_facts.merge({
:osfamily => 'RedHat',
:operatingsystemrelease => '7.0'
})
end
let :platform_parameters do
{
:httpd_service_name => 'httpd',
:httpd_ports_file => '/etc/httpd/conf/ports.conf',
}
end
it_configures 'apache serving a service with mod_wsgi'
end
context 'on Debian platforms' do
let :facts do
global_facts.merge({
:osfamily => 'Debian',
:operatingsystem => 'Debian',
:operatingsystemrelease => '7.0'
})
end
let :platform_parameters do
{
:httpd_service_name => 'apache2',
:httpd_ports_file => '/etc/apache2/ports.conf',
}
end
it_configures 'apache serving a service with mod_wsgi'
end
end

View File

@ -0,0 +1,134 @@
require 'spec_helper'
describe 'os_database_connection' do
it 'refuses String' do
is_expected.to run.with_params('foo').\
and_raise_error(Puppet::ParseError, /Requires an hash/)
end
it 'refuses Array' do
is_expected.to run.with_params(['foo']).\
and_raise_error(Puppet::ParseError, /Requires an hash/)
end
it 'refuses without at least one argument' do
is_expected.to run.with_params().\
and_raise_error(Puppet::ParseError, /Wrong number of arguments/)
end
it 'refuses too many arguments' do
is_expected.to run.with_params('foo', 'bar').\
and_raise_error(Puppet::ParseError, /Wrong number of arguments/)
end
it 'fails if port is provided with missing host' do
is_expected.to run.with_params({
'dialect' => 'sqlite',
'database' => '/var/lib/keystone/keystone.db',
'port' => '3306',
'charset' => 'utf-8'
}).and_raise_error(Puppet::ParseError, /host is required with port/)
end
context 'creates the correct connection URI' do
it 'with all parameters' do
is_expected.to run.with_params({
'dialect' => 'mysql',
'host' => '127.0.0.1',
'port' => '3306',
'database' => 'test',
'username' => 'guest',
'password' => 's3cr3t',
'charset' => 'utf-8'
}).and_return('mysql://guest:s3cr3t@127.0.0.1:3306/test?charset=utf-8')
end
it 'without port' do
is_expected.to run.with_params({
'dialect' => 'mysql',
'host' => '127.0.0.1',
'database' => 'test',
'username' => 'guest',
'password' => 's3cr3t',
'charset' => 'utf-8'
}).and_return('mysql://guest:s3cr3t@127.0.0.1/test?charset=utf-8')
end
it 'without host and port' do
is_expected.to run.with_params({
'dialect' => 'sqlite',
'database' => '/var/lib/keystone/keystone.db',
'charset' => 'utf-8'
}).and_return('sqlite:////var/lib/keystone/keystone.db?charset=utf-8')
end
it 'without username and password' do
is_expected.to run.with_params({
'dialect' => 'mysql',
'host' => '127.0.0.1',
'port' => '3306',
'database' => 'test',
'charset' => 'utf-8'
}).and_return('mysql://127.0.0.1:3306/test?charset=utf-8')
end
it 'with username set to undef' do
is_expected.to run.with_params({
'dialect' => 'mysql',
'host' => '127.0.0.1',
'port' => '3306',
'database' => 'test',
'username' => :undef,
'charset' => 'utf-8'
}).and_return('mysql://127.0.0.1:3306/test?charset=utf-8')
end
it 'with username set to an empty string' do
is_expected.to run.with_params({
'dialect' => 'mysql',
'host' => '127.0.0.1',
'port' => '3306',
'database' => 'test',
'username' => '',
'charset' => 'utf-8'
}).and_return('mysql://127.0.0.1:3306/test?charset=utf-8')
end
it 'without password' do
is_expected.to run.with_params({
'dialect' => 'mysql',
'host' => '127.0.0.1',
'port' => '3306',
'database' => 'test',
'username' => 'guest',
'charset' => 'utf-8'
}).and_return('mysql://guest@127.0.0.1:3306/test?charset=utf-8')
end
it 'with password set to undef' do
is_expected.to run.with_params({
'dialect' => 'mysql',
'host' => '127.0.0.1',
'port' => '3306',
'database' => 'test',
'username' => 'guest',
'password' => :undef,
'charset' => 'utf-8'
}).and_return('mysql://guest@127.0.0.1:3306/test?charset=utf-8')
end
it 'with password set to an empty string' do
is_expected.to run.with_params({
'dialect' => 'mysql',
'host' => '127.0.0.1',
'port' => '3306',
'database' => 'test',
'username' => 'guest',
'password' => '',
'charset' => 'utf-8'
}).and_return('mysql://guest@127.0.0.1:3306/test?charset=utf-8')
end
end
end

View File

@ -0,0 +1,5 @@
shared_examples_for "a Puppet::Error" do |description|
it "with message matching #{description.inspect}" do
expect { is_expected.to have_class_count(1) }.to raise_error(Puppet::Error, description)
end
end

View File

@ -0,0 +1,7 @@
require 'puppetlabs_spec_helper/module_spec_helper'
require 'shared_examples'
RSpec.configure do |c|
c.alias_it_should_behave_like_to :it_configures, 'configures'
c.alias_it_should_behave_like_to :it_raises, 'raises'
end

View File

@ -0,0 +1,42 @@
require 'beaker-rspec'
hosts.each do |host|
install_puppet
on host, "mkdir -p #{host['distmoduledir']}"
end
RSpec.configure do |c|
# Project root
proj_root = File.expand_path(File.join(File.dirname(__FILE__), '..'))
# Readable test descriptions
c.formatter = :documentation
# Configure all nodes in nodeset
c.before :suite do
# Install module and dependencies
hosts.each do |host|
# install git
install_package host, 'git'
# clean out any module cruft
shell('rm -fr /etc/puppet/modules/*')
# install library modules from the forge
on host, puppet('module','install','puppetlabs-mysql'), { :acceptable_exit_codes => 0 }
on host, puppet('module','install','puppetlabs-apache'), { :acceptable_exit_codes => 0 }
on host, puppet('module','install','puppetlabs-postgresql'), { :acceptable_exit_codes => 0 }
on host, puppet('module','install','stahnma-epel'), { :acceptable_exit_codes => 0 }
# until https://github.com/tamaskozak/puppetlabs-rabbitmq/commit/8bbfe320035fae2ae900211501008d63dc3c171c is part of a release
shell('git clone https://github.com/puppetlabs/puppetlabs-rabbitmq /etc/puppet/modules/rabbitmq')
shell('git clone https://git.openstack.org/openstack/puppet-openstack_extras /etc/puppet/modules/openstack_extras')
# Install the module being tested
puppet_module_install(:source => proj_root, :module_name => 'openstacklib')
# List modules installed to help with debugging
on hosts[0], puppet('module','list'), { :acceptable_exit_codes => 0 }
end
end
end

View File

@ -0,0 +1,181 @@
require 'puppet'
require 'spec_helper'
require 'puppet/provider/openstack'
require 'puppet/provider/openstack/auth'
require 'tempfile'
class Puppet::Provider::Openstack::AuthTester < Puppet::Provider::Openstack
extend Puppet::Provider::Openstack::Auth
end
klass = Puppet::Provider::Openstack::AuthTester
describe Puppet::Provider::Openstack::Auth do
let(:type) do
Puppet::Type.newtype(:test_resource) do
newparam(:name, :namevar => true)
newparam(:log_file)
end
end
let(:resource_attrs) do
{
:name => 'stubresource'
}
end
let(:provider) do
klass.new(type.new(resource_attrs))
end
before(:each) do
ENV['OS_USERNAME'] = nil
ENV['OS_PASSWORD'] = nil
ENV['OS_PROJECT_NAME'] = nil
ENV['OS_AUTH_URL'] = nil
ENV['OS_TOKEN'] = nil
ENV['OS_URL'] = nil
end
describe '#set_credentials' do
it 'adds keys to the object' do
credentials = Puppet::Provider::Openstack::CredentialsV2_0.new
set = { 'OS_USERNAME' => 'user', 'OS_PASSWORD' => 'secret',
'OS_PROJECT_NAME' => 'tenant',
'OS_AUTH_URL' => 'http://127.0.0.1:5000',
'OS_TOKEN' => 'token',
'OS_URL' => 'http://127.0.0.1:35357',
'OS_IDENTITY_API_VERSION' => '2.0'
}
klass.set_credentials(credentials, set)
expect(credentials.to_env).to eq("OS_AUTH_URL" => "http://127.0.0.1:5000",
"OS_IDENTITY_API_VERSION" => '2.0',
"OS_PASSWORD" => "secret",
"OS_PROJECT_NAME" => "tenant",
"OS_TOKEN" => "token",
"OS_URL" => "http://127.0.0.1:35357",
"OS_USERNAME" => "user")
end
end
describe '#rc_filename' do
it 'returns RCFILENAME' do
expect(klass.rc_filename).to eq("#{ENV['HOME']}/openrc")
end
end
describe '#get_os_from_env' do
context 'with Openstack environment variables set' do
it 'provides a hash' do
ENV['OS_AUTH_URL'] = 'http://127.0.0.1:5000'
ENV['OS_PASSWORD'] = 'abc123'
ENV['OS_PROJECT_NAME'] = 'test'
ENV['OS_USERNAME'] = 'test'
response = klass.get_os_vars_from_env
expect(response).to eq({"OS_AUTH_URL" => "http://127.0.0.1:5000","OS_PASSWORD" => "abc123","OS_PROJECT_NAME" => "test","OS_USERNAME" => "test"})
end
end
end
describe '#get_os_vars_from_rcfile' do
context 'with a valid RC file' do
it 'provides a hash' do
mock = "export OS_USERNAME='test'\nexport OS_PASSWORD='abc123'\nexport OS_PROJECT_NAME='test'\nexport OS_AUTH_URL='http://127.0.0.1:5000'"
filename = 'file'
File.expects(:exists?).with('file').returns(true)
File.expects(:open).with('file').returns(StringIO.new(mock))
response = klass.get_os_vars_from_rcfile(filename)
expect(response).to eq({"OS_AUTH_URL" => "http://127.0.0.1:5000","OS_PASSWORD" => "abc123","OS_PROJECT_NAME" => "test","OS_USERNAME" => "test"})
end
end
context 'with an empty file' do
it 'provides an empty hash' do
filename = 'file'
File.expects(:exists?).with(filename).returns(true)
File.expects(:open).with(filename).returns(StringIO.new(""))
response = klass.get_os_vars_from_rcfile(filename)
expect(response).to eq({})
end
end
end
before(:each) do
class Puppet::Provider::Openstack::AuthTester
@credentials = Puppet::Provider::Openstack::CredentialsV2_0.new
end
end
describe '#request' do
context 'with no valid credentials' do
it 'fails to authenticate' do
expect { klass.request('project', 'list', ['--long']) }.to raise_error(Puppet::Error::OpenstackAuthInputError, "Insufficient credentials to authenticate")
end
end
context 'with user credentials in env' do
it 'is successful' do
klass.expects(:get_os_vars_from_env)
.returns({ 'OS_USERNAME' => 'test',
'OS_PASSWORD' => 'abc123',
'OS_PROJECT_NAME' => 'test',
'OS_AUTH_URL' => 'http://127.0.0.1:5000' })
klass.expects(:openstack)
.with('project', 'list', '--quiet', '--format', 'csv', ['--long'])
.returns('"ID","Name","Description","Enabled"
"1cb05cfed7c24279be884ba4f6520262","test","Test tenant",True
')
response = klass.request('project', 'list', ['--long'])
expect(response.first[:description]).to eq("Test tenant")
end
end
context 'with service token credentials in env' do
it 'is successful' do
klass.expects(:get_os_vars_from_env)
.returns({ 'OS_TOKEN' => 'test',
'OS_URL' => 'http://127.0.0.1:5000' })
klass.expects(:openstack)
.with('project', 'list', '--quiet', '--format', 'csv', ['--long'])
.returns('"ID","Name","Description","Enabled"
"1cb05cfed7c24279be884ba4f6520262","test","Test tenant",True
')
response = klass.request('project', 'list', ['--long'])
expect(response.first[:description]).to eq("Test tenant")
end
end
context 'with a RC file containing user credentials' do
it 'is successful' do
mock = "export OS_USERNAME='test'\nexport OS_PASSWORD='abc123'\nexport OS_PROJECT_NAME='test'\nexport OS_AUTH_URL='http://127.0.0.1:5000'"
File.expects(:exists?).with("#{ENV['HOME']}/openrc").returns(true)
File.expects(:open).with("#{ENV['HOME']}/openrc").returns(StringIO.new(mock))
klass.expects(:openstack)
.with('project', 'list', '--quiet', '--format', 'csv', ['--long'])
.returns('"ID","Name","Description","Enabled"
"1cb05cfed7c24279be884ba4f6520262","test","Test tenant",True
')
response = provider.class.request('project', 'list', ['--long'])
expect(response.first[:description]).to eq("Test tenant")
end
end
context 'with a RC file containing service token credentials' do
it 'is successful' do
mock = "export OS_TOKEN='test'\nexport OS_URL='abc123'\n"
File.expects(:exists?).with("#{ENV['HOME']}/openrc").returns(true)
File.expects(:open).with("#{ENV['HOME']}/openrc").returns(StringIO.new(mock))
klass.expects(:openstack)
.with('project', 'list', '--quiet', '--format', 'csv', ['--long'])
.returns('"ID","Name","Description","Enabled"
"1cb05cfed7c24279be884ba4f6520262","test","Test tenant",True
')
response = klass.request('project', 'list', ['--long'])
expect(response.first[:description]).to eq("Test tenant")
end
end
end
end

View File

@ -0,0 +1,92 @@
require 'puppet'
require 'spec_helper'
require 'puppet/provider/openstack'
require 'puppet/provider/openstack/credentials'
describe Puppet::Provider::Openstack::Credentials do
let(:creds) do
creds = Puppet::Provider::Openstack::CredentialsV2_0.new
end
describe '#service_token_set?' do
context "with service credentials" do
it 'is successful' do
creds.token = 'token'
creds.url = 'url'
expect(creds.service_token_set?).to be_truthy
end
end
end
describe '#password_set?' do
context "with user credentials" do
it 'is successful' do
creds.auth_url = 'auth_url'
creds.password = 'password'
creds.project_name = 'project_name'
creds.username = 'username'
expect(creds.user_password_set?).to be_truthy
end
end
end
describe '#set?' do
context "without any credential" do
it 'fails' do
expect(creds.set?).to be_falsey
end
end
end
describe '#to_env' do
context "with an exhaustive data set" do
it 'successfully returns content' do
creds.auth_url = 'auth_url'
creds.password = 'password'
creds.project_name = 'project_name'
creds.username = 'username'
creds.token = 'token'
creds.url = 'url'
creds.identity_api_version = 'identity_api_version'
expect(creds.auth_url).to eq("auth_url")
expect(creds.password).to eq("password")
expect(creds.project_name).to eq("project_name")
expect(creds.username).to eq("username")
expect(creds.token).to eq('token')
expect(creds.url).to eq('url')
expect(creds.identity_api_version).to eq('identity_api_version')
end
end
end
describe 'using v3' do
let(:creds) do
creds = Puppet::Provider::Openstack::CredentialsV3.new
end
describe 'with v3' do
it 'uses v3 identity api' do
creds.identity_api_version == '3'
end
end
describe '#password_set? with username and project_name' do
it 'is successful' do
creds.auth_url = 'auth_url'
creds.password = 'password'
creds.project_name = 'project_name'
creds.username = 'username'
expect(creds.user_password_set?).to be_truthy
end
end
describe '#password_set? with user_id and project_id' do
it 'is successful' do
creds.auth_url = 'auth_url'
creds.password = 'password'
creds.project_id = 'projid'
creds.user_id = 'userid'
expect(creds.user_password_set?).to be_truthy
end
end
end
end

View File

@ -0,0 +1,89 @@
require 'puppet'
require 'spec_helper'
require 'puppet/provider/openstack'
describe Puppet::Provider::Openstack do
before(:each) do
ENV['OS_USERNAME'] = nil
ENV['OS_PASSWORD'] = nil
ENV['OS_PROJECT_NAME'] = nil
ENV['OS_AUTH_URL'] = nil
end
let(:type) do
Puppet::Type.newtype(:test_resource) do
newparam(:name, :namevar => true)
newparam(:log_file)
end
end
describe '#request' do
let(:resource_attrs) do
{
:name => 'stubresource',
}
end
let(:provider) do
Puppet::Provider::Openstack.new(type.new(resource_attrs))
end
it 'makes a successful request' do
provider.class.stubs(:openstack)
.with('project', 'list', '--quiet', '--format', 'csv', ['--long'])
.returns('"ID","Name","Description","Enabled"
"1cb05cfed7c24279be884ba4f6520262","test","Test tenant",True
')
response = Puppet::Provider::Openstack.request('project', 'list', ['--long'])
expect(response.first[:description]).to eq("Test tenant")
end
context 'on connection errors' do
it 'retries' do
ENV['OS_USERNAME'] = 'test'
ENV['OS_PASSWORD'] = 'abc123'
ENV['OS_PROJECT_NAME'] = 'test'
ENV['OS_AUTH_URL'] = 'http://127.0.0.1:5000'
provider.class.stubs(:openstack)
.with('project', 'list', '--quiet', '--format', 'csv', ['--long'])
.raises(Puppet::ExecutionFailure, 'Unable to establish connection')
.then
.returns('')
provider.class.expects(:sleep).with(2).returns(nil)
Puppet::Provider::Openstack.request('project', 'list', ['--long'])
end
end
end
describe 'parse_csv' do
context 'with mixed stderr' do
text = "ERROR: Testing\n\"field\",\"test\",1,2,3\n"
csv = Puppet::Provider::Openstack.parse_csv(text)
it 'should ignore non-CSV text at the beginning of the input' do
expect(csv).to be_kind_of(Array)
expect(csv[0]).to match_array(['field', 'test', '1', '2', '3'])
expect(csv.size).to eq(1)
end
end
context 'with \r\n line endings' do
text = "ERROR: Testing\r\n\"field\",\"test\",1,2,3\r\n"
csv = Puppet::Provider::Openstack.parse_csv(text)
it 'ignore the carriage returns' do
expect(csv).to be_kind_of(Array)
expect(csv[0]).to match_array(['field', 'test', '1', '2', '3'])
expect(csv.size).to eq(1)
end
end
context 'with embedded newlines' do
text = "ERROR: Testing\n\"field\",\"te\nst\",1,2,3\n"
csv = Puppet::Provider::Openstack.parse_csv(text)
it 'should parse correctly' do
expect(csv).to be_kind_of(Array)
expect(csv[0]).to match_array(['field', "te\nst", '1', '2', '3'])
expect(csv.size).to eq(1)
end
end
end
end