Merge "Use openstack cli to manage neutron router interfaces"

This commit is contained in:
Zuul 2021-11-29 16:54:22 +00:00 committed by Gerrit Code Review
commit f063826af4
8 changed files with 253 additions and 277 deletions

View File

@ -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}]")

View File

@ -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 = []

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.