402 lines
15 KiB
Python
402 lines
15 KiB
Python
# Copyright 2017 Red Hat, Inc.
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import logging
|
|
|
|
from sushy import exceptions
|
|
from sushy.resources import base
|
|
from sushy.resources import common
|
|
from sushy.resources.system import bios
|
|
from sushy.resources.system import constants as sys_cons
|
|
from sushy.resources.system import ethernet_interface
|
|
from sushy.resources.system import mappings as sys_maps
|
|
from sushy.resources.system import processor
|
|
from sushy.resources.system import simple_storage as sys_simple_storage
|
|
from sushy.resources.system.storage import storage as sys_storage
|
|
from sushy import utils
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class ActionsField(base.CompositeField):
|
|
reset = common.ResetActionField('#ComputerSystem.Reset')
|
|
|
|
|
|
class BootField(base.CompositeField):
|
|
allowed_values = base.Field(
|
|
'BootSourceOverrideTarget@Redfish.AllowableValues',
|
|
adapter=list)
|
|
|
|
enabled = base.MappedField('BootSourceOverrideEnabled',
|
|
sys_maps.BOOT_SOURCE_ENABLED_MAP)
|
|
|
|
mode = base.MappedField('BootSourceOverrideMode',
|
|
sys_maps.BOOT_SOURCE_MODE_MAP)
|
|
|
|
target = base.MappedField('BootSourceOverrideTarget',
|
|
sys_maps.BOOT_SOURCE_TARGET_MAP)
|
|
|
|
|
|
class MemorySummaryField(base.CompositeField):
|
|
health = base.Field(['Status', 'HealthRollup'])
|
|
"""The overall health state of memory.
|
|
|
|
This signifies health state of memory along with its dependent resources.
|
|
"""
|
|
|
|
size_gib = base.Field('TotalSystemMemoryGiB', adapter=utils.int_or_none)
|
|
"""The size of memory of the system in GiB.
|
|
|
|
This signifies the total installed, operating system-accessible memory
|
|
(RAM), measured in GiB.
|
|
"""
|
|
|
|
|
|
class System(base.ResourceBase):
|
|
|
|
asset_tag = base.Field('AssetTag')
|
|
"""The system asset tag"""
|
|
|
|
bios_version = base.Field('BiosVersion')
|
|
"""The system BIOS version"""
|
|
|
|
boot = BootField('Boot', required=True)
|
|
"""A dictionary containg the current boot device, frequency and mode"""
|
|
|
|
description = base.Field('Description')
|
|
"""The system description"""
|
|
|
|
hostname = base.Field('HostName')
|
|
"""The system hostname"""
|
|
|
|
identity = base.Field('Id', required=True)
|
|
"""The system identity string"""
|
|
|
|
indicator_led = base.MappedField('IndicatorLED',
|
|
sys_maps.SYSTEM_INDICATOR_LED_MAP)
|
|
"""Whether the indicator LED is lit or off"""
|
|
|
|
manufacturer = base.Field('Manufacturer')
|
|
"""The system manufacturer"""
|
|
|
|
name = base.Field('Name')
|
|
"""The system name"""
|
|
|
|
part_number = base.Field('PartNumber')
|
|
"""The system part number"""
|
|
|
|
power_state = base.MappedField('PowerState',
|
|
sys_maps.SYSTEM_POWER_STATE_MAP)
|
|
"""The system power state"""
|
|
|
|
serial_number = base.Field('SerialNumber')
|
|
"""The system serial number"""
|
|
|
|
sku = base.Field('SKU')
|
|
"""The system stock-keeping unit"""
|
|
|
|
status = common.StatusField('Status')
|
|
"""The system status"""
|
|
|
|
# TODO(lucasagomes): Create mappings for the system_type
|
|
system_type = base.Field('SystemType')
|
|
"""The system type"""
|
|
|
|
uuid = base.Field('UUID')
|
|
"""The system UUID"""
|
|
|
|
memory_summary = MemorySummaryField('MemorySummary')
|
|
"""The summary info of memory of the system in general detail"""
|
|
|
|
_actions = ActionsField('Actions', required=True)
|
|
|
|
# reference to ProcessorCollection instance
|
|
_processors = None
|
|
|
|
# reference to EthernetInterfaceCollection instance
|
|
_ethernet_interfaces = None
|
|
|
|
# reference to BIOS instance
|
|
_bios = None
|
|
|
|
# reference to SimpleStorageCollection instance
|
|
_simple_storage = None
|
|
|
|
# reference to StorageCollection instance
|
|
_storage = None
|
|
|
|
def __init__(self, connector, identity, redfish_version=None):
|
|
"""A class representing a ComputerSystem
|
|
|
|
:param connector: A Connector instance
|
|
:param identity: The identity of the System resource
|
|
:param redfish_version: The version of RedFish. Used to construct
|
|
the object according to schema of the given version.
|
|
"""
|
|
super(System, self).__init__(connector, identity, redfish_version)
|
|
|
|
def _get_reset_action_element(self):
|
|
reset_action = self._actions.reset
|
|
# TODO(dtantsur): make this check also declarative?
|
|
if not reset_action:
|
|
raise exceptions.MissingActionError(action='#ComputerSystem.Reset',
|
|
resource=self._path)
|
|
return reset_action
|
|
|
|
def get_allowed_reset_system_values(self):
|
|
"""Get the allowed values for resetting the system.
|
|
|
|
:returns: A set with the allowed values.
|
|
"""
|
|
reset_action = self._get_reset_action_element()
|
|
|
|
if not reset_action.allowed_values:
|
|
LOG.warning('Could not figure out the allowed values for the '
|
|
'reset system action for System %s', self.identity)
|
|
return set(sys_maps.RESET_SYSTEM_VALUE_MAP_REV)
|
|
|
|
return set([sys_maps.RESET_SYSTEM_VALUE_MAP[v] for v in
|
|
set(sys_maps.RESET_SYSTEM_VALUE_MAP).
|
|
intersection(reset_action.allowed_values)])
|
|
|
|
def reset_system(self, value):
|
|
"""Reset the system.
|
|
|
|
:param value: The target value.
|
|
:raises: InvalidParameterValueError, if the target value is not
|
|
allowed.
|
|
"""
|
|
valid_resets = self.get_allowed_reset_system_values()
|
|
if value not in valid_resets:
|
|
raise exceptions.InvalidParameterValueError(
|
|
parameter='value', value=value, valid_values=valid_resets)
|
|
|
|
value = sys_maps.RESET_SYSTEM_VALUE_MAP_REV[value]
|
|
target_uri = self._get_reset_action_element().target_uri
|
|
|
|
# TODO(lucasagomes): Check the return code and response body ?
|
|
# Probably we should call refresh() as well.
|
|
self._conn.post(target_uri, data={'ResetType': value})
|
|
|
|
def get_allowed_system_boot_source_values(self):
|
|
"""Get the allowed values for changing the boot source.
|
|
|
|
:returns: A set with the allowed values.
|
|
"""
|
|
if not self.boot.allowed_values:
|
|
LOG.warning('Could not figure out the allowed values for '
|
|
'configuring the boot source for System %s',
|
|
self.identity)
|
|
return set(sys_maps.BOOT_SOURCE_TARGET_MAP_REV)
|
|
|
|
return set([sys_maps.BOOT_SOURCE_TARGET_MAP[v] for v in
|
|
set(sys_maps.BOOT_SOURCE_TARGET_MAP).
|
|
intersection(self.boot.allowed_values)])
|
|
|
|
def set_system_boot_source(self, target,
|
|
enabled=sys_cons.BOOT_SOURCE_ENABLED_ONCE,
|
|
mode=None):
|
|
"""Set the boot source.
|
|
|
|
Set the boot source to use on next reboot of the System.
|
|
|
|
:param target: The target boot source.
|
|
:param enabled: The frequency, whether to set it for the next
|
|
reboot only (BOOT_SOURCE_ENABLED_ONCE) or persistent to all
|
|
future reboots (BOOT_SOURCE_ENABLED_CONTINUOUS) or disabled
|
|
(BOOT_SOURCE_ENABLED_DISABLED).
|
|
:param mode: The boot mode, UEFI (BOOT_SOURCE_MODE_UEFI) or
|
|
BIOS (BOOT_SOURCE_MODE_BIOS).
|
|
:raises: InvalidParameterValueError, if any information passed is
|
|
invalid.
|
|
"""
|
|
valid_targets = self.get_allowed_system_boot_source_values()
|
|
if target not in valid_targets:
|
|
raise exceptions.InvalidParameterValueError(
|
|
parameter='target', value=target, valid_values=valid_targets)
|
|
|
|
if enabled not in sys_maps.BOOT_SOURCE_ENABLED_MAP_REV:
|
|
raise exceptions.InvalidParameterValueError(
|
|
parameter='enabled', value=enabled,
|
|
valid_values=list(sys_maps.BOOT_SOURCE_ENABLED_MAP_REV))
|
|
|
|
data = {
|
|
'Boot': {
|
|
'BootSourceOverrideTarget':
|
|
sys_maps.BOOT_SOURCE_TARGET_MAP_REV[target],
|
|
'BootSourceOverrideEnabled':
|
|
sys_maps.BOOT_SOURCE_ENABLED_MAP_REV[enabled]
|
|
}
|
|
}
|
|
|
|
if mode is not None:
|
|
if mode not in sys_maps.BOOT_SOURCE_MODE_MAP_REV:
|
|
raise exceptions.InvalidParameterValueError(
|
|
parameter='mode', value=mode,
|
|
valid_values=list(sys_maps.BOOT_SOURCE_MODE_MAP_REV))
|
|
|
|
data['Boot']['BootSourceOverrideMode'] = (
|
|
sys_maps.BOOT_SOURCE_MODE_MAP_REV[mode])
|
|
|
|
# TODO(lucasagomes): Check the return code and response body ?
|
|
# Probably we should call refresh() as well.
|
|
self._conn.patch(self.path, data=data)
|
|
|
|
# TODO(lucasagomes): All system have a Manager and Chassis object,
|
|
# include a get_manager() and get_chassis() once we have an abstraction
|
|
# for those resources.
|
|
|
|
def _get_processor_collection_path(self):
|
|
"""Helper function to find the ProcessorCollection path"""
|
|
return utils.get_sub_resource_path_by(self, 'Processors')
|
|
|
|
@property
|
|
def processors(self):
|
|
"""Property to reference `ProcessorCollection` instance
|
|
|
|
It is set once when the first time it is queried. On refresh,
|
|
this property is marked as stale (greedy-refresh not done).
|
|
Here the actual refresh of the sub-resource happens, if stale.
|
|
"""
|
|
if self._processors is None:
|
|
self._processors = processor.ProcessorCollection(
|
|
self._conn, self._get_processor_collection_path(),
|
|
redfish_version=self.redfish_version)
|
|
|
|
self._processors.refresh(force=False)
|
|
return self._processors
|
|
|
|
@property
|
|
def ethernet_interfaces(self):
|
|
"""Property to reference `EthernetInterfaceCollection` instance
|
|
|
|
It is set once when the first time it is queried. On refresh,
|
|
this property is marked as stale (greedy-refresh not done).
|
|
Here the actual refresh of the sub-resource happens, if stale.
|
|
"""
|
|
if self._ethernet_interfaces is None:
|
|
self._ethernet_interfaces = (
|
|
ethernet_interface.EthernetInterfaceCollection(
|
|
self._conn,
|
|
utils.get_sub_resource_path_by(self, "EthernetInterfaces"),
|
|
redfish_version=self.redfish_version))
|
|
|
|
self._ethernet_interfaces.refresh(force=False)
|
|
return self._ethernet_interfaces
|
|
|
|
@property
|
|
def bios(self):
|
|
"""Property to reference `Bios` instance
|
|
|
|
It is set once when the first time it is queried. On refresh,
|
|
this property is marked as stale (greedy-refresh not done).
|
|
Here the actual refresh of the sub-resource happens, if stale.
|
|
"""
|
|
if self._bios is None:
|
|
self._bios = bios.Bios(
|
|
self._conn,
|
|
utils.get_sub_resource_path_by(self, 'Bios'),
|
|
redfish_version=self.redfish_version)
|
|
|
|
self._bios.refresh(force=False)
|
|
return self._bios
|
|
|
|
@property
|
|
def simple_storage(self):
|
|
"""A collection of simple storage associated with system.
|
|
|
|
This returns a reference to `SimpleStorageCollection` instance.
|
|
SimpleStorage represents the properties of a storage controller and its
|
|
directly-attached devices.
|
|
|
|
It is set once when the first time it is queried. On refresh,
|
|
this property is marked as stale (greedy-refresh not done).
|
|
Here the actual refresh of the sub-resource happens, if stale.
|
|
|
|
:raises: MissingAttributeError if 'SimpleStorage/@odata.id' field
|
|
is missing.
|
|
:returns: `SimpleStorageCollection` instance
|
|
"""
|
|
if self._simple_storage is None:
|
|
self._simple_storage = sys_simple_storage.SimpleStorageCollection(
|
|
self._conn,
|
|
utils.get_sub_resource_path_by(self, "SimpleStorage"),
|
|
redfish_version=self.redfish_version)
|
|
|
|
self._simple_storage.refresh(force=False)
|
|
return self._simple_storage
|
|
|
|
@property
|
|
def storage(self):
|
|
"""A collection of storage subsystems associated with system.
|
|
|
|
This returns a reference to `StorageCollection` instance.
|
|
A storage subsystem represents a set of storage controllers (physical
|
|
or virtual) and the resources such as drives and volumes that can be
|
|
accessed from that subsystem.
|
|
|
|
It is set once when the first time it is queried. On refresh,
|
|
this property is marked as stale (greedy-refresh not done).
|
|
Here the actual refresh of the sub-resource happens, if stale.
|
|
|
|
:raises: MissingAttributeError if 'Storage/@odata.id' field
|
|
is missing.
|
|
:returns: `StorageCollection` instance
|
|
"""
|
|
if self._storage is None:
|
|
self._storage = sys_storage.StorageCollection(
|
|
self._conn, utils.get_sub_resource_path_by(self, "Storage"),
|
|
redfish_version=self.redfish_version)
|
|
|
|
self._storage.refresh(force=False)
|
|
return self._storage
|
|
|
|
def _do_refresh(self, force=False):
|
|
"""Do custom resource specific refresh activities
|
|
|
|
On refresh, all sub-resources are marked as stale, i.e.
|
|
greedy-refresh not done for them unless forced by ``force``
|
|
argument.
|
|
"""
|
|
if self._processors is not None:
|
|
self._processors.invalidate(force)
|
|
if self._ethernet_interfaces is not None:
|
|
self._ethernet_interfaces.invalidate(force)
|
|
if self._bios is not None:
|
|
self._bios.invalidate(force)
|
|
if self._simple_storage is not None:
|
|
self._simple_storage.invalidate(force)
|
|
if self._storage is not None:
|
|
self._storage.invalidate(force)
|
|
|
|
|
|
class SystemCollection(base.ResourceCollectionBase):
|
|
|
|
@property
|
|
def _resource_type(self):
|
|
return System
|
|
|
|
def __init__(self, connector, path, redfish_version=None):
|
|
"""A class representing a ComputerSystemCollection
|
|
|
|
:param connector: A Connector instance
|
|
:param path: The canonical path to the System collection resource
|
|
:param redfish_version: The version of RedFish. Used to construct
|
|
the object according to schema of the given version.
|
|
"""
|
|
super(SystemCollection, self).__init__(connector, path,
|
|
redfish_version)
|