First implementation of the new rsd-virt-for-nova driver

- Includes driver code
 - Some unit tests
 - Some documentation
 - Devstack installation plugin

Change-Id: Ie3b2483fbf587a037a3fd9d6af1bd64fa5635ed2
Signed-off-by: Helena McGough <helena.mcgough@intel.com>
This commit is contained in:
Helena McGough 2019-02-25 11:53:08 +00:00
parent 20e0545b5d
commit bdc51defdc
38 changed files with 2814 additions and 2 deletions

View File

@ -15,8 +15,8 @@
README
######
nova-rsd
--------
rsd-virt-for-nova
-----------------
A nova virt driver to communicate with IntelRSD's PODM and PSME to create
composed nodes.

View File

@ -0,0 +1,3 @@
# Plug-in overrides
VIRT_DRIVER=rsd

74
devstack/plugin.sh Normal file
View File

@ -0,0 +1,74 @@
local xtrace=$(set +o | grep xtrace)
local error_on_clone=${ERROR_ON_CLONE}
if [ "$VERBOSE" == 'True' ]; then
# enabling verbosity on whole plugin - default behavior
set -o xtrace
fi
function configure_nova_rsd {
# set neutron configs
iniset $NEUTRON_CONF quotas quota_network -1
iniset $NEUTRON_CONF quotas quota_subnet -1
iniset $NEUTRON_CONF quotas quota_port -1
iniset $NEUTRON_CONF quotas quota_security_group -1
iniset $NEUTRON_CONF quotas quota_security_group_rule -1
# set nova configs
iniset $NOVA_CONF DEFAULT compute_driver "rsd.driver.RSDDriver"
iniset $NOVA_CONF DEFAULT cpu_allocation_ratio 1.0
iniset $NOVA_CONF DEFAULT ram_allocation_ratio 1.0
# Disable arbitrary limits
iniset $NOVA_CONF DEFAULT quota_instances -1
iniset $NOVA_CONF DEFAULT quota_cores -1
iniset $NOVA_CONF DEFAULT quota_ram -1
iniset $NOVA_CONF DEFAULT quota_floating_ips -1
iniset $NOVA_CONF DEFAULT quota_fixed_ips -1
iniset $NOVA_CONF DEFAULT quota_metadata_items -1
iniset $NOVA_CONF DEFAULT quota_injected_files -1
iniset $NOVA_CONF DEFAULT quota_injected_file_path_length -1
iniset $NOVA_CONF DEFAULT quota_security_groups -1
iniset $NOVA_CONF DEFAULT quota_security_group_rules -1
iniset $NOVA_CONF DEFAULT quota_key_pairs -1
iniset $NOVA_CONF filter_scheduler enabled_filters "RetryFilter,AvailabilityZoneFilter,ComputeFilter,ComputeCapabilitiesFilter,ImagePropertiesFilter,CoreFilter,RamFilter,DiskFilter"
iniset $NOVA_CONF rsd podm_ip ${PODM_IP}
iniset $NOVA_CONF rsd podm_user ${PODM_USER}
iniset $NOVA_CONF rsd podm_password ${PODM_PASSWD}
iniset $NOVA_CONF rsd podm_port ${PODM_PORT}
}
# disabling ERROR_NO_CLONE to allow this plugin work with devstack-gate
ERROR_ON_CLONE=False
case $1 in
"stack")
case $2 in
"pre-install")
# cloning source code
echo_summary "Cloning of src files for nova-rsd not required"
# sudo pip install -e "git+https://github.com/openstack/rsd-lib@517275b24fc86ce67a345b3aae2d4fa8564d18c1#egg=rsd_lib"
;;
"install")
sudo pip install -e "${NOVA_RSD_DIR}"
;;
"post-config")
configure_nova_rsd
;;
"extra")
:
;;
esac
;;
"unstack")
sudo pip uninstall "${NOVA_RSD_DIR}"
;;
"clean")
# Remove state and transient data
# Remember clean.sh first calls unstack.sh
# this is a noop
:
;;
esac
ERROR_ON_CLONE=$error_on_clone
$xtrace

31
devstack/settings Normal file
View File

@ -0,0 +1,31 @@
# This driver is enabled in override-defaults with:
# VIRT_DRIVER=${VIRT_DRIVER:-rsd}
if [ "$VERBOSE" == "False" ]; then
# allow local debugging
set -o xtrace
fi
NOVA_RSD_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd )
NOVA_RSD_REPO_ENABLE=$(trueorfalse True NOVA_RSD_REPO_ENABLE)
if [ "${NOVA_RSD_REPO_ENABLE}" == "True" ]; then
# Ensure that the fake neutron agent is enabled
if [[ ! "$Q_ML2_PLUGIN_MECHANISM_DRIVERS" =~ "openvswitch" ]]; then
Q_ML2_PLUGIN_MECHANISM_DRIVERS+=",openvswitch"
fi
if [[ ! "$Q_ML2_PLUGIN_MECHANISM_DRIVERS" =~ "fake_agent" ]]; then
Q_ML2_PLUGIN_MECHANISM_DRIVERS+=",fake_agent"
fi
fi
# RSD comfigurations
PODM_IP=${PODM_IP:-'localhost'}
PODM_USER=${PODM_USER:-'admin'}
PODM_PASSWD=${PODM_PASSWD:-'admin'}
PODM_PORT=${PODM_PORT:-8443}
if [ "$VERBOSE" == "False" ]; then
# turn off debugging again
set +o xtrace
fi

160
doc/source/conf.py Normal file
View File

@ -0,0 +1,160 @@
# -*- coding: utf-8 -*-
#
# Configuration file for the Sphinx documentation builder.
#
# This file does only contain a selection of the most common options. For a
# full list see the documentation:
# http://www.sphinx-doc.org/en/master/config
"""Configuration for documentation setup."""
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# -- Project information -----------------------------------------------------
project = u'rsd_virt_for_nova'
copyright = u'2018, Intel Corporation'
author = u'Helena McGough'
# The short X.Y version
version = u''
# The full version, including alpha/beta/rc tags
release = u''
# -- General configuration ---------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path .
exclude_patterns = []
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'alabaster'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Custom sidebar templates, must be a dictionary that maps document names
# to template names.
#
# The default sidebars (for documents that don't match any pattern) are
# defined by theme itself. Builtin themes are using these templates by
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
# 'searchbox.html']``.
#
# html_sidebars = {}
# -- Options for HTMLHelp output ---------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = 'rsd_virt_for_novadoc'
# -- Options for LaTeX output ------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'rsd_virt_for_nova.tex', u'nova\\_rsd Documentation',
u'Helena McGough', 'manual'),
]
# -- Options for manual page output ------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'rsd_virt_for_nova', u'rsd_virt_for_nova Documentation',
[author], 1)
]
# -- Options for Texinfo output ----------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'rsd_virt_for_nova', u'rsd_virt_for_nova Documentation'
author, 'rsd_virt_for_nova', 'One line description of project.',
'Miscellaneous'),
]
# -- Extension configuration -------------------------------------------------

View File

@ -0,0 +1,29 @@
[[local|localrc]]
HOST_IP=<HOST_IP_ADDRESS>
SERVICE_TOKEN=password
ADMIN_PASSWORD=password
MYSQL_PASSWORD=password
RABBIT_PASSWORD=password
DATABASE_PASSWORD=password
SERVICE_PASSWORD=$ADMIN_PASSWORD
LOGFILE=$DEST/logs/stack.sh.log
LOGDAYS=2
GIT_BASE=https://github.com
RECLONE=True
SUBNETPOOL_PREFIX_V4=${SUBNETPOOL_PREFIX_V4:-192.168.208.0/20}
SUBNETPOOL_SIZE_V4=${SUBNETPOOL_SIZE_V4:-25}
enable_plugin rsd-virt-for-nova <PATH_TO_NOVA_RSD_REPO> <REPO_BRANCH_NAME>
NOVA_RSD_REPO_ENABLE=True
# Optional RSD deployment parameters can be specified
# Defaults are defined below but can be changed once configured
# PODM_IP=localhost
# PODM_USER=admin
# PODM_PASSWD=admin
# PODM_PORT=8443

34
doc/source/index.rst Normal file
View File

@ -0,0 +1,34 @@
..
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.
Welcome to nova_rsd's documentation!
====================================
Contents:
.. toctree::
:maxdepth: 2
:caption: Contents
* :ref:`examples/sample_local.conf`
* :ref:`installation_guide.rst`
* :ref:`known_issues.rst`
* :ref:`user_guide.rsd`
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

View File

@ -0,0 +1,102 @@
..
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.
==================
Installation Guide
==================
Pre-requisites
--------------
* Access to the internet
* An IntelRSD deployment, managed by a PODM
* Min. one PSME communicating with yout PODM, with available compute systems
Installation of OpenStack
-------------------------
The following guide provides developer instructions on how to deploy OpenStack
using the deployment tool DevStack:
https://docs.openstack.org/devstack/latest/guides/single-machine.html
Once you have set up your environment to deploy DevStack please refer to the
below configuration instructions to enable the nova-rsd driver contained in
this repo.
DevStack Configuration
~~~~~~~~~~~~~~~~~~~~~~
These configuration instructions provide directions for this minimal
installation of the nova-rsd driver with OpenStack. It also assumes that you
already have setup and configured an RSD PODM that is communicating with
available composable compute systems, via one or more PSMEs.
.. Note::
For instructions on setting up a PODM and PSMEs please refer to the user guides
and code repositories referred to in the following links:
https://github.com/intel/intelRSD
https://www.intel.com/content/www/us/en/architecture-and-technology/rack-scale-design/rack-scale-design-resources.html
A sample local.conf for DevStack is provided here, `examples/sample_local.conf`.
Please copy this file into your devstack reposititory and rename it local.conf
and adjust the configuration options provided where appropriate.
::
cp examples/sample_local.conf devstack/local.conf
local.conf settings
~~~~~~~~~~~~~~~~~~~
In the local.conf the following parameters can be changed:
* HOST_IP:
Set this option to be the ip address of where your OpenStack deployment
will be running.
* ``enable_plugin nova-rsd <PATH_TO_NOVA_RSD_REPO> <REPO_BRANCH_NAME>``:
Clone the nova-rsd repository and point PATH_TO_NOVA_RSD_REPO to its
location.
Set the REPO_BRANCH_NAME to be the branch of the above repo to be the one
with the code version that you require. If left unspecified will default to
master.
* PODM_IP:
This parameter specifies the IP address of where your PODM is running to
facilitate communication with its APIs.
Default: ``localhost``
* PODM_USER:
This parameter specifies the username to authenticate to the PODM with.
Default: ``admin``
* PODM_PASSWD:
This parameter specifies the password to authenticate to the PODM with,
inconjuction with the username defined above.
Default: ``admin``
* PODM_PORT:
This parameter specifies the port that the PODM is certified to transport
trsffic through.
Default: ``8443``

View File

