Use openstack cli to manage neutron router interfaces
Partial-Bug: #1808317 Change-Id: I2e78558a75d70d48a36a44f58c6d660f8921951d
This commit is contained in:
parent
4d63cad365
commit
541aa3ba43
@ -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}]")
|
||||
|
@ -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 = []
|
||||
|
@ -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
|
111
lib/puppet/provider/neutron_router_interface/openstack.rb
Normal file
111
lib/puppet/provider/neutron_router_interface/openstack.rb
Normal 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
|
@ -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
|
@ -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
|
128
spec/unit/provider/neutron_router_interface/openstack_spec.rb
Normal file
128
spec/unit/provider/neutron_router_interface/openstack_spec.rb
Normal 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
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user