class NCC::Connection

Attributes

fog[R]

Public Class Methods

connect(ncc, cloud=:default, opt={}) click to toggle source
# File lib/ncc/connection.rb, line 30
def self.connect(ncc, cloud=:default, opt={})
    cfg = ncc.config
    provider = cfg[:clouds][cloud]['provider']
    case provider
    when 'aws'
        require 'ncc/connection/aws'
        NCC::Connection::AWS.new(ncc, cloud, opt)
    when 'openstack'
        require 'ncc/connection/openstack'
        NCC::Connection::OpenStack.new(ncc, cloud, opt)
    end
end
new(ncc, cloud, opt={}) click to toggle source
# File lib/ncc/connection.rb, line 43
def initialize(ncc, cloud, opt={})
    @ncc = ncc
    @cache = { }
    @cfg = ncc.config
    @cfg_mtime = @cfg[:clouds][cloud].mtime
    @cloud = cloud
    @logger = opt[:logger] if opt.has_key? :logger
    @cache_timeout = opt[:cache_timeout] || 600
    @auth_retry_limit = opt[:auth_retry_limit] || 1
    @create_timeout = opt[:create_timeout] || 160
    do_connect
end

Public Instance Methods

collection(type, key=nil) click to toggle source
# File lib/ncc/connection.rb, line 309
def collection(type, key=nil)
    maybe_invalidate type
    @cache[type] ||= {
        :timestamp => Time.now,
        :data => Hash[get_provider_objects(type).map { |o| [o.id, o] }]
    }
    if key.nil?
        @cache[type][:data].values
    else
        @cache[type][:data][key]
    end
end
communication_error(message) click to toggle source
# File lib/ncc/connection.rb, line 576
def communication_error(message)
    raise NCC::Error::Cloud,
    "Error communicating with #{provider} cloud #{@cloud}: " + message unless
        /Error .*communicating with/.match(message)
end
connection_params() click to toggle source
# File lib/ncc/connection.rb, line 113
def connection_params
    []
end
console(instance_id) click to toggle source
# File lib/ncc/connection.rb, line 605
def console(instance_id)
    raise NCC::Error::NotFound, "Cloud #{@cloud} provider " +
        "#{provider} does not support interactive console"
end
console_log(instance_id) click to toggle source
# File lib/ncc/connection.rb, line 600
def console_log(instance_id)
    raise NCC::Error::NotFound, "Cloud #{@cloud} provider " +
        "#{provider} does not support console logs"
end
create_instance(instance_spec, wait_for_ip=nil) click to toggle source
# File lib/ncc/connection.rb, line 508
def create_instance(instance_spec, wait_for_ip=nil)
    wait_for_ip ||= @create_timeout

    if ! instance_spec.kind_of? NCC::Instance
        instance_spec = NCC::Instance.new(@cfg, instance_spec)
    end
    req_id = ['ncc',
        @cloud,
        UUIDTools::UUID.random_create.to_s].join('-')
    begin
        info "#{@cloud} requesting name from inventory using #{req_id}"
        fqdn = @ncc.inventory.get_or_assign_system_name(req_id)['fqdn']
    rescue StandardError => e
        raise NCC::Error::Cloud, "Error [#{e.class}] " +
            "communicating with inventory to " +
            "assign name using (ncc_req_id=#{req_id}): #{e.message}"
    end
    instance_spec.name = fqdn
    begin
        req = provider_request(instance_spec)
        t0 = Time.now
        info "#{@cloud} sending request: #{req.inspect}"
        server = do_fog { |fog| fog.servers.create(req) }
        # The reason we wait for an IP is so we can add it to
        # inventory, which then calculates the datacenter
        if wait_for_ip > 0 and instance_ip_address(server).nil?
            info "#{@cloud} waiting for ip on #{server.id}"
            this = self
            server.wait_for(wait_for_ip) { ready? and this.instance_ip_address(server)  }
        end
    rescue StandardError => err
        debug "Error [#{err.class}]: #{err.message} #{err.backtrace.join("\n   ")}"
        inv_update = { 'fqdn' => fqdn, 'status' => 'decommissioned' }
        if ! server.nil? and server.id
            inv_update['uuid'] = server.id
            inv_update['serial_number'] = server.id
        end
        @ncc.inventory.update('system', inv_update, fqdn)
        server_id = (server.nil? ? 'nil' : server.id)
        communication_error "[#{err.class}] (ncc_req_id=#{req_id} " +
            "instance_id=#{server_id}): #{err.message}"
    end
    elapsed = Time.now - t0
    info "Created instance instance_id=#{server.id} at #{provider} cloud #{@cloud} in #{elapsed}s"
    instance = instance_for server
    instance_spec.id = instance.id
    instance_spec.ip_address = instance.ip_address
    instance_spec.host = instance.host
    inv_req = inventory_request(instance_spec)
    inv_req['cloud'] = @cloud
    inv_req['status'] = 'building'
    begin
        info "#{@cloud} updating inventory #{fqdn}/#{req_id} -> #{inv_req.inspect}"
        @ncc.inventory.update('system', inv_req, fqdn)
    rescue StandardError => e
        raise NCC::Error::Cloud, "Error [#{e.class}] updating inventory " +
            "system #{fqdn} " +
            "(cloud=#{@cloud} ncc_req_id=#{req_id} " +
            "instance_id=#{server.id}): " + e.message
    end
    instance
end
current?() click to toggle source
# File lib/ncc/connection.rb, line 56
def current?
    provider == @cfg[:clouds][@cloud]['provider'] and
        @cfg_mtime == @cfg[:clouds][@cloud].mtime
end
debug(msg=nil) { || ... } click to toggle source
# File lib/ncc/connection.rb, line 82
def debug(msg=nil)
    msg ||= yield if block_given?
    if @logger and @logger.respond_to? :debug
        @logger.debug "#{self} #{msg}"
    end
end
delete(instance_id) click to toggle source
# File lib/ncc/connection.rb, line 582
def delete(instance_id)
    server = do_fog { |fog| fog.servers.get(instance_id) }
    if server.nil?
        instance_not_found instance_id
    else
        begin
            instance = instance_for server
            server.destroy
            inv_update = { 'fqdn' => instance.name,
                'status' => 'decommissioned' }
            @ncc.inventory.update('system', inv_update, instance.name)
        rescue StandardError => e
            communication_error "deleting fqdn=#{instance.name} " +
                "instance_id=#{server.id}: #{e.message}"
        end
    end
end
do_connect() click to toggle source
# File lib/ncc/connection.rb, line 117
def do_connect
    info "Connecting to #{provider} cloud #{@cloud}"
    cloud_config = @cfg[:clouds][@cloud]
    pnames = connection_params + ['provider']
    params = Hash[cloud_config.to_hash(*pnames).map do |k, v|
                      [k.intern, v]
                  end ]
    @fog = Fog::Compute.new(params)
end
do_fog() { |fog| ... } click to toggle source
# File lib/ncc/connection.rb, line 127
def do_fog
    remaining_tries = @auth_retry_limit
    begin
        unless @fog
            debug "do_fog: connecting because @fog is nil (@auth_retry_limit=#{@auth_retry_limit})"
            do_connect
        end
        debug "do_fog: beginning fog operation"
        yield @fog
    rescue Excon::Errors::Unauthorized => err
        # might be an expired auth token, retry
        if remaining_tries > 0
            do_connect
            remaining_tries -= 1
            retry
        else
            @fog = nil
            raise NCC::Error::Cloud, "Error communicating with #{provider} " +
                "cloud #{@cloud} (#{e.class}): #{e.message}"
        end
    rescue NCC::Error::Cloud => err
        @fog = nil
        raise err
    rescue NCC::Error => err
        raise err
    rescue StandardError => err
        debug "Unknown error [#{err.class}]: #{err.message} #{err.backtrace.join("\n   ")}"
        @fog = nil
        raise NCC::Error::Cloud, "Error communicating with #{provider} " +
            "cloud #{@cloud} [#{err.class}]: #{err.message}"
    end
