Files
puppet-keystone/lib/puppet/provider/keystone_endpoint/openstack.rb
Sofer Athlan-Guyot fe0edef97d Fix endpoint update when one endpoint is missing.
Endpoint are created for admin, internal and public network by this
provider.  If only one of the endpoint is missing then all the endpoints
are recreated as puppet fails to match the resource with the remaining
endpoint.

This fix enable one to update the resource where update means "recreate
the missing endpoint".

Change-Id: Ic605725d1923680c6518ebadda36cb5d596c08fe
Closes-bug: 1559013
2016-06-23 18:44:24 +02:00

297 lines
8.4 KiB
Ruby

require File.join(File.dirname(__FILE__), '..','..','..', 'puppet/provider/keystone')
Puppet::Type.type(:keystone_endpoint).provide(
:openstack,
:parent => Puppet::Provider::Keystone
) do
desc "Provider to manage keystone endpoints."
include PuppetX::Keystone::CompositeNamevar::Helpers
attr_accessor :property_hash, :property_flush
@endpoints = nil
@services = nil
@credentials = Puppet::Provider::Openstack::CredentialsV3.new
@do_not_manage = false
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 create
if self.class.do_not_manage
fail("Not managing Keystone_endpoint[#{@resource[:name]}] due to earlier Keystone API failures.")
end
name = resource[:name]
region = resource[:region]
type = resource[:type]
type = self.class.type_from_service(name) unless set?(:type)
@property_hash[:type] = type
ids = []
s_id = service_id
created = false
[:admin_url, :internal_url, :public_url].each do |scope|
if resource[scope]
created = true
ids << endpoint_create(s_id, region, scope.to_s.sub(/_url$/, ''),
resource[scope])[:id]
end
end
if created
@property_hash[:id] = ids.join(',')
@property_hash[:ensure] = :present
else
warning('Specifying a keystone_endpoint without an ' \
'admin_url/public_url/internal_url ' \
"won't create the endpoint at all, despite what Puppet is saying.")
@property_hash[:ensure] = :absent
end
end
def destroy
if self.class.do_not_manage
fail("Not managing Keystone_endpoint[#{@resource[:name]}] due to earlier Keystone API failures.")
end
ids = @property_hash[:id].split(',')
ids.each do |id|
self.class.request('endpoint', 'delete', id)
end
@property_hash.clear
end
def exists?
@property_hash[:ensure] == :present
end
mk_resource_methods
def public_url=(value)
if self.class.do_not_manage
fail("Not managing Keystone_endpoint[#{@resource[:name]}] due to earlier Keystone API failures.")
end
@property_flush[:public_url] = value
end
def internal_url=(value)
if self.class.do_not_manage
fail("Not managing Keystone_endpoint[#{@resource[:name]}] due to earlier Keystone API failures.")
end
@property_flush[:internal_url] = value
end
def admin_url=(value)
if self.class.do_not_manage
fail("Not managing Keystone_endpoint[#{@resource[:name]}] due to earlier Keystone API failures.")
end
@property_flush[:admin_url] = value
end
def region=(_)
fail(Puppet::Error, "Updating the endpoint's region is not currently supported.")
end
def self.instances
names = []
list = []
endpoints.each do |current|
name = transform_name(current[:region], current[:service_name], current[:service_type])
unless names.include?(name)
names << name
endpoint = { :name => name, current[:interface].to_sym => current }
endpoints.each do |ep_osc|
if (ep_osc[:id] != current[:id]) &&
(ep_osc[:service_name] == current[:service_name]) &&
(ep_osc[:service_type] == current[:service_type]) &&
(ep_osc[:region] == current[:region])
endpoint.merge!(ep_osc[:interface].to_sym => ep_osc)
end
end
list << endpoint
end
end
list.collect do |endpoint|
new(
:name => endpoint[:name],
:ensure => :present,
:id => make_id(endpoint),
:region => get_region(endpoint),
:admin_url => get_url(endpoint, :admin),
:internal_url => get_url(endpoint, :internal),
:public_url => get_url(endpoint, :public)
)
end
end
def self.prefetch(resources)
prefetch_composite(resources) do |sorted_namevars|
name = sorted_namevars[0]
region = sorted_namevars[1]
type = sorted_namevars[2]
transform_name(region, name, type)
end
end
def flush
if property_flush && property_hash[:id]
scopes = [:admin_url, :internal_url, :public_url]
ids = Hash[scopes.zip(property_hash[:id].split(','))]
scopes.each do |scope|
if property_flush[scope]
if ids[scope].nil? || ids[scope].empty?
ids[scope] = endpoint_create(service_id, resource[:region],
scope.to_s.sub(/_url$/, ''),
property_flush[scope])[:id]
else
self.class.request('endpoint',
'set',
[ids[scope],
"--url=#{resource[scope]}"])
end
end
end
@property_hash = resource.to_hash
@property_hash[:id] = scopes.map { |s| ids[s] }.join(',')
@property_hash[:ensure] = :present
end
end
private
def endpoint_create(name, region, interface, url)
properties = [name, interface, url, '--region', region]
self.class.request('endpoint', 'create', properties)
end
private
def self.endpoints
return @endpoints unless @endpoints.nil?
prev_do_not_manage = self.do_not_manage
self.do_not_manage = true
@endpoints = request('endpoint', 'list')
self.do_not_manage = prev_do_not_manage
@endpoints
end
def self.endpoints=(value)
@endpoints = value
end
def self.services
return @services unless @services.nil?
prev_do_not_manage = self.do_not_manage
self.do_not_manage = true
@services = request('service', 'list')
self.do_not_manage = prev_do_not_manage
@services
end
def self.services=(value)
@services = value
end
def self.endpoint_from_region_name(region, name)
endpoints.find_all { |e| e[:region] == region && e[:service_name] == name }
.map { |e| e[:service_type] }.uniq
end
def self.type_from_service(name)
types = services.find_all { |s| s[:name] == name }.map { |e| e[:type] }.uniq
if types.count == 1
types[0]
else
# We don't fail here as it can happen during a ensure => absent.
PuppetX::Keystone::CompositeNamevar::Unset
end
end
def self.service_type(services, region, name)
nbr_of_services = services.count
err_msg = ["endpoint matching #{region}/#{name}:"]
type = nil
case
when nbr_of_services == 1
type = services[0]
when nbr_of_services > 1
err_msg += [endpoint_from_region_name(region, name).join(' ')]
when nbr_of_services < 1
# Then we try to get the type by service name.
type = type_from_service(name)
end
if !type.nil?
type
else
fail(Puppet::Error, 'Cannot get the correct endpoint type: ' \
"#{err_msg.join(' ')}")
end
end
def self.transform_name(region, name, type)
if type == PuppetX::Keystone::CompositeNamevar::Unset
type = service_type(endpoint_from_region_name(region, name), region, name)
end
if type == PuppetX::Keystone::CompositeNamevar::Unset
Puppet.debug("Could not find the type for endpoint #{region}/#{name}")
"#{region}/#{name}"
else
"#{region}/#{name}::#{type}"
end
end
def self.make_id(endpoint)
id_str = ''
id_sep = ''
[:admin, :internal, :public].each do |type|
id_str += "#{id_sep}#{endpoint[type][:id]}" if endpoint[type]
id_sep = ','
end
id_str
end
def self.get_region(endpoint)
type = [:admin, :internal, :public].detect { |t| endpoint.key? t }
type ? endpoint[type][:region] : ''
end
def self.get_url(endpoint, type, default='')
endpoint[type][:url] rescue default
end
def service_id
# Reset the cache.
self.class.services = nil
name = resource[:name]
type = resource[:type]
services = self.class.services.find_all { |s| s[:name] == name }
service = services.find { |s| s[:type] == type }
service_id = ''
if service.nil? && services.count == 1
# For backward comptatibility, match the service by name only.
service_id = services[0][:id]
else
# Math the service by id.
service_id = service[:id] if service
end
if service_id.nil? || service_id.empty?
title = self.class.transform_name(resource[:region], resource[:name], resource[:type])
fail(Puppet::Error, "Cannot find service associated with #{title}")
end
service_id
end
end