Module: RightScale::ModelsHelper

Included in:
GlobalObjectReplicatorSink, GlobalObjectReplicatorSource
Defined in:
lib/right_infrastructure_agent/models_helper.rb

Overview

Helper methods for accessing ActiveRecord models They are only usable when executing in a Rails environment

Constant Summary collapse

WRONG_SHARD_ERROR =

Pattern in exception message from trigger in core when account is not in current shard

"ERROR_UPDATE_NOT_ALLOWED does not exist"
WRONG_SHARD_RETRY_MESSAGE =

Retry exception message for when get wrong shard exception

"Account temporarily unavailable"
DISABLED_SHARD_RETRY_MESSAGE =

Retry exception message for when account disabled due to shard migration

"Account temporarily unavailable"
DEFAULT_RETRY_MESSAGE =

Default retry exception message

"RightScale database temporarily unavailable"
RETRYABLE_ERRORS =

Mysql and ActiveRecord case-insensitive exception message content that if present causes the error to be propagated to the requester as retryable after max retries is exceeded internally

["deadlock found", "lock wait timeout", "can't connect to", "has gone away", WRONG_SHARD_ERROR]

Instance Method Summary collapse

Instance Method Details

#account(id, options = {}) ⇒ Object

Retrieve account with given id

Parameters

id(Integer)

Id of account to be retrieved

options(Hash)

Query options:

:retryable_error(Boolean)

Whether to raise RetryableError if exceed maximum retries

Return

(Account|nil)

Corresponding account, or nil if no account with this id exists



336
337
338
# File 'lib/right_infrastructure_agent/models_helper.rb', line 336

def (id, options = {})
  run_query(options) { Account.find(id) }
end

#audit_entry(id, options = {}) ⇒ Object

Retrieve AuditEntry with given id

Parameters

id(Integer)

Id of AuditEntry to be retrieved

options(Hash)

Query options:

:retryable_error(Boolean)

Whether to raise RetryableError if exceed maximum retries

Return

(AuditEntry|nil)

Corresponding setting, or nil if no setting with this id exists



349
350
351
# File 'lib/right_infrastructure_agent/models_helper.rb', line 349

def audit_entry(id, options = {})
  run_query(options) { AuditEntry.find(id) }
end

#instance(token_id, options = {}) ⇒ Object

Retrieve instance model with given API token id

Parameters

token_id(Integer)

Id of InstanceApiToken of instance to be retrieved

options(Hash)

Query options:

:retryable_error(Boolean)

Whether to raise RetryableError if exceed maximum retries

Return

instance(Ec2Instance|Instance|nil)

Corresponding instance, or nil if no token with

this id exists

Raise

RetryableError

If account disabled for shard migration, or if exceed maximum retries



269
270
271
272
273
274
275
276
# File 'lib/right_infrastructure_agent/models_helper.rb', line 269

def instance(token_id, options = {})
  token = instance_token(token_id, options)
  token && (instance = token.instance)
  if instance && instance..disabled_in_every_shard?
    raise RightScale::Exceptions::RetryableError.new(DISABLED_SHARD_RETRY_MESSAGE)
  end
  instance
end

#instance_from_agent_id(agent_id, options = {}) ⇒ Object

Get instance model corresponding to instance agent with given identity

Parameters

identity(String)

Serialized instance agent identity

options(Hash)

Query options:

:retryable_error(Boolean)

Whether to raise RetryableError if exceed maximum retries

Return

(Instance)

Corresponding instance

Raise

ArgumentError

Invalid agent identity

Raises:

  • (ArgumentError)


322
323
324
325
# File 'lib/right_infrastructure_agent/models_helper.rb', line 322

def instance_from_agent_id(agent_id, options = {})
  raise ArgumentError, "Invalid agent identity" unless AgentIdentity.valid?(agent_id)
  instance_from_token_id(AgentIdentity.parse(agent_id).base_id, options)
end

#instance_from_token_id(token_id, options = {}) ⇒ Object

Get instance from API token id Cache all tokens retrieved

Parameters

token_id(Integer)

API token id

options(Hash)

Query options:

:retryable_error(Boolean)

Whether to raise RetryableError if exceed maximum retries

Return

instance(Instance)

Corresponding instance

Raise

RetryableError

If account disabled for shard migration, or if exceed maximum retries

RightScale::Exceptions::Application

If InstanceApiToken or Instance not found



292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# File 'lib/right_infrastructure_agent/models_helper.rb', line 292

def instance_from_token_id(token_id, options = {})
  @tokens ||= {}
  if instance = @tokens[token_id]
    run_query(options) { instance.reload }
  else
    # Not in cache, look it up
    instance_api_token = instance_token(token_id)
    raise RightScale::Exceptions::Application, "Instance token with id '#{token_id}' not found" unless instance_api_token
    instance = instance_api_token.instance
    raise RightScale::Exceptions::Application, "Instance with token id '#{token_id}' not found" unless instance
    @tokens[token_id] = instance
  end
  if instance && instance..disabled_in_every_shard?
    raise RightScale::Exceptions::RetryableError.new(DISABLED_SHARD_RETRY_MESSAGE)
  end
  instance
end

#instance_token(id, options = {}) ⇒ Object

Retrieve InstanceApiToken model with given id

Parameters

id(Integer)

Id of InstanceApiToken to be retrieved

options(Hash)

Query options:

:retryable_error(Boolean)

Whether to raise RetryableError if exceed maximum retries

Return

(InstanceApiToken|nil)

Corresponding API token, or nil if no token with this id exists



252
253
254
# File 'lib/right_infrastructure_agent/models_helper.rb', line 252

def instance_token(id, options = {})
  run_query(options) { InstanceApiToken.find(id) }
end

#is_retryable_error?(e, local = false) ⇒ Boolean

Is given exception a MySQL exception worth retrying, e.g., a deadlock or timeout?

Parameter

e(Exception)

Exception to be tested

local(Boolean)

Whether making this decision for local or external consumption

Return

(Boolean)

true if worth retrying, otherwise false or nil

Returns:

  • (Boolean)


139
140
141
142
# File 'lib/right_infrastructure_agent/models_helper.rb', line 139

def is_retryable_error?(e, local = false)
  (e.is_a?(MysqlError) || e.is_a?(ActiveRecord::ActiveRecordError)) && (local ||
  (RETRYABLE_ERRORS + ActiveRecord::ConnectionAdapters::MysqlAdapter::LOST_CONNECTION_ERROR_MESSAGES).find { |m| e.message =~ /#{m}/i })
end

#permission(id, options = {}) ⇒ Object

Retrieve Permission with given id

Parameters

id(Integer)

Id of Permission to be retrieved

options(Hash)

Query options:

:retryable_error(Boolean)

Whether to raise RetryableError if exceed maximum retries

Return

(Permission|nil)

Corresponding permission, or nil if no permission with this id exists



426
427
428
# File 'lib/right_infrastructure_agent/models_helper.rb', line 426

def permission(id, options = {})
  run_query(options) { Permission.find(id) }
end

#query(description, options = {}, &blk) ⇒ Object

Query database using retry on failure and reconnect handling Audit and/or log error if given block returns nil or raises, return block result otherwise Store any error message in @last_error

Parameters

description(String)

Description of query action that is used in error messages

options(Hash)

Query options:

:audit(AuditEntry)

Audit entry used to append error message if any

:include_backtrace_in_last_error(Boolean)

Whether to pass :trace to Log.format for exceptions

:email_errors(Boolean)

Whether to send email for errors

:retryable_error(Boolean)

Whether to raise RetryableError if exceed maximum retries

Block

Accesses MySQL and returns result, required

Return

(Object|nil)

Value returned by block, or nil if failed



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/right_infrastructure_agent/models_helper.rb', line 51

def query(description, options = {}, &blk)
  begin
    @last_error = nil
    run_query(options, &blk)
  rescue Exception => e
    retryable = e.is_a?(RightScale::Exceptions::RetryableError)
    description = "Failed to #{description}" + (retryable ? " but retryable" : "")
    Log.error(description, e, :trace)
    if options[:include_backtrace_in_last_error]
      @last_error = Log.format(description, e, :trace)
    else
      @last_error = Log.format(description, e)
    end
    options[:audit].append(AuditFormatter.error(@last_error)) if options[:audit]

    if(options[:email_errors])
      ExceptionMailer.deliver_notification(description, e.message, e)
    end

    raise if retryable
    nil
  end
end

#recipe(id, options = {}) ⇒ Object

Retrieve Chef recipe with given id

Parameters

id(Integer)

Id of ServerTemplateChefRecipe to be retrieved

options(Hash)

Query options:

:retryable_error(Boolean)

Whether to raise RetryableError if exceed maximum retries

Return

(ServerTemplateChefRecipe|nil)

Corresponding recipe, or nil if no recipe with this id exists



394
395
396
# File 'lib/right_infrastructure_agent/models_helper.rb', line 394

def recipe(id, options = {})
  run_query(options) { ServerTemplateChefRecipe.find(id) }
end

#recipe_from_name(name, instance, options = {}) ⇒ Object

Retrieve recipe with given name on given instance

Parameters

name(String)

Name of recipe that should be retrieved

instance(Instance)

Instance on which recipe is defined

options(Hash)

Query options:

:retryable_error(Boolean)

Whether to raise RetryableError if exceed maximum retries

Return

recipe(ServerTemplateChefRecipe)

Corresponding recipe



408
409
410
411
412
413
414
415
# File 'lib/right_infrastructure_agent/models_helper.rb', line 408

def recipe_from_name(name, instance, options = {})
  recipe = nil
  template = run_query(options) { instance.server_template }
  if template
    recipe = run_query(options) { template.server_template_chef_recipes.find_by_recipe(name) }
  end
  recipe
end

#repositories(options = {}) ⇒ Object

Retrieve all software repositories

Parameters

options(Hash)

Query options:

:retryable_error(Boolean)

Whether to raise RetryableError if exceed maximum retries

Return

(Array)

Array of Repository



477
478
479
# File 'lib/right_infrastructure_agent/models_helper.rb', line 477

def repositories(options = {})
  run_query(options) { Repository.find(:all) }
end

#retrieve(description, options = {}) ⇒ Object

Retrieve database object using the ModelsHelper functions below that themselves use #run_query, e.g., retrieve(“recipe for instance”, :audit => audit) { recipe(id, :retryable_error => true } Audit and/or log error if block returns nil or raises exception, otherwise return block result Store any error message in @last_error

Parameters

description(String)

Description of object that is used in error messages

options(Hash)

Query options:

:audit(AuditEntry)

Audit entry used to append error message if any

:log(Boolean)

Whether to log message when object does not exist

Block

Performs query to retrieve object, required The block should retrieve using other ModelsHelper functions like #instance or #account so that #run_query is applied rather than accessing models directly in the block

Return

item(Object)

Value returned by block, or nil if not found or failed

Raise

RuntimeError

Block is missing



222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/right_infrastructure_agent/models_helper.rb', line 222

def retrieve(description, options = {})
  raise 'Missing block' unless block_given?
  retryable = nil
  begin
    @last_error = nil
    unless item = yield
      @last_error = "Could not find #{description}"
      Log.warning(@last_error) if options[:log]
    end
  rescue Exception => e
    retryable = e if e.is_a?(RightScale::Exceptions::RetryableError)
    description = "Failed to retrieve #{description}" + (retryable ? " but retryable" : "")
    Log.error(description, e, e.is_a?(RightScale::Exceptions) ? :caller : :trace)
    @last_error = Log.format(description, e)
    item = nil
  end
  options[:audit].append(AuditFormatter.error(@last_error)) if options[:audit] && item.nil? && @last_error
  raise retryable if retryable
  item
end

#retrieve_or_create_audit(instance, summary = '', detail = '', account = instance.account, user = nil, options = {}) ⇒ Object

Retrieve existing audit or create new one

Parameters

instance(Instance)

Instance used as auditee

summary(String)

New audit summary, default to empty string

detail(String)

New audit detail, default to empty string

account(Account)

Account associated with audit, default to instance’s account

user(User)

User who caused the activity

options(Hash)

Query options:

:audit_id(Integer)

Audit entry id if audit is to be retrieved

:agent_id(String)

Serialized agent identity if audit is to be created (:agent_identity

  is an alternative deprecated option name)
:retryable_error(Boolean):: Whether to raise RetryableError if exceed maximum retries

Return

(AuditEntry)

Audit entry model

Raise

ArgumentError

If neither :audit_id nor :agent_id is specified



163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/right_infrastructure_agent/models_helper.rb', line 163

def retrieve_or_create_audit(instance, summary = '', detail = '',  = instance., user = nil, options = {})
  if options[:audit_id] && options[:audit_id] != -1
    retrieve("audit with id #{options[:audit_id]}") { audit_entry(options[:audit_id], options) }
  elsif (agent_id = options[:agent_id] || options[:agent_identity])
    query("create audit for instance agent #{agent_id}", options) do
      AuditEntry.create!( {:auditee => instance, :summary => summary, :detail => detail,
                           :account => , :user => user} )
    end
  else
    raise ArgumentError, "Must specify audit ID or agent ID"
  end
end

#retrieve_or_default_user(instance, options = {}) ⇒ Object

Retrieve existing user or get default user stub Store any error message in @last_error

Parameters

instance(Instance)

Instance for account

options(Hash)

Query options:

:user_id(Integer)

User id or 0 meaning use stub

:retryable_error(Boolean)

Whether to raise RetryableError if exceed maximum retries

Return

current_user(User)

User retrieved or stubbed



187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/right_infrastructure_agent/models_helper.rb', line 187

def retrieve_or_default_user(instance, options = {})
  user_id = (options[:user_id] || 0).to_i  # ensure user id is non-nil integer
  if user_id == 0
    current_user = User.new(:email => '[email protected]')
    current_user.id = 0
  else
     = instance.
    current_user = query("User in account #{} with id #{user_id}", options) do
      .users.detect { |u| u.id == user_id }
    end
  end
  current_user
end

#right_script(id, options = {}) ⇒ Object

Retrieve RightScript with given id

Parameters

id(Integer)

Id of RightScript to be retrieved

options(Hash)

Query options:

:retryable_error(Boolean)

Whether to raise RetryableError if exceed maximum retries

Return

(RightScript|nil)

Corresponding RightScript or nil if no RightScript with this id exists



362
363
364
# File 'lib/right_infrastructure_agent/models_helper.rb', line 362

def right_script(id, options = {})
  run_query(options) { RightScript.find(id) }
end

#right_script_from_name(name, instance, options = {}) ⇒ Object

Retrieve RightScript with given name on given instance

Parameters

name(String)

Name of RightScript that should be retrieved

instance(Instance)

Instance on which RightScript is defined

options(Hash)

Query options:

:retryable_error(Boolean)

Whether to raise RetryableError if exceed maximum retries

Return

script(RightScript)

Corresponding RightScript



376
377
378
379
380
381
382
383
# File 'lib/right_infrastructure_agent/models_helper.rb', line 376

def right_script_from_name(name, instance, options = {})
  script = nil
  template = run_query(options) { instance.server_template }
  if template
    script = run_query(options) { template.right_scripts.find_by_name(name) }
  end
  script
end

#run_query(options = {}) ⇒ Object

Run database query block When catch retryable MySQL and ActiveRecord errors, rerun block, retry up to 3 times

Parameters

options(Hash)

Query options:

:retryable_error(Boolean)

Whether to raise RetryableError if exceed maximum retries

Block

Accesses MySQL and returns result, required

Return

res(Object|nil)

Value returned by given block, or nil if desired data was not found

Raise

RuntimeError

Block is missing

RetryableError: If exceed max retries and :retryable_error option enabled Also re-raises any query block exceptions



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/right_infrastructure_agent/models_helper.rb', line 92

def run_query(options = {})
  raise 'Missing block' unless block_given?
  res = nil
  disconnected = true
  while disconnected do
    retries = 0
    begin
      res = yield
      disconnected = false
    rescue ActiveRecord::RecordNotFound
      res = nil
      disconnected = false
    rescue Exception => e
      if is_retryable_error?(e, local = true)
        if retries >= 3
          Log.warning("Aborting query after 3 failed retries")
          if options[:retryable_error] && is_retryable_error?(e)
            if e.message =~ /#{WRONG_SHARD_ERROR}/i
              raise RightScale::Exceptions::RetryableError.new(WRONG_SHARD_RETRY_MESSAGE, e)
            else
              raise RightScale::Exceptions::RetryableError.new(DEFAULT_RETRY_MESSAGE, e)
            end
          else
            raise # re-raise the exception
          end
        else
          retries += 1
          Log.error("Failed running MySQL query", e, :trace)
          Log.info("Retrying query...")
          retry
        end
      else
        raise # re-raise the exception
      end
    end
  end
  res
end

#setting(id, options = {}) ⇒ Object

Retrieve Setting with given id

Parameters

id(Integer)

Id of Setting to be retrieved

options(Hash)

Query options:

:retryable_error(Boolean)

Whether to raise RetryableError if exceed maximum retries

Return

(Setting|nil)

Corresponding setting, or nil if no setting with this id exists



465
466
467
# File 'lib/right_infrastructure_agent/models_helper.rb', line 465

def setting(id, options = {})
  run_query(options) { Setting.find(id) }
end

#user_credential(id, options = {}) ⇒ Object

Retrieve UserCredential with given id

Parameters

id(Integer)

Id of UserCredential to be retrieved

options(Hash)

Query options:

:retryable_error(Boolean)

Whether to raise RetryableError if exceed maximum retries

Return

(UserCredential|nil)

Corresponding user credential, or nil if no credential with this id exists



439
440
441
# File 'lib/right_infrastructure_agent/models_helper.rb', line 439

def user_credential(id, options = {})
  run_query(options) { UserCredential.find(id) }
end

#user_credential_from_fingerprint(public_value_fingerprint, options = {}) ⇒ Object

Retrieve UserCredential with given public value fingerprint

Parameters

public_value_fingerprint(String)

Public value fingerprint of UserCredential to be retrieved

options(Hash)

Query options:

:retryable_error(Boolean)

Whether to raise RetryableError if exceed maximum retries

Return

(UserCredential|nil)

Corresponding user credential, or nil if no credential with this fingerprint exists



452
453
454
# File 'lib/right_infrastructure_agent/models_helper.rb', line 452

def user_credential_from_fingerprint(public_value_fingerprint, options = {})
  run_query(options) { UserCredential.find_by_public_value_fingerprint(public_value_fingerprint) }
end