Use openstack cli to manage neutron routers

... and cleans up unused implementations from the base neutron
provider.

Closes-Bug: #1808317
Change-Id: I257d55be0acfecc529f86d5fe734c6c142f64e5f
This commit is contained in:
Takashi Kajinami 2021-11-02 00:20:27 +09:00
parent 541aa3ba43
commit 75ca125cfa
7 changed files with 575 additions and 615 deletions

View File

@ -13,7 +13,6 @@ class Puppet::Provider::Neutron < Puppet::Provider::Openstack
extend Puppet::Provider::Openstack::Auth
initvars
commands :neutron => 'neutron'
def self.request(service, action, properties=nil)
begin
@ -42,20 +41,6 @@ class Puppet::Provider::Neutron < Puppet::Provider::Openstack
'/etc/neutron/neutron.conf'
end
def self.withenv(hash, &block)
saved = ENV.to_hash
hash.each do |name, val|
ENV[name.to_s] = val
end
yield
ensure
ENV.clear
saved.each do |name, val|
ENV[name] = val
end
end
def self.neutron_conf
return @neutron_conf if @neutron_conf
@neutron_conf = Puppet::Util::IniConfig::File.new
@ -113,58 +98,6 @@ class Puppet::Provider::Neutron < Puppet::Provider::Openstack
@auth_endpoint ||= get_auth_endpoint
end
def self.auth_neutron(*args)
q = neutron_credentials
authenv = {
:OS_AUTH_URL => self.auth_endpoint,
:OS_USERNAME => q['username'],
:OS_PROJECT_NAME => q['project_name'],
:OS_PASSWORD => q['password'],
:OS_PROJECT_DOMAIN_NAME => q['project_domain_name'],
:OS_USER_DOMAIN_NAME => q['user_domain_name']
}
if q.key?('region_name')
authenv[:OS_REGION_NAME] = q['region_name']
end
rv = nil
timeout = 10
end_time = Time.now.to_i + timeout
loop do
begin
withenv authenv do
rv = neutron(args)
end
break
rescue Puppet::ExecutionFailure => e
if ! e.message =~ /(\(HTTP\s+400\))|
(400-\{\'message\'\:\s+\'\'\})|
(\[Errno 111\]\s+Connection\s+refused)|
(503\s+Service\s+Unavailable)|
(504\s+Gateway\s+Time-out)|
(\:\s+Maximum\s+attempts\s+reached)|
(Unauthorized\:\s+bad\s+credentials)|
(Max\s+retries\s+exceeded)/
raise(e)
end
current_time = Time.now.to_i
if current_time > end_time
break
else
wait = end_time - current_time
notice("Unable to complete neutron request due to non-fatal error: \"#{e.message}\". Retrying for #{wait} sec.")
end
sleep(2)
# Note(xarses): Don't remove, we know that there is one of the
# Recoverable erros above, So we will retry a few more times
end
end
return rv
end
def auth_neutron(*args)
self.class.auth_neutron(args)
end
def self.reset
@neutron_conf = nil
@neutron_credentials = nil
@ -194,82 +127,12 @@ class Puppet::Provider::Neutron < Puppet::Provider::Openstack
end
end
def self.list_neutron_resources(type)
ids = []
list = cleanup_csv_with_id(auth_neutron("#{type}-list", '--format=csv',
'--column=id', '--quote=none'))
if list.nil?
raise(Puppet::ExecutionFailure, "Can't retrieve #{type}-list because Neutron or Keystone API is not available.")
end
(list.split("\n")[1..-1] || []).compact.collect do |line|
ids << line.strip
end
return ids
end
def self.get_neutron_resource_attrs(type, id)
attrs = {}
net = auth_neutron("#{type}-show", '--format=shell', id)
if net.nil?
raise(Puppet::ExecutionFailure, "Can't retrieve #{type}-show because Neutron or Keystone API is not available.")
end
last_key = nil
(net.split("\n") || []).compact.collect do |line|
if line.include? '='
k, v = line.split('=', 2)
attrs[k] = v.gsub(/\A"|"\Z/, '')
last_key = k
def self.parse_availability_zone_hint(value)
hints = JSON.parse(value.gsub(/\\"/,'"').gsub('u\'', '"').gsub('\'','"'))
if hints.length > 1
hints
else
# Handle the case of a list of values
v = line.gsub(/\A"|"\Z/, '')
attrs[last_key] = [attrs[last_key], v].flatten
hints.first
end
end
return attrs
end
def self.get_tenant_id(catalog, name, domain='Default')
instance_type = 'keystone_tenant'
instance = catalog.resource("#{instance_type.capitalize!}[#{name}]")
if ! instance
instance = Puppet::Type.type(instance_type).instances.find do |i|
# We need to check against the Default domain name because of
# https://review.opendev.org/#/c/226919/ which changed the naming
# format for the tenant to include <Domain name>. This should be
# removed when we drop the resource without a domain name.
# TODO(aschultz): remove ::domain lookup as part of M-cycle
i.provider.name == name || i.provider.name == "#{name}::#{domain}"
end
end
if instance
return instance.provider.id
else
fail("Unable to find #{instance_type} for name #{name}")
end
end
def self.parse_creation_output(data)
hash = {}
data.split("\n").compact.each do |line|
if line.include? '='
hash[line.split('=').first] = line.split('=', 2)[1].gsub(/\A"|"\Z/, '')
end
end
hash
end
def self.cleanup_csv(text)
# Ignore warnings - assume legitimate output starts with a double quoted
# string. Errors will be caught and raised prior to this
text = text.split("\n").drop_while { |line| line !~ /^\".*\"/ }.join("\n")
"#{text}\n"
end
def self.cleanup_csv_with_id(text)
return nil if text.nil?
text = text.split("\n").drop_while { |line| line !~ /^\s*id$/ }.join("\n")
"#{text}\n"
end
end

View File

@ -167,15 +167,6 @@ Puppet::Type.type(:neutron_network).provide(
@property_hash[:ensure] = :absent
end
def self.parse_availability_zone_hint(value)
hints = JSON.parse(value.gsub(/\\"/,'"').gsub('u\'', '"').gsub('\'','"'))
if hints.length > 1
hints
else
hints.first
end
end
[
:admin_state_up,
:shared,

View File

@ -1,207 +0,0 @@
require File.join(File.dirname(__FILE__), '..','..','..',
'puppet/provider/neutron')
Puppet::Type.type(:neutron_router).provide(
:neutron,
:parent => Puppet::Provider::Neutron
) do
desc <<-EOT
Neutron provider to manage neutron_router type.
Assumes that the neutron service is configured on the same host.
EOT
mk_resource_methods
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
list = list_neutron_resources('router').collect do |id|
attrs = get_neutron_resource_attrs('router', id)
new(
:ensure => :present,
:name => attrs['name'],
:id => attrs['id'],
:admin_state_up => attrs['admin_state_up'],
:external_gateway_info => attrs['external_gateway_info'],
:status => attrs['status'],
:distributed => attrs['distributed'],
:ha => attrs['ha'],
:tenant_id => attrs['tenant_id'],
:availability_zone_hint => attrs['availability_zone_hint']
)
end
self.do_not_manage = false
list
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
if self.class.do_not_manage
fail("Not managing Neutron_router[#{@resource[:name]}] due to earlier Neutron API failures.")
end
opts = Array.new
if @resource[:admin_state_up] == 'False'
opts << '--admin-state-down'
end
if @resource[:tenant_name]
tenant_id = self.class.get_tenant_id(@resource.catalog,
@resource[:tenant_name])
opts << "--tenant_id=#{tenant_id}"
elsif @resource[:tenant_id]
opts << "--tenant_id=#{@resource[:tenant_id]}"
end
if @resource[:distributed]
opts << "--distributed=#{@resource[:distributed]}"
end
if @resource[:ha]
opts << "--ha=#{@resource[:ha]}"
end
if @resource[:availability_zone_hint]
opts << "--availability-zone-hint=#{@resource[:availability_zone_hint]}"
end
results = auth_neutron("router-create", '--format=shell',
opts, resource[:name])
attrs = self.class.parse_creation_output(results)
@property_hash = {
:ensure => :present,
:name => resource[:name],
:id => attrs['id'],
:admin_state_up => attrs['admin_state_up'],
:external_gateway_info => attrs['external_gateway_info'],
:status => attrs['status'],
:tenant_id => attrs['tenant_id'],
:availability_zone_hint => attrs['availability_zone_hint']
}
if @resource[:gateway_network_name]
results = auth_neutron('router-gateway-set',
@resource[:name],
@resource[:gateway_network_name])
attrs = self.class.get_neutron_resource_attrs('router',
@resource[:name])
@property_hash[:external_gateway_info] = \
attrs['external_gateway_info']
end
end
def destroy
if self.class.do_not_manage
fail("Not managing Neutron_router[#{@resource[:name]}] due to earlier Neutron API failures.")
end
auth_neutron('router-delete', name)
@property_hash[:ensure] = :absent
end
def gateway_network_name
if @gateway_network_name == nil and gateway_network_id
Puppet::Type.type('neutron_network').instances.each do |instance|
if instance.provider.id == gateway_network_id
@gateway_network_name = instance.provider.name
end
end
end
@gateway_network_name
end
def gateway_network_name=(value)
if self.class.do_not_manage
fail("Not managing Neutron_router[#{@resource[:name]}] due to earlier Neutron API failures.")
end
if value == ''
auth_neutron('router-gateway-clear', name)
else
auth_neutron('router-gateway-set', name, value)
end
end
def parse_gateway_network_id(external_gateway_info_)
match_data = /\{"network_id": "(.*?)"/.match(external_gateway_info_.gsub(/\\"/,'"'))
if match_data
match_data[1]
else
''
end
end
def gateway_network_id
@gateway_network_id ||= parse_gateway_network_id(external_gateway_info)
end
def admin_state_up=(value)
if self.class.do_not_manage
fail("Not managing Neutron_router[#{@resource[:name]}] due to earlier Neutron API failures.")
end
set_admin_state_up(value)
end
def set_admin_state_up(value)
auth_neutron('router-update', "--admin-state-up=#{value}", name)
end
def distributed=(value)
if self.class.do_not_manage
fail("Not managing Neutron_router[#{@resource[:name]}] due to earlier Neutron API failures.")
end
results = auth_neutron("router-show", '--format=shell', resource[:name])
attrs = self.class.parse_creation_output(results)
set_admin_state_up(false)
auth_neutron('router-update', "--distributed=#{value}", name)
if attrs['admin_state_up'] == 'True'
set_admin_state_up(true)
end
end
def ha=(value)
if self.class.do_not_manage
fail("Not managing Neutron_router[#{@resource[:name]}] due to earlier Neutron API failures.")
end
results = auth_neutron("router-show", '--format=shell', resource[:name])
attrs = self.class.parse_creation_output(results)
set_admin_state_up(false)
auth_neutron('router-update', "--ha=#{value}", name)
if attrs['admin_state_up'] == 'True'
set_admin_state_up(true)
end
end
def availability_zone_hint=(value)
if self.class.do_not_manage
fail("Not managing Neutron_router[#{@resource[:name]}] due to earlier Neutron API failures.")
end
results = auth_neutron("router-show", '--format=shell', resource[:name])
attrs = self.class.parse_creation_output(results)
set_admin_state_up(false)
auth_neutron('router-update', "--availability-zone-hint=#{value}", name)
if attrs['admin_state_up'] == 'True'
set_admin_state_up(true)
end
end
end

View File

@ -0,0 +1,218 @@
require File.join(File.dirname(__FILE__), '..','..','..',
'puppet/provider/neutron')
Puppet::Type.type(:neutron_router).provide(
:openstack,
:parent => Puppet::Provider::Neutron
) do
desc <<-EOT
Neutron provider to manage neutron_router type.
Assumes that the neutron service is configured on the same host.
EOT
@credentials = Puppet::Provider::Openstack::CredentialsV3.new
mk_resource_methods
def initialize(value={})
super(value)
@property_flush = {}
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
list = request('router', 'list').collect do |attrs|
router = request('router', 'show', attrs[:id])
new(
:ensure => :present,
:name => attrs[:name],
:id => attrs[:id],
:admin_state_up => router[:admin_state_up],
:external_gateway_info => router[:external_gateway_info],
:status => router[:status],
:distributed => router[:distributed],
:ha => router[:ha],
:tenant_id => router[:tenant_id],
:availability_zone_hint => parse_availability_zone_hint(router[:availability_zone_hints])
)
end
self.do_not_manage = false
list
end
def self.prefetch(resources)
routers = instances
resources.keys.each do |name|
if provider = routers.find{ |router| router.name == name }
resources[name].provider = provider
end
end
end
def exists?
@property_hash[:ensure] == :present
end
def create
if self.class.do_not_manage
fail("Not managing Neutron_router[#{@resource[:name]}] due to earlier Neutron API failures.")
end
opts = [@resource[:name]]
if @resource[:admin_state_up] == 'False'
opts << '--disable'
end
if @resource[:tenant_name]
opts << "--project=#{@resource[:tenant_name]}"
elsif @resource[:tenant_id]
opts << "--project=#{@resource[:tenant_id]}"
end
if @resource[:distributed]
if @resource[:distributed] == 'False'
opts << '--centralized'
else
opts << '--distributed'
end
end
if @resource[:ha]
if @resource[:ha] == 'False'
opts << '--no-ha'
else
opts << '--ha'
end
end
if @resource[:availability_zone_hint]
opts << \
"--availability-zone-hint=#{@resource[:availability_zone_hint]}"
end
router = self.class.request('router', 'create', opts)
if @resource[:gateway_network_id]
self.class.request('router', 'set',
[@resource[:name],
"--external-gateway=#{@resource[:gateway_network_id]}"])
router = self.class.request('router', 'show', [@resource[:name]])
elsif @resource[:gateway_network_name]
self.class.request('router', 'set',
[@resource[:name],
"--external-gateway=#{@resource[:gateway_network_name]}"])
router = self.class.request('router', 'show', [@resource[:name]])
end
@property_hash = {
:ensure => :present,
:name => router[:name],
:id => router[:id],
:admin_state_up => router[:admin_state_up],
:external_gateway_info => router[:external_gateway_info],
:status => router[:status],
:distributed => router[:distributed],
:ha => router[:ha],
:tenant_id => router[:tenant_id],
:availability_zone_hint => self.class.parse_availability_zone_hint(router[:availability_zone_hints])
}
end
def flush
if !@property_flush.empty?
opts = [@resource[:name]]
clear_opts = [@resource[:name]]
if @property_flush.has_key?(:admin_state_up)
if @property_flush[:admin_state_up] == 'False'
opts << '--disable'
else
opts << '--enable'
end
end
if @property_flush.has_key?(:distributed)
if @property_flush[:distributed] == 'False'
opts << '--centralized'
else
opts << '--distributed'
end
end
if @property_flush.has_key?(:gateway_network_id)
if @property_flush[:gateway_network_id] == ''
clear_opts << '--external-gateway'
else
opts << "--external-gateway=#{@property_flush[:gateway_network_id]}"
end
elsif @property_flush.has_key?(:gateway_network_name)
if @property_flush[:gateway_network_name] == ''
clear_opts << '--external-gateway'
else
opts << "--external-gateway=#{@property_flush[:gateway_network_name]}"
end
end
if @property_flush.has_key?(:ha)
if @property_flush[:ha] == 'False'
opts << '--no-ha'
else
opts << '--ha'
end
end
if clear_opts.length > 1
self.class.request('router', 'unset', clear_opts)
end
if opts.length > 1
self.class.request('router', 'set', opts)
end
@property_flush.clear
end
end
def destroy
if self.class.do_not_manage
fail("Not managing Neutron_router[#{@resource[:name]}] due to earlier Neutron API failures.")
end
self.class.request('router', 'delete', @resource[:name])
@property_flush.clear
@property_flush[:ensure] = :absent
end
[
:admin_state_up,
:gateway_network_id,
:gateway_network_name,
:distributed,
:ha,
].each do |attr|
define_method(attr.to_s + "=") do |value|
if self.class.do_not_manage
fail("Not managing Neutron_router[#{@resource[:name]}] due to earlier Neutron API failures.")
end
@property_flush[attr] = value
end
end
[
:availability_zone_hint,
:tenant_id,
:tenant_name,
].each do |attr|
define_method(attr.to_s + "=") do |value|
fail("Property #{attr.to_s} does not support being updated")
end
end
end

View File

@ -1,114 +0,0 @@
require 'puppet'
require 'spec_helper'
require 'puppet/provider/neutron_router/neutron'
provider_class = Puppet::Type.type(:neutron_router).provider(:neutron)
klass = Puppet::Provider::Neutron
describe provider_class do
let :router_name do
'router1'
end
let :router_attrs do
{
:name => router_name,
:ensure => 'present',
:admin_state_up => 'True',
:distributed => 'True',
:ha => 'False',
:tenant_id => '60f9544eb94c42a6b7e8e98c2be981b1',
:availability_zone_hint => 'zone1',
}
end
let :resource do
Puppet::Type::Neutron_router.new(router_attrs)
end
let :provider do
provider_class.new(resource)
end
describe 'when creating a router' do
it 'should call router-create with appropriate command line options' do
provider.class.stubs(:get_tenant_id).returns(router_attrs[:tenant_id])
output = 'Created a new router:
admin_state_up="True"
external_gateway_info=""
id="c5f799fa-b3e0-47ca-bdb7-abeff209b816"
name="router1"
status="ACTIVE"
distributed="True"
ha="False"
tenant_id="60f9544eb94c42a6b7e8e98c2be981b1"
availability-zone-hint="zone1"'
provider.expects(:auth_neutron).with('router-create',
'--format=shell', ["--tenant_id=#{router_attrs[:tenant_id]}",
"--distributed=#{router_attrs[:distributed]}",
"--ha=#{router_attrs[:ha]}",
"--availability-zone-hint=#{router_attrs[:availability_zone_hint]}"],
router_name).returns(output)
provider.create
end
end
describe 'when updating a router' do
it 'should call router-update to change admin_state_up' do
provider.expects(:auth_neutron).with('router-update',
'--admin-state-up=False',
router_name)
provider.admin_state_up=('False')
end
it 'should call router-gateway-clear for an empty network name' do
provider.expects(:auth_neutron).with('router-gateway-clear',
router_name)
provider.gateway_network_name=('')
end
it 'should call router-gateway-set to configure an external network' do
provider.expects(:auth_neutron).with('router-gateway-set',
router_name,
'net1')
provider.gateway_network_name=('net1')
end
end
describe 'when parsing an external gateway info' do
let :resource do
Puppet::Type::Neutron_router.new(router_attrs)
end
let :provider do
provider_class.new(resource)
end
after :each do
klass.reset
end
it 'should detect a gateway net id' do
klass.stubs(:auth_neutron).returns(
'external_gateway_info="{\"network_id\": \"1b-b1\", \"enable_snat\": true, \"external_fixed_ips\": [{\"subnet_id\": \"1b-b1\", \"ip_address\": \"1.1.1.1\"}]}"'
)
result = klass.get_neutron_resource_attrs 'foo', nil
expect(provider.parse_gateway_network_id(result['external_gateway_info'])).to eql('1b-b1')
end
it 'should return empty value, if there is no net id found' do
klass.stubs(:auth_neutron).returns('external_gateway_info="{}"')
result = klass.get_neutron_resource_attrs 'foo', nil
expect(provider.parse_gateway_network_id(result['external_gateway_info'])).to eql('')
end
end
end

View File

@ -0,0 +1,352 @@
require 'puppet'
require 'spec_helper'
require 'puppet/provider/neutron_router/openstack'
provider_class = Puppet::Type.type(:neutron_router).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 routers' do
let :router_name do
'router1'
end
let :router_attrs do
{
:name => router_name,
:ensure => 'present',
}
end
let :resource do
Puppet::Type::Neutron_router.new(router_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' do
provider_class.expects(:openstack)
.with('router', 'create', '--format', 'shell',
['router1'])
.returns('admin_state_up="True"
availability_zone_hints="[]"
distributed="False"
external_gateway_info="None"
ha="True"
id="d73f453a-77ca-4843-977a-3af0fda8dfcb"
name="router1"
project_id="60f9544eb94c42a6b7e8e98c2be981b1"
project_id="60f9544eb94c42a6b7e8e98c2be981b1"
status="ACTIVE"')
provider.create
expect(provider.exists?).to be_truthy
expect(provider.admin_state_up).to eq('True')
expect(provider.ha).to eq('True')
expect(provider.distributed).to eq('False')
expect(provider.status).to eq('ACTIVE')
end
end
context 'with admin_state_up' do
let :router_attrs do
{
:name => router_name,
:ensure => 'present',
:admin_state_up => 'False',
}
end
it 'creates router' do
provider_class.expects(:openstack)
.with('router', 'create', '--format', 'shell',
['router1', '--disable'])
.returns('admin_state_up="False"
availability_zone_hints="[]"
distributed="False"
external_gateway_info="None"
ha="True"
id="d73f453a-77ca-4843-977a-3af0fda8dfcb"
name="router1"
project_id="60f9544eb94c42a6b7e8e98c2be981b1"
status="ACTIVE"')
provider.create
expect(provider.exists?).to be_truthy
expect(provider.admin_state_up).to eq('False')
end
end
context 'with centralized' do
let :router_attrs do
{
:name => router_name,
:ensure => 'present',
:distributed => 'False',
}
end
it 'creates router' do
provider_class.expects(:openstack)
.with('router', 'create', '--format', 'shell',
['router1', '--centralized'])
.returns('admin_state_up="True"
availability_zone_hints="[]"
distributed="False"
external_gateway_info="None"
ha="True"
id="d73f453a-77ca-4843-977a-3af0fda8dfcb"
name="router1"
project_id="60f9544eb94c42a6b7e8e98c2be981b1"
status="ACTIVE"')
provider.create
expect(provider.exists?).to be_truthy
expect(provider.distributed).to eq('False')
end
end
context 'with distributed' do
let :router_attrs do
{
:name => router_name,
:ensure => 'present',
:distributed => 'True',
}
end
it 'creates router' do
provider_class.expects(:openstack)
.with('router', 'create', '--format', 'shell',
['router1', '--distributed'])
.returns('admin_state_up="True"
availability_zone_hints="[]"
distributed="True"
external_gateway_info="None"
ha="True"
id="d73f453a-77ca-4843-977a-3af0fda8dfcb"
name="router1"
project_id="60f9544eb94c42a6b7e8e98c2be981b1"
status="ACTIVE"')
provider.create
expect(provider.exists?).to be_truthy
expect(provider.distributed).to eq('True')
end
end
context 'with ha' do
let :router_attrs do
{
:name => router_name,
:ensure => 'present',
:ha => 'True',
}
end
it 'creates router' do
provider_class.expects(:openstack)
.with('router', 'create', '--format', 'shell',
['router1', '--ha'])
.returns('admin_state_up="True"
availability_zone_hints="[]"
distributed="False"
external_gateway_info="None"
ha="True"
id="d73f453a-77ca-4843-977a-3af0fda8dfcb"
name="router1"
project_id="60f9544eb94c42a6b7e8e98c2be981b1"
status="ACTIVE"')
provider.create
expect(provider.exists?).to be_truthy
expect(provider.ha).to eq('True')
end
end
context 'with non-ha' do
let :router_attrs do
{
:name => router_name,
:ensure => 'present',
:ha => 'False',
}
end
it 'creates router' do
provider_class.expects(:openstack)
.with('router', 'create', '--format', 'shell',
['router1', '--no-ha'])
.returns('admin_state_up="True"
availability_zone_hints="[]"
distributed="False"
external_gateway_info="None"
ha="False"
id="d73f453a-77ca-4843-977a-3af0fda8dfcb"
name="router1"
project_id="60f9544eb94c42a6b7e8e98c2be981b1"
status="ACTIVE"')
provider.create
expect(provider.exists?).to be_truthy
expect(provider.ha).to eq('False')
end
end
context 'with gateway_network_name' do
let :router_attrs do
{
:name => router_name,
:ensure => 'present',
:gateway_network_name => 'net1',
}
end
it 'creates router' do
provider_class.expects(:openstack)
.with('router', 'create', '--format', 'shell',
['router1'])
.returns('admin_state_up="True"
availability_zone_hints="[]"
distributed="False"
external_gateway_info="None"
ha="False"
id="d73f453a-77ca-4843-977a-3af0fda8dfcb"
name="router1"
project_id="60f9544eb94c42a6b7e8e98c2be981b1"
status="ACTIVE"')
provider_class.expects(:openstack)
.with('router', 'set', ['router1', '--external-gateway=net1'])
provider_class.expects(:openstack)
.with('router', 'show', '--format', 'shell',
['router1'])
.returns('admin_state_up="True"
availability_zone_hints="[]"
distributed="False"
external_gateway_info="{\'network_id\': \'076520cc-b783-4cf5-a4a9-4cb5a5e93a9b\'}"
ha="False"
id="d73f453a-77ca-4843-977a-3af0fda8dfcb"
name="router1"
project_id="60f9544eb94c42a6b7e8e98c2be981b1"
status="ACTIVE"')
provider.create
expect(provider.exists?).to be_truthy
end
end
end
describe '#destroy' do
it 'removes router' do
provider_class.expects(:openstack)
.with('router', 'delete', 'router1')
provider.destroy
expect(provider.exists?).to be_falsey
end
end
describe '#flush' do
context '.admin_state_up' do
it 'updates router' do
provider_class.expects(:openstack)
.with('router', 'set', ['router1', '--disable'])
provider.admin_state_up = 'False'
provider.flush
provider_class.expects(:openstack)
.with('router', 'set', ['router1', '--enable'])
provider.admin_state_up = 'True'
provider.flush
end
end
context '.distributed' do
it 'updates router' do
provider_class.expects(:openstack)
.with('router', 'set', ['router1', '--distributed'])
provider.distributed = 'True'
provider.flush
provider_class.expects(:openstack)
.with('router', 'set', ['router1', '--centralized'])
provider.distributed = 'False'
provider.flush
end
end
context '.ha' do
it 'updates router' do
provider_class.expects(:openstack)
.with('router', 'set', ['router1', '--ha'])
provider.ha = 'True'
provider.flush
provider_class.expects(:openstack)
.with('router', 'set', ['router1', '--no-ha'])
provider.ha = 'False'
provider.flush
end
end
context '.gateway_network_name' do
it 'updates router' do
provider_class.expects(:openstack)
.with('router', 'set', ['router1', '--external-gateway=net1'])
provider.gateway_network_name = 'net1'
provider.flush
provider_class.expects(:openstack)
.with('router', 'unset', ['router1', '--external-gateway'])
provider.gateway_network_name = ''
provider.flush
end
end
end
describe '#instances' do
it 'lists router' do
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","DOWN","False","60f9544eb94c42a6b7e8e98c2be981b1",False,True
')
provider_class.expects(:openstack)
.with('router', 'show', '--format', 'shell', 'd73f453a-77ca-4843-977a-3af0fda8dfcb')
.returns('admin_state_up="True"
availability_zone_hints="[]"
distributed="False"
external_gateway_info="None"
ha="True"
id="d73f453a-77ca-4843-977a-3af0fda8dfcb"
name="router1"
project_id="60f9544eb94c42a6b7e8e98c2be981b1"
status="ACTIVE"')
provider_class.expects(:openstack)
.with('router', 'show', '--format', 'shell', 'c3e93a5b-45ee-4029-b3a3-3331cb3e3f2a')
.returns('admin_state_up="False"
availability_zone_hints="[]"
distributed="True"
external_gateway_info="None"
ha="False"
id="c3e93a5b-45ee-4029-b3a3-3331cb3e3f2a"
name="router2"
project_id="60f9544eb94c42a6b7e8e98c2be981b1"
project_id="60f9544eb94c42a6b7e8e98c2be981b1"
status="DOWN"')
instances = provider_class.instances
expect(instances.length).to eq(2)
expect(instances[0].id).to eq('d73f453a-77ca-4843-977a-3af0fda8dfcb')
expect(instances[0].name).to eq('router1')
expect(instances[0].admin_state_up).to eq('True')
expect(instances[0].ha).to eq('True')
expect(instances[0].distributed).to eq('False')
expect(instances[0].status).to eq('ACTIVE')
expect(instances[1].id).to eq('c3e93a5b-45ee-4029-b3a3-3331cb3e3f2a')
expect(instances[1].name).to eq('router2')
expect(instances[1].admin_state_up).to eq('False')
expect(instances[1].ha).to eq('False')
expect(instances[1].distributed).to eq('True')
expect(instances[1].status).to eq('DOWN')
end
end
end
end

View File

@ -57,148 +57,5 @@ describe Puppet::Provider::Neutron do
klass.neutron_credentials
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_USERNAME => credential_hash['username'],
:OS_PROJECT_NAME => credential_hash['project_name'],
:OS_PASSWORD => credential_hash['password'],
:OS_PROJECT_DOMAIN_NAME => credential_hash['project_domain_name'],
:OS_USER_DOMAIN_NAME => credential_hash['user_domain_name'],
}
klass.expects(:get_neutron_credentials).with().returns(credential_hash)
klass.expects(:withenv).with(authenv)
klass.auth_neutron('test_retries')
end
it 'should set region in the environment if needed' do
authenv = {
:OS_AUTH_URL => credential_hash['auth_url'],
:OS_USERNAME => credential_hash['username'],
:OS_PROJECT_NAME => credential_hash['project_name'],
:OS_PASSWORD => credential_hash['password'],
:OS_REGION_NAME => 'REGION_NAME',
:OS_PROJECT_DOMAIN_NAME => credential_hash['project_domain_name'],
:OS_USER_DOMAIN_NAME => credential_hash['user_domain_name'],
}
cred_hash = credential_hash.merge({'region_name' => 'REGION_NAME'})
klass.expects(:get_neutron_credentials).with().returns(cred_hash)
klass.expects(:withenv).with(authenv)
klass.auth_neutron('test_retries')
end
['[Errno 111] Connection refused',
'400-{\'message\': \'\'}',
'(HTTP 400)',
'503 Service Unavailable',
'504 Gateway Time-out',
'Maximum attempts reached',
'Unauthorized: bad credentials',
'Max retries exceeded'].reverse.each do |valid_message|
it "should retry when neutron cli returns with error #{valid_message}" do
klass.expects(:get_neutron_credentials).with().returns({})
klass.expects(:sleep).with(2).returns(nil)
klass.expects(:neutron).twice.with(['test_retries']).raises(
Puppet::ExecutionFailure, valid_message).then.returns('')
klass.auth_neutron('test_retries')
end
end
end
describe 'when listing neutron resources' do
it 'should exclude the column header' do
output = <<-EOT
id
net1
net2
EOT
klass.expects(:auth_neutron).returns(output)
result = klass.list_neutron_resources('foo')
expect(result).to eql(['net1', 'net2'])
end
it 'should return empty list when there are no neutron resources' do
output = <<-EOT
EOT
klass.stubs(:auth_neutron).returns(output)
result = klass.list_neutron_resources('foo')
expect(result).to eql([])
end
it 'should fail if resources list is nil' do
klass.stubs(:auth_neutron).returns(nil)
expect do
klass.list_neutron_resources('foo')
end.to raise_error(Puppet::Error, exec_error)
end
end
describe 'when retrieving attributes for neutron resources' do
it 'should parse single-valued attributes into a key-value pair' do
klass.expects(:auth_neutron).returns('admin_state_up="True"')
result = klass.get_neutron_resource_attrs('foo', 'id')
expect(result).to eql({"admin_state_up" => 'True'})
end
it 'should parse multi-valued attributes into a key-list pair' do
output = <<-EOT
subnets="subnet1
subnet2
subnet3"
EOT
klass.expects(:auth_neutron).returns(output)
result = klass.get_neutron_resource_attrs('foo', 'id')
expect(result).to eql({"subnets" => ['subnet1', 'subnet2', 'subnet3']})
end
end
describe 'when parsing creation output' do
it 'should parse valid output into a hash' do
data = <<-EOT
Created a new network:
admin_state_up="True"
id="5f9cbed2-d31c-4e9c-be92-87229acb3f69"
name="foo"
tenant_id="3056a91768d948d399f1d79051a7f221"
EOT
expected = {
'admin_state_up' => 'True',
'id' => '5f9cbed2-d31c-4e9c-be92-87229acb3f69',
'name' => 'foo',
'tenant_id' => '3056a91768d948d399f1d79051a7f221',
}
expect(klass.parse_creation_output(data)).to eq(expected)
end
end
describe 'garbage in the csv output' do
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.
InsecurePlatformWarning
id
4a305398-d806-46c5-a6aa-dcd6a4a99330
EOT
klass.expects(:auth_neutron).
with('subnet-list', '--format=csv', '--column=id', '--quote=none').
returns(output)
expected = ['4a305398-d806-46c5-a6aa-dcd6a4a99330']
result = klass.list_neutron_resources('subnet')
expect(result).to eql(expected)
end
end
end