diff --git a/lib/puppet/provider/neutron.rb b/lib/puppet/provider/neutron.rb index 953317d81..8dfc9276b 100644 --- a/lib/puppet/provider/neutron.rb +++ b/lib/puppet/provider/neutron.rb @@ -180,6 +180,20 @@ class Puppet::Provider::Neutron < Puppet::Provider::Openstack return subnet[:name] end + def self.parse_subnet_id(value) + fixed_ips = JSON.parse(value.gsub(/\\"/,'"').gsub('u\'', '"').gsub('\'','"')) + subnet_ids = [] + fixed_ips.each do |fixed_ip| + subnet_ids << fixed_ip['subnet_id'] + end + + if subnet_ids.length > 1 + subnet_ids + else + subnet_ids.first + end + end + def self.list_neutron_resources(type) ids = [] list = cleanup_csv_with_id(auth_neutron("#{type}-list", '--format=csv', @@ -216,31 +230,6 @@ class Puppet::Provider::Neutron < Puppet::Provider::Openstack return attrs end - def self.list_router_ports(router_name_or_id) - results = [] - cmd_output = auth_neutron("router-port-list", - '--format=csv', - router_name_or_id) - if ! cmd_output - return results - end - - headers = nil - CSV.parse(cleanup_csv(cmd_output)) do |row| - if headers == nil - headers = row - else - result = Hash[*headers.zip(row).flatten] - match_data = /.*"subnet_id": "(.*)", .*/.match(result['fixed_ips']) - if match_data - result['subnet_id'] = match_data[1] - end - results << result - end - end - return results - end - def self.get_tenant_id(catalog, name, domain='Default') instance_type = 'keystone_tenant' instance = catalog.resource("#{instance_type.capitalize!}[#{name}]") diff --git a/lib/puppet/provider/neutron_port/openstack.rb b/lib/puppet/provider/neutron_port/openstack.rb index 2d9b780a5..a65f0e6a5 100644 --- a/lib/puppet/provider/neutron_port/openstack.rb +++ b/lib/puppet/provider/neutron_port/openstack.rb @@ -175,20 +175,6 @@ Puppet::Type.type(:neutron_port).provide( @property_hash[:ensure] = :absent end - def self.parse_subnet_id(value) - fixed_ips = JSON.parse(value.gsub(/\\"/,'"').gsub('u\'', '"').gsub('\'','"')) - subnet_ids = [] - fixed_ips.each do |fixed_ip| - subnet_ids << fixed_ip['subnet_id'] - end - - if subnet_ids.length > 1 - subnet_ids - else - subnet_ids.first - end - end - def self.parse_ip_address(value) fixed_ips = JSON.parse(value.gsub(/\\"/,'"').gsub('u\'', '"').gsub('\'','"')) ips = [] diff --git a/lib/puppet/provider/neutron_router_interface/neutron.rb b/lib/puppet/provider/neutron_router_interface/neutron.rb deleted file mode 100644 index cc3bb71bf..000000000 --- a/lib/puppet/provider/neutron_router_interface/neutron.rb +++ /dev/null @@ -1,87 +0,0 @@ -require File.join(File.dirname(__FILE__), '..','..','..', - 'puppet/provider/neutron') - -Puppet::Type.type(:neutron_router_interface).provide( - :neutron, - :parent => Puppet::Provider::Neutron -) do - desc <<-EOT - Neutron provider to manage neutron_router_interface type. - - Assumes that the neutron service is configured on the same host. - - It is not possible to manage an interface for the subnet used by - the gateway network, and such an interface will appear in the list - of resources ('puppet resource [type]'). Attempting to manage the - gateway interfae will result in an error. - - EOT - - mk_resource_methods - - def self.instances - subnet_name_hash = {} - Puppet::Type.type('neutron_subnet').instances.each do |instance| - subnet_name_hash[instance.provider.id] = instance.provider.name - end - instances_ = [] - Puppet::Type.type('neutron_router').instances.each do |instance| - list_router_ports(instance.provider.id).each do |port_hash| - router_name = instance.provider.name - subnet_name = subnet_name_hash[port_hash['subnet_id']] - name = "#{router_name}:#{subnet_name}" - instances_ << new( - :ensure => :present, - :name => name, - :id => port_hash['id'], - :port => port_hash['name'] - ) - end - end - return instances_ - end - - def self.prefetch(resources) - instances_ = instances - resources.keys.each do |name| - if provider = instances_.find{ |instance| instance.name == name } - resources[name].provider = provider - end - end - end - - def exists? - @property_hash[:ensure] == :present - end - - def create - router,subnet = resource[:name].split(':', 2) - port = resource[:port] - args = ["router-interface-add", "--format=shell", router] - if port - args << "port=#{port}" - else - args << "subnet=#{subnet}" - end - results = auth_neutron(args) - - @property_hash = { - :ensure => :present, - :name => resource[:name], - } - end - - def router_name - name.split(':', 2).first - end - - def subnet_name - name.split(':', 2).last - end - - def destroy - auth_neutron('router-interface-delete', router_name, subnet_name) - @property_hash[:ensure] = :absent - end - -end diff --git a/lib/puppet/provider/neutron_router_interface/openstack.rb b/lib/puppet/provider/neutron_router_interface/openstack.rb new file mode 100644 index 000000000..586256f9e --- /dev/null +++ b/lib/puppet/provider/neutron_router_interface/openstack.rb @@ -0,0 +1,111 @@ +require File.join(File.dirname(__FILE__), '..','..','..', + 'puppet/provider/neutron') + +Puppet::Type.type(:neutron_router_interface).provide( + :openstack, + :parent => Puppet::Provider::Neutron +) do + desc <<-EOT + Neutron provider to manage neutron_router_interface type. + + Assumes that the neutron service is configured on the same host. + + It is not possible to manage an interface for the subnet used by + the gateway network, and such an interface will appear in the list + of resources ('puppet resource [type]'). Attempting to manage the + gateway interfae will result in an error. + EOT + + @credentials = Puppet::Provider::Openstack::CredentialsV3.new + + mk_resource_methods + + def initialize(value={}) + super(value) + end + + def self.do_not_manage + @do_not_manage + end + + def self.do_not_manage=(value) + @do_not_manage = value + end + + def self.instances + self.do_not_manage = true + subnet_name_hash = {} + request('subnet', 'list').each do |subnet| + subnet_name_hash[subnet[:id]] = subnet[:name] + end + + instances_ = [] + request('router', 'list').each do |router| + request('port', 'list', ['--router', router[:id]]).each do |port| + subnet_id_ = parse_subnet_id(port[:fixed_ip_addresses]) + subnet_name_ = subnet_name_hash[subnet_id_] + router_name_ = router[:name] + name_ = "#{router_name_}:#{subnet_name_}" + instances_ << new( + :ensure => :present, + :name => name_, + :id => port[:id], + :port => port[:name] + ) + end + end + self.do_not_manage = false + return instances_ + end + + def self.prefetch(resources) + interfaces = instances + resources.keys.each do |name| + if provider = interfaces.find{ |interface| interface.name == name } + resources[name].provider = provider + end + end + end + + def exists? + @property_hash[:ensure] == :present + end + + def create + router, subnet = name.split(':', 2) + port = resource[:port] + if port + self.class.request('router', 'add port', [router, port]) + else + self.class.request('router', 'add subnet', [router, subnet]) + end + @property_hash = { + :ensure => :present, + :name => resource[:name] + } + end + + def destroy + if self.class.do_not_manage + fail("Not managing Neutron_router_interface[#{@resource[:name]}] due to earlier Neutron API failures.") + end + router, subnet = name.split(':', 2) + port = resource[:port] + if port + self.class.request('router', 'remove port', [router, port]) + else + self.class.request('router', 'remove subnet', [router, subnet]) + end + @property_hash.clear + @property_hash[:ensure] = :absent + end + + def router_name + name.split(':', 2).first + end + + def subnet_name + name.split(':', 2).last + end + +end diff --git a/spec/unit/provider/neutron_router_interface/neutron_spec.rb b/spec/unit/provider/neutron_router_interface/neutron_spec.rb deleted file mode 100644 index a53f81cec..000000000 --- a/spec/unit/provider/neutron_router_interface/neutron_spec.rb +++ /dev/null @@ -1,36 +0,0 @@ -require 'puppet' -require 'spec_helper' -require 'puppet/provider/neutron_router_interface/neutron' - -provider_class = Puppet::Type.type(:neutron_router_interface). - provider(:neutron) - -describe provider_class do - - let :interface_attrs do - { - :name => 'router:subnet', - :ensure => 'present', - } - end - - describe 'when accessing attributes of an interface' do - let :resource do - Puppet::Type::Neutron_router_interface.new(interface_attrs) - end - - let :provider do - provider_class.new(resource) - end - - it 'should return the correct router name' do - expect(provider.router_name).to eql('router') - end - - it 'should return the correct subnet name' do - expect(provider.subnet_name).to eql('subnet') - end - - end - -end diff --git a/spec/unit/provider/neutron_router_interface/new_neutron_spec.rb b/spec/unit/provider/neutron_router_interface/new_neutron_spec.rb deleted file mode 100644 index 0e8ce814c..000000000 --- a/spec/unit/provider/neutron_router_interface/new_neutron_spec.rb +++ /dev/null @@ -1,49 +0,0 @@ -require 'puppet' -require 'spec_helper' -require 'puppet/provider/neutron_router_interface/neutron' - -provider_class = Puppet::Type.type(:neutron_router_interface).provider(:neutron) - -describe provider_class do - - let :interface_attrs do - { - :name => 'router:subnet', - :ensure => 'present', - } - end - - let :resource do - Puppet::Type::Neutron_router_interface.new(interface_attrs) - end - - let :provider do - provider_class.new(resource) - end - - describe 'when creating a router interface' do - - it 'should call port-create with appropriate command line options' do - provider.class.stubs(:get_tenant_id).returns(interface_attrs[:tenant_id]) - - output = 'Added interface b03610fd-ac31-4521-ad06-2ac74af959ad to router router' - - provider.expects(:auth_neutron).with(['router-interface-add', - '--format=shell', 'router', 'subnet=subnet']).returns(output) - - provider.create - end - end - - describe 'when accessing attributes of an interface' do - it 'should return the correct router name' do - expect(provider.router_name).to eql('router') - end - - it 'should return the correct subnet name' do - expect(provider.subnet_name).to eql('subnet') - end - - end - -end diff --git a/spec/unit/provider/neutron_router_interface/openstack_spec.rb b/spec/unit/provider/neutron_router_interface/openstack_spec.rb new file mode 100644 index 000000000..0b8fd5fc9 --- /dev/null +++ b/spec/unit/provider/neutron_router_interface/openstack_spec.rb @@ -0,0 +1,128 @@ +require 'puppet' +require 'spec_helper' +require 'puppet/provider/neutron_router_interface/openstack' + +provider_class = Puppet::Type.type(:neutron_router_interface).provider(:openstack) + +describe provider_class do + + let(:set_env) do + ENV['OS_USERNAME'] = 'test' + ENV['OS_PASSWORD'] = 'abc123' + ENV['OS_PROJECT_NAME'] = 'test' + ENV['OS_AUTH_URL'] = 'http://127.0.0.1:5000' + end + + describe 'manage networks' do + let :interface_name do + 'router1:subnet1' + end + + let :interface_attrs do + { + :name => interface_name, + :ensure => 'present', + } + end + + let :resource do + Puppet::Type::Neutron_router_interface.new(interface_attrs) + end + + let :provider do + provider_class.new(resource) + end + + before :each do + set_env + end + + describe '#create' do + context 'with defaults' do + it 'creates router interface' do + provider_class.expects(:openstack) + .with('router', 'add subnet', ['router1', 'subnet1']) + provider.create + expect(provider.exists?).to be_truthy + end + end + + context 'with port' do + let :interface_attrs do + { + :name => interface_name, + :ensure => 'present', + :port => 'port1', + } + end + it 'creates router interface' do + provider_class.expects(:openstack) + .with('router', 'add port', ['router1', 'port1']) + provider.create + expect(provider.exists?).to be_truthy + end + end + end + + describe '#destroy' do + context 'with defaults' do + it 'removes router interface' do + provider_class.expects(:openstack) + .with('router', 'remove subnet', ['router1', 'subnet1']) + provider.destroy + expect(provider.exists?).to be_falsey + end + end + context 'with port' do + let :interface_attrs do + { + :name => interface_name, + :ensure => 'present', + :port => 'port1', + } + end + it 'removes router interface' do + provider_class.expects(:openstack) + .with('router', 'remove port', ['router1', 'port1']) + provider.destroy + expect(provider.exists?).to be_falsey + end + end + end + + describe '#instances' do + it 'lists router interfaces' do + provider_class.expects(:openstack) + .with('subnet', 'list', '--quiet', '--format', 'csv', []) + .returns('"ID","Name","Network","Subnet" +"dd5e0ef1-2c88-4b0b-ba08-7df65be87963","subnet1","076520cc-b783-4cf5-a4a9-4cb5a5e93a9b","10.0.0.0/24", +"0da7a631-0f8f-4e51-8b1c-7a29d0d4f7b5","subnet2","34e8f42b-89db-4a5b-92db-76ca7073414d","10.0.1.0/24", +') + provider_class.expects(:openstack) + .with('router', 'list', '--quiet', '--format', 'csv', []) + .returns('"ID","Name","Status","State","Project","Distributed","HA" +"d73f453a-77ca-4843-977a-3af0fda8dfcb","router1","ACTIVE","True","60f9544eb94c42a6b7e8e98c2be981b1",True,False +"c3e93a5b-45ee-4029-b3a3-3331cb3e3f2a","router2","ACTIVE","True","60f9544eb94c42a6b7e8e98c2be981b1",True,False +') + provider_class.expects(:openstack) + .with('port', 'list', '--quiet', '--format', 'csv', ['--router', 'd73f453a-77ca-4843-977a-3af0fda8dfcb']) + .returns('"ID","Name","MAC Address","Fixed IP Addresses","Status" +"5222573b-314d-45f9-b6bd-299288ba667a","port1","fa:16:3e:45:3c:10","[{\'subnet_id\': \'dd5e0ef1-2c88-4b0b-ba08-7df65be87963\', \'ip_address\': \'10.0.0.1\'}]","ACTIVE"') + provider_class.expects(:openstack) + .with('port', 'list', '--quiet', '--format', 'csv', ['--router', 'c3e93a5b-45ee-4029-b3a3-3331cb3e3f2a']) + .returns('"ID","Name","MAC Address","Fixed IP Addresses","Status" +"c880affb-b15e-4632-b5e7-3adba6e3ab35","port2","fa:16:3e:45:3c:11","[{\'subnet_id\': \'0da7a631-0f8f-4e51-8b1c-7a29d0d4f7b5\', \'ip_address\': \'10.0.1.1\'}]","ACTIVE"') + + instances = provider_class.instances + expect(instances.length).to eq(2) + + expect(instances[0].name).to eq('router1:subnet1') + expect(instances[0].id).to eq('5222573b-314d-45f9-b6bd-299288ba667a') + expect(instances[0].port).to eq('port1') + expect(instances[1].name).to eq('router2:subnet2') + expect(instances[1].id).to eq('c880affb-b15e-4632-b5e7-3adba6e3ab35') + expect(instances[1].port).to eq('port2') + end + end + end +end diff --git a/spec/unit/provider/neutron_spec.rb b/spec/unit/provider/neutron_spec.rb index ca899737d..58b45c291 100644 --- a/spec/unit/provider/neutron_spec.rb +++ b/spec/unit/provider/neutron_spec.rb @@ -164,50 +164,6 @@ subnet3" end - describe 'when listing router ports' do - - let :router do - 'router1' - end - - it 'should handle an empty port list' do - klass.expects(:auth_neutron).with('router-port-list', - '--format=csv', - router) - result = klass.list_router_ports(router) - expect(result).to eql([]) - end - - it 'should handle several ports' do - output = <<-EOT -"id","name","mac_address","fixed_ips" -"1345e576-a21f-4c2e-b24a-b245639852ab","","fa:16:3e:e3:e6:38","{""subnet_id"": ""839a1d2d-2c6e-44fb-9a2b-9b011dce8c2f"", ""ip_address"": ""10.0.0.1""}" -"de0dc526-02b2-467c-9832-2c3dc69ac2b4","","fa:16:3e:f6:b5:72","{""subnet_id"": ""e4db0abd-276a-4f69-92ea-8b9e4eacfd43"", ""ip_address"": ""172.24.4.226""}" - EOT - expected = - [{ "fixed_ips"=> - "{\"subnet_id\": \"839a1d2d-2c6e-44fb-9a2b-9b011dce8c2f\", \ -\"ip_address\": \"10.0.0.1\"}", - "name"=>"", - "subnet_id"=>"839a1d2d-2c6e-44fb-9a2b-9b011dce8c2f", - "id"=>"1345e576-a21f-4c2e-b24a-b245639852ab", - "mac_address"=>"fa:16:3e:e3:e6:38"}, - {"fixed_ips"=> - "{\"subnet_id\": \"e4db0abd-276a-4f69-92ea-8b9e4eacfd43\", \ -\"ip_address\": \"172.24.4.226\"}", - "name"=>"", - "subnet_id"=>"e4db0abd-276a-4f69-92ea-8b9e4eacfd43", - "id"=>"de0dc526-02b2-467c-9832-2c3dc69ac2b4", - "mac_address"=>"fa:16:3e:f6:b5:72"}] - klass.expects(:auth_neutron). - with('router-port-list', '--format=csv', router). - returns(output) - result = klass.list_router_ports(router) - expect(result).to eql(expected) - end - - end - describe 'when parsing creation output' do it 'should parse valid output into a hash' do @@ -230,28 +186,6 @@ tenant_id="3056a91768d948d399f1d79051a7f221" end describe 'garbage in the csv output' do - it '#list_router_ports' do - output = <<-EOT -/usr/lib/python2.7/dist-packages/urllib3/util/ssl_.py:90: InsecurePlatformWarning: A true SSLContext object is not available. This prevents urllib3 from configuring SSL appropriately and may cause certain SSL connections to fail. For more information, see https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning. - InsecurePlatformWarning -"id","name","mac_address","fixed_ips" -"1345e576-a21f-4c2e-b24a-b245639852ab","","fa:16:3e:e3:e6:38","{""subnet_id"": ""839a1d2d-2c6e-44fb-9a2b-9b011dce8c2f"", ""ip_address"": ""10.0.0.1""}" - EOT - expected = [{ "fixed_ips"=> - "{\"subnet_id\": \"839a1d2d-2c6e-44fb-9a2b-9b011dce8c2f\", \ -\"ip_address\": \"10.0.0.1\"}", - "name"=>"", - "subnet_id"=>"839a1d2d-2c6e-44fb-9a2b-9b011dce8c2f", - "id"=>"1345e576-a21f-4c2e-b24a-b245639852ab", - "mac_address"=>"fa:16:3e:e3:e6:38"}] - - klass.expects(:auth_neutron). - with('router-port-list', '--format=csv', 'router1'). - returns(output) - result = klass.list_router_ports('router1') - expect(result).to eql(expected) - end - it '#list_neutron_resources' do output = <<-EOT /usr/lib/python2.7/dist-packages/urllib3/util/ssl_.py:90: InsecurePlatformWarning: A true SSLContext object is not available. This prevents urllib3 from configuring SSL appropriately and may cause certain SSL connections to fail. For more information, see https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning.