class Consul::Extensions::UID

Global Unique ID Generator Extension.

A utility extension that helps in syncronously and safely generating unique id.

Constants

MAX_ATTEMPTS

Public Class Methods

new(options) click to toggle source

Public: Constructor for this extension. Ensures a global unique ID for this client for a given namespace.

Under the covers UID generator associates the Consul agent to the determine its individuality away from other clients. the opts parameter is used to expose an external parameter to allow clients to determin uniqueness.

For Example:

catOpts = {:name => 'animal', :client_id => 'cat'}
otherCatOpts = {:name => 'animal', :client_id => 'cat'}
dogOpts = {:name => 'animal', :client_id => 'dog'}

UID.new(catOpts).get will return the same UID as UID.new(otherCatOpts).get UID.new(catOpts).get will return different UIDs from UID.new(dogOpts).get

options               - (Required) Hash of Consul Client and extension options.
options[:name]        - (Required) The name or name space of the GUID to generate.  This extension will
                        generate a GUID with respect to other clients is this name space.
options[:client_id]   - (Optional) External Client ID.  This is an additional semantic parameter external to consul.
                        This provides the capability to unique identify your external client.
                        Default: Consul Agent name.  Cannot begin with "."
options[:data_center] - (Optional) The Consul data center. Default: 'dc1'.
options[:api_host]    - (Optional) The Consul api host to request against.  Default: '127.0.0.1'.
options[:api_port]    - (Optional) The Consul api port the api host is listening to. Default: '8500'.
options[:version]     - (Optional) The Consul API version to use. Default: 'v1'.
options[:logger]      - (Optional) The default logging mechanism. Default: Logger.new(STDOUT).

Extension instance capable of generating GUID.

# File lib/consul/extensions/uid.rb, line 43
def initialize(options)
  raise TypeError.new "Options must not be a Hash that contains \":name\"" unless options.is_a?(Hash)
  if options[:name].nil? or options[:name].strip.empty?
    raise ArgumentError.new "Illegal GUID Name: \"#{options[:name]}\". Must not be nil or empty"
  end
  raise ArgumentError.new "GUID Name cannot start with special character" unless /^[^0-9A-Za-z].*/.match(options[:name]).nil?
  @name = options[:name]
  options[:namespace] = namespace
  @options = options.clone
end

Public Instance Methods

available_lock_path() click to toggle source
# File lib/consul/extensions/uid.rb, line 122
def available_lock_path
  '.available.lock'
end
available_uid_path() click to toggle source

Path to getting the next available UID.

# File lib/consul/extensions/uid.rb, line 118
def available_uid_path
  '.available.uid'
end
client_id() click to toggle source

The individual client id for this uid generator

# File lib/consul/extensions/uid.rb, line 132
def client_id
  c_id = nil
  unless @options[:client_id].nil? or @options[:client_id].strip.empty?
    c_id = @options[:client_id].strip
  end
  @client_id ||= c_id || agent.describe.member.name
end
client_uid_path() click to toggle source

Path to this specific Client UID.

# File lib/consul/extensions/uid.rb, line 127
def client_uid_path
  client_id
end
get() click to toggle source

Public: Generate a global unique id synchronously with other

# File lib/consul/extensions/uid.rb, line 55
def get
  # Check if there is already an UID associated with this client_id.
  existing_uid = key_value_store.get(client_uid_path)
  unless existing_uid.nil? or (existing_uid.respond_to?(:empty?) and existing_uid.empty?)
    return existing_uid[0].value.to_i
  end

  # No existing UID so we need to provision our own.
  # Create a Consul Session the the underlying namespace
  cur_session = session.create(Session.for_name(namespace))
  raise 'Unable to create session to generate UID.' if cur_session.nil?

  # No existing UID so let provision one for this node.
  for i in 1..MAX_ATTEMPTS
    session.renew cur_session # Renew the current session so we can obtain a lock

    # Get the current available uid with
    auid = key_value_store.get(available_uid_path, {:index => nil})
    if auid.nil? # Key does not exists. Which also means its the first ever UID.
      logger.debug("First ever UID for #{namespace}")
      auid = 0
    else
      auid = auid[0].value.to_i
      logger.debug("Found available UID for #{namespace} value: #{auid}")
    end
    # Acquire the lock
    member = agent.describe.member.to_json
    acquire_lock_success, body = key_value_store.put(available_lock_path,
                                                     member,
                                                     {:acquire => cur_session.id})
    unless acquire_lock_success
      logger.warn("Attempt: #{i} Unable to acquire lock for #{namespace} reason: #{body}")
      next
    end

    # Update the available uid with the next value
    available_uid_update_success, body = key_value_store.put(available_uid_path, auid + 1)
    unless available_uid_update_success
      logger.warn("Attempt: #{i} Unable to update available uid for #{namespace} reason: #{body}")
      next
    end

    # Release the lock
    release_lock_success, body = key_value_store.put(available_lock_path,
                                                     member,
                                                     {:release => cur_session.id})
    logger.warn("Unable to release lock for #{namespace} reason: #{body}.  Resorting to Timeout.") unless release_lock_success

    # Update the Key Value store for this client id so we don't provision another one.
    client_id_update_success, body = key_value_store.put(client_uid_path, auid)
    unless client_id_update_success
      logger.warn("Attempt: #{i} Unable to update id for client #{client_id} with value #{auid} due to #{body}")
      next
    end

    # After successfully and synchronously updating available universal id and this client id continue
    return auid
  end
  logger.error("Unable to generate key after #{MAX_ATTEMPTS} attempts")
  nil
end

Private Instance Methods

namespace() click to toggle source

The FQ namespace for this GUID

# File lib/consul/extensions/uid.rb, line 143
def namespace
  @namespace ||= "#{extensions_namespace}/uid/#{@name}"
end