Added neutron security groups and OpenstackClient auth

Creating security group using nova module is deprecated.
This patch will give neutron puppet module adds the ability
to create security groups.

OpenstackClient will allow easier create new Puppet Neutron
provider classes.

Change-Id: I1a900fadce6b4d1c006e43164d380034ea5cef2d
Partial-Bug: #1671474
This commit is contained in:
Daniel Pawlik 2017-03-12 20:33:30 +00:00
parent bdb9ef6e3e
commit 99b3203923
7 changed files with 322 additions and 34 deletions

View File

@ -1,11 +1,45 @@
require 'csv'
require 'puppet/util/inifile'
# Add openstacklib code to $LOAD_PATH so that we can load this during
# standalone compiles without error.
File.expand_path('../../../../openstacklib/lib', File.dirname(__FILE__)).tap { |dir| $LOAD_PATH.unshift(dir) unless $LOAD_PATH.include?(dir) }
class Puppet::Provider::Neutron < Puppet::Provider
require 'puppet/util/inifile'
require 'puppet/provider/openstack'
require 'puppet/provider/openstack/auth'
require 'puppet/provider/openstack/credentials'
require 'csv'
class Puppet::Provider::Neutron < Puppet::Provider::Openstack
extend Puppet::Provider::Openstack::Auth
initvars
commands :neutron => 'neutron'
def self.request(service, action, properties=nil)
begin
super
rescue Puppet::Error::OpenstackAuthInputError => error
neutron_request(service, action, error, properties)
end
end
def self.neutron_request(service, action, error, properties=nil)
properties ||= []
@credentials.username = neutron_credentials['username']
@credentials.password = neutron_credentials['password']
@credentials.project_name = neutron_credentials['project_name']
@credentials.auth_url = auth_endpoint
if @credentials.version == '3'
@credentials.user_domain_name = neutron_credentials['user_domain_name']
@credentials.project_domain_name = neutron_credentials['project_domain_name']
end
if neutron_credentials['region_name']
@credentials.region_name = neutron_credentials['region_name']
end
raise error unless @credentials.set?
Puppet::Provider::Openstack.request(service, action, properties, @credentials)
end
def self.conf_filename
'/etc/neutron/neutron.conf'
end
@ -24,33 +58,6 @@ class Puppet::Provider::Neutron < Puppet::Provider
end
end
def self.neutron_credentials
@neutron_credentials ||= get_neutron_credentials
end
def self.get_neutron_credentials
auth_keys = ['project_name', 'username', 'password', 'auth_url']
conf = neutron_conf
if conf and conf['keystone_authtoken'] and
!conf['keystone_authtoken']['password'].nil? and
auth_keys.all?{|k| !conf['keystone_authtoken'][k].nil?}
creds = Hash[ auth_keys.map \
{ |k| [k, conf['keystone_authtoken'][k].strip] } ]
if !conf['keystone_authtoken']['region_name'].nil?
creds['region_name'] = conf['keystone_authtoken']['region_name'].strip
end
return creds
else
raise(Puppet::Error, "File: #{conf_filename} does not contain all \
required sections. Neutron types will not work if neutron is not \
correctly configured.")
end
end
def neutron_credentials
self.class.neutron_credentials
end
def self.neutron_conf
return @neutron_conf if @neutron_conf
@neutron_conf = Puppet::Util::IniConfig::File.new
@ -58,10 +65,56 @@ correctly configured.")
@neutron_conf
end
def self.neutron_credentials
@neutron_credentials ||= get_neutron_credentials
end
def neutron_credentials
self.class.neutron_credentials
end
def self.get_neutron_credentials
#needed keys for authentication
auth_keys = ['auth_uri', 'project_name', 'username', 'password']
conf = neutron_conf
if conf and conf['keystone_authtoken'] and
auth_keys.all?{|k| !conf['keystone_authtoken'][k].nil?}
creds = Hash[ auth_keys.map \
{ |k| [k, conf['keystone_authtoken'][k].strip] } ]
if conf['neutron'] and conf['neutron']['region_name']
creds['region_name'] = conf['neutron']['region_name'].strip
end
if !conf['keystone_authtoken']['project_domain_name'].nil?
creds['project_domain_name'] = conf['keystone_authtoken']['project_domain_name'].strip
else
creds['project_domain_name'] = 'Default'
end
if !conf['keystone_authtoken']['user_domain_name'].nil?
creds['user_domain_name'] = conf['keystone_authtoken']['user_domain_name'].strip
else
creds['user_domain_name'] = 'Default'
end
return creds
else
raise(Puppet::Error, "File: #{conf_filename} does not contain all " +
"required sections. Neutron types will not work if neutron is not " +
"correctly configured.")
end
end
def self.get_auth_endpoint
q = neutron_credentials
"#{q['auth_uri']}"
end
def self.auth_endpoint
@auth_endpoint ||= get_auth_endpoint
end
def self.auth_neutron(*args)
q = neutron_credentials
authenv = {
:OS_AUTH_URL => q['auth_url'],
:OS_AUTH_URL => self.auth_endpoint,
:OS_USERNAME => q['username'],
:OS_PROJECT_NAME => q['project_name'],
:OS_PASSWORD => q['password']

View File

@ -0,0 +1,76 @@
require File.join(File.dirname(__FILE__), '..','..','..',
'puppet/provider/neutron')
Puppet::Type.type(:neutron_security_group).provide(
:openstack,
:parent => Puppet::Provider::Neutron
) do
desc <<-EOT
Manage Neutron security group
EOT
@credentials = Puppet::Provider::Openstack::CredentialsV3.new
def initialize(value={})
super(value)
end
def create
opts = [@resource[:name]]
(opts << '--id' << @resource[:id]) if @resource[:id]
(opts << '--description' << @resource[:description]) if @resource[:description]
(opts << '--project' << @resource[:project]) if @resource[:project]
(opts << '--project-domain' << @resource[:project_domain]) if @resource[:project_domain]
@property_hash = self.class.request('security group', 'create', opts)
@property_hash[:ensure] = :present
end
def exists?
@property_hash[:ensure] == :present
end
def destroy
self.class.request('security group', 'delete', @property_hash[:id])
end
mk_resource_methods
def id=(value)
fail('id is read only')
end
def description=(value)
fail('description is read only')
end
def project=(value)
fail('project is read only')
end
def project_domain=(value)
fail('project_domain is read only')
end
def self.instances
request('security group', 'list', ['--all']).collect do |attrs|
new(
:ensure => :present,
:name => attrs[:name],
:id => attrs[:id],
:description => attrs[:description],
:project => attrs[:project],
:project_domain => attrs[:project_domain]
)
end
end
def self.prefetch(resources)
sec_groups = instances
resources.keys.each do |name|
if provider = sec_groups.find{ |sg| sg.name == name }
resources[name].provider = provider
end
end
end
end

View File

@ -0,0 +1,76 @@
# neutron_security_group type
#
# == Parameters
# [*name*]
# Name for the security group
# Required
#
# [*id*]
# Unique ID (integer or UUID) for the security group.
# Optional
#
# [*description*]
# Description of the security group.
# Optional
#
# [*project*]
# Project of the security group.
# Optional
#
# [*project_domain*]
# Project domain of the security group.
# Optional
#
require 'puppet'
Puppet::Type.newtype(:neutron_security_group) do
@doc = "Manage creation of neutron security group"
ensurable
autorequire(:neutron_config) do
['auth_uri', 'project_name', 'username', 'password']
end
# Require the neutron-server service to be running
autorequire(:service) do
['neutron-server']
end
newparam(:name, :namevar => true) do
desc 'Name for the security group'
validate do |value|
if not value.is_a? String
raise ArgumentError, "name parameter must be a String"
end
unless value =~ /^[a-zA-Z0-9\-\._]+$/
raise ArgumentError, "#{value} is not a valid name"
end
end
end
newparam(:id) do
desc 'Unique ID (integer or UUID) for the security group.'
end
newparam(:description) do
desc 'Description of the security group.'
end
newparam(:project) do
desc 'Project of the security group.'
end
newparam(:project_domain) do
desc 'Project domain of the security group.'
end
validate do
unless self[:name]
raise(ArgumentError, 'Name must be set')
end
end
end

View File

@ -0,0 +1,5 @@
---
features:
- Added Openstack Client for Neutron providers.
It will help to add new provider classes for
puppet Neutron module.

View File

@ -78,6 +78,12 @@ describe 'basic neutron' do
}
class { '::neutron::services::lbaas::haproxy': }
class { '::neutron::services::lbaas::octavia': }
# Create Neutron security group for admin tenant
neutron_security_group { 'test':
ensure => present,
description => 'Test security group',
}
EOS

