Class: Sfn::Provider

Inherits:
Object
  • Object
show all
Includes:
Bogo::AnimalStrings
Defined in:
lib/sfn/provider.rb

Overview

Remote provider interface

Constant Summary collapse

STACK_EXPAND_INTERVAL =

Minimum number of seconds to wait before re-expanding in progress stack

45
STACK_LIST_INTERVAL =

Default interval for refreshing stack list in cache

120

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(args = {}) ⇒ Provider

Create new instance

Parameters:

  • args (Hash) (defaults to: {})

Options Hash (args):

  • :miasma (Hash)

    miasma connection hash

  • :cache (Cache)
  • :async (TrueClass, FalseClass)

    fetch stacks async (defaults true)

  • :logger (Logger)

    use custom logger

  • :stack_expansion_interval (Numeric)

    interval to wait between stack data expands

  • :stack_list_interval (Numeric)

    interval to wait between stack list refresh



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/sfn/provider.rb', line 41

def initialize(args={})
  args = args.to_smash
  unless(args.get(:miasma, :provider))
    best_guess = (args[:miasma] || {}).keys.group_by do |key|
      key.to_s.split('_').first
    end.sort do |x, y|
      y.size <=> x.size
    end.first
    if(best_guess)
      provider = best_guess.first.to_sym
    else
      raise ArgumentError.new 'Cannot auto determine :provider value for credentials'
    end
  else
    provider = args[:miasma].delete(:provider).to_sym
  end
  if(provider == :aws)
    if(args[:miasma][:region])
      args[:miasma][:aws_region] = args[:miasma].delete(:region)
    end
  end
  if(ENV['DEBUG'].to_s.downcase == 'true')
    log_to = STDOUT
  else
    if(Gem.win_platform?)
      log_to = 'NUL'
    else
      log_to = '/dev/null'
    end
  end
  @logger = args.fetch(:logger, Logger.new(log_to))
  @stack_expansion_interval = args.fetch(:stack_expansion_interval, STACK_EXPAND_INTERVAL)
  @stack_list_interval = args.fetch(:stack_list_interval, STACK_LIST_INTERVAL)
  @connection = Miasma.api(
    :provider => provider,
    :type => :orchestration,
    :credentials => args[:miasma]
  )
  @cache = args.fetch(:cache, Cache.new(:local))
  @async = args.fetch(:async, true)
  @miasma_args = args[:miasma].dup
  cache.init(:stacks_lock, :lock, :timeout => 0.1)
  cache.init(:stacks, :stamped)
  cache.init(:stack_expansion_lock, :lock, :timeout => 0.1)
  if(args.fetch(:fetch, false))
    async ? update_stack_list! : fetch_stacks
  end
end

Instance Attribute Details

#asyncTrueClass, FalseClass (readonly)

Returns async updates.

Returns:

  • (TrueClass, FalseClass)

    async updates



24
25
26
# File 'lib/sfn/provider.rb', line 24

def async
  @async
end

#cacheCache (readonly)

Returns:



20
21
22
# File 'lib/sfn/provider.rb', line 20

def cache
  @cache
end

#connectionMiasma::Models::Orchestration (readonly)

Returns:

  • (Miasma::Models::Orchestration)


18
19
20
# File 'lib/sfn/provider.rb', line 18

def connection
  @connection
end

#loggerLogger, NilClass (readonly)

Returns logger in use.

Returns:

  • (Logger, NilClass)

    logger in use



26
27
28
# File 'lib/sfn/provider.rb', line 26

def logger
  @logger
end

#stack_expansion_intervalNumeric (readonly)

Returns interval between stack expansions.

Returns:

  • (Numeric)

    interval between stack expansions



28
29
30
# File 'lib/sfn/provider.rb', line 28

def stack_expansion_interval
  @stack_expansion_interval
end

#stack_list_intervalNumeric (readonly)

Returns interval between stack list updates.

Returns:

  • (Numeric)

    interval between stack list updates



30
31
32
# File 'lib/sfn/provider.rb', line 30

def stack_list_interval
  @stack_list_interval
end

#updaterThread, NilClass

Returns stack list updater.

Returns:

  • (Thread, NilClass)

    stack list updater



22
23
24
# File 'lib/sfn/provider.rb', line 22

def updater
  @updater
end

Instance Method Details

#cached_stacks(stack_id = nil) ⇒ String

Returns json representation of cached stacks.

Returns:

  • (String)

    json representation of cached stacks



96
97
98
99
100
101
102
103
104
105
106
# File 'lib/sfn/provider.rb', line 96

def cached_stacks(stack_id=nil)
  if(!@initial_fetch_complete || stack_id)
    recache = true
    if(stack_id && @initial_fetch_complete)
      recache = !!stacks.get(stack_id)
    end
    fetch_stacks(stack_id) if recache
  end
  value = cache[:stacks].value
  value ? MultiJson.dump(MultiJson.load(value).values) : '[]'
end

#expand_stack(stack) ⇒ Object

Expand all lazy loaded attributes within stack

Parameters:

  • stack (Miasma::Models::Orchestration::Stack)


146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/sfn/provider.rb', line 146

def expand_stack(stack)
  logger.info "Stack expansion requested (#{stack.id})"
  if((stack.in_progress? && Time.now.to_i - stack.attributes['Cached'].to_i > stack_expansion_interval) ||
      !stack.attributes['Cached'])
    begin
      expanded = false
      cache.locked_action(:stack_expansion_lock) do
        expanded = true
        stack.reload
        stack.data['Cached'] = Time.now.to_i
      end
      if(expanded)
        save_expanded_stack(stack.id, stack.to_json)
      end
    rescue => e
      logger.error "Stack expansion failed (#{stack.id}) - #{e.class}: #{e}"
    end
  else
    logger.info "Stack has been cached within expand interval. Expansion prevented. (#{stack.id})"
  end
end

#fetch_stacks(stack_id = nil) ⇒ TrueClass

Request stack information and store in cache

Returns:

  • (TrueClass)


171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/sfn/provider.rb', line 171

def fetch_stacks(stack_id=nil)
  cache.locked_action(:stacks_lock) do
    logger.info "Lock aquired for stack update. Requesting stacks from upstream. (#{Thread.current})"
    if(stack_id)
      single_stack = connection.stacks.get(stack_id)
      stacks = single_stack ? {single_stack.id => single_stack} : {}
    else
      stacks = Hash[
        connection.stacks.reload.all.map do |stack|
          [stack.id, stack.attributes]
        end
      ]
    end
    if(cache[:stacks].value)
      existing_stacks = MultiJson.load(cache[:stacks].value)
      # Force common types
      stacks = MultiJson.load(MultiJson.dump(stacks))
      if(stack_id)
        stacks = existing_stacks.to_smash.deep_merge(stacks)
      else
        # Remove stacks that have been deleted
        stale_ids = existing_stacks.keys - stacks.keys
        stacks = existing_stacks.to_smash.deep_merge(stacks)
        stale_ids.each do |stale_id|
          stacks.delete(stale_id)
        end
      end
    end
    cache[:stacks].value = stacks.to_json
    logger.info 'Stack list has been updated from upstream and cached locally'
  end
  @initial_fetch_complete = true
end

#remove_stack(stack_id) ⇒ TrueClass, FalseClass

Remove stack from the cache

Parameters:

  • stack_id (String)

Returns:

  • (TrueClass, FalseClass)


132
133
134
135
136
137
138
139
140
141
# File 'lib/sfn/provider.rb', line 132

def remove_stack(stack_id)
  current_stacks = MultiJson.load(cached_stacks)
  logger.info "Attempting to remove stack from internal cache (#{stack_id})"
  cache.locked_action(:stacks_lock) do
    val = current_stacks.delete(stack_id)
    logger.info "Successfully removed stack from internal cache (#{stack_id})"
    cache[:stacks].value = MultiJson.dump(current_stacks)
    !!val
  end
end

#save_expanded_stack(stack_id, stack_attributes) ⇒ TrueClass

Store stack attribute changes

Parameters:

  • stack_id (String)
  • stack_attributes (Hash)

Returns:

  • (TrueClass)


118
119
120
121
122
123
124
125
126
# File 'lib/sfn/provider.rb', line 118

def save_expanded_stack(stack_id, stack_attributes)
  current_stacks = MultiJson.load(cached_stacks)
  cache.locked_action(:stacks_lock) do
    logger.info "Saving expanded stack attributes in cache (#{stack_id})"
    current_stacks[stack_id] = stack_attributes.merge('Cached' => Time.now.to_i)
    cache[:stacks].value = MultiJson.dump(current_stacks)
  end
  true
end

#service_for(service) ⇒ Miasma::Model

Build API connection for service type

Parameters:

  • service (String, Symbol)

Returns:

  • (Miasma::Model)


231
232
233
# File 'lib/sfn/provider.rb', line 231

def service_for(service)
  connection.api_for(service)
end

#stack(stack_id) ⇒ Miasma::Orchestration::Stack, NilClass

Returns:

  • (Miasma::Orchestration::Stack, NilClass)


109
110
111
# File 'lib/sfn/provider.rb', line 109

def stack(stack_id)
  stacks(stack_id).get(stack_id)
end

#stacks(stack_id = nil) ⇒ Miasma::Orchestration::Stacks

Returns:

  • (Miasma::Orchestration::Stacks)


91
92
93
# File 'lib/sfn/provider.rb', line 91

def stacks(stack_id=nil)
  connection.stacks.from_json(cached_stacks(stack_id))
end

#update_stack_list!TrueClass, FalseClass

Start async stack list update. Creates thread that loops every ‘self.stack_list_interval` seconds and refreshes stack list in cache

Returns:

  • (TrueClass, FalseClass)


209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/sfn/provider.rb', line 209

def update_stack_list!
  if(updater.nil? || !updater.alive?)
    self.updater = Thread.new{
      loop do
        begin
          fetch_stacks
          sleep(stack_list_interval)
        rescue => e
          logger.error "Failure encountered on stack fetch: #{e.class} - #{e}"
        end
      end
    }
    true
  else
    false
  end
end