@ -0,0 +1,40 @@
..
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.
============
Known Issues
============
Introduction
------------
This project is being created as a new out-of-tree virt driver for nova. It is
currently a work-in-progress and there are a few issues and area that need to
be looked into and developed to bring the project to maturity.
Issues + Future work
--------------------
* Implementation of additional unit tests and inclusion of functional tests for
the complete code coverage of the new virt driver.
* Needs to be tested on physical RSD compatible hardware.
* Implement the usage of the EventSubscription service provided by the PODM to
improve the tracking of the removal/addition of physical resources from the
RSD deployment.
* Check the status of the creation of duplicate flavors when they are auto
generated. May have to be re-implemented now that the extra_specs are being
used by the resource provider to track the inventory of the systems.

98
doc/source/user_guide.rst Normal file
View File

@ -0,0 +1,98 @@
..
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.
==========
User guide
==========
Introduction
------------
This project is a wip. It is a new nova-virt driver that allows the management
an IntelRSD deployment software architecture. This architecture deployment
facilitates the orchestration of a composable infrastructure through OpenStack.
The new driver itself enables the management of RSD composable nodes through
the use of nova compute service. Therefore you can manage the deployement of a
composed node like you would a VM, container or a baremetal instance, etc.
To set up your OpenStack deployment to use the new virt driver follow the
instructions provided in `installation_guide.rst`.
Usage Instructions
------------------
The new nova-rsd virt driver aims to use the same CLI commands as any of the
other standard virt drivers.
Currently the following commands are supported by the new nova-rsd virt driver:
::
openstack server create --image <IMAGE_ID> --flavor <FLAVOR_ID> <INSTANCE_NAME>
openstack server delete <INSTANCE_NAME>
openstack server start <INSTANCE_NAME>
openstack server stop <INSTANCE_NAME>
openstack server reboot --hard <INSTANCE_NAME>
openstack server reboot --soft <INSTANCE_NAME>
openstack server reboot <INSTANCE_NAME>
To create an instance of type composed node through OpenStack you have to use
one of the specific RSD flavors that are automatically generated based on the
resources available in the RSD deployment. These flavors can be identified by
their name and the extra_specs used to define them when created. The
extra_specs define the custom resources used by the resource provider to track
resource consumption through the placement API.
.. Example::
+----------------------------+----------------------------+
| Field | Value |
+----------------------------+----------------------------+
| OS-FLV-DISABLED:disabled | False |
| OS-FLV-EXT-DATA:ephemeral | 0 |
| access_project_ids | None |
| disk | 0 |
| id | 785919MB-32vcpus |
| name | RSD-785919MB-32vcpus |
| os-flavor-access:is_public | True |
| properties | resources:CUSTOM_1_S_3='1' |
| ram | 785919 |
| rxtx_factor | 1.0 |
| swap | |
| vcpus | 32 |
+----------------------------+----------------------------+
In general a flavor can only be used once depending on the amount of composable
systems available in the RSD deployment. They will define the specific system that
will be consumed from the RSD deployment.
Usage Tracking
--------------
The consumption of resource can be tracked through nova hypervisors used to boot
the composed node instances from. These are defined at the ``Chassis`` level of
the RSD deployment.
It can also be tracked in the placement API in terms of resource providers and
their inventory. There is a resource provider for each of the hypervisors at
the ``Chassis`` level and the a child resource provider for each ``System``
contained within the defined ``Chassis``.

15
nova/__init__.py Normal file
View File

@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
# 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.
"""Initialize virt driver."""
__import__('pkg_resources').declare_namespace(__name__)

15
nova/virt/__init__.py Normal file
View File

@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
# 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.
"""Initialize virt driver."""
__import__('pkg_resources').declare_namespace(__name__)

View File

@ -0,0 +1 @@
"""Initialize new virt driver."""

18
nova/virt/rsd/driver.py Normal file
View File

@ -0,0 +1,18 @@
# 2017 - 2018 Intel Corporation. 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.
"""Initialize new virt driver for nova."""
import rsd_virt_for_nova.virt.rsd.driver as rsd_driver
RSDDriver = rsd_driver.RSDDriver

View File

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# 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.
"""Register new config options for RSD."""
import nova.conf
from rsd_virt_for_nova.conf import rsd
CONF = nova.conf.CONF
rsd.register_opts(CONF)

View File

@ -0,0 +1,51 @@
# -*- coding: utf-8 -*-
# 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.
"""Additional configuration opentions for the rsd-virt driver."""
from oslo_config import cfg
CONF = cfg.CONF
rsd_group = cfg.OptGroup(
'rsd',
title='RSD Options')
rsd_opts = [
cfg.StrOpt('podm_ip',
default='localhost',
help='Specifying the IP address of the PODM which is talking '
'to the appropriate PSME. Defaults to localhost, '
'assuming PODM is running on the same machine as '
'OpenStack. '),
cfg.StrOpt('podm_user',
default='admin',
help='Specifying the username for communication with the '
'PODM. '),
cfg.StrOpt('podm_password',
default='admin',
help='Specifying the password for communication with the '
'PODM. '),
cfg.IntOpt('podm_port',
default=8443,
help='Specifying port on PODM for communication. ')
]
STATIC_OPTIONS = (rsd_opts)
def register_opts(conf):
"""Register the new configuration options for RSD."""
conf.register_group(rsd_group)
conf.register_opts(STATIC_OPTIONS, group=rsd_group)

View File

@ -0,0 +1 @@
"""Initialize tests for virt driver."""

View File

@ -0,0 +1 @@
"""Initialize config for the virt driver."""

View File

@ -0,0 +1,42 @@
# 2017 - 2018 Intel Corporation. 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.
"""Tests for the RSD configuration options."""
from nova import test
from rsd_virt_for_nova import conf as cfg
CONF = cfg.CONF
class TestConf(test.NoDBTestCase):
"""Test class for configurations."""
def setUp(self):
"""Initialize configuration test class."""
super(TestConf, self).setUp()
def test_conf(self):
"""Test the default rsd config values."""
# Try an option from each grouping of static options
# PODM IP
self.assertEqual('localhost', CONF.rsd.podm_ip)
# PODM username
self.assertEqual('admin', CONF.rsd.podm_user)
# PODM password
self.assertEqual('admin', CONF.rsd.podm_password)
# PODM port
self.assertEqual(8443, CONF.rsd.podm_port)

View File

@ -0,0 +1,10 @@
{
"@odata.context": "/redfish/v1/$metadata#Chassis",
"@odata.id": "/redfish/v1/Chassis",
"@odata.type": "#ChassisCollection.ChassisCollection",
"Members": [
],
"Members@odata.count": 8,
"Name": "Chassis Collection"
}

View File

@ -0,0 +1,73 @@
{
"@odata.context": "/redfish/v1/$metadata#Chassis/Members/$entity",
"@odata.id": "/redfish/v1/Chassis/Chassis1",
"@odata.type": "#Chassis.1.0.0.Chassis",
"AssetTag": "FlexChassis1",
"ChassisType": "Drawer",
"Description": "this is a chassis",
"Id": "Chassis1",
"IndicatorLED": "On",
"Links": {
"ComputerSystems": [
{
"@odata.id": "/redfish/v1/Systems/System1"
},
{
"@odata.id": "/redfish/v1/Systems/System2"
},
{
"@odata.id": "/redfish/v1/Systems/System3"
},
{
"@odata.id": "/redfish/v1/Systems/System4"
}
],
"ContainedBy": {
"@odata.id": "/redfish/v1/Chassis/Rack1"
},
"Contains": [
{
"@odata.id": "/redfish/v1/Chassis/Chassis1"
}
],
"ManagedBy": [
{
"@odata.id": "/redfish/v1/Managers/manager1"
}
],
"ManagersIn": [
{
"@odata.id": "/redfish/v1/Managers/manager1"
}
],
"Oem": { },
"Switches": [
{
"@odata.id": "/redfish/v1/EthernetSwitches/switch1"
}
]
},
"Manufacturer": "Intel Corporaion",
"Model": "Lenovo FLEX 8731",
"Name": "FLEX-1",
"Oem": {
"Intel:RackScale": {
"@odata.type": "#Intel.Oem.Chassis",
"Location": {
"Rack": "Rack1",
"UHeight": "10 U",
"ULocation": "25 U",
"UWidth": "1 U"
},
"UUID": "e1c2d764-5c72-36d6-9945-a78255edab51"
}
},
"PartNumber": "5c72-36d6",
"SKU": "e1c2d764-5c72",
"SerialNumber": "a78255edab51",
"Status": {
"Health": "OK",
"HealthRollup": "OK",
"State": "Enabled"
}
}

View File

@ -0,0 +1,37 @@
{
"@odata.context": "/redfish/v1/$metadata#Chassis",
"@odata.id": "/redfish/v1/Chassis",
"@odata.type": "#ChassisCollection.ChassisCollection",
"Members": [
{
"@odata.id": "/redfish/v1/Chassis/Rack1"
},
{
"@odata.id": "/redfish/v1/Chassis/Rack2"
},
{
"@odata.id": "/redfish/v1/Chassis/Chassis1"
},
{
"@odata.id": "/redfish/v1/Chassis/Chassis2"
},
{
"@odata.id": "/redfish/v1/Chassis/Drawer1"
},
{
"@odata.id": "/redfish/v1/Chassis/Drawer2"
},
{
"@odata.id": "/redfish/v1/Chassis/NVMEChassis1"
},
{
"@odata.id": "/redfish/v1/Chassis/NVMEChassis2"
},
{
"@odata.id": "/redfish/v1/Chassis/System1"
}
],
"Members@odata.count": 8,
"Name": "Chassis Collection"
}

View File

@ -0,0 +1,114 @@
{
"@odata.context": "/redfish/v1/$metadata#Nodes/Members/$entity",
"@odata.id": "/redfish/v1/Nodes/Node1",
"@odata.type": "#ComposedNode.1.1.0.ComposedNode",
"Id": "Node1",
"Name": "Composed Node",
"Description": "Node #1",
"UUID": "fa39d108-7d70-400a-9db2-6940375c31c2",
"PowerState": "Off",
"Status": {
"State": "Enabled",
"Health": "OK",
"HealthRollup": "OK"
},
"Processors": {
"Count": 2,
"Model": "Multi-Core Intel(R) Xeon(R) processor 7xxx Series",
"Status": {
"State": "Enabled",
"Health": "OK",
"HealthRollup" : "OK"
}
},
"Memory": {
"TotalSystemMemoryGiB": 32,
"Status": {
"State": "Enabled",
"Health": "OK",
"HealthRollup" : "OK"
}
},
"ComposedNodeState": "Allocated",
"Boot": {
"BootSourceOverrideEnabled": "Disabled",
"BootSourceOverrideTarget": "None",
"BootSourceOverrideTarget@Redfish.AllowableValues": [
"None",
"Pxe",
"Hdd",
"RemoteDrive"
],
"BootSourceOverrideMode": "Legacy",
"BootSourceOverrideMode@Redfish.AllowableValues": ["Legacy",
"UEFI"]
},
"Oem": {},
"Links": {
"ComputerSystem": {
"@odata.id": "/redfish/v1/Systems/System1"
},
"Processors": [
{
"@odata.id": "/redfish/v1/Systems/System1/Processors/CPU1"
}
],
"Memory": [
{
"@odata.id": "/redfish/v1/Systems/System1/Memory/Dimm1"
}
],
"EthernetInterfaces": [
{
"@odata.id":
"/redfish/v1/Systems/System1/EthernetInterfaces/LAN1"
}
],
"LocalDrives": [
{
"@odata.id": "/redfish/v1/Chassis/Blade1/Drives/1"
}
],
"RemoteDrives": [
{
"@odata.id": "/redfish/v1/Services/RSS1/Targets/target1"
}
],
"ManagedBy": [
{
"@odata.id": "/redfish/v1/Managers/PODM"
}
],
"Oem": {}
},
"Actions": {
"#ComposedNode.Reset": {
"target": "/redfish/v1/Nodes/Node1/Actions/ComposedNode.Reset",
"ResetType@Redfish.AllowableValues": [
"On",
"ForceOff",
"GracefulRestart",
"ForceRestart",
"Nmi",
"ForceOn",
"PushPowerButton",
"GracefulShutdown"
]
},
"#ComposedNode.Assemble": {
"target": "/redfish/v1/Nodes/Node1/Actions/ComposedNode.Assemble"
},
"#ComposedNode.AttachResource": {
"target": "/redfish/v1/Nodes/Node1/Actions/ComposedNode.AttachResource",
"@Redfish.ActionInfo": {
"@odata.id":"/redfish/v1/Nodes/Node1/Actions/AttachResourceActionInfo"
}
},
"#ComposedNode.DetachResource": {
"target": "/redfish/v1/Nodes/Node1/Actions/ComposedNode.DetachResource",
"@Redfish.ActionInfo": {
"@odata.id":"/redfish/v1/Nodes/Node1/Actions/DetachResourceActionInfo"
}
}
}
}