View File

@ -0,0 +1,71 @@
require 'puppet'
require 'spec_helper'
require 'puppet/provider/neutron_security_group/openstack'
provider_class = Puppet::Type.type(:neutron_security_group).provider(:openstack)
describe provider_class do
let(:set_env) do
ENV['OS_USERNAME'] = 'admin'
ENV['OS_PASSWORD'] = 'password'
ENV['OS_PROJECT_NAME'] = 'admin_tenant'
ENV['OS_AUTH_URL'] = 'https://192.168.56.210:35357/v2.0/'
end
before(:each) do
set_env
end
describe 'managing security group' do
let(:sec_group_attrs) do
{
:name => 'example',
:id => '593db854-a47d-411e-a894-66bf90959768',
:description => 'test',
:project => '1a2b3c',
:project_domain => 'Default',
:ensure => 'present',
}
end
let :resource do
Puppet::Type::Neutron_security_group.new(sec_group_attrs)
end
let(:provider) do
provider_class.new(resource)
end
describe '#create' do
it 'creates security group' do
provider.class.stubs(:openstack)
.with('security group', 'list', ['--all'])
.returns('"ID", "Name", "Description", "Project"')
provider.class.stubs(:openstack)
.with('security group', 'create', 'shell', ['example', 'description', 'test', 'project', '1a2b3c', 'project_domain', 'Default'])
.returns('created_at="2017-03-15T09:32:03Z"
description="test"
headers=""
id="593db854-a47d-411e-a894-66bf90959768"
name="example"
project_id="1a2b3c"
project_id="1a2b3c"
revision_number="1"
rules="created_at=\'2017-03-15T09:32:03Z\', direction=\'egress\', ethertype=\'IPv4\', id=\'cf462eac-821e-4583-8e91-3294d5be5cce\', project_id=\'1a2b3c\', revision_number=\'1\', updated_at=\'2017-03-15T09:32:03Z\'
created_at=\'2017-03-15T09:32:03Z\', direction=\'egress\', ethertype=\'IPv6\', id=\'afac00e8-4fec-4a1e-8faa-43e2278a0d79\', project_id=\'1a2b3c\', revision_number=\'1\', updated_at=\'2017-03-15T09:32:03Z\'"
updated_at="2017-03-15T09:32:03Z"')
end
end
describe '#destroy' do
it 'removes security group' do
provider_class.expects(:openstack)
.with('security group', 'delete', '593db854-a47d-411e-a894-66bf90959768')
provider.instance_variable_set(:@property_hash, sec_group_attrs)
provider.destroy
expect(provider.exists?).to be_falsey
end
end
end
end

View File

@ -14,7 +14,7 @@ describe Puppet::Provider::Neutron do
'project_name' => 'admin_tenant',
'username' => 'admin',
'password' => 'password',
'auth_url' => 'https://192.168.56.210:35357'
'auth_uri' => 'https://192.168.56.210:35357/v2.0/',
}
end
@ -56,13 +56,14 @@ describe Puppet::Provider::Neutron do
end.to raise_error(Puppet::Error, credential_error)
end
end
describe 'when invoking the neutron cli' do
it 'should set auth credentials in the environment' do
authenv = {
:OS_AUTH_URL => credential_hash['auth_url'],
:OS_AUTH_URL => credential_hash['auth_uri'],
:OS_USERNAME => credential_hash['username'],
:OS_PROJECT_NAME => credential_hash['project_name'],
:OS_PASSWORD => credential_hash['password'],
@ -74,7 +75,7 @@ describe Puppet::Provider::Neutron do
it 'should set region in the environment if needed' do
authenv = {
:OS_AUTH_URL => credential_hash['auth_url'],
:OS_AUTH_URL => credential_hash['auth_uri'],
:OS_USERNAME => credential_hash['username'],
:OS_PROJECT_NAME => credential_hash['project_name'],
:OS_PASSWORD => credential_hash['password'],