class OpenNebula::Role

Service Role Class (Generic Role type)

Constants

FAILURE_STATES
IMMUTABLE_ATTRS

List of attributes that can't be changed in update operation last_vmname: this is internal information managed by OneFlow server nodes: this is internal information managed by OneFlow server parents: this has only sense in deploy operation state: this is internal information managed by OneFlow server template_id: this will affect scale operation cardinality: this is internal information managed by OneFlow server

LOG_COMP
RECOVER_DEPLOY_STATES
RECOVER_SCALE_STATES
RECOVER_UNDEPLOY_STATES
SCALE_WAYS
SCHEDULE_ACTIONS

Actions that can be performed on the VMs of a given Role

STATE
STATE_STR
VM_INFO

Information to save in document

Attributes

service[R]

Public Class Methods

for(body, service) click to toggle source

Return a role object based on type attribute of the role template @param [Hash] Role template in Hash format @return [Role] Role object type

# File lib/models/role.rb, line 150
def for(body, service)
    role_type = body.fetch('type', 'vm')

    case role_type.downcase
    when 'vm'
        VMRole.new(body, service)
    when 'vr'
        VRRole.new(body, service)
    else
        raise "Unsupported role type: #{role_type}"
    end
end
init_default_cooldown(default_cooldown) click to toggle source

rubocop:disable Style/ClassVars

# File lib/models/role.rb, line 180
def init_default_cooldown(default_cooldown)
    @@default_cooldown = default_cooldown
end
init_default_shutdown(shutdown_action) click to toggle source
# File lib/models/role.rb, line 184
def init_default_shutdown(shutdown_action)
    @@default_shutdown = shutdown_action
end
init_default_vm_name_template(vm_name_template) click to toggle source
# File lib/models/role.rb, line 192
def init_default_vm_name_template(vm_name_template)
    @@vm_name_template = vm_name_template
end
init_default_vr_name_template(vr_name_template) click to toggle source
# File lib/models/role.rb, line 196
def init_default_vr_name_template(vr_name_template)
    @@vr_name_template = vr_name_template
end
init_force_deletion(force_deletion) click to toggle source
# File lib/models/role.rb, line 188
def init_force_deletion(force_deletion)
    @@force_deletion = force_deletion
end
new(body, service) click to toggle source
# File lib/models/role.rb, line 203
def initialize(body, service)
    @body    = body
    @service = service

    @body['nodes']  ||= []
    @body['on_hold']  = false if @body['on_hold'].nil?
end
vm_failure?(vm_state, lcm_state) click to toggle source

Returns true if the VM state is failure @param [Integer] vm_state VM state @param [Integer] lcm_state VM LCM state @return [true,false] True if the lcm state is one of *_FAILURE

# File lib/models/role.rb, line 167
def vm_failure?(vm_state, lcm_state)
    vm_state_str  = VirtualMachine::VM_STATE[vm_state.to_i]
    lcm_state_str = VirtualMachine::LCM_STATE[lcm_state.to_i]

    if vm_state_str == 'ACTIVE' &&
    FAILURE_STATES.include?(lcm_state_str)
        return true
    end

    false
end

Public Instance Methods

any_parent_on_hold?() click to toggle source

Checks if any parent role is currently on hold. @return [Boolean] Returns `true` if any parent role is in an

`on_hold` state, `false` otherwise.
# File lib/models/role.rb, line 330
def any_parent_on_hold?
    parents.each do |parent|
        next unless @service.roles[parent]

        return true if @service.roles[parent].on_hold?
    end
    false
end
batch_action(action, period, vms_per_period, args) click to toggle source
# File lib/models/role.rb, line 428
def batch_action(action, period, vms_per_period, args)
    raise NotImplementedError
end
can_recover_deploy?() click to toggle source

Determines whether the current deployment can be recovered based on its state and the states of its parent roles. @return [Boolean] Returns `true` if the deployment

can be recovered, `false` otherwise.
# File lib/models/role.rb, line 519
def can_recover_deploy?
    if state != STATE['PENDING']
        return RECOVER_DEPLOY_STATES.include? STATE_STR[state]
    end

    parents.each do |parent|
        next unless @service.roles[parent]

        return false if @service.roles[parent].state != STATE['RUNNING']
    end

    true
end
can_recover_scale?() click to toggle source
# File lib/models/role.rb, line 553
def can_recover_scale?
    return false unless RECOVER_SCALE_STATES.include? STATE_STR[state]

    true
end
can_recover_undeploy?() click to toggle source

Determines if the current deployment can be recovered and undeployed based on its state and the states of its child roles. @return [Boolean] Returns `true` if the deployment can be

recovered and undeployed, `false` otherwise.
# File lib/models/role.rb, line 537
def can_recover_undeploy?
    if !RECOVER_UNDEPLOY_STATES.include? STATE_STR[state]
        # TODO, check childs if !empty? check if can be undeployed
        @service.roles.each do |role_name, role|
            next if role_name == name

            if role.parents.include?(name) &&
               role.state != STATE['DONE']
                return false
            end
        end
    end

    true
end
can_release?() click to toggle source

Checks if the current role is in a state where it can be released. @return [Boolean] Returns `true` if the current state is `HOLD`,

`false` otherwise.
# File lib/models/role.rb, line 384
def can_release?
    state == STATE['HOLD']
end
cardinality() click to toggle source

Returns the role cardinality @return [Integer] the role cardinality

# File lib/models/role.rb, line 288
def cardinality
    @body['cardinality'].to_i
end
cardinality=(target_cardinality) click to toggle source

Sets a new cardinality for this role @param [Integer] the new cardinality

# File lib/models/role.rb, line 294
def cardinality=(target_cardinality)
    if target_cardinality > cardinality
        dir = 'up'
    else
        dir = 'down'
    end

    msg = "Role #{name} scaling #{dir} from #{cardinality} to " \
          "#{target_cardinality} nodes"

    Log.info(LOG_COMP, msg, @service.id)
    @service.log_info(msg)

    @body['cardinality'] = target_cardinality.to_i
end
check_new_template(template) click to toggle source

Check that changes values are correct

@param template_json [String] New template

@return [Boolean, String] True, nil if everything is correct

False, attr if attr was changed
# File lib/models/role.rb, line 406
def check_new_template(template)
    IMMUTABLE_ATTRS.each do |attr|
        next if template[attr] == @body[attr]

        return [false, "role/#{attr}"]
    end

    [true, nil]
end
chown(uid, gid) click to toggle source
# File lib/models/role.rb, line 388
def chown(uid, gid)
    raise NotImplementedError
end
clean_scale_way() click to toggle source
# File lib/models/role.rb, line 476
def clean_scale_way
    return NotImplementedError
end
cooldown() click to toggle source
# File lib/models/role.rb, line 464
def cooldown
    raise NotImplementedError
end
deploy() click to toggle source

Deployment

# File lib/models/role.rb, line 484
def deploy
    raise NotImplementedError
end
elasticity_policies() click to toggle source
# File lib/models/role.rb, line 456
def elasticity_policies
    raise NotImplementedError
end
info_nodes(vm_pool) click to toggle source
# File lib/models/role.rb, line 249
def info_nodes(vm_pool)
    ret = []

    monitoring = vm_pool[:monitoring]
    vm_pool    = vm_pool[:vm_pool]

    @body['nodes'].each do |node|
        id = node['deploy_id']
        vm = vm_pool.retrieve_xmlelements("/VM_POOL/VM[ID=#{id}]")[0]

        if vm.nil?
            Log.error LOG_COMP,
                      "Error getting VM #{id}",
                      @service.id
        else
            obj = {}
            obj['deploy_id'] = node['deploy_id']

            hash     = vm.to_hash
            vm_monit = monitoring.select {|v| v['ID'].to_i == id }[0]

            hash['VM']['MONITORING'] = vm_monit if vm_monit
            obj['vm_info']           = hash

            ret << obj
        end
    end

    ret
end
max_cardinality() click to toggle source

Returns the role max cardinality @return [Integer,nil] the role cardinality or nil if it isn't defined

# File lib/models/role.rb, line 438
def max_cardinality
    raise NotImplementedError
end
min_cardinality() click to toggle source

Returns the role min cardinality @return [Integer,nil] the role cardinality or nil if it isn't defined

# File lib/models/role.rb, line 444
def min_cardinality
    raise NotImplementedError
end
name() click to toggle source
# File lib/models/role.rb, line 211
def name
    @body['name']
end
nodes() click to toggle source

Returns the nodes of the role @return [Array] the nodes

# File lib/models/role.rb, line 241
def nodes
    @body['nodes']
end
nodes_ids() click to toggle source
# File lib/models/role.rb, line 245
def nodes_ids
    @body['nodes'].map {|node| node['deploy_id'] }
end
on_hold=(on_hold) click to toggle source

Change the `on_hold` option value

# File lib/models/role.rb, line 311
def on_hold=(on_hold)
    @body['on_hold'] = on_hold
end
on_hold?() click to toggle source

Returns the `on_hold` role option @return [true, false] `true` if the `on_hold` option is enabled

# File lib/models/role.rb, line 317
def on_hold?
    @body['on_hold']
end
parents() click to toggle source

Returns the role parents @return [Array] the role parents

# File lib/models/role.rb, line 282
def parents
    @body['parents'] || []
end
recover_deploy(report) click to toggle source
# File lib/models/role.rb, line 559
def recover_deploy(report)
    nodes = @body['nodes']
    deployed_nodes = []

    nodes.each do |node|
        vm_id = node['deploy_id']

        vm = OpenNebula::VirtualMachine.new_with_id(vm_id,
                                                    @service.client)

        rc = vm.info

        if OpenNebula.is_error?(rc)
            msg = "Role #{name} : Retry failed for VM "\
                  "#{vm_id}; #{rc.message}"
            Log.error LOG_COMP, msg, @service.id

            next true
        end

        vm_state = vm.state
        lcm_state = vm.lcm_state

        # ACTIVE/RUNNING
        next false if vm_state == 3 && lcm_state == 3 && !report

        next true if vm_state == '6' # Delete DONE nodes

        if Role.vm_failure?(vm_state, lcm_state)
            rc = vm.recover(2)

            if OpenNebula.is_error?(rc)
                msg = "Role #{name} : Retry failed for VM "\
                      "#{vm_id}; #{rc.message}"

                Log.error LOG_COMP, msg, @service.id
                @service.log_error(msg)
            else
                deployed_nodes << vm_id
            end
        else
            vm.resume

            deployed_nodes << vm_id
        end
    end

    rc = deploy

    unless rc[0]
        return [false, "Error deploying nodes for role `#{name}`"]
    end

    deployed_nodes.concat(rc[0])

    deployed_nodes
end
recover_scale() click to toggle source
# File lib/models/role.rb, line 627
def recover_scale
    raise NotImplementedError
end
recover_undeploy() click to toggle source
# File lib/models/role.rb, line 617
def recover_undeploy
    undeployed_nodes = []

    rc = shutdown(true)

    undeployed_nodes.concat(rc[0]) if rc[1].nil?

    undeployed_nodes
end
release() click to toggle source

Release all the nodes in this role @return [Array, Bool] true if all the VMs

were released, false otherwise and Array with VMs released
# File lib/models/role.rb, line 346
def release
    release_nodes = []
    success       = true

    # Release all vms in the role
    nodes.each do |node|
        vm_id = node['deploy_id']

        Log.debug(LOG_COMP,
                  "Role #{name}: Releasing VM #{vm_id}",
                  @service.id)

        vm = OpenNebula::VirtualMachine.new_with_id(vm_id,
                                                    @service.client)
        rc = vm.release

        if OpenNebula.is_error?(rc)
            msg = "Role #{name}: Release failed for VM #{vm_id}, " \
                  "#{rc.message}"

            Log.error(LOG_COMP, msg, @service.id)
            @service.log_error(msg)
            success = false
        else
            Log.debug(LOG_COMP,
                      "Role #{name}: Release success for VM #{vm_id}",
                      @service.id)

            release_nodes << vm_id
        end
    end

    [release_nodes, success]
end
scale?(vm_pool) click to toggle source

Returns a positive, 0, or negative number of nodes to adjust,

according to the elasticity and scheduled policies

@return [Array<Integer>] positive, 0, or negative number of nodes to

adjust, plus the cooldown period duration
# File lib/models/role.rb, line 452
def scale?(vm_pool)
    raise NotImplementedError
end
scale_way(_) click to toggle source
# File lib/models/role.rb, line 472
def scale_way(_)
    return NotImplementedError
end
scheduled_policies() click to toggle source

Scheduler

# File lib/models/role.rb, line 420
def scheduled_policies
    @body['scheduled_policies']
end
service_on_hold?() click to toggle source

Returns the `on_hold` service option @return [true, false] `true` if the `on_hold` option is enabled

# File lib/models/role.rb, line 323
def service_on_hold?
    @service.on_hold?
end
shutdown(recover) click to toggle source

Terminate all the nodes in this role

@param scale_down [true, false] true to terminate and dispose the

number of VMs needed to get down to cardinality nodes

@return [Array<true, nil>, Array<false, String>] true if all the VMs

were terminated, false and the error reason if there was a problem
shutting down the VMs
# File lib/models/role.rb, line 495
def shutdown(recover)
    if nodes.size != cardinality
        n_nodes = nodes.size - cardinality
    else
        n_nodes = nodes.size
    end

    rc = shutdown_nodes(nodes, n_nodes, recover)

    unless rc[0]
        return [false, "Error undeploying nodes for role `#{name}`"]
    end

    [rc[1], nil]
end
state() click to toggle source
# File lib/models/role.rb, line 215
def state
    @body['state']
end
state=(state) click to toggle source

Sets a new state @param [Integer] the new state

# File lib/models/role.rb, line 221
def state=(state)
    return if state < 0 || state > STATE_STR.size

    @body['state'] = state.to_i

    Log.info(
        LOG_COMP,
        "Role #{name} new state: #{STATE_STR[state]}",
        @service.id
    )
end
state_str() click to toggle source

Returns the string representation of the service state @return [String] the state string

# File lib/models/role.rb, line 235
def state_str
    STATE_STR[state]
end
update(template) click to toggle source

Updates the role @param [Hash] template @return [nil, OpenNebula::Error] nil in case of success, Error

otherwise
# File lib/models/role.rb, line 396
def update(template)
    raise NotImplementedError
end
update_cooldown(new_cooldown) click to toggle source
# File lib/models/role.rb, line 468
def update_cooldown(new_cooldown)
    raise NotImplementedError
end
update_elasticity_policies(new_policies) click to toggle source
# File lib/models/role.rb, line 460
def update_elasticity_policies(new_policies)
    raise NotImplementedError
end
update_scheduled_policies(new_policies) click to toggle source
# File lib/models/role.rb, line 424
def update_scheduled_policies(new_policies)
    @body['scheduled_policies'] = new_policies
end

Protected Instance Methods

evaluate(template) click to toggle source

Evaluate rules that references to parent roles

@param template [String] Role template with $ to replace

# File lib/models/role.rb, line 725
def evaluate(template)
    client = service.client

    template.scan(/\$\{(.*?)\}/).flatten.each do |value|
        s_value = value.split('.') # 0 -> parent, 1..N -> XPATH

        # If parent not found, instead of error, replace it by blank
        unless parents.include?(s_value[0])
            template.gsub!("${#{value}}", '')
            next
        end

        found   = false
        p_nodes = service.roles[s_value[0]].nodes
        xpath   = "//#{s_value[1..-1].join('/').upcase}"

        # Iterate over parent nodes to find the XPATH on their template
        p_nodes.each do |node|
            id = node['deploy_id']
            vm = OpenNebula::VirtualMachine.new_with_id(id, client)

            # If error continue searching in other nodes
            next if OpenNebula.is_error?(vm.info)

            next unless vm[xpath]

            template.gsub!("${#{value}}", vm[xpath])

            # If found, continue with next expression
            found = true
            break
        end

        next if found

        # If value not found, replace it by blank to avoid issues
        template.gsub!("${#{value}}", '')
    end
end
fill_node_info(vm_id) click to toggle source
# File lib/models/role.rb, line 677
def fill_node_info(vm_id)
    node = { 'deploy_id' => vm_id }
    vm   = OpenNebula::VirtualMachine.new_with_id(vm_id, @service.client)

    max_retries = 3
    attemps     = 0

    begin
        attemps += 1
        rc       = vm.info

        if OpenNebula.is_error?(rc)
            sleep(attemps)
            raise "Error retrieving info for VM #{vm_id}"
        end

        hash_vm         = vm.to_hash['VM']
        node['vm_info'] = { 'VM' => hash_vm.select {|k, _| VM_INFO.include?(k) } }

        @body['nodes'] << node
    rescue StandardError => e
        retry if attemps < max_retries

        node['vm_info'] = nil

        msg = "Role #{name} : Cannot get info for VM #{vm_id}: #{e.message}"
        Log.error LOG_COMP, msg, @service.id

        @service.log_error(msg)

        return [false,
                "Error getting VM #{vm_id} info in " \
                "Role #{name}: #{e.message}"]
    end
end
init_template_attributes() click to toggle source

Helpers

# File lib/models/role.rb, line 637
def init_template_attributes
    @body['last_vmname'] ||= 0

    template_id    = @body['template_id']
    template       = OpenNebula::Template.new_with_id(template_id, @service.client)
    extra_template = @body.fetch('template_contents', {}).dup

    # Since the OpenNebula core does not apply a deep merge, we replace
    # here the values to avoid the entire CONTEXT replacement.
    if !extra_template.empty?
        rc = template.info

        if OpenNebula.is_error?(rc)
            msg = "Role #{name} : Info template #{template_id}; #{rc.message}"

            Log.error(LOG_COMP, msg, @service.id)
            @service.log_error(msg)

            return [
                false,
                "Error fetching Info to instantiate template #{template_id} " \
                "in Role #{name}: #{rc.message}"
            ]
        end

        vm_template    = template.to_hash['VMTEMPLATE']['TEMPLATE'].dup
        extra_template = vm_template.deep_merge(extra_template, false)
    end

    extra_template['SERVICE_ID'] = @service.id
    extra_template['ROLE_NAME']  = @body['name']

    extra_template = Hash.to_raw(extra_template)

    # Evaluate attributes with parent roles
    evaluate(extra_template)

    [template_id, template, extra_template]
end
vm_failure?(node) click to toggle source
# File lib/models/role.rb, line 713
def vm_failure?(node)
    if node && node['vm_info']
        return Role.vm_failure?(node['vm_info']['VM']['STATE'],
                                node['vm_info']['VM']['LCM_STATE'])
    end

    false
end