diff --git a/lib/puppet/provider/neutron.rb b/lib/puppet/provider/neutron.rb index 8dfc9276b..8de181b2f 100644 --- a/lib/puppet/provider/neutron.rb +++ b/lib/puppet/provider/neutron.rb @@ -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 - else - # Handle the case of a list of values - v = line.gsub(/\A"|"\Z/, '') - attrs[last_key] = [attrs[last_key], v].flatten - 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 . 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 + def self.parse_availability_zone_hint(value) + hints = JSON.parse(value.gsub(/\\"/,'"').gsub('u\'', '"').gsub('\'','"')) + if hints.length > 1 + hints else - fail("Unable to find #{instance_type} for name #{name}") + hints.first 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 diff --git a/lib/puppet/provider/neutron_network/openstack.rb b/lib/puppet/provider/neutron_network/openstack.rb index 1d82ba9bb..c39006658 100644 --- a/lib/puppet/provider/neutron_network/openstack.rb +++ b/lib/puppet/provider/neutron_network/openstack.rb @@ -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, diff --git a/lib/puppet/provider/neutron_router/neutron.rb b/lib/puppet/provider/neutron_router/neutron.rb deleted file mode 100644 index 074db975c..000000000 --- a/lib/puppet/provider/neutron_router/neutron.rb +++ /dev/null @@ -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 diff --git a/lib/puppet/provider/neutron_router/openstack.rb b/lib/puppet/provider/neutron_router/openstack.rb new file mode 100644 index 000000000..e95da8354 --- /dev/null +++ b/lib/puppet/provider/neutron_router/openstack.rb @@ -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 diff --git a/spec/unit/provider/neutron_router/neutron_spec.rb b/spec/unit/provider/neutron_router/neutron_spec.rb deleted file mode 100644 index 4bec1a337..000000000 --- a/spec/unit/provider/neutron_router/neutron_spec.rb +++ /dev/null @@ -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 diff --git a/spec/unit/provider/neutron_router/openstack_spec.rb b/spec/unit/provider/neutron_router/openstack_spec.rb new file mode 100644 index 000000000..cf03766d3 --- /dev/null +++ b/spec/unit/provider/neutron_router/openstack_spec.rb @@ -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 diff --git a/spec/unit/provider/neutron_spec.rb b/spec/unit/provider/neutron_spec.rb index 58b45c291..cb0fddfe4 100644 --- a/spec/unit/provider/neutron_spec.rb +++ b/spec/unit/provider/neutron_spec.rb @@ -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