end
error(msg) click to toggle source
# File lib/ncc/connection.rb, line 101
def error(msg)
    if @logger and @logger.respond_to? :error
        @logger.error "#{self} #{msg}"
    end
end
fatal(msg) click to toggle source
# File lib/ncc/connection.rb, line 107
def fatal(msg)
    if @logger and @logger.respond_to? :fatal
        @logger.error "#{self} #{msg}"
    end
end
fields(obj, *flist) click to toggle source
# File lib/ncc/connection.rb, line 166
def fields(obj, *flist)
    Hash[flist.map { |f| [f, obj.send(f)] }]
end
generic_provider_request(instance) click to toggle source
# File lib/ncc/connection.rb, line 464
def generic_provider_request(instance)
    {
        :name => instance.name,
        :flavor => map_to_provider_id(:size, instance.size),
        :size => map_to_provider_id(:image, instance.image),
    }.merge(instance.extra provider)
end
generic_translate(abstract_object_type, provider_object) click to toggle source
# File lib/ncc/connection.rb, line 390
def generic_translate(abstract_object_type, provider_object)
    abstract_object =  {
        'id' => id_of(abstract_object_type, provider_object),
        'provider_id' => provider_object.id
    }
    [:name, :description].each do |field|
        abstract_object[field.to_s] = provider_object.send(field) if
            provider_object.respond_to? field
    end
    abstract_object
end
get_provider_object(provider_object_spec) click to toggle source
# File lib/ncc/connection.rb, line 338
def get_provider_object(provider_object_spec)
    abstract_object_type, abstract_object_id = provider_object_spec.first
    if id_field(abstract_object_type) != :id
        collection(abstract_object_type).find do |provider_object|
            id_of(abstract_object_type, provider_object) ==
                abstract_object_id
        end
    else
        provider_object_id = map_to_provider_id(abstract_object_type,
                                             abstract_object_id)
        collection(abstract_object_type, provider_object_id)
    end
end
get_provider_objects(type) click to toggle source
# File lib/ncc/connection.rb, line 322
def get_provider_objects(type)
    enum = case type
           when :size
               :flavors
           when :image
               :images
           end
    if use_only_mapped(type) and id_field(type) == :id
        id_map(type).values.map do |id|
            do_fog { |fog| fog.send(enum).get(id) }
        end
    else
        do_fog { |fog| fog.send(enum).map { |e| e } }
    end
end
host_fqdn(host) click to toggle source
# File lib/ncc/connection.rb, line 496
def host_fqdn(host)
    if @cfg[:clouds][@cloud].has_key? 'host_domain'
        host + '.' + @cfg[:clouds][@cloud]['host_domain']
    else
        host
    end
end
id_field(abstract_object_type) click to toggle source
# File lib/ncc/connection.rb, line 284
def id_field(abstract_object_type)
    methodname = abstract_object_type.to_s + '_id_field'
    if self.respond_to? methodname
        self.send methodname
    else
        :id
    end
end
id_map(type) click to toggle source
# File lib/ncc/connection.rb, line 206
def id_map(type)
    debug "making id_map for #{type.inspect}"
    keyname = keyname_for(type)
    r = { }
    debug "keyname = #{keyname} (for #{type.inspect})"
    debug "provider map: #{@cfg[:providers][provider].to_hash.inspect}"
    r.update(id_map_hash(@cfg[:providers][provider][keyname].to_hash)) if
        @cfg[:providers][provider].has_key? keyname
    #debug "cloud map: #{@cfg[:clouds][@cloud][keyname].to_hash}"
    r.update(id_map_hash(@cfg[:clouds][@cloud][keyname].to_hash)) if
        @cfg[:clouds][@cloud].has_key? keyname
    debug "  >> #{r.inspect}"
    r