View File

@ -0,0 +1,114 @@
{
"@odata.context": "/redfish/v1/$metadata#Nodes/Members/$entity",
"@odata.id": "/redfish/v1/Nodes/Node1",
"@odata.type": "#ComposedNode.1.1.0.ComposedNode",
"Id": "Node1",
"Name": "Composed Node",
"Description": "Node #1",
"UUID": "fa39d108-7d70-400a-9db2-6940375c31c2",
"PowerState": "Off",
"Status": {
"State": "Enabled",
"Health": "OK",
"HealthRollup": "OK"
},
"Processors": {
"Count": 2,
"Model": "Multi-Core Intel(R) Xeon(R) processor 7xxx Series",
"Status": {
"State": "Enabled",
"Health": "OK",
"HealthRollup" : "OK"
}
},
"Memory": {
"TotalSystemMemoryGiB": 32,
"Status": {
"State": "Enabled",
"Health": "OK",
"HealthRollup" : "OK"
}
},
"ComposedNodeState": "Assembled",
"Boot": {
"BootSourceOverrideEnabled": "Disabled",
"BootSourceOverrideTarget": "None",
"BootSourceOverrideTarget@Redfish.AllowableValues": [
"None",
"Pxe",
"Hdd",
"RemoteDrive"
],
"BootSourceOverrideMode": "Legacy",
"BootSourceOverrideMode@Redfish.AllowableValues": ["Legacy",
"UEFI"]
},
"Oem": {},
"Links": {
"ComputerSystem": {
"@odata.id": "/redfish/v1/Systems/System1"
},
"Processors": [
{
"@odata.id": "/redfish/v1/Systems/System1/Processors/CPU1"
}
],
"Memory": [
{
"@odata.id": "/redfish/v1/Systems/System1/Memory/Dimm1"
}
],
"EthernetInterfaces": [
{
"@odata.id":
"/redfish/v1/Systems/System1/EthernetInterfaces/LAN1"
}
],
"LocalDrives": [
{
"@odata.id": "/redfish/v1/Chassis/Blade1/Drives/1"
}
],
"RemoteDrives": [
{
"@odata.id": "/redfish/v1/Services/RSS1/Targets/target1"
}
],
"ManagedBy": [
{
"@odata.id": "/redfish/v1/Managers/PODM"
}
],
"Oem": {}
},
"Actions": {
"#ComposedNode.Reset": {
"target": "/redfish/v1/Nodes/Node1/Actions/ComposedNode.Reset",
"ResetType@Redfish.AllowableValues": [
"On",
"ForceOff",
"GracefulRestart",
"ForceRestart",
"Nmi",
"ForceOn",
"PushPowerButton",
"GracefulShutdown"
]
},
"#ComposedNode.Assemble": {
"target": "/redfish/v1/Nodes/Node1/Actions/ComposedNode.Assemble"
},
"#ComposedNode.AttachResource": {
"target": "/redfish/v1/Nodes/Node1/Actions/ComposedNode.AttachResource",
"@Redfish.ActionInfo": {
"@odata.id":"/redfish/v1/Nodes/Node1/Actions/AttachResourceActionInfo"
}
},
"#ComposedNode.DetachResource": {
"target": "/redfish/v1/Nodes/Node1/Actions/ComposedNode.DetachResource",
"@Redfish.ActionInfo": {
"@odata.id":"/redfish/v1/Nodes/Node1/Actions/DetachResourceActionInfo"
}
}
}
}

View File

@ -0,0 +1,13 @@
{
"@odata.context": "/redfish/v1/$metadata#ComposedNodeCollection.ComposedNodeCollection",
"@odata.id": "/redfish/v1/Nodes",
"@odata.type": "#ComposedNodeCollection.ComposedNodeCollection",
"Actions": {
"#ComposedNodeCollection.Allocate": {
"target": "/redfish/v1/Nodes/Actions/Allocate"
}
},
"Members": [],
"Members@odata.count": 0,
"Name": "Composed Node Collection"
}

View File

@ -0,0 +1,44 @@
{
"@odata.context": "/redfish/v1/$metadata#ServiceRoot.ServiceRoot",
"@odata.id": "/redfish/v1/",
"@odata.type": "#ServiceRoot.v1_1_1.ServiceRoot",
"Id": "RootService",
"Name": "Root Service",
"Description": "description-as-string",
"RedfishVersion": "1.1.0",
"UUID": "92384634-2938-2342-8820-489239905423",
"Systems": {
"@odata.id": "/redfish/v1/Systems"
},
"Chassis": {
"@odata.id": "/redfish/v1/Chassis"
},
"Managers": {
"@odata.id": "/redfish/v1/Managers"
},
"EventService": {
"@odata.id": "/redfish/v1/EventService"
},
"Fabrics": {
"@odata.id": "/redfish/v1/Fabrics"
},
"EthernetSwitches": {
"@odata.id": "/redfish/v1/EthernetSwitches"
},
"StorageServices": {
"@odata.id": "/redfish/v1/StorageServices"
},
"Oem": {
"Intel_RackScale": {
"@odata.type": "#Intel.Oem.ServiceRoot",
"ApiVersion": "2.3.0",
"Nodes": {
"@odata.id": "/redfish/v1/Nodes"
},
"EthernetSwitches": {
"@odata.id": "/redfish/v1/EthernetSwitches"
}
}
},
"Links": {}
}

View File

@ -0,0 +1,16 @@
{
"@odata.context": "/redfish/v1/$metadata#ComputerSystemCollection.ComputerSystemCollection",
"@odata.id": "/redfish/v1/Systems",
"@odata.type": "#ComputerSystemCollection.ComputerSystemCollection",
"Name": "Computer System Collection",
"Description": "description-as-string",
"Members@odata.count": 2,
"Members": [
{
"@odata.id": "/redfish/v1/Systems/System1"
},
{
"@odata.id": "/redfish/v1/Systems/System2"
}
]
}

View File

@ -0,0 +1,123 @@
{
"@odata.context": "/redfish/v1/$metadata#ComputerSystem.ComputerSystem", "@odata.id": "/redfish/v1/Systems/System1",
"@odata.type": "#ComputerSystem.v1_3_0.ComputerSystem",
"Id": "System1",
"Name": "My Computer System",
"Description": "Description of server",
"SystemType": "Physical",
"AssetTag": "free form asset tag",
"Manufacturer": "Manufacturer Name",
"Model": "Model Name",
"SKU": "SKU",
"SerialNumber": "2M220100SL",
"PartNumber": "Computer1",
"UUID": "00000000-0000-0000-0000-000000000000",
"HostName": null,
"Status": {
"State": "Enabled",
"Health": "OK",
"HealthRollup": "OK"
},
"IndicatorLED": null,
"PowerState": "On",
"Boot": {
"@odata.type": "#ComputerSystem.v1_1_0.Boot",
"BootSourceOverrideEnabled": "Disabled",
"BootSourceOverrideTarget": "None",
"BootSourceOverrideTarget@Redfish.AllowableValues": ["None"],
"BootSourceOverrideMode": null,
"BootSourceOverrideMode@Redfish.AllowableValues": []
},
"BiosVersion": null,
"ProcessorSummary": {
"Count": 1,
"Model": null,
"Status": {
"State": null,
"Health": null,
"HealthRollup": null
}
},
"MemorySummary": {
"TotalSystemMemoryGiB": 32,
"Status": {
"State": null,
"Health": null,
"HealthRollup": null
}
},
"Processors": {
"@odata.id": "/redfish/v1/Systems/System1/Processors"
},
"EthernetInterfaces": {
"@odata.id": "/redfish/v1/Systems/System1/EthernetInterfaces"
},
"SimpleStorage": {},
"Storage": {
"@odata.id": "/redfish/v1/Systems/System1/Storage"
},
"Memory": {
"@odata.id": "/redfish/v1/Systems/System1/Memory"
},
"PCIeDevices": [
{
"@odata.id": "/redfish/v1/Chassis/PCIeSwitch1/PCIeDevices/Device1"
}
],
"PCIeFunctions": [],
"TrustedModules": [],
"Links": {
"@odata.type": "#ComputerSystem.v1_2_0.Links",
"Chassis": [{
"@odata.id": "/redfish/v1/Chassis/4"
}],
"ManagedBy": [{
"@odata.id": "/redfish/v1/Managers/1"
}],
"Endpoints": [],
"Oem": {}
},
"Actions": {
"#ComputerSystem.Reset": {
"target": "/redfish/v1/Systems/System1/Actions/ComputerSystem.Reset",
"ResetType@Redfish.AllowableValues": ["On",
"ForceOff",
"GracefulShutdown",
"ForceRestart",
"Nmi",
"GracefulRestart",
"ForceOn",
"PushPowerButton"]
},
"Oem": {
"#Intel.Oem.StartDeepDiscovery": {
"target": "/redfish/v1/Systems/System1/Actions/Oem/Intel.Oem.StartDeepDiscovery"
},
"#Intel.Oem.StartDiscoveryOnDemand": {
"target": "/redfish/v1/Systems/System1/Actions/Oem/Intel.Oem.StartDiscoveryOnDemand"
},
"#Intel.Oem.ChangeTPMState": {
"target": "/redfish/v1/Systems/System1/Actions/Oem/Intel.Oem.ChangeTPMState",
"InterfaceType@Redfish.AllowableValues": [
"TPM1_2",
"TPM2_0"
]
}
}
},
"Oem": {
"Intel_RackScale": {
"@odata.type": "#Intel.Oem.ComputerSystem",
"PciDevices": [],
"DiscoveryState": "Basic",
"ProcessorSockets": 1,
"MemorySockets": 2,
"PCIeConnectionId": [],
"UserModeEnabled": false,
"TrustedExecutionTechnologyEnabled": false,
"Metrics": {
"@odata.id": "/redfish/v1/Systems/System1/Metrics"
}
}
}
}

View File

@ -0,0 +1 @@
"""Initialize tests for the virt driver."""

View File

@ -0,0 +1 @@
"""Initialize tests for the virt driver."""

View File

@ -0,0 +1,854 @@
# -*- coding: utf-8 -*-
# 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.
"""Unit tests for the RSD virt driver."""
import json
import mock
from nova import context
from nova import exception
from nova import objects
from nova import rc_fields as fields
from nova.compute import power_state
from nova.compute import provider_tree
from nova.objects import flavor
from nova.virt import fake
from nova.virt import hardware
from rsd_virt_for_nova.conf import rsd as cfg
from rsd_virt_for_nova.virt import rsd
from rsd_virt_for_nova.virt.rsd import driver
from oslo_utils import versionutils
from oslo_utils.fixture import uuidsentinel as uuids
from oslotest import base
import rsd_lib
from rsd_lib.resources.v2_1.chassis import chassis
from rsd_lib.resources.v2_2.system import system
from rsd_lib.resources.v2_3.node import node
from rsd_lib.resources.v2_3.node import node as v2_3_node
from sushy import connector
CONF = cfg.CONF
class FakeInstance(object):
"""A class to fake out nova instances."""
def __init__(self, name, state, uuid, new_flavor):
"""Initialize the variables for fake instances."""
self.name = name
self.power_state = state
self.uuid = uuid
self.display_description = None
self.flavor = new_flavor
def __getitem__(self, key):
"""Method to retrieve fake instance variables."""
return getattr(self, key)
def delete_node(self):
"""Fake delete node function."""
pass
def reset_node(self, action):
"""Fake reset node function."""
pass
class FakeFlavor(object):
"""A class to fake out a flavor for a nova instance."""
def __init__(self, vcpus, memory_mb, name, flavorid, extra_specs):
"""Initialize the variables for a fake flavor."""
self.vcpus = vcpus
self.memory_mb = memory_mb
self.name = name
self.flavorid = flavorid
self.extra_specs = extra_specs
def __getitem__(self, key):
"""Method to retrieve fake flavor variables."""
return getattr(self, key)
class TestRSDDriver(base.BaseTestCase):
"""A test class for the driver."""
@mock.patch.object(rsd, 'PODM_connection', autospaec=True)
@mock.patch.object(connector, 'Connector', autospec=True)
def setUp(self, mock_connector, pod_conn):
"""Initial setup of mocks for all of the unit tests."""
super(TestRSDDriver, self).setUp()
# Mock out the connection to the RSD redfish API
self.root_conn = mock.MagicMock()
mock_connector.return_value = self.root_conn
# Create sample collections and instances of Chassis/System/Nodes
with open('rsd_virt_for_nova/tests/json_samples/root.json', 'r') as f:
self.root_conn.get.return_value.json.return_value = json.loads(
f.read())
self.rsd = rsd_lib.main.RSDLib('http://foo.bar:8442', username='foo',
password='bar', verify=False).factory()
with open('rsd_virt_for_nova/tests/json_samples/chassis_col.json', 'r') as f:
self.root_conn.get.return_value.json.return_value = json.loads(
f.read())
self.chassis_col = chassis.ChassisCollection(
self.root_conn, '/redfish/v1/Chassis',
redfish_version='1.0.2')
with open('rsd_virt_for_nova/tests/json_samples/chassis.json', 'r') as f:
self.root_conn.get.return_value.json.return_value = json.loads(
f.read())
self.chassis_inst = chassis.Chassis(
self.root_conn, '/redfish/v1/Chassis/Chassis1',
redfish_version='1.0.2')
with open('rsd_virt_for_nova/tests/json_samples/node_col.json', 'r') as f:
self.root_conn.get.return_value.json.return_value = json.loads(
f.read())
self.node_collection = node.NodeCollection(
self.root_conn, '/redfish/v1/Nodes', redfish_version='1.0.2')
with open('rsd_virt_for_nova/tests/json_samples/node.json', 'r') as f:
self.root_conn.get.return_value.json.return_value = json.loads(
f.read())
self.node_inst = node.Node(
self.root_conn, '/redfish/v1/Nodes/Node1',
redfish_version='1.0.2')
with open('rsd_virt_for_nova/tests/json_samples/node_assembled.json', 'r') as f:
self.root_conn.get.return_value.json.return_value = json.loads(
f.read())
self.node_ass_inst = node.Node(
self.root_conn, '/redfish/v1/Nodes/Node1',
redfish_version='1.0.2')
with open('rsd_virt_for_nova/tests/json_samples/sys_collection.json', 'r') as f:
self.root_conn.get.return_value.json.return_value = \
json.loads(f.read())
self.system_col = system.SystemCollection(
self.root_conn, '/redfish/v1/Systems',
redfish_version='1.0.2')
with open('rsd_virt_for_nova/tests/json_samples/system.json', 'r') as f:
self.root_conn.get.return_value.json.return_value = json.loads(
f.read())
self.system_inst = system.System(
self.root_conn, '/redfish/v1/Systems/System1',
redfish_version='1.0.2')
# Mock out a fake virt driver and its dependencies/parameters
self.RSD = driver.RSDDriver(fake.FakeVirtAPI())
# Create Fake flavors and instances
gb = self.system_inst.memory_summary.size_gib
mem = self.RSD.conv_GiB_to_MiB(gb)
proc = self.system_inst.processors.summary.count
flav_id = str(mem) + 'MB-' + str(proc) + 'vcpus'
res = fields.ResourceClass.normalize_name(self.system_inst.identity)
spec = 'resources:' + res
# Mock out some instances for testing
self.flavor = FakeFlavor(
gb, mem, str('RSD.' + flav_id),
self.system_inst.identity,
spec)
self.inst1 = FakeInstance('inst1', power_state.RUNNING,
'inst1id', self.flavor)
self.invalid_inst = FakeInstance(
'inv_inst', power_state.RUNNING, 'inv_inst_id',
self.flavor)
self.RSD.instances = {self.inst1.uuid: self.inst1}
# A provider tree for testing on the placement API
self.ptree = provider_tree.ProviderTree()
self.test_image_meta = {
"disk_format": "raw",
}
@mock.patch.object(driver, 'set_nodes')
@mock.patch.object(driver.RSDDriver, 'check_chassis_systems')
def test_init_nodes_success(self, check_chas_sys, set_nodes):
"""Initialize nodes successful test."""
# Setup for test to successfully create nodes for each valid chassis
chas_col = self.RSD.driver.PODM.get_chassis_collection.return_value
chas_col.members_identities = ['/redfish/v1/Chassis/Chassis1']
self.RSD._init_nodes()
# Confirm that the correct functionality is called and the correct
# compute nodes are created to boot hypervisors from
self.RSD.driver.podm_connection.assert_called()
self.RSD.driver.PODM.get_chassis_collection.assert_called()
chas_col.get_member.assert_called_with('/redfish/v1/Chassis/Chassis1')
check_chas_sys.assert_called_with(chas_col.get_member.return_value)
set_nodes.assert_called_with(['/redfish/v1/Chassis/Chassis1'])
@mock.patch.object(driver, 'set_nodes')
@mock.patch.object(driver.RSDDriver, 'check_chassis_systems')
def test_init_nodes_failure(self, check_chas_sys, set_nodes):
"""Initialize nodes failure test."""
# Setup for test failing to create nodes for each chassis
chas_col = self.RSD.driver.PODM.get_chassis_collection.return_value
self.RSD._init_nodes()
# Verify failed nodes and insufficient function calls
self.RSD.driver.podm_connection.assert_called()
self.RSD.driver.PODM.get_chassis_collection.assert_called()
chas_col.get_member.assert_not_called()
check_chas_sys.assert_not_called()
set_nodes.assert_called_with([])
def test_init_host(self):
"""Test initializing the host."""
# Run test
host = self.RSD.init_host(self.chassis_inst)
# Confirm the correct hostname is identified
self.assertEqual(host, self.chassis_inst)
@mock.patch.object(hardware, 'InstanceInfo')
def test_get_info_valid(self, info):
"""Test getting information for a valid instance."""
# Run test
self.RSD.get_info(self.inst1)
# Confirm that the correct hardware info os collected
info.assert_called_once_with(state=self.inst1.power_state)
@mock.patch.object(hardware, 'InstanceInfo')
def test_get_info_invalid(self, info):
"""Test getting information for an invalid instance."""
# An invalid instance throws an exception, hardware info not requested
self.assertRaises(
exception.InstanceNotFound, self.RSD.get_info, self.invalid_inst)
info.assert_not_called()
@mock.patch.object(driver.RSDDriver, '_init_nodes')
def test_get_available_nodes_false_refresh(self, init_nodes):
"""Test getting a list of the available nodes, no refresh."""
# Run test checking the list of available nodes
nodes = self.RSD.get_available_nodes(refresh=False)
# Confirm that the correst functions are called and all of the correct
# nodes are available
init_nodes.assert_called_once()
self.assertEqual(nodes, self.RSD._nodes)
@mock.patch.object(driver.RSDDriver, '_init_nodes')
def test_get_available_nodes_true_refresh(self, init_nodes):
"""Test getting a list of the available nodes, with refresh."""
# Run test checking the list of available nodes, refresh
nodes = self.RSD.get_available_nodes(refresh=True)
# Confirm that the correst functions are called and all of the correct
# nodes are available
init_nodes.assert_called_once()
self.assertEqual(nodes, self.RSD._nodes)
@mock.patch.object(driver.RSDDriver, 'get_available_nodes')
def test_node_is_available_invalid(self, getNodes):
"""Test if a node is available for an instance, failure."""
# Run test checking a node is available
avail = self.RSD.node_is_available(self.chassis_inst.identity)
# Confirm the correct functions are called and confirm that the
# node being checked is not available
getNodes.assert_called()
self.assertEqual(self.RSD.instance_node, None)
self.assertEqual(avail, False)
@mock.patch.object(driver.RSDDriver, 'get_available_nodes')
def test_node_is_available_valid(self, getNodes):
"""Test if a node is available for an instance, success."""
# Run test checking a node is available
# Setup mocks for a successful test
getNodes.return_value = self.chassis_col.members_identities
avail = self.RSD.node_is_available('/redfish/v1/Chassis/Chassis1')
# Confirm successful check that node is available through the correct
# function calls
getNodes.assert_called()
self.assertEqual(self.RSD.instance_node,
'/redfish/v1/Chassis/Chassis1')
self.assertEqual(avail, True)
def test_list_instances(self):
"""Test listing all instances."""
# Run test to list available instances
instances = self.RSD.list_instances()
# Confirm the result matches the internal list
self.assertEqual(instances, {self.inst1.uuid: self.inst1})
@mock.patch.object(driver.RSDDriver, 'power_on')
def test_spawn_success(self, power_on):
"""Test spawning an instance successfully."""
# Mock out setup to successfully create a node
node_col = self.RSD.driver.PODM.get_node_collection.return_value
node_col.members_identities = ['/redfish/v1/Nodes/Node1']
self.RSD.driver.PODM.get_node.return_value = self.node_ass_inst
mock_context = context.get_admin_context()
self.RSD.rsd_flavors = {self.flavor.flavorid: {
'id': 'flav_id',
'rsd_systems': [self.system_inst.identity]}}
image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
# Run spawning test
self.RSD.spawn(mock_context, self.inst1, image_meta,
[], None, {})
# Confirm that a node instances is spawned and the physical composed
# node is powered on
self.RSD.driver.PODM.get_node_collection.assert_called_once()
self.RSD.driver.PODM.get_node.assert_called_with(
'/redfish/v1/Nodes/Node1')
power_on.assert_called_once_with(mock_context, self.inst1, None)
@mock.patch.object(v2_3_node, 'Node', autospec=True)
def test_destroy_success(self, mock_node):
"""Test destroying an instance and deleting the composed node."""
# Mock out instances and composed nodes for testing purposes
node_collection = self.RSD.driver.PODM.get_node_collection
node_inst = node_collection.return_value.compose_node.return_value
self.RSD._composed_nodes = {self.inst1.uuid: mock_node}
# Try to destroy the instance
self.RSD.destroy("context", self.inst1, network_info=None)
# Confirm that the instance has been delete from the list of instances
mock_node.delete_node.assert_called_once()
node_collection.assert_called_once()
node_collection.return_value.compose_node.assert_called_once()
node_inst.assemble_node.assert_called_once()
self.assertNotIn(self.inst1.uuid, self.RSD.instances)
@mock.patch.object(v2_3_node, 'Node', autospec=True)
def test_destory_failure(self, mock_node):
"""Test failure to destroy a composed node instance."""
# Mock out instances and composed nodes for testing purposes
self.RSD._composed_nodes = {}
node_collection = self.RSD.driver.PODM.get_node_collection
node_inst = node_collection.return_value.compose_node.return_value
# Try to destroy the instance
self.RSD.destroy("context", self.inst1, network_info=None)
# Confirm that the instance failed to delete and a new node was not
# created to replace it
mock_node.delete_node.assert_not_called()
self.RSD.driver.PODM.get_node_collection.assert_not_called()
node_collection.return_value.compose_node.assert_not_called()
node_inst.assemble_node.assert_not_called()
self.assertNotIn(self.inst1.uuid, self.RSD.instances)
@mock.patch.object(driver.RSDDriver, '_create_flavors')
@mock.patch.object(driver.RSDDriver, 'check_flavors')
@mock.patch.object(driver.RSDDriver, 'check_chassis_systems')
@mock.patch.object(versionutils, 'convert_version_to_int')
@mock.patch.object(driver.RSDDriver, 'get_sys_proc_info')
@mock.patch.object(driver.RSDDriver, 'get_sys_memory_info')
def test_get_available_resource_success(self, mem_info, proc_info, conv_v,
check_chas, check_flav,
create_flav):
"""Test successfully getting available resources for a node."""
# Set up the parameters for the test
chas_str = '/redfish/v1/Chassis/Chassis1'
self.RSD._nodes = [chas_str]
sys_col = self.RSD.driver.PODM.get_system_collection.return_value
chas_col = self.RSD.driver.PODM.get_chassis_collection.return_value
resources = self.RSD.get_available_resource(chas_str)
# Perform checks on all methods called on a successful run
self.RSD.driver.PODM.get_chassis_collection.assert_called()
self.RSD.driver.PODM.get_system_collection.assert_called_once()
chas_col.get_member.assert_called_with(chas_str)
check_chas.assert_called_with(chas_col.get_member.return_value)
check_flav.assert_called_with(sys_col, sys_col.members_identities)
create_flav.assert_called_once()
mem_info.assert_called_with(check_chas.return_value)
proc_info.assert_called_with(check_chas.return_value)
conv_v.assert_called_with('1.0')
self.assertEqual({'cpu_info': '',
'disk_available_least': 0,
'hypervisor_hostname': chas_str,
'hypervisor_type': 'composable',
'hypervisor_version': conv_v.return_value,
'local_gb': 0,
'local_gb_used': 0,
'memory_mb': mem_info.return_value,
'memory_mb_used': mem_info.return_value,
'numa_topology': None,
'supported_instances':
[('x86_64', 'baremetal', 'hvm')],
'vcpus': proc_info.return_value,
'vcpus_used': proc_info.return_value}, resources)
@mock.patch.object(driver.RSDDriver, '_create_flavors')
@mock.patch.object(driver.RSDDriver, 'check_flavors')
@mock.patch.object(driver.RSDDriver, 'check_chassis_systems')
@mock.patch.object(versionutils, 'convert_version_to_int')
@mock.patch.object(driver.RSDDriver, 'get_sys_proc_info')
@mock.patch.object(driver.RSDDriver, 'get_sys_memory_info')
def test_get_available_resource_failure(self, mem_info, proc_info, conv_v,
check_chas, check_flav,
create_flav):
"""Test failing to available resources for a node."""
# If there is no composed node for the compute node = Failure
self.RSD._nodes = []
resources = self.RSD.get_available_resource(self.chassis_inst.identity)
# Confirm that there are no available resource to boot composed node
# instances from
self.RSD.driver.PODM.get_chassis_collection.assert_called_once()
self.RSD.driver.PODM.get_system_collection.assert_not_called()
check_chas.assert_not_called()
mem_info.assert_not_called()
proc_info.assert_not_called()
conv_v.assert_not_called()
check_flav.assert_not_called()
create_flav.assert_not_called()
self.assertEqual(resources, {})
@mock.patch.object(driver.RSDDriver, 'create_child_inventory')
@mock.patch.object(driver.RSDDriver, 'create_inventory')
@mock.patch.object(driver.RSDDriver, 'check_chassis_systems')
def test_update_provider_tree_success(self, check_chas, create_inv,
create_child_inv):
"""Successfully updating the RP tree test."""
# Setup a valid resource provider tree for the test
self.ptree = provider_tree.ProviderTree()
self.ptree.new_root('/redfish/v1/Chassis/Chassis1', uuids.cn)
# Setup other mocked calls for a successful test
sys_col = self.RSD.driver.PODM.get_system_collection.return_value
sys_col.get_member.return_value = self.system_inst
chas_col = self.RSD.driver.PODM.get_chassis_collection.return_value
chas_col.members_identities = ['/redfish/v1/Chassis/Chassis1']
chas_col.get_member.return_value = self.chassis_inst
check_chas.return_value = ['/redfish/v1/Systems/System1']
self.RSD.update_provider_tree(self.ptree,
'/redfish/v1/Chassis/Chassis1')
# Confirm that the provider tree for the placement API has been
# updated correctly with a child node for each compute system available
self.RSD.driver.PODM.get_system_collection.assert_called_once()
self.RSD.driver.PODM.get_chassis_collection.assert_called()
chas_col.get_member.assert_called_with('/redfish/v1/Chassis/Chassis1')
check_chas.assert_called_with(self.chassis_inst)
sys_col.get_member.assert_called_with('/redfish/v1/Systems/System1')
create_child_inv.assert_called_once_with(self.system_inst.identity)
create_inv.assert_called_once_with(check_chas.return_value)
@mock.patch.object(driver.RSDDriver, 'create_child_inventory')
@mock.patch.object(driver.RSDDriver, 'create_inventory')
@mock.patch.object(driver.RSDDriver, 'check_chassis_systems')
def test_update_provider_tree_failure(self, check_chas, create_inv,
create_child_inv):
"""Failing to update the RP tree test."""
# Setup a valid resource provider tree for the test
self.ptree = provider_tree.ProviderTree()
# Setup other mocked calls for a successful test
sys_col = self.RSD.driver.PODM.get_system_collection.return_value
chas_col = self.RSD.driver.PODM.get_chassis_collection.return_value
self.RSD.update_provider_tree(self.ptree,
'/redfish/v1/Chassis/Chassis1')
# Confirn that the provider tree for the placement API was not updated
# correctly and no new nodes were created
self.RSD.driver.PODM.get_system_collection.assert_called_once()
self.RSD.driver.PODM.get_chassis_collection.assert_called()
chas_col.get_member.assert_not_called()
check_chas.assert_not_called()
sys_col.get_member.assert_not_called()
create_child_inv.assert_not_called()
create_inv.assert_not_called()
def test_get_sys_proc_info_failure(self):
"""Test failing to get sys_proc info."""
# Set up a failing test for getting system processor information
sys_col = self.RSD.driver.PODM.get_system_collection.return_value
cpus = self.RSD.get_sys_proc_info(None)
# Confirm that the relavant functions fail when called
self.RSD.driver.PODM.get_system_collection.assert_called_once()
sys_col.get_member.assert_not_called()
self.assertEqual(cpus, 0)
def test_get_sys_proc_info_success(self):
"""Test succeeding in getting sys_proc info."""
# Set up for a successful test for getting system processor information
sys_col = self.RSD.driver.PODM.get_system_collection.return_value
cpus = self.RSD.get_sys_proc_info(['/redfish/v1/Systems/System1'])
# Confirm that the relavant functions fail when called
# And correct proccessor information is calculated
self.RSD.driver.PODM.get_system_collection.assert_called_once()
sys_col.get_member.assert_called_with('/redfish/v1/Systems/System1')
self.assertEqual(cpus, self.system_inst.processors.summary.count)
@mock.patch.object(driver.RSDDriver, 'conv_GiB_to_MiB')
def test_get_sys_memory_info_failure(self, conv_mem):
"""Test failing to get sys_mem info."""
# Set up a failing test for getting system memory information
sys_col = self.RSD.driver.PODM.get_system_collection.return_value
mem_mb = self.RSD.get_sys_memory_info(None)
# Confirm that the relavant functions fail when called
self.RSD.driver.PODM.get_system_collection.assert_called_once()
sys_col.get_member.assert_not_called()
conv_mem.assert_not_called()
self.assertEqual(mem_mb, 0)
@mock.patch.object(driver.RSDDriver, 'conv_GiB_to_MiB')
def test_get_sys_memory_info_success(self, conv_mem):
"""Test suceeding at getting sys_mem info."""
# Set up mocks to successfully get memory information for a system
sys_col = self.RSD.driver.PODM.get_system_collection.return_value
sys_col.members_identities = ['/redfish/v1/Systems/System1']
sys_col.get_member.return_value = self.system_inst
self.RSD.driver.composed_nodes = {
self.node_inst.system.identity: self.node_inst.identity}
# Run the test and get the result
mem_mb = self.RSD.get_sys_memory_info(['/redfish/v1/Systems/System1'])
# Confirm that the relavant functions fail when called
self.RSD.driver.PODM.get_system_collection.assert_called_once()
sys_col.get_member.assert_called_once_with(
'/redfish/v1/Systems/System1')
conv_mem.assert_called_with(self.system_inst.memory_summary.size_gib)
# Confirm the result is as to be expected
self.assertEqual(
mem_mb,
conv_mem(self.system_inst.memory_summary.size_gib).__radd__())
def test_conv_GiB_to_MiB(self):
"""Test the conversion of GiB to MiB."""
# Run test on memory conversion function
MiB = self.RSD.conv_GiB_to_MiB(8)
# Confirm the correct result is generated
self.assertEqual(8191, MiB)
@mock.patch.object(v2_3_node, 'Node', autospec=True)
def test_invalid_power_off(self, mock_node):
"""Test the failed powering off of and instance."""
# power off the invalid instance test
self.RSD.power_off(self.invalid_inst)
# power state is not as it should be and the action function
# reset is not called
self.assertNotEqual(self.inst1.power_state, power_state.SHUTDOWN)
mock_node.reset_node.assert_not_called()
@mock.patch.object(v2_3_node, 'Node', autospec=True)
def test_valid_power_off(self, mock_node):
"""Test the powering off of an instance."""
# Mock out a node and instance to power off
self.RSD._composed_nodes = {self.inst1.uuid: mock_node}
# Run power off test
self.RSD.power_off(self.inst1)
# Confirm that the composed node instance is in the shutdown state
self.assertEqual(self.inst1.power_state, power_state.SHUTDOWN)
mock_node.reset_node.assert_called_once_with('graceful shutdown')
@mock.patch.object(v2_3_node, 'Node', autospec=True)
def test_invalid_power_on(self, mock_node):
"""Test the powering on of an invalid instance."""
# Run power on test
self.RSD.power_on(mock.MagicMock(), self.invalid_inst, 'network_info')
# No reset action is called on the node
mock_node.reset_node.assert_not_called()
@mock.patch.object(v2_3_node, 'Node', autospec=True)
def test_valid_power_on(self, mock_node):
"""Test the powering on of an instance."""
# Mock out instances and composed nodes for testing purposes
self.RSD._composed_nodes = {self.inst1.uuid: mock_node}
# Power on a valid instance
self.RSD.power_on(mock.MagicMock(), self.inst1, 'network_info')
# Confirm that the composed node instance is in the running state
self.assertEqual(self.inst1.power_state, power_state.RUNNING)
mock_node.reset_node.assert_called_once_with('force on')
@mock.patch.object(v2_3_node, 'Node', autospec=True)
def test_invalid_reboot(self, mock_node):
"""Test reboot of an invalid instance."""
# Perform a hard reboot on an invalid node
self.RSD.reboot(
mock.MagicMock(), self.invalid_inst, 'network_info', 'HARD')
# No reset action is called
mock_node.reset_node.assert_not_called()
@mock.patch.object(v2_3_node, 'Node', autospec=True)
def test_valid_hard_reboot(self, mock_node):
"""Test valid reboot of a composed node instance."""
# Mock out instances and composed nodes for testing purposes
self.RSD._composed_nodes = {self.inst1.uuid: mock_node}
# Perform a hard reboot on a valid node
self.RSD.reboot(mock.MagicMock(), self.inst1, 'network_info', 'HARD')
# Confirm the correct reset action is called
mock_node.reset_node.assert_called_with('force restart')
@mock.patch.object(v2_3_node, 'Node', autospec=True)
def test_valid_soft_reboot(self, mock_node):
"""Test valid reboot of a composed node instance."""
# Mock out instances and composed nodes for testing purposes
self.RSD._composed_nodes = {self.inst1.uuid: mock_node}
# Perform a soft reboot on a valid node
self.RSD.reboot(mock.MagicMock(), self.inst1, 'network_info', 'SOFT')
# Confirm the correct reset action is called
mock_node.reset_node.assert_called_with('graceful restart')
@mock.patch.object(driver.RSDDriver, 'conv_GiB_to_MiB')
@mock.patch.object(driver.RSDDriver, 'get_sys_memory_info')
@mock.patch.object(driver.RSDDriver, 'get_sys_proc_info')
def test_create_inventory_success(self, sys_proc_info, sys_mem_info,
conv_mem):
"""Test creating a inventory for a provider tree."""
# Setup test to successfully create inventory
sys_mem_info.return_value = self.system_inst.memory_summary.size_gib
sys_proc_info.return_value = self.system_inst.processors.summary.count
inv = self.RSD.create_inventory([self.system_inst.identity])
# Check that the correct functions are called and the inventory
# is generated correctly
sys_proc_info.assert_called()
sys_mem_info.assert_called()
self.assertEqual(inv, {'MEMORY_MB': {
'reserved': 0,
'total': sys_mem_info.return_value,
'max_unit': sys_mem_info.return_value,
'min_unit': 1,
'step_size': 1,
'allocation_ratio': 1
},
'VCPU': {
'reserved': 0,
'total': 1,
'max_unit': 1,
'min_unit': 1,
'step_size': 1,
'allocation_ratio': 1}
})
@mock.patch.object(fields.ResourceClass, 'normalize_name')
def test_create_child_inventory(self, norm_name):
"""Test creating inventory for the child RP's."""
# Set up a test to create the inventory for child resource providers
child_inv = self.RSD.create_child_inventory(
[self.system_inst.identity])
# Check that the correct functions are called and the inventory
# is generated correctly
norm_name.assert_called_once_with([self.system_inst.identity])
self.assertEqual(child_inv, {norm_name.return_value: {
'total': 1,
'reserved': 0,
'min_unit': 1,
'max_unit': 1,
'step_size': 1,
'allocation_ratio': 1,
}
})
def test_check_chassis_systems_invalid(self):
"""Test checking the systems available through an invalid chassis."""
# Error raied for invalid chassis system check
self.assertRaises(
AttributeError, self.RSD.check_chassis_systems,
'invalid_chassis_instance')
def test_check_chassis_systems_valid(self):
"""Test checking the systems available through a valid chassis."""
# Run test to check available systems for a Chassis
systems = self.RSD.check_chassis_systems(self.chassis_inst)
# Confirm that the generated list equals the systems linked to the
# Chassis
self.assertEqual(systems, ['/redfish/v1/Systems/System1',
'/redfish/v1/Systems/System2',
'/redfish/v1/Systems/System3',
'/redfish/v1/Systems/System4'])
@mock.patch.object(context, 'get_admin_context')
@mock.patch.object(flavor.Flavor, '_flavor_get_by_flavor_id_from_db')
@mock.patch.object(flavor, '_flavor_create')
@mock.patch.object(fields.ResourceClass, 'normalize_name')
@mock.patch.object(driver.RSDDriver, 'conv_GiB_to_MiB')
def test_create_flavors_success(self, conv_mem, norm_name, flav_create,
get_flav, admin_context):
"""Test creation of new flavors for a System, success."""
# Set up the mock objects for a sucessful creation test
sys_col = self.RSD.driver.PODM.get_system_collection.return_value
sys_col.members_identities = ['/redfish/v1/Systems/System1']
sys_col.get_member.return_value = self.system_inst
# Run test
self.RSD._create_flavors()
# Check the function calls for the test
self.RSD.driver.PODM.get_system_collection.assert_called_once()
sys_col.get_member.assert_called_with('/redfish/v1/Systems/System1')
conv_mem.assert_called_with(self.system_inst.memory_summary.size_gib)
norm_name.assert_called_with(self.system_inst.identity)
admin_context.assert_called()
# Flavor creation call check
spec = 'resources:' + norm_name.return_value
mem = conv_mem.return_value - 512
proc = self.system_inst.processors.summary.count
flav_id = str(mem) + 'MB-' + str(proc) + 'vcpus'
flav_create.assert_called_once_with(
admin_context.return_value,
{'name': 'RSD-' + flav_id,
'flavorid': flav_id,
'memory_mb': mem,
'vcpus': self.system_inst.processors.summary.count,
'root_gb': 0,
'extra_specs': {spec: '1'}})
get_flav.assert_not_called()
@mock.patch.object(context, 'get_admin_context')
@mock.patch.object(flavor.Flavor, '_flavor_get_by_flavor_id_from_db')
@mock.patch.object(flavor, '_flavor_create')
@mock.patch.object(fields.ResourceClass, 'normalize_name')
@mock.patch.object(driver.RSDDriver, 'conv_GiB_to_MiB')
def test_create_flavors_exists(self, conv_mem, norm_name, flav_create,
get_flav, admin_context):
"""Test failing to create a flavor that already exists."""
# Set up mocks to ensure flavors fail to be created
sys_col = self.RSD.driver.PODM.get_system_collection.return_value
sys_col.members_identities = ['/redfish/v1/Systems/System1']
sys_col.get_member.return_value = self.system_inst
spec = 'resources:' + norm_name.return_value
mem = conv_mem.return_value - 512
proc = self.system_inst.processors.summary.count
flav_id = str(mem) + 'MB-' + str(proc) + 'vcpus'
self.RSD.rsd_flavors = {flav_id: {
'id': flav_create.return_value['id'],
'rsd_systems': [self.system_inst.identity]
}}
flav_create.return_value = Exception
# Run test to try and create new flavors based on available systems
self.RSD._create_flavors()
# Confirm no new flavors have been created
self.RSD.driver.PODM.get_system_collection.assert_called_once()
sys_col.get_member.assert_called_with('/redfish/v1/Systems/System1')
conv_mem.assert_called_with(self.system_inst.memory_summary.size_gib)
norm_name.assert_called_with(self.system_inst.identity)
admin_context.assert_called()
# Flavor creation call check
flav_create.assert_called_once_with(
admin_context.return_value,
{'name': 'RSD-' + flav_id,
'flavorid': flav_id,
'memory_mb': mem,
'vcpus': self.system_inst.processors.summary.count,
'root_gb': 0,
'extra_specs': {spec: '1'}})
# Confirm that the flavor already exists
get_flav.assert_called_once_with(admin_context.return_value, flav_id)
@mock.patch.object(flavor.Flavor, '_flavor_get_by_flavor_id_from_db')
@mock.patch.object(flavor, '_flavor_create')
@mock.patch.object(fields.ResourceClass, 'normalize_name')
@mock.patch.object(driver.RSDDriver, 'conv_GiB_to_MiB')
def test_create_flavors_failure(self, conv_mem, norm_name, flav_create,
flav_from_db):
"""Test failing the creation of new flavors for a System."""
# Setup for a failed flavor creation test
sys_col = self.RSD.driver.PODM.get_system_collection.return_value
self.RSD._create_flavors()
# Verification of flavor creation failure and invalid function calls
self.RSD.driver.PODM.get_system_collection.assert_called_once()
sys_col.get_member.assert_not_called()
conv_mem.assert_not_called()
norm_name.assert_not_called()
flav_create.assert_not_called()
flav_from_db.assert_not_called()
@mock.patch.object(flavor, '_flavor_destroy')
def test_check_flavors_failure(self, flav_destroy):
"""Test for failing to check existing flavors."""
# Setup for the test to fail to check flavors
sys_col = self.RSD.driver.PODM.get_system_collection.return_value
self.RSD.check_flavors(self.system_col, [])
# Confirm that checking the available flavors failed
sys_col.get_member.assert_not_called()
flav_destroy.assert_not_called()
@mock.patch.object(flavor, '_flavor_destroy')
def test_check_flavors_success(self, flav_destroy):
"""Test for successfully checking existing flavors."""
# Setup for check flavor that exists
sys_col = self.RSD.driver.PODM.get_system_collection.return_value
sys_col.get_member.return_value = self.system_inst
self.RSD.rsd_flavors = {
'mock_flav_id': {'id': 'flav_id',
'rsd_systems': [self.system_inst.identity]}}
self.RSD.check_flavors(sys_col, ['/redfish/v1/Systems/System1'])
# Confirm the list of available flavors
# No flavors need to be deleted
sys_col.get_member.assert_called_with('/redfish/v1/Systems/System1')
flav_destroy.assert_not_called()
@mock.patch.object(context, 'get_admin_context')
@mock.patch.object(flavor, '_flavor_destroy')
def test_check_flavors_success_update(self, flav_destroy, get_context):
"""Test checking existing flavors, update/delete flavors, success."""
# Setup a test for flavors that need to be delete in a flavor check
sys_str = '/redfish/v1/Systems/System1'
sys_col = self.RSD.driver.PODM.get_system_collection.return_value
self.RSD.rsd_flavors = {
'mock_flav_id': {'id': 'flav_id',
'rsd_systems': [sys_str]}}
self.RSD.check_flavors(sys_col, [sys_str])
# Confirm the list of availbable flavors
# Delete all flavors that no longer have associated systems
sys_col.get_member.assert_called_with(sys_str)
get_context.assert_called_once()
flav_destroy.assert_called_with(get_context.return_value, 'flav_id')
self.assertEqual(self.RSD.rsd_flavors, {})

View File

@ -0,0 +1 @@
"""Initialize virt driver."""

View File

@ -0,0 +1,85 @@
# 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.
"""Initialize the rsd virt driver."""
from rsd_virt_for_nova.conf import rsd as cfg
from oslo_log import log as logging
import rsd_lib
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
class PODM_connection(object):
"""A class to make the connection to the PODM."""
def __init__(self):
"""Initialize class and connection."""
self.PODM = None
self._RSD_NODES = list()
self.composed_nodes = {}
self.PODM_UUID = None
def podm_connection(self):
"""Establish/Refresh the connection to the PODM."""
PODM_IP = CONF.rsd.podm_ip
PODM_PORT = str(CONF.rsd.podm_port)
PODM_URI = str('https://' + PODM_IP + ':' + PODM_PORT + '/redfish/v1')
self.PODM = rsd_lib.RSDLib(PODM_URI, username=CONF.rsd.podm_user,
password=CONF.rsd.podm_password,
verify=False).factory()
podm_info = self.PODM.json
if "UUID" in podm_info.keys():
self.PODM_UUID = podm_info["UUID"]
# Gets the RSD compute systems to create compute agents from
SYS_COL = self.PODM.get_system_collection()
SYSTEMS = SYS_COL.members_identities
COMPOSED_NODE_COL = self.PODM.get_node_collection()
for s in SYSTEMS:
# Allocate the resources for all of the systems availablea
try:
node = COMPOSED_NODE_COL.compose_node()
except Exception as ex:
LOG.warn("Node is already allocated: %s", ex)
# Set up the rsd hypervisor nodes.
COMPOSED_NODE_COL = self.PODM.get_node_collection()
COMPOSED_NODES = COMPOSED_NODE_COL.members_identities
for cn in COMPOSED_NODES:
# Assemble all the composed nodes
try:
node_inst = self.PODM.get_node(cn)
node_inst.assemble_node()
except Exception as a_ex:
LOG.warn("Failed to assemble node: %s", a_ex)
try:
node = COMPOSED_NODE_COL.get_member(cn)
if node.system.identity not in self._RSD_NODES:
self._RSD_NODES.append(node.system.identity)
self.composed_nodes[node.system.identity] = cn
except Exception as ce:
LOG.warn("Failed to establish a connection to the PODM.%s", ce)
if cn in self.composed_nodes.keys():
key = self.composed_nodes[cn]
del self.composed_nodes[cn]
self._RSD_NODES.remove(key)

View File

@ -0,0 +1,456 @@
# -*- coding: utf-8 -*-
# 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.
"""
An RSD hypervisor+api.
Inherits from Nova's FakeDriver to facilicate allocating/composing RSD
nodes. Enables communication to the PODM to enable this.
"""
import copy
import json
from collections import OrderedDict
from nova import context
from nova import exception
from nova import rc_fields as fields
from nova.compute import power_state
from nova.objects import fields as obj_fields
from nova.objects import flavor
from nova.virt import driver
from nova.virt import hardware
from rsd_virt_for_nova.conf import rsd as cfg
from rsd_virt_for_nova.virt import rsd
from oslo_log import log as logging
from oslo_utils import versionutils
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
PODM_NODE = ()
def set_nodes(nodes):
"""Set RSDDriver's node.list.
It has effect on the following methods:
get_available_nodes()
get_available_resource
To restore the change, call restore_nodes()
"""
global PODM_NODE
PODM_NODE = nodes
class RSDDriver(driver.ComputeDriver):
"""Implementation of nova compute driver to compose RSD nodes from nova."""
def __init__(self, virtapi, read_only=False):
"""Initialize the RSDDriver."""
super(RSDDriver, self).__init__(virtapi)
# Initializes vairables to track compute nodes and instances
self.driver = rsd.PODM_connection()
self.instances = OrderedDict()
self.rsd_flavors = OrderedDict()
self._nodes = self._init_nodes()
self._composed_nodes = OrderedDict()
self.instance_node = None
def _init_nodes(self):
"""Create a compute node for every compute sled."""
self.driver.podm_connection()
nodes = []
CHASSIS_COL = self.driver.PODM.get_chassis_collection()
for c in CHASSIS_COL.members_identities:
chas = CHASSIS_COL.get_member(c)
cha_sys = self.check_chassis_systems(chas)
if cha_sys != []:
nodes.append(c)
set_nodes(nodes)
return copy.copy(PODM_NODE)
def init_host(self, host):
"""Initialize anything that is necessary for the driver to function."""
return host
def get_info(self, instance):
"""Get instance info including power state."""
if instance.uuid not in self.instances:
raise exception.InstanceNotFound(instance_id=instance.uuid)
i = self.instances[instance.uuid]
return hardware.InstanceInfo(state=i.power_state)
def get_available_nodes(self, refresh=True):
"""Return nodenames of all nodes managed by the compute service."""
self._nodes = self._init_nodes()
return self._nodes
def node_is_available(self, nodename):
"""Return whether this compute service manages a particular node."""
if nodename in self.get_available_nodes():
self.instance_node = nodename
return True
# Refresh and check again.
return nodename in self.get_available_nodes(refresh=True)
def list_instances(self):
"""List all available instances."""
return self.instances
def spawn(self, context, instance, image_meta, injected_files,
admin_password, allocations, network_info=None,
block_device_info=None):
"""Spawn an RSD composed node on a boot request."""
uuid = instance.uuid
# Assembles the composed node and tracks node from the instance
COMPOSED_NODE_COL = self.driver.PODM.get_node_collection()
node_inst = None
flav_id = instance.flavor.flavorid
sys_list = self.rsd_flavors[flav_id]['rsd_systems']
for n in COMPOSED_NODE_COL.members_identities:
try:
node_inst = self.driver.PODM.get_node(n)
except Exception as ex:
LOG.warn("Malformed composed node instance:%s", ex)
if node_inst is not None:
node_state = node_inst.composed_node_state
p_state = node_inst.power_state
if node_state == 'assembled' and p_state == 'off':
# Provide nova instance with composed node info
node_sys_id = node_inst.system.identity
if node_sys_id in sys_list:
self.instances[uuid] = instance
self._composed_nodes[uuid] = node_inst
instance.display_description = \
json.dumps({"node_identity": n,
"node_uuid": node_inst.uuid})
self.power_on(context, instance, network_info)
return
raise Exception("Failed to assign composed node for instance.")
def destroy(self, context, instance, network_info, block_device_info=None,
destroy_disks=True):
"""Destroy RSD composed node on nova delete request."""
key = instance.uuid
if key in self._composed_nodes:
node_inst = self._composed_nodes[key]
LOG.info("Disassembling RSD composed node.")
node_inst.delete_node()
if key in self.instances:
# Looks up the correct node to deallocate resources froma
del self.instances[key]
else:
LOG.warning("Key not in instances.")
LOG.info("Reallocating resources for composed node.")
COMPOSED_NODE_COL = self.driver.PODM.get_node_collection()
try:
node_inst = COMPOSED_NODE_COL.compose_node()
node_inst.assemble_node()
except Exception as ex:
LOG.warn("Node is already allocated: %s", ex)
else:
if key in self.instances:
# Looks up the correct node to deallocate resources from
del self.instances[key]
else:
LOG.warning("Key '%(key)s' not in instances '%(inst)s'",
{'key': key,
'inst': self.instances}, instance=instance)
def get_available_resource(self, nodename):
"""Update compute manager resource info on ComputeNode table."""
cpu_info = ''
if nodename not in self._nodes:
return {}
SYSTEM_COL = self.driver.PODM.get_system_collection()
members = SYSTEM_COL.members_identities
CHASSIS_COL = self.driver.PODM.get_chassis_collection()
chas = CHASSIS_COL.get_member(nodename)
cha_sys = self.check_chassis_systems(chas)
# Check if all flavors are valid
self.check_flavors(SYSTEM_COL, members)
self._create_flavors()
res = {
'vcpus': self.get_sys_proc_info(cha_sys),
'memory_mb': self.get_sys_memory_info(cha_sys),
'local_gb': 0,
'vcpus_used': self.get_sys_proc_info(cha_sys),
'memory_mb_used': self.get_sys_memory_info(cha_sys),
'local_gb_used': 0,
'hypervisor_type': 'composable',
'hypervisor_version': versionutils.convert_version_to_int('1.0'),
'hypervisor_hostname': nodename,
'cpu_info': cpu_info,
'disk_available_least': 0,
'supported_instances': [(
obj_fields.Architecture.X86_64,
obj_fields.HVType.BAREMETAL,
obj_fields.VMMode.HVM)],
'numa_topology': None,
}
return res
def update_provider_tree(self, provider_tree, nodename, allocations=None):
"""Update ProviderTree with current resources + inventory information.
When this method returns, provider_tree should represent the correct
hierarchy of nested resource providers associated with this compute
node, as well as the inventory, aggregates, and traits associated with
those resource providers.
This method supersedes get_inventory(): if this method is implemented,
get_inventory() is not used.
:note: Renaming a provider (by deleting it from provider_tree and
re-adding it with a different name) is not supported at this time.
See the developer reference documentation for more details:
https://docs.openstack.org/nova/latest/reference/update-provider-tree.html
"""
SYSTEM_COL = self.driver.PODM.get_system_collection()
sys_s = SYSTEM_COL.members_identities
systems = []
for s in sys_s:
systems.append(s)
CHASSIS_COL = self.driver.PODM.get_chassis_collection()
for c in CHASSIS_COL.members_identities:
chas = CHASSIS_COL.get_member(nodename)
cha_sys = self.check_chassis_systems(chas)
if cha_sys != []:
for s in cha_sys:
system = SYSTEM_COL.get_member(s)
sys_inv = self.create_child_inventory(system.identity)
try:
provider_tree.new_child(s, nodename)
except Exception as ex:
LOG.warn("Failed to create new RP: %s", ex)
provider_tree.update_inventory(s, sys_inv)
chas_inv = self.create_inventory(cha_sys)
provider_tree.update_inventory(nodename, chas_inv)
def get_sys_proc_info(self, systems):
"""Track vcpus made available by the PODM."""
cpus = 1
SYSTEM_COL = self.driver.PODM.get_system_collection()
try:
cpus = 0
for s in systems:
ss = SYSTEM_COL.get_member(s)
if ss.identity in self.driver.composed_nodes.keys():
cpus = cpus + ss.processors.summary.count
except Exception as ex:
LOG.info("Failed to get processor info: %s", ex)
return cpus
def get_sys_memory_info(self, systems):
"""Track memory available in the PODM."""
ram = 1
SYSTEM_COL = self.driver.PODM.get_system_collection()
try:
ram = 0
for s in systems:
ss = SYSTEM_COL.get_member(s)
if ss.identity in self.driver.composed_nodes.keys():
mem = ss.memory_summary.size_gib
ram = \
ram + self.conv_GiB_to_MiB(mem)
except Exception as ex:
LOG.info("Failed to get memory info: %s", ex)
return ram
def conv_GiB_to_MiB(self, input_GiB):
"""Convert gib to mib."""
return int(input_GiB / 0.000976562500000003)
def power_off(self, instance, timeout=0, retry_interval=0):
"""Power off the specified instance.
:param instance: nova.objects.instance.Instance
:param timeout: time to wait for GuestOS to shutdown
:param retry_interval: How often to signal guest while
waiting for it to shutdown
"""
key = instance.uuid
timeout = timeout or None
if key in self.instances and key in self._composed_nodes:
LOG.info("Powering off composed node: %s", key)
self.instances[instance.uuid].power_state = power_state.SHUTDOWN
node_inst = self._composed_nodes[key]
node_inst.reset_node('graceful shutdown')
def power_on(self, context, instance, network_info,
block_device_info=None):
"""Power on the specified instance.
:param instance: nova.objects.instance.Instance
"""
key = instance.uuid
if key in self.instances and key in self._composed_nodes:
LOG.info("Powering on composed node: %s", key)
self.instances[instance.uuid].power_state = power_state.RUNNING
node_inst = self._composed_nodes[key]
node_inst.reset_node('force on')
def reboot(self, context, instance, network_info, reboot_type,
block_device_info=None, bad_volumes_callback=None):
"""Reboot the specified instance.
After this is called successfully, the instance's state
goes back to power_state.RUNNING. The virtualization
platform should ensure that the reboot action has completed
successfully even in cases in which the underlying domain/vm
is paused or halted/stopped.
:param instance: nova.objects.instance.Instance
:param network_info: instance network information
:param reboot_type: Either a HARD or SOFT reboot
:param block_device_info: Info pertaining to attached volumes
:param bad_volumes_callback: Function to handle any bad volumes
encountered
"""
key = instance.uuid
if key in self.instances and key in self._composed_nodes:
node_inst = self._composed_nodes[key]
if reboot_type == 'HARD':
LOG.info(
"Force restart composed node for a hard reboot: %s", key)
node_inst.reset_node('force restart')
else:
LOG.info("Graceful restart composed node for reboot: %s", key)
node_inst.reset_node('graceful restart')
def create_inventory(self, system):
"""Function to create provider tree inventory."""
mem_max = 1
proc_max = 1
if self.get_sys_memory_info(system) >= mem_max:
mem_max = self.get_sys_memory_info(system)
if self.get_sys_proc_info(system) >= proc_max:
proc_max = self.get_sys_proc_info(system)
return {
'VCPU': {
'total': proc_max,
'max_unit': proc_max,
'min_unit': 1,
'step_size': 1,
'allocation_ratio': 1,
'reserved': 0
},
'MEMORY_MB': {
'total': mem_max,
'max_unit': mem_max,
'min_unit': 1,
'step_size': 1,
'allocation_ratio': 1,
'reserved': 0
}
}
def create_child_inventory(self, system):
"""Create custom resources for all of the child RP's."""
res = fields.ResourceClass.normalize_name(system)
return {
res: {
'total': 1,
'reserved': 0,
'min_unit': 1,
'max_unit': 1,
'step_size': 1,
'allocation_ratio': 1,
}
}
def check_chassis_systems(self, chassis):
"""Check the chassis for linked systems."""
systems = chassis.json['Links']['ComputerSystems']
cha_sys = []
for s in systems:
cha_sys += s.values()
return cha_sys
def _create_flavors(self):
"""Auto-generate the flavors for the compute systems available."""
SYSTEM_COL = self.driver.PODM.get_system_collection()
for s in SYSTEM_COL.members_identities:
sys = SYSTEM_COL.get_member(s)
mem = self.conv_GiB_to_MiB(sys.memory_summary.size_gib) - 512
proc = sys.processors.summary.count
flav_id = str(mem) + 'MB-' + str(proc) + 'vcpus'
res = fields.ResourceClass.normalize_name(sys.identity)
spec = 'resources:' + res
values = {
'name': 'RSD-' + flav_id,
'flavorid': flav_id,
'memory_mb': mem,
'vcpus': proc,
'root_gb': 0,
'extra_specs': {
spec: '1'}
}
if sys.identity not in self.rsd_flavors:
try:
LOG.debug("New flavor for system: %s", sys.identity)
rsd_flav = flavor._flavor_create(
context.get_admin_context(), values)
self.rsd_flavors[flav_id] = {
'id': rsd_flav['id'],
'rsd_systems': [sys.identity]
}
except Exception as ex:
LOG.debug(
"A flavor already exists for this rsd system: %s", ex)
ex_flav = flavor.Flavor._flavor_get_by_flavor_id_from_db(
context.get_admin_context(), flav_id)
if flav_id not in self.rsd_flavors.keys():
self.rsd_flavors[flav_id] = {
'id': ex_flav['id'],
'rsd_systems': [sys.identity]
}
else:
sys_list = self.rsd_flavors[flav_id]['rsd_systems']
sys_list.append(sys.identity)
self.rsd_flavors[flav_id]['rsd_systems'] = sys_list
def check_flavors(self, collection, systems):
"""Check if flavors should be deleted based on system removal."""
sys_ids = []
LOG.debug("Checking existing flavors.")
for s in systems:
sys = collection.get_member(s)
sys_ids.append(sys.identity)
for k in list(self.rsd_flavors):
sys_list = self.rsd_flavors[k]['rsd_systems']
for s in sys_list:
if s not in sys_ids:
rsd_id = self.rsd_flavors[k]['id']
flavor._flavor_destroy(context.get_admin_context(), rsd_id)
LOG.debug("Deleting flavor for removed systems: %s", k)
del self.rsd_flavors[k]
return

18
test-requirements.txt Normal file
View File

@ -0,0 +1,18 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
coverage!=4.4,>=4.0 # Apache-2.0
fixtures>=3.0.0 # Apache-2.0/BSD
mock>=2.0.0 # BSD
PyMySQL>=0.7.6 # MIT License
oslotest>=3.2.0 # Apache-2.0
python-subunit>=0.0.18 # Apache-2.0/BSD
sphinx>=1.6.2 # BSD
stestr>=1.0.0 # Apache-2.0
flake8
pycodestyle
wsgi-intercept>=1.4.1 # MIT License
sushy>=1.7.0 # Apache-2.0
-e git+https://github.com/openstack/nova.git#egg=nova # Apache-2.0

53
tools/tox_install.sh Executable file
View File

@ -0,0 +1,53 @@
#!/usr/bin/env bash
# Library constraint file contains this library version pin that is in conflict
# with installing the library from source.
ZUUL_CLONER=/usr/zuul-env/bin/zuul-cloner
BRANCH_NAME=master
LIB_NAMES=(nova-rsd nova)
requirements_installed=$(echo "import openstack_requirements" | python 2>/dev/null ; echo $?)
set -e
CONSTRAINTS_FILE=$1
shift
install_cmd="pip install"
mydir=$(mktemp -dt "nova-rsd-tox_install-XXXXXXX")
trap "rm -rf $mydir" EXIT
localfile=$mydir/upper-constraints.txt
if [[ $CONSTRAINTS_FILE != http* ]]; then
CONSTRAINTS_FILE=file://$CONSTRAINTS_FILE
fi
curl $CONSTRAINTS_FILE -k -o $localfile
install_cmd="$install_cmd -c$localfile"
if [ $requirements_installed -eq 0 ]; then
echo "Requirements already installed; using existing package"
elif [ -x "$ZUUL_CLONER" ]; then
pushd $mydir
$ZUUL_CLONER --cache-dir \
/opt/git \
--branch $BRANCH_NAME \
git://git.openstack.org \
openstack/requirements
cd openstack/requirements
$install_cmd -e .
popd
else
if [ -z "$REQUIREMENTS_PIP_LOCATION" ]; then
REQUIREMENTS_PIP_LOCATION="git+https://git.openstack.org/openstack/requirements@$BRANCH_NAME#egg=requirements"
fi
$install_cmd -U -e ${REQUIREMENTS_PIP_LOCATION}
fi
# This is the main purpose of the script: Allow local installation of
# the current repo. It is listed in constraints file and thus any
# install will be constrained and we need to unconstrain it.
for LIB_NAME in "${LIB_NAMES[@]}"; do
edit-constraints $localfile $LIB_NAME
done
$install_cmd -U $*
exit $?

62
tox.ini Normal file
View File

@ -0,0 +1,62 @@
# tox (https://tox.readthedocs.io/) is a tool for running tests
# in multiple virtualenvs. This configuration file will run the
# test suite on all supported python versions. To use it, "pip install tox"
# and then run "tox" from this directory.
[tox]
minversion = 2.0
envlist = py27,py36,pycodestyle
skipsdist = True
[testenv]
basepython = python3
usedevelop = True
install_command = pip install {opts} {packages}
#{toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
setenv =
VIRTUAL_ENV={envdir}
PYTHONWARNINGS=default::DeprecationWarning
OS_STDOUT_CAPTURE=1
OS_STDERR_CAPTURE=1
OS_TEST_TIMEOUT=60
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands = stestr run {posargs}
[testenv:pycodestyle]
commands =
flake8 {posargs}
pycodestyle {posargs}
[testenv:venv]
commands = {posargs}
[testenv:cover]
setenv =
VIRTUAL_ENV={envdir}
PYTHON=coverage run --source rsd_virt_for_nova --parallel-mode
commands =
stestr run {posargs}
coverage combine
coverage html -d cover
coverage xml -o cover/coverage.xml
[testenv:docs]
commands = python setup.py build_sphinx
[testenv:py27]
basepython = python2.7
commands =
stestr run {posargs}
[testenv:py36]
commands =
stestr run {posargs}
[flake8]
ignore = E121,E122,E123,E124,E125,E126,E127,E128,E129,E131,E251,H405,N342
exclude = .venv,.git,.tox,dist,*lib/python*,*egg,build,tools/xenserver*,releasenotes,*openstack/common*,*src,*/src
[pycodestyle]
ignore = E121,E122,E123,E124,E125,E126,E127,E128,E129,E131,E251,H405,N342
exclude = .venv,.git,.tox,dist,*lib/python*,*egg,build,tools/xenserver*,releasenotes,*openstack/common*,*src,*/src