From 99b3203923f12ba4d17f4c20b010978fa813d53b Mon Sep 17 00:00:00 2001 From: Daniel Pawlik Date: Sun, 12 Mar 2017 20:33:30 +0000 Subject: [PATCH] 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 --- lib/puppet/provider/neutron.rb | 115 +++++++++++++----- .../neutron_security_group/openstack.rb | 76 ++++++++++++ lib/puppet/type/neutron_security_group.rb | 76 ++++++++++++ ...utron_providers_auth-567e7914227bb859.yaml | 5 + spec/acceptance/basic_neutron_spec.rb | 6 + .../neutron_security_group/openstack_spec.rb | 71 +++++++++++ spec/unit/provider/neutron_spec.rb | 7 +- 7 files changed, 322 insertions(+), 34 deletions(-) create mode 100644 lib/puppet/provider/neutron_security_group/openstack.rb create mode 100644 lib/puppet/type/neutron_security_group.rb create mode 100644 releasenotes/notes/use_openstackclient_for_neutron_providers_auth-567e7914227bb859.yaml create mode 100644 spec/unit/provider/neutron_security_group/openstack_spec.rb diff --git a/lib/puppet/provider/neutron.rb b/lib/puppet/provider/neutron.rb index 40e5f2dbe..9637dcf4f 100644 --- a/lib/puppet/provider/neutron.rb +++ b/lib/puppet/provider/neutron.rb @@ -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'] diff --git a/lib/puppet/provider/neutron_security_group/openstack.rb b/lib/puppet/provider/neutron_security_group/openstack.rb new file mode 100644 index 000000000..32bcfb985 --- /dev/null +++ b/lib/puppet/provider/neutron_security_group/openstack.rb @@ -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 diff --git a/lib/puppet/type/neutron_security_group.rb b/lib/puppet/type/neutron_security_group.rb new file mode 100644 index 000000000..1429a8c32 --- /dev/null +++ b/lib/puppet/type/neutron_security_group.rb @@ -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 + diff --git a/releasenotes/notes/use_openstackclient_for_neutron_providers_auth-567e7914227bb859.yaml b/releasenotes/notes/use_openstackclient_for_neutron_providers_auth-567e7914227bb859.yaml new file mode 100644 index 000000000..214a12076 --- /dev/null +++ b/releasenotes/notes/use_openstackclient_for_neutron_providers_auth-567e7914227bb859.yaml @@ -0,0 +1,5 @@ +--- +features: + - Added Openstack Client for Neutron providers. + It will help to add new provider classes for + puppet Neutron module. diff --git a/spec/acceptance/basic_neutron_spec.rb b/spec/acceptance/basic_neutron_spec.rb index d2a9aa753..fedd396e7 100644 --- a/spec/acceptance/basic_neutron_spec.rb +++ b/spec/acceptance/basic_neutron_spec.rb @@ -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 diff --git a/spec/unit/provider/neutron_security_group/openstack_spec.rb b/spec/unit/provider/neutron_security_group/openstack_spec.rb new file mode 100644 index 000000000..570de5df7 --- /dev/null +++ b/spec/unit/provider/neutron_security_group/openstack_spec.rb @@ -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 diff --git a/spec/unit/provider/neutron_spec.rb b/spec/unit/provider/neutron_spec.rb index c0a2b3b9c..f76fdfd5c 100644 --- a/spec/unit/provider/neutron_spec.rb +++ b/spec/unit/provider/neutron_spec.rb @@ -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'],