end
id_map_hash(h) click to toggle source

Something like “images” => { “$abstract_id” => x } can have an x where it’s the provider id, or an x which is a hash. If the hash has an id key, than that is used as a mapped id, if it doesn’t, it’s ignored (for the purposes of id mapping) -jbrinkley/20130410

# File lib/ncc/connection.rb, line 192
def id_map_hash(h)
    Hash[
    h.map do |k, v|
             if v.respond_to? :has_key?
                 if v.has_key? 'id'
                     [k, v['id']]
                 end
             else
                 [k, v]
             end
         end.reject { |p| p.nil? }
                       ]
end
id_of(abstract_object_type, obj) click to toggle source
# File lib/ncc/connection.rb, line 293
def id_of(abstract_object_type, obj)
    map_to_id(abstract_object_type,
           translated_id_of(abstract_object_type, obj))
end
ids(a) click to toggle source
# File lib/ncc/connection.rb, line 170
def ids(a)
    Hash[a.map { |e| [e['id'], e] }]
end
images(image_id=nil) click to toggle source
# File lib/ncc/connection.rb, line 273
def images(image_id=nil)
    if image_id.nil?
        collection(:image).find_all do |provider_image|
                @cfg[:images].has_key? id_of(:image, provider_image)
            end.map { |i| object_of(:image, i) }
    else
        object_of(:image,
               get_provider_object(:image => image_id))
    end
end
info(msg) click to toggle source
# File lib/ncc/connection.rb, line 95
def info(msg)
    if @logger and @logger.respond_to? :info
        @logger.info "#{self} #{msg}"
    end
end
instance_for(server) click to toggle source
# File lib/ncc/connection.rb, line 402
def instance_for(server)
    instance = NCC::Instance.new(@cfg, :logger => @logger)
    instance.set_without_validation(:id => server.id)
    instance.set_without_validation(:name => instance_name(server))
    instance.set_without_validation(:size => instance_size(server))
    instance.set_without_validation(:image => instance_image(server))
    instance.set_without_validation(:ip_address =>
                         instance_ip_address(server))
    instance.set_without_validation(:host => instance_host(server))
    instance.set_without_validation(:status => instance_status(server))
    instance
end
instance_host(server) click to toggle source
# File lib/ncc/connection.rb, line 431
def instance_host(server)
    nil
end
instance_image(server) click to toggle source
# File lib/ncc/connection.rb, line 423
def instance_image(server)
    map_to_id(:image, server.image)
end
instance_ip_address(server) click to toggle source
# File lib/ncc/connection.rb, line 435
def instance_ip_address(server)
    server.ip_address.to_s
end
instance_name(server) click to toggle source
# File lib/ncc/connection.rb, line 415
def instance_name(server)
    server.name
end
instance_not_found(instance_id) click to toggle source
# File lib/ncc/connection.rb, line 571
def instance_not_found(instance_id)
    raise NCC::Error::NotFound, "Instance #{instance_id.inspect} not " +
        "found in #{provider} cloud #{@cloud}"
end
instance_size(server) click to toggle source
# File lib/ncc/connection.rb, line 419
def instance_size(server)
    map_to_id(:size, server.size)
end
instance_status(server) click to toggle source
# File lib/ncc/connection.rb, line 427
def instance_status(server)
    map_to_status(server.status)
end
instances(instance_id=nil) click to toggle source
# File lib/ncc/connection.rb, line 439
def instances(instance_id=nil)
    debug "instances(#{instance_id.inspect})"
    if instance_id.nil?
        do_fog { |fog| fog.servers.map { |server| instance_for server } }
    else
        server = do_fog { |fog| fog.servers.get instance_id }
        if server.nil?
            instance_not_found instance_id
        end
        instance_for server
    end
end
inventory_request(instance) click to toggle source
# File lib/ncc/connection.rb, line 480
def inventory_request(instance)
    i = instance.with_defaults(@cfg[:clouds][@cloud]['defaults'],
                      @cfg[:providers][provider]['defaults'])
    {
        'fqdn' => i.name,
        'size' => i.size,
        'image' => i.image,
        'serial_number' => i.id,
        'uuid' => i.id,
        'roles' => i.role.sort.join(','),
        'ipaddress' => i.ip_address,
        'host_fqdn' => host_fqdn(i.host),
        'environment_name' => i.environment
    }.merge(instance.extra 'inventory')
end
keyintern(h) click to toggle source
# File lib/ncc/connection.rb, line 472
def keyintern(h)
    Hash[h.map do |k, v|
             [k.to_sym,
                 ((v.is_a? Hash and (k.to_sym != :tags)) ?
                     keyintern(v) : v)]
         end]
end
keyname_for(type) click to toggle source
# File lib/ncc/connection.rb, line 178
def keyname_for(type)
    case type
    when :size
        'sizes'
    when :image
        'images'
    end
end
map_from_raw_provider_id(type, raw_provider_id) click to toggle source

raw meaning not using “our” notion of id_field

# File lib/ncc/connection.rb, line 245
def map_from_raw_provider_id(type, raw_provider_id)
    # Using collection here for cache
    provider_object = collection(type).find do |item|
        item.id == raw_provider_id
    end
    id_of(type, provider_object)
end
map_to_id(type, provider_id) click to toggle source
# File lib/ncc/connection.rb, line 235
def map_to_id(type, provider_id)
    debug "provider_id_map = #{provider_id_map(type).inspect}"
    provider_id_map(type)[provider_id] || provider_id
end
map_to_provider_id(type, abstract_id) click to toggle source
# File lib/ncc/connection.rb, line 240
def map_to_provider_id(type, abstract_id)
    id_map(type)[abstract_id] || abstract_id
end
map_to_provider_status(abstract_status) click to toggle source
# File lib/ncc/connection.rb, line 257
def map_to_provider_status(abstract_status)
    abstract_status
end
map_to_status(provider_status) click to toggle source
# File lib/ncc/connection.rb, line 253
def map_to_status(provider_status)
    provider_status
end
maybe_invalidate(type) click to toggle source
# File lib/ncc/connection.rb, line 61
def maybe_invalidate(type)
    if @cache.has_key? type
        @cache.delete(type) if
            (Time.now - @cache[type][:timestamp]) > @cache_timeout
    end
end
me() click to toggle source
# File lib/ncc/connection.rb, line 68
def me
    [self.class, provider, @cloud].join(' ')
end
merge_configured(abstract_object, data) click to toggle source
# File lib/ncc/connection.rb, line 362
def merge_configured(abstract_object, data)
    if data.respond_to? :each_pair
        data.each_pair { |k, v| abstract_object[k] ||= v }
    else
        abstract_object['provider_id'] ||= data
    end
end
modify_instance_request(instance) click to toggle source
# File lib/ncc/connection.rb, line 504
def modify_instance_request(instance)
    instance
end
notice(msg) click to toggle source
# File lib/ncc/connection.rb, line 89
def notice(msg)
    if @logger and @logger.respond_to? :info
        @logger.info "#{self} #{msg}"
    end
end
object_of(abstract_object_type, provider_object) click to toggle source
# File lib/ncc/connection.rb, line 370
def object_of(abstract_object_type, provider_object)
    debug "object_of(#{abstract_object_type.inspect}, #{provider_object})"
    abstract_object = translate(abstract_object_type, provider_object)
    keyname = keyname_for abstract_object_type
    id = abstract_object['id']
    [@cfg[:clouds][@cloud], @cfg[:providers][provider]].each do |cfg|
        if cfg.has_key? keyname and cfg[keyname].has_key? id
            debug "merging cloud/provider data: cfg[#{keyname}][#{id}]"
            merge_configured(abstract_object, cfg[keyname][id])
        end
    end
    if @cfg.has_key? keyname.intern and @cfg[keyname.intern].has_key? id
        debug "merging abstract data: " +
            "@cfg[#{keyname.intern.inspect}][#{id}]"
        merge_configured(abstract_object, @cfg[keyname.intern][id].to_hash)
    end
    debug "object_of returns: #{abstract_object.inspect}"
    abstract_object
end
provider() click to toggle source
# File lib/ncc/connection.rb, line 174
def provider
    generic
end
provider_id_map(type) click to toggle source
# File lib/ncc/connection.rb, line 221
def provider_id_map(type)
    debug "making provider_id_map for #{type.inspect}"
    keyname = keyname_for(type)
    r = { }
    r.update(id_map_hash(@cfg[:providers][provider][keyname].
                         to_hash).invert) if
        @cfg[:providers][provider].has_key? keyname
    r.update(id_map_hash(@cfg[:clouds][@cloud][keyname].
                         to_hash).invert) if
        @cfg[:clouds][@cloud].has_key? keyname
    debug "  >>> #{r.inspect}"
    r
end
provider_request(instance) click to toggle source
# File lib/ncc/connection.rb, line 452
def provider_request(instance)
    keyintern(
    provider_request_of(instance.
                     with_defaults(@cfg[:clouds][@cloud]['defaults'],
                          @cfg[:providers][provider]['defaults']))
    )
end
provider_request_of(instance) click to toggle source
# File lib/ncc/connection.rb, line 460
def provider_request_of(instance)
    generic_provider_request(instance)
end
reboot(instance_id) click to toggle source
# File lib/ncc/connection.rb, line 610
def reboot(instance_id)
    server = do_fog { |fog| fog.servers.get(instance_id) }
    if server.nil?
        instance_not_found instance_id
    else
        server.reboot
    end
end
sizes(size_id=nil) click to toggle source
# File lib/ncc/connection.rb, line 261
def sizes(size_id=nil)
    debug "sizes(#{size_id.inspect})"
    if size_id.nil?
        collection(:size).find_all do |flavor|
                @cfg[:sizes].has_key? id_of(:size, flavor)
            end.map { |f| object_of(:size, f) }
    else
        object_of(:size,
               get_provider_object(:size => size_id))
    end
end
to_s() click to toggle source
# File lib/ncc/connection.rb, line 72
def to_s
    '#<' + me + '>'
end
translate(abstract_object_type, provider_object) click to toggle source
# File lib/ncc/connection.rb, line 352
def translate(abstract_object_type, provider_object)
    debug "translate(#{abstract_object_type.inspect}, #{provider_object})"
    methodname = 'translate_' + abstract_object_type.to_s
    if self.respond_to? methodname
        self.send(methodname, provider_object)
    else
        generic_translate(abstract_object_type, provider_object)
    end
end
translated_id_of(abstract_object_type, obj) click to toggle source
# File lib/ncc/connection.rb, line 298
def translated_id_of(abstract_object_type, obj)
    id_fielder = id_field abstract_object_type
    if id_fielder.kind_of? Proc
        id_fielder.call obj
    elsif obj.nil?
        nil
    else
        obj.send id_fielder
    end
end
use_only_mapped(type) click to toggle source

Warning. use_only_mapped is ignored for types with alternate id fields

# File lib/ncc/connection.rb, line 162
def use_only_mapped(type)
    false
end
warn(msg) click to toggle source
# File lib/ncc/connection.rb, line 76
def warn(msg)
    if @logger and @logger.respond_to? :warn
        @logger.warn "#{self} #{msg}"
    end
end