class OpenNebula::Service

Service class as wrapper of DocumentJSON

Constants

DOCUMENT_TYPE
FAILED_STATES
IMMUTABLE_ATTRS

List of attributes that can't be changed in update operation

user_inputs: it only has sense when deploying, not in running user_inputs_values: it only has sense when deploying, not in running deployment: changing this, changes the undeploy operation log: this is just internal information, no sense to change it name: this has to be changed using rename operation networks: it only has sense when deploying, not in running networks_values: it only has sense when deploying, not in running ready_status_gate: it only has sense when deploying, not in running state: this is internal information managed by OneFlow server start_time: this is internal information managed by OneFlow server

LOG_COMP
MAX_LOG

Maximum number of log entries per service TODO: Make this value configurable

RECOVER_DEPLOY_NETS_STATES
RECOVER_DEPLOY_STATES
RECOVER_SCALE_STATES
RECOVER_UNDEPLOY_NETS_STATES
RECOVER_UNDEPLOY_STATES
STATE
STATE_STR
TRANSIENT_STATES

Attributes

client[R]
roles[R]

Public Instance Methods

add_role(template) click to toggle source

Adds a role to the service

@param template [Hash] Role information

@return [OpenNebula::Role] New role

# File lib/models/service.rb, line 414
def add_role(template)
    template['state'] ||= Role::STATE['PENDING']
    role                = Role.for(template, self)

    if @roles[role.name]
        return OpenNebula::Error.new("Role #{role.name} already exists")
    end

    @roles[role.name] = role
    @body['roles'] << template if @body && @body['roles']

    role
end
all_roles_done?() click to toggle source

Returns true if all the nodes are in done state @return [true, false] true if all the nodes are correctly deployed

# File lib/models/service.rb, line 275
def all_roles_done?
    @roles.each do |_name, role|
        if role.state != Role::STATE['DONE']
            return false
        end
    end

    true
end
all_roles_hold?() click to toggle source

Returns true if all the nodes are in hold state @return [true, false] true if all the nodes are in hold state

# File lib/models/service.rb, line 287
def all_roles_hold?
    @roles.each do |_name, role|
        if role.state != Role::STATE['HOLD']
            return false
        end
    end

    true
end
all_roles_running?() click to toggle source

Returns true if all the nodes are correctly deployed @return [true, false] true if all the nodes are correctly deployed

# File lib/models/service.rb, line 263
def all_roles_running?
    @roles.each do |_name, role|
        if role.state != Role::STATE['RUNNING']
            return false
        end
    end

    true
end
allocate(template_json) click to toggle source

Create a new service based on the template provided @param [String] template_json @return [nil, OpenNebula::Error] nil in case of success, Error

otherwise
Calls superclass method
# File lib/models/service.rb, line 319
def allocate(template_json)
    template = JSON.parse(template_json)
    template['state'] = STATE['PENDING']

    if template['roles']
        template['roles'].each do |elem|
            elem['state'] ||= Role::STATE['PENDING']
        end
    end

    template['start_time'] = Integer(Time.now)

    super(template.to_json, template['name'])
end
can_recover_deploy?() click to toggle source
# File lib/models/service.rb, line 187
def can_recover_deploy?
    RECOVER_DEPLOY_STATES.include? STATE_STR[state]
end
can_recover_deploy_nets?() click to toggle source
# File lib/models/service.rb, line 199
def can_recover_deploy_nets?
    RECOVER_DEPLOY_NETS_STATES.include?(STATE_STR[state])
end
can_recover_scale?() click to toggle source
# File lib/models/service.rb, line 195
def can_recover_scale?
    RECOVER_SCALE_STATES.include? STATE_STR[state]
end
can_recover_undeploy?() click to toggle source
# File lib/models/service.rb, line 191
def can_recover_undeploy?
    RECOVER_UNDEPLOY_STATES.include? STATE_STR[state]
end
can_recover_undeploy_nets?() click to toggle source
# File lib/models/service.rb, line 203
def can_recover_undeploy_nets?
    RECOVER_UNDEPLOY_NETS_STATES.include?(STATE_STR[state])
end
can_scale?() click to toggle source
# File lib/models/service.rb, line 704
def can_scale?
    state == Service::STATE['RUNNING']
end
can_undeploy?() click to toggle source

Return true if the service can be undeployed @return true if the service can be undeployed, false otherwise

# File lib/models/service.rb, line 170
def can_undeploy?
    # rubocop:disable Style/IfWithBooleanLiteralBranches
    if (transient_state? && state != Service::STATE['UNDEPLOYING']) ||
        state == Service::STATE['DONE'] || failed_state?
        false
    else
        true
    end
    # rubocop:enable Style/IfWithBooleanLiteralBranches
end
can_update?() click to toggle source

Return true if the service can be updated @return true if the service can be updated, false otherwise

# File lib/models/service.rb, line 183
def can_update?
    !transient_state? && !failed_state?
end
check_new_template(template_json, append) click to toggle source

Check that changes values are correct

@param template_json [String] New template @param append [Boolean] True to append template to the current

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

False, attr if attr was changed
# File lib/models/service.rb, line 605
def check_new_template(template_json, append)
    template = JSON.parse(template_json)

    if append
        IMMUTABLE_ATTRS.each do |attr|
            next if template[attr].nil?

            return [false, "service/#{attr}"]
        end
    else
        if template['roles'].size != @roles.size
            return [false, 'service/roles size']
        end

        IMMUTABLE_ATTRS.each do |attr|
            next if template[attr] == @body[attr]

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

        template['roles'].each do |role|
            # Role name can't be changed, if it is changed some problems
            # may appear, as name is used to reference roles
            return [false, 'name'] unless @roles[role['name']]

            rc = @roles[role['name']].check_new_template(role)

            return rc unless rc[0]
        end
    end

    [true, nil]
end
check_role(role) click to toggle source

Check if role is terminated or not

@param role [OpenNebula::Role] Role information

@return [Boolean]

True if the service should be undeployed
False otherwise
# File lib/models/service.rb, line 715
def check_role(role)
    return unless @body['automatic_deletion']

    return unless role.nodes.empty?

    ret = true

    @body['roles'].each {|r| ret &= r['nodes'].empty? }

    ret
end
chown(uid, gid) click to toggle source

Changes the owner/group

@param [Integer] uid the new owner id. Use -1 to leave the current one @param [Integer] gid the new group id. Use -1 to leave the current one

@return [nil, OpenNebula::Error] nil in case of success, Error

otherwise
Calls superclass method
# File lib/models/service.rb, line 478
def chown(uid, gid)
    old_uid = self['UID'].to_i
    old_gid = self['GID'].to_i

    rc = super(uid, gid)

    if OpenNebula.is_error?(rc)
        return rc
    end

    @roles.each do |_name, role|
        rc = role.chown(uid, gid)

        break if rc[0] == false
    end

    if rc[0] == false
        log_error('Chown operation failed, will try to rollback ' \
                  'all VMs to the old user and group')

        update

        super(old_uid, old_gid)

        @roles.each do |_name, role|
            role.chown(old_uid, old_gid)
        end

        return OpenNebula::Error.new(rc[1])
    end

    nil
end
delete_networks() click to toggle source
# File lib/models/service.rb, line 682
def delete_networks
    vnets        = @body['networks_values']
    vnets_failed = []

    return if vnets.nil?

    vnets.each do |vnet|
        vnet.each do |_, net|
            next unless net.key?('template_id') || net.key?('reserve_from')

            rc = OpenNebula::VirtualNetwork.new_with_id(
                net['id'],
                @client
            ).delete

            vnets_failed << net['id'] if OpenNebula.is_error?(rc)
        end
    end

    vnets_failed
end
deploy_networks(deploy = true) click to toggle source
# File lib/models/service.rb, line 652
def deploy_networks(deploy = true)
    body = if deploy
               JSON.parse(self['TEMPLATE/BODY'])
           else
               @body
           end

    return if body['networks_values'].nil?

    body['networks_values'].each do |vnet|
        vnet.each do |name, net|
            next if net.key?('id')

            if net.key?('template_id')
                rc = create_vnet(name, net)
            elsif net.key?('reserve_from')
                rc = reserve(name, net)
            end

            return rc if OpenNebula.is_error?(rc)

            net['id'] = rc
        end
    end if deploy

    # @body = template.to_hash

    update_body(body)
end
failed_state?() click to toggle source

Return true if the service is in failed state @return true if the service is in failed state, false otherwise

# File lib/models/service.rb, line 164
def failed_state?
    FAILED_STATES.include? STATE_STR[state]
end
fill_template() click to toggle source

Fills the service template with the provided values.

This method replaces placeholders in the service template with corresponding values Placeholders are expected to be in the format $key.

@return [nil, OpenNebula::Error] nil in case of success, Error otherwise

# File lib/models/service.rb, line 645
def fill_template
    generate_template_contents
rescue StandardError => e
    Log.error LOG_COMP, "Error generating VM template contents: #{e.message}"
    return OpenNebula::Error.new('Error generating VM template contents')
end
gid() click to toggle source
# File lib/models/service.rb, line 227
def gid
    self['GID'].to_i
end
info() click to toggle source

Retrieves the information of the Service and all its Nodes.

@return [nil, OpenNebula::Error] nil in case of success, Error

otherwise
Calls superclass method
# File lib/models/service.rb, line 390
def info
    rc = super
    if OpenNebula.is_error?(rc)
        return rc
    end

    @roles = {}

    if @body['roles']
        @body['roles'].each do |elem|
            elem['state'] ||= Role::STATE['PENDING']
            role = Role.for(elem, self)
            @roles[role.name] = role
        end
    end

    nil
end
info_roles() click to toggle source

Retrieves the information of the Service and all its Nodes.

@return [nil, OpenNebula::Error] nil in case of success, Error

otherwise
# File lib/models/service.rb, line 443
def info_roles
    @roles = {}

    if @body['roles']
        @body['roles'].each do |elem|
            elem['state'] ||= Role::STATE['PENDING']
            role = Role.for(elem, self)
            @roles[role.name] = role
        end
    end

    nil
end
log_error(message) click to toggle source

Add an error message in the service information that will be stored

in OpenNebula

@param [String] message

# File lib/models/service.rb, line 467
def log_error(message)
    add_log(Logger::ERROR, message)
end
log_info(message) click to toggle source

Add an info message in the service information that will be stored

in OpenNebula

@param [String] message

# File lib/models/service.rb, line 460
def log_info(message)
    add_log(Logger::INFO, message)
end
name() click to toggle source

Returns the service name @return [String] the service name

# File lib/models/service.rb, line 134
def name
    @body['name']
end
networks(deploy) click to toggle source

Returns virtual networks IDs @return [Array] Array of integers containing the IDs

# File lib/models/service.rb, line 299
def networks(deploy)
    ret = []

    return ret unless @body['networks_values']

    @body['networks_values'].each do |vnet|
        vnet.each do |_, net|
            next if net.key?('id') && !deploy

            ret << net['id'].to_i
        end
    end

    ret
end
on_hold=(on_hold) click to toggle source

Change the `on_hold` option value

# File lib/models/service.rb, line 238
def on_hold=(on_hold)
    @body['on_hold'] = on_hold
end
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/service.rb, line 233
def on_hold?
    @body['on_hold']
end
recover() click to toggle source

Recover a failed service. @return [nil, OpenNebula::Error] nil in case of success, Error

otherwise
# File lib/models/service.rb, line 337
def recover
    if [Service::STATE['FAILED_DEPLOYING']].include?(state)
        @roles.each do |_name, role|
            if role.state == Role::STATE['FAILED_DEPLOYING']
                role.state = Role::STATE['PENDING']
            end
        end

        self.state = Service::STATE['DEPLOYING']

    elsif state == Service::STATE['FAILED_SCALING']
        @roles.each do |_name, role|
            if role.state == Role::STATE['FAILED_SCALING']
                role.state = Role::STATE['SCALING']
            end
        end

        self.state = Service::STATE['SCALING']

    elsif state == Service::STATE['FAILED_UNDEPLOYING']
        @roles.each do |_name, role|
            if role.state == Role::STATE['FAILED_UNDEPLOYING']
                role.state = Role::STATE['RUNNING']
            end
        end

        self.state = Service::STATE['UNDEPLOYING']

    elsif state == Service::STATE['COOLDOWN']
        @roles.each do |_name, role|
            if role.state == Role::STATE['COOLDOWN']
                role.state = Role::STATE['RUNNING']
            end
        end

        self.state = Service::STATE['RUNNING']

    elsif state == Service::STATE['WARNING']
        @roles.each do |_name, role|
            if role.state == Role::STATE['WARNING']
                role.recover_warning
            end
        end
    else
        OpenNebula::Error.new('Action recover: Wrong state' \
                              " #{state_str}")
    end
end
remove_role(name) click to toggle source

Removes a role from the service

@param name [String] Role name to delete

# File lib/models/service.rb, line 431
def remove_role(name)
    @roles.delete(name)

    @body['roles'].delete_if do |role|
        role['name'] == name
    end
end
replace_client(owner_client) click to toggle source

Replaces this object's client with a new one @param [OpenNebula::Client] owner_client the new client

# File lib/models/service.rb, line 244
def replace_client(owner_client)
    @client = owner_client
end
report_ready?() click to toggle source

Returns the running_status_vm option @return [true, false] true if the running_status_vm option is enabled

# File lib/models/service.rb, line 215
def report_ready?
    @body['ready_status_gate']
end
running?() click to toggle source

Return true if the service is running @return true if the service is runnning, false otherwise

# File lib/models/service.rb, line 209
def running?
    state_str == 'RUNNING'
end
shutdown_action() click to toggle source
# File lib/models/service.rb, line 554
def shutdown_action
    @body['shutdown_action']
end
state() click to toggle source

Returns the service state @return [Integer] the service state

# File lib/models/service.rb, line 140
def state
    @body['state'].to_i
end
state=(state) click to toggle source

Sets a new state @param [Integer] the new state @return [true, false] true if the value was changed

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

    @body['state'] = state.to_i

    msg = "New state: #{STATE_STR[state]}"
    Log.info LOG_COMP, msg, id
    log_info(msg)
end
state_str() click to toggle source

Returns the string representation of the service state @return the state string

# File lib/models/service.rb, line 152
def state_str
    STATE_STR[state]
end
strategy() click to toggle source

Returns the service strategy @return [String] the service strategy

# File lib/models/service.rb, line 146
def strategy
    @body['deployment']
end
transient_state?() click to toggle source

Returns true if the service is in transient state @return true if the service is in transient state, false otherwise

# File lib/models/service.rb, line 158
def transient_state?
    TRANSIENT_STATES.include? STATE_STR[state]
end
uid() click to toggle source
# File lib/models/service.rb, line 223
def uid
    self['UID'].to_i
end
uname() click to toggle source
# File lib/models/service.rb, line 219
def uname
    self['UNAME']
end
update(template_json = nil, append = false) click to toggle source

Replaces the template contents

@param template_json [String] New template contents @param append [true, false] True to append new attributes instead of

replace the whole template

@return [nil, OpenNebula::Error] nil in case of success, Error

otherwise
Calls superclass method
# File lib/models/service.rb, line 566
def update(template_json = nil, append = false)
    if template_json
        template = JSON.parse(template_json)

        if append
            rc = info

            if OpenNebula.is_error? rc
                return rc
            end

            template = @body.merge(template)
        end

        template_json = template.to_json
    end

    super(template_json, append)
end
update_raw(template_raw, append = false) click to toggle source

Replaces the raw template contents

@param template [String] New template contents, in the form KEY = VAL @param append [true, false] True to append new attributes instead of

replace the whole template

@return [nil, OpenNebula::Error] nil in case of success, Error

otherwise
Calls superclass method
# File lib/models/service.rb, line 594
def update_raw(template_raw, append = false)
    super(template_raw, append)
end
update_role(role_name, template_json) click to toggle source

Updates a role @param [String] role_name @param [String] template_json @return [nil, OpenNebula::Error] nil in case of success, Error

otherwise
# File lib/models/service.rb, line 517
def update_role(role_name, template_json)
    if ![Service::STATE['RUNNING'], Service::STATE['WARNING']]
       .include?(state)

        return OpenNebula::Error.new('Update role: Wrong state' \
                                     " #{state_str}")
    end

    template = JSON.parse(template_json)

    # TODO: Validate template?

    role = @roles[role_name]

    if role.nil?
        return OpenNebula::Error.new("ROLE \"#{role_name}\" " \
                                     'does not exist')
    end

    rc = role.update(template)

    if OpenNebula.is_error?(rc)
        return rc
    end

    # TODO: The update may not change the cardinality, only
    # the max and min vms...

    role.state = Role::STATE['SCALING']

    role.set_default_cooldown_duration

    self.state = Service::STATE['SCALING']

    update
end

Private Instance Methods

add_log(severity, message) click to toggle source

@param [Logger::Severity] severity @param [String] message

# File lib/models/service.rb, line 752
def add_log(severity, message)
    severity_str = Logger::SEV_LABEL[severity][0..0]

    @body['log'] ||= []
    @body['log'] << {
        :timestamp => Time.now.to_i,
        :severity  => severity_str,
        :message   => message
    }

    # Truncate the number of log entries
    @body['log'] = @body['log'].last(MAX_LOG)
end
create_vnet(name, net) click to toggle source
# File lib/models/service.rb, line 766
def create_vnet(name, net)
    extra = ''
    extra = net['extra'] if net.key? 'extra'

    OpenNebula::VNTemplate.new_with_id(
        net['template_id'].to_i,
        @client
    ).instantiate(get_vnet_name(name), extra)
end
generate_template_contents() click to toggle source

Generates and updates the `template_contents` for each role within a service. This method handles VM attributes (like MEMORY, CPU, etc.) and CONTEXT attributes within `template_contents` for each role. The contents are generated by combining the `user_inputs_values` from both the service and the individual role, with the role inputs taking precedence over the service inputs.

The method also resolves network configurations for each role by mapping network IDs from the service-level `networks_values` to the NICs defined in the role's `template_contents`.

@example

Given the following input data:
template_contents = {
  'MEMORY' => '1024',
  'NIC'   => [
    {
      'NAME' => 'NIC_0',
      'NETWORK_ID' => '$private'
    }
  ]
}

networks_values    = [{"private": {"id":"0"}}]
user_inputs_values = {"ATT_A": "VALUE_A"}

After executing `generate_template_contents`, the result would be:
{
  'ATT_A'   => 'VALUE_A',
  'MEMORY' => '1024',
  'NIC' => [
    {
      'NAME' => 'NIC_0',
      'NETWORK_ID' => '0'
    }
  ],
  'CONTEXT' => {
    'ATT_A' => '$VALUE_A',
  }
}
# File lib/models/service.rb, line 834
def generate_template_contents
    service_inputs   = @body['user_inputs_values'] || {}
    service_networks = @body['networks_values'] || []

    @body['roles'].each do |role|
        template_contents = role['template_contents'] || {}
        role_inputs      = role['user_inputs_values'] || {}
        role_nets        = template_contents['NIC'] || []

        # Resolve networks
        unless role_nets.empty?
            template_contents['NIC'] = resolve_networks(role_nets, service_networks)
        end

        # Resolve inputs
        unless service_inputs.empty? && role_inputs.empty?
            # role inputs have precedence over service inputs
            role_inputs = service_inputs.deep_merge(role_inputs)

            # Add the role inputs to the template_contents,
            # creating the CONTEXT section in case it doesn't exist
            template_contents['CONTEXT'] = {} unless template_contents.key?('CONTEXT')

            role_inputs.each do |key, value|
                template_contents[key] = value
                template_contents['CONTEXT'][key] = "$#{key}"
            end
        end

        role['template_contents'] = template_contents
    end
end
get_vnet_name(net) click to toggle source
# File lib/models/service.rb, line 790
def get_vnet_name(net)
    "#{net}-#{id}"
end
reserve(name, net) click to toggle source
# File lib/models/service.rb, line 776
def reserve(name, net)
    extra = ''
    extra = net['extra'] if net.key? 'extra'

    return false if !extra || extra.empty?

    extra.concat("\nNAME=\"#{get_vnet_name(name)}\"\n")

    OpenNebula::VirtualNetwork.new_with_id(
        net['reserve_from'].to_i,
        @client
    ).reserve_with_extra(extra)
end
resolve_networks(nics, networks_values) click to toggle source

Replaces the `NETWORK_ID` placeholders in the given NICs with their corresponding network IDs based on the provided `networks_values`. This method is used to resolve dynamic network references (e.g., `$private`) in the role's NIC configuration with the actual network IDs.

@param nics [Array<Hash>] An array of NIC hashes for a role. Each NIC hash should

contain a  `NETWORK_ID` key, which may have a value that
is a placeholder in the form `$network_name`.

@param networks_values [Array<Hash>] An array of network values, where each value

is a hash containing a network name as the key
and a network configuration as the value. The network
configuration should include an `id` key with the
actual network ID.

@return [Array<Hash>] An array of NIC hashes with the `NETWORK_ID` placeholders replaced

by the corresponding network IDs from `networks_values`.

@example

Given the following input data:
nics = [
  { 'NAME' => 'NIC_0', 'NETWORK_ID' => '$private' },
  { 'NAME' => 'NIC_1', 'NETWORK_ID' => '1' }
]

networks_values = [{ 'private' => { 'id' => '0' } }]

After calling `resolve_networks(nics, networks_values)`, the result would be:
[
  { 'NAME' => 'NIC_0', 'NETWORK_ID' => '0' },
  { 'NAME' => 'NIC_1', 'NETWORK_ID' => '1' }
]
# File lib/models/service.rb, line 898
def resolve_networks(nics, networks_values)
    nics.each do |nic|
        next unless nic['NETWORK_ID'].is_a?(String)

        match = nic['NETWORK_ID'].match(/\$(\w+)/)
        next unless match

        net_name          = match[1]
        network           = networks_values.find {|att| att.key?(net_name) }
        nic['NETWORK_ID'] = network[net_name]['id'] if network
    end

    nics
end
update_body(body) click to toggle source
# File lib/models/service.rb, line 733
def update_body(body)
    @body = body

    # Update @roles attribute with the new @body content
    @roles = {}
    if @body['roles']
        @body['roles'].each do |elem|
            elem['state'] ||= Role::STATE['PENDING']
            role = Role.for(elem, self)
            @roles[role.name] = role
        end
    end

    # Update @xml attribute with the new body content
    @xml.at_xpath('/DOCUMENT/TEMPLATE/BODY').children[0].content = @body
end