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



40
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
# File 'lib/sfn/provider.rb', line 40

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



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

def async
  @async
end

#cacheCache (readonly)

Returns:



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

def cache
  @cache
end

#connectionMiasma::Models::Orchestration (readonly)

Returns:

  • (Miasma::Models::Orchestration)


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

def connection
  @connection
end

#loggerLogger, NilClass (readonly)

Returns logger in use

Returns:

  • (Logger, NilClass)

    logger in use



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

def logger
  @logger
end

#stack_expansion_intervalNumeric (readonly)

Returns interval between stack expansions

Returns:

  • (Numeric)

    interval between stack expansions



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

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



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

def stack_list_interval
  @stack_list_interval
end

#updaterThread, NilClass

Returns stack list updater

Returns:

  • (Thread, NilClass)

    stack list updater



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

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



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/sfn/provider.rb', line 95

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
  if value
    value = MultiJson.load(value)
    if value.respond_to?(:values)
      value = value.values
    end
    MultiJson.dump(value)
  else
    "[]"
  end
end

#expand_stack(stack) ⇒ Object

Expand all lazy loaded attributes within stack

Parameters:

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


153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/sfn/provider.rb', line 153

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)


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
204
205
206
207
208
209
210
# File 'lib/sfn/provider.rb', line 178

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)


139
140
141
142
143
144
145
146
147
148
# File 'lib/sfn/provider.rb', line 139

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)


125
126
127
128
129
130
131
132
133
# File 'lib/sfn/provider.rb', line 125

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)


238
239
240
# File 'lib/sfn/provider.rb', line 238

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

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

Returns:

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


116
117
118
# File 'lib/sfn/provider.rb', line 116

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

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

Returns:

  • (Miasma::Orchestration::Stacks)


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

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)


216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/sfn/provider.rb', line 216

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