Add memoization to expensive emulator calls
Drivers of Redfish emulator now cache the outcome of a select calls choosing either the two policies: * Cache the information for the lifetime of the emulator process if it's unlikely for such data to get out of sync * Cache the more dynamic information just for the duration of a single Redfish REST API call This change improves emulator responsiveness. Change-Id: Ief46a0a7b82900ccf6d4c9ebb417f711a9b00513
This commit is contained in:
parent
b6ccaf6da8
commit
b13ea4e9c7
@ -21,6 +21,25 @@ import six
|
||||
class AbstractDriver(object):
|
||||
"""Base class for all virtualization drivers"""
|
||||
|
||||
@classmethod
|
||||
def initialize(cls, **kwargs):
|
||||
"""Initialize class attributes
|
||||
|
||||
Since drivers may need to cache thing short-term. The emulator
|
||||
instantiates the driver every time it serves a client query.
|
||||
|
||||
Driver objects can cache whenever it makes sense for the duration
|
||||
of a single session. It is guaranteed that the driver object will
|
||||
never be reused for any other session.
|
||||
|
||||
The `initialize` method is provided to set up the driver in a way
|
||||
that would affect all the subsequent sessions.
|
||||
|
||||
:params **kwargs: driver-specific parameters
|
||||
:returns: initialized driver class
|
||||
"""
|
||||
return cls
|
||||
|
||||
@abc.abstractproperty
|
||||
def driver(self):
|
||||
"""Return human-friendly driver information
|
||||
|
@ -13,12 +13,13 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from collections import namedtuple
|
||||
import logging
|
||||
import uuid
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from collections import namedtuple
|
||||
from sushy_tools.emulator.drivers.base import AbstractDriver
|
||||
from sushy_tools.emulator.drivers import memoize
|
||||
from sushy_tools import error
|
||||
|
||||
try:
|
||||
@ -102,15 +103,17 @@ class LibvirtDriver(AbstractDriver):
|
||||
"NicBoot1": "NetworkBoot",
|
||||
"ProcTurboMode": "Enabled"}
|
||||
|
||||
def __init__(self, config, uri=None):
|
||||
self._config = config
|
||||
self._uri = uri or self.LIBVIRT_URI
|
||||
self.BOOT_LOADER_MAP = self._config.get(
|
||||
'SUSHY_EMULATOR_BOOT_LOADER_MAP', self.BOOT_LOADER_MAP)
|
||||
self.KNOWN_BOOT_LOADERS = set(y
|
||||
for x in self.BOOT_LOADER_MAP.values()
|
||||
for y in x.values())
|
||||
@classmethod
|
||||
def initialize(cls, config, uri=None):
|
||||
cls._config = config
|
||||
cls._uri = uri or cls.LIBVIRT_URI
|
||||
cls.BOOT_LOADER_MAP = cls._config.get(
|
||||
'SUSHY_EMULATOR_BOOT_LOADER_MAP', cls.BOOT_LOADER_MAP)
|
||||
cls.KNOWN_BOOT_LOADERS = set(y for x in cls.BOOT_LOADER_MAP.values()
|
||||
for y in x.values())
|
||||
return cls
|
||||
|
||||
@memoize.memoize()
|
||||
def _get_domain(self, identity, readonly=False):
|
||||
with libvirt_open(self._uri, readonly=readonly) as conn:
|
||||
try:
|
||||
@ -517,6 +520,7 @@ class LibvirtDriver(AbstractDriver):
|
||||
update_existing_attributes)
|
||||
|
||||
if result.attributes_written:
|
||||
|
||||
try:
|
||||
with libvirt_open(self._uri) as conn:
|
||||
conn.defineXML(ET.tostring(result.tree).decode('utf-8'))
|
||||
|
56
sushy_tools/emulator/drivers/memoize.py
Normal file
56
sushy_tools/emulator/drivers/memoize.py
Normal file
@ -0,0 +1,56 @@
|
||||
# Copyright 2018 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.
|
||||
from functools import wraps
|
||||
|
||||
|
||||
def memoize(permanent_cache=None):
|
||||
"""Cache the return value of the decorated method.
|
||||
|
||||
:param permanent_cache: a `dict` like object to use as a cache.
|
||||
If not given, the `._cache` attribute would be added to
|
||||
the object of the decorated method pointing to a newly
|
||||
created `dict`.
|
||||
:return: decorated function
|
||||
"""
|
||||
|
||||
def decorator(method):
|
||||
|
||||
@wraps(method)
|
||||
def wrapped(self, *args, **kwargs):
|
||||
if permanent_cache is None:
|
||||
try:
|
||||
cache = self._cache
|
||||
|
||||
except AttributeError:
|
||||
cache = self._cache = {}
|
||||
|
||||
else:
|
||||
cache = permanent_cache
|
||||
|
||||
method_cache = cache.setdefault(method, {})
|
||||
|
||||
key = frozenset(args), frozenset(kwargs)
|
||||
|
||||
try:
|
||||
return method_cache[key]
|
||||
|
||||
except KeyError:
|
||||
rv = method(self, *args, **kwargs)
|
||||
method_cache[key] = rv
|
||||
return rv
|
||||
|
||||
return wrapped
|
||||
|
||||
return decorator
|
@ -17,6 +17,7 @@ import logging
|
||||
import math
|
||||
|
||||
from sushy_tools.emulator.drivers.base import AbstractDriver
|
||||
from sushy_tools.emulator.drivers import memoize
|
||||
from sushy_tools import error
|
||||
|
||||
try:
|
||||
@ -51,11 +52,16 @@ class OpenStackDriver(AbstractDriver):
|
||||
|
||||
BOOT_MODE_MAP_REV = {v: k for k, v in BOOT_MODE_MAP.items()}
|
||||
|
||||
def __init__(self, config, os_cloud, readonly=False):
|
||||
self._cc = openstack.connect(cloud=os_cloud)
|
||||
self._os_cloud = os_cloud
|
||||
self._config = config
|
||||
PERMANENT_CACHE = {}
|
||||
|
||||
@classmethod
|
||||
def initialize(cls, config, os_cloud, readonly=False):
|
||||
cls._cc = openstack.connect(cloud=os_cloud)
|
||||
cls._os_cloud = os_cloud
|
||||
cls._config = config
|
||||
return cls
|
||||
|
||||
@memoize.memoize()
|
||||
def _get_instance(self, identity):
|
||||
instance = self._cc.get_server(identity)
|
||||
if instance:
|
||||
@ -72,10 +78,20 @@ class OpenStackDriver(AbstractDriver):
|
||||
|
||||
raise error.FishyError(msg)
|
||||
|
||||
@memoize.memoize(permanent_cache=PERMANENT_CACHE)
|
||||
def _get_flavor(self, identity):
|
||||
instance = self._get_instance(identity)
|
||||
flavor = self._cc.get_flavor(instance.flavor.id)
|
||||
return flavor
|
||||
return self._cc.get_flavor(instance.flavor.id)
|
||||
|
||||
@memoize.memoize(permanent_cache=PERMANENT_CACHE)
|
||||
def _get_image_info(self, identity):
|
||||
return self._cc.image.find_image(identity)
|
||||
|
||||
def _get_server_metadata(self, identity):
|
||||
return self._cc.compute.get_server_metadata(identity).to_dict()
|
||||
|
||||
def _set_server_metadata(self, identity, metadata):
|
||||
self._cc.compute.set_server_metadata(identity, metadata)
|
||||
|
||||
@property
|
||||
def driver(self):
|
||||
@ -190,7 +206,7 @@ class OpenStackDriver(AbstractDriver):
|
||||
except error.FishyError:
|
||||
return
|
||||
|
||||
metadata = self._cc.compute.get_server_metadata(instance.id).to_dict()
|
||||
metadata = self._get_server_metadata(instance.id)
|
||||
|
||||
# NOTE(etingof): the following probably only works with
|
||||
# libvirt-backed compute nodes
|
||||
@ -224,7 +240,6 @@ class OpenStackDriver(AbstractDriver):
|
||||
|
||||
# NOTE(etingof): the following probably only works with
|
||||
# libvirt-backed compute nodes
|
||||
|
||||
self._cc.compute.set_server_metadata(
|
||||
instance.id, **{'libvirt:pxe-first': '1'
|
||||
if target == 'network' else ''}
|
||||
@ -238,7 +253,7 @@ class OpenStackDriver(AbstractDriver):
|
||||
"""
|
||||
instance = self._get_instance(identity)
|
||||
|
||||
image = self._cc.image.find_image(instance.image['id'])
|
||||
image = self._get_image_info(instance.image['id'])
|
||||
|
||||
hw_firmware_type = getattr(image, 'hw_firmware_type', None)
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
# under the License.
|
||||
|
||||
import argparse
|
||||
import contextlib
|
||||
import functools
|
||||
import json
|
||||
import os
|
||||
@ -32,22 +33,22 @@ app = flask.Flask(__name__)
|
||||
# Turn off strict_slashes on all routes
|
||||
app.url_map.strict_slashes = False
|
||||
|
||||
driver = None
|
||||
DRIVER = None
|
||||
|
||||
|
||||
def init_virt_driver(decorated_func):
|
||||
@functools.wraps(decorated_func)
|
||||
def decorator(*args, **kwargs):
|
||||
global driver
|
||||
global DRIVER
|
||||
|
||||
if driver is None:
|
||||
if DRIVER is None:
|
||||
|
||||
if 'OS_CLOUD' in os.environ:
|
||||
if not novadriver.is_loaded:
|
||||
app.logger.error('Nova driver not loaded')
|
||||
sys.exit(1)
|
||||
|
||||
driver = novadriver.OpenStackDriver(
|
||||
DRIVER = novadriver.OpenStackDriver.initialize(
|
||||
app.config, os.environ['OS_CLOUD'])
|
||||
|
||||
else:
|
||||
@ -55,7 +56,7 @@ def init_virt_driver(decorated_func):
|
||||
app.logger.error('libvirt driver not loaded')
|
||||
sys.exit(1)
|
||||
|
||||
driver = libvirtdriver.LibvirtDriver(
|
||||
DRIVER = libvirtdriver.LibvirtDriver.initialize(
|
||||
app.config,
|
||||
os.environ.get(
|
||||
'SUSHY_EMULATOR_LIBVIRT_URI',
|
||||
@ -63,7 +64,7 @@ def init_virt_driver(decorated_func):
|
||||
os.environ.get('SUSHY_EMULATOR_LIBVIRT_URL'))
|
||||
)
|
||||
|
||||
app.logger.debug('Running with %s', driver.driver)
|
||||
app.logger.debug('Running with %s', DRIVER.driver)
|
||||
|
||||
return decorated_func(*args, **kwargs)
|
||||
|
||||
@ -99,6 +100,11 @@ def ensure_instance_access(decorated_func):
|
||||
return decorator
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def virt_driver():
|
||||
yield DRIVER()
|
||||
|
||||
|
||||
def returns_json(decorated_func):
|
||||
@functools.wraps(decorated_func)
|
||||
def decorator(*args, **kwargs):
|
||||
@ -135,8 +141,9 @@ def root_resource():
|
||||
@init_virt_driver
|
||||
@returns_json
|
||||
def system_collection_resource():
|
||||
systems = [system for system in driver.systems
|
||||
if not instance_denied(identity=system)]
|
||||
with virt_driver() as driver:
|
||||
systems = [system for system in driver.systems
|
||||
if not instance_denied(identity=system)]
|
||||
|
||||
app.logger.debug('Serving systems list')
|
||||
|
||||
@ -153,46 +160,47 @@ def system_resource(identity):
|
||||
|
||||
app.logger.debug('Serving resources for system "%s"', identity)
|
||||
|
||||
return flask.render_template(
|
||||
'system.json',
|
||||
identity=identity,
|
||||
name=driver.name(identity),
|
||||
uuid=driver.uuid(identity),
|
||||
power_state=driver.get_power_state(identity),
|
||||
total_memory_gb=driver.get_total_memory(identity),
|
||||
total_cpus=driver.get_total_cpus(identity),
|
||||
boot_source_target=driver.get_boot_device(identity),
|
||||
boot_source_mode=driver.get_boot_mode(identity)
|
||||
)
|
||||
with virt_driver() as driver:
|
||||
return flask.render_template(
|
||||
'system.json', identity=identity,
|
||||
uuid=driver.uuid(identity),
|
||||
power_state=driver.get_power_state(identity),
|
||||
total_memory_gb=driver.get_total_memory(identity),
|
||||
total_cpus=driver.get_total_cpus(identity),
|
||||
boot_source_target=driver.get_boot_device(identity),
|
||||
boot_source_mode=driver.get_boot_mode(identity)
|
||||
)
|
||||
|
||||
elif flask.request.method == 'PATCH':
|
||||
boot = flask.request.json.get('Boot')
|
||||
if not boot:
|
||||
return 'PATCH only works for the Boot element', 400
|
||||
|
||||
target = boot.get('BootSourceOverrideTarget')
|
||||
with virt_driver() as driver:
|
||||
|
||||
if target:
|
||||
# NOTE(lucasagomes): In libvirt we always set the boot
|
||||
# device frequency to "continuous" so, we are ignoring the
|
||||
# BootSourceOverrideEnabled element here
|
||||
target = boot.get('BootSourceOverrideTarget')
|
||||
|
||||
driver.set_boot_device(identity, target)
|
||||
if target:
|
||||
# NOTE(lucasagomes): In libvirt we always set the boot
|
||||
# device frequency to "continuous" so, we are ignoring the
|
||||
# BootSourceOverrideEnabled element here
|
||||
|
||||
app.logger.info('Set boot device to "%s" for system "%s"',
|
||||
target, identity)
|
||||
driver.set_boot_device(identity, target)
|
||||
|
||||
mode = boot.get('BootSourceOverrideMode')
|
||||
app.logger.info('Set boot device to "%s" for system "%s"',
|
||||
target, identity)
|
||||
|
||||
if mode:
|
||||
driver.set_boot_mode(identity, mode)
|
||||
mode = boot.get('BootSourceOverrideMode')
|
||||
|
||||
app.logger.info('Set boot mode to "%s" for system "%s"',
|
||||
mode, identity)
|
||||
if mode:
|
||||
driver.set_boot_mode(identity, mode)
|
||||
|
||||
if not target and not mode:
|
||||
return ('Missing the BootSourceOverrideTarget and/or '
|
||||
'BootSourceOverrideMode element', 400)
|
||||
app.logger.info('Set boot mode to "%s" for system "%s"',
|
||||
mode, identity)
|
||||
|
||||
if not target and not mode:
|
||||
return ('Missing the BootSourceOverrideTarget and/or '
|
||||
'BootSourceOverrideMode element', 400)
|
||||
|
||||
return '', 204
|
||||
|
||||
@ -203,7 +211,9 @@ def system_resource(identity):
|
||||
@ensure_instance_access
|
||||
@returns_json
|
||||
def ethernet_interfaces_collection(identity):
|
||||
nics = driver.get_nics(identity)
|
||||
with virt_driver() as driver:
|
||||
nics = driver.get_nics(identity)
|
||||
|
||||
return flask.render_template(
|
||||
'ethernet_interfaces_collection.json', identity=identity,
|
||||
nics=nics)
|
||||
@ -215,7 +225,9 @@ def ethernet_interfaces_collection(identity):
|
||||
@ensure_instance_access
|
||||
@returns_json
|
||||
def ethernet_interface(identity, nic_id):
|
||||
nics = driver.get_nics(identity)
|
||||
with virt_driver() as driver:
|
||||
nics = driver.get_nics(identity)
|
||||
|
||||
for nic in nics:
|
||||
if nic['id'] == nic_id:
|
||||
return flask.render_template(
|
||||
@ -232,7 +244,8 @@ def ethernet_interface(identity, nic_id):
|
||||
def system_reset_action(identity):
|
||||
reset_type = flask.request.json.get('ResetType')
|
||||
|
||||
driver.set_power_state(identity, reset_type)
|
||||
with virt_driver() as driver:
|
||||
driver.set_power_state(identity, reset_type)
|
||||
|
||||
app.logger.info('System "%s" power state set to "%s"',
|
||||
identity, reset_type)
|
||||
@ -245,7 +258,8 @@ def system_reset_action(identity):
|
||||
@ensure_instance_access
|
||||
@returns_json
|
||||
def bios(identity):
|
||||
bios = driver.get_bios(identity)
|
||||
with virt_driver() as driver:
|
||||
bios = driver.get_bios(identity)
|
||||
|
||||
app.logger.debug('Serving BIOS for system "%s"', identity)
|
||||
|
||||
@ -263,7 +277,8 @@ def bios(identity):
|
||||
def bios_settings(identity):
|
||||
|
||||
if flask.request.method == 'GET':
|
||||
bios = driver.get_bios(identity)
|
||||
with virt_driver() as driver:
|
||||
bios = driver.get_bios(identity)
|
||||
|
||||
app.logger.debug('Serving BIOS Settings for system "%s"', identity)
|
||||
|
||||
@ -275,7 +290,9 @@ def bios_settings(identity):
|
||||
elif flask.request.method == 'PATCH':
|
||||
attributes = flask.request.json.get('Attributes')
|
||||
|
||||
driver.set_bios(identity, attributes)
|
||||
with virt_driver() as driver:
|
||||
driver.set_bios(identity, attributes)
|
||||
|
||||
app.logger.info('System "%s" BIOS attributes "%s" updated',
|
||||
identity, attributes)
|
||||
return '', 204
|
||||
@ -288,8 +305,11 @@ def bios_settings(identity):
|
||||
@returns_json
|
||||
def system_reset_bios(identity):
|
||||
|
||||
driver.reset_bios(identity)
|
||||
with virt_driver() as driver:
|
||||
driver.reset_bios(identity)
|
||||
|
||||
app.logger.info('BIOS for system "%s" reset', identity)
|
||||
|
||||
return '', 204
|
||||
|
||||
|
||||
@ -336,7 +356,7 @@ def parse_args():
|
||||
|
||||
|
||||
def main():
|
||||
global driver
|
||||
global DRIVER
|
||||
|
||||
args = parse_args()
|
||||
|
||||
@ -350,20 +370,21 @@ def main():
|
||||
app.logger.error('Nova driver not loaded')
|
||||
return 1
|
||||
|
||||
driver = novadriver.OpenStackDriver(app.config, os_cloud)
|
||||
DRIVER = novadriver.OpenStackDriver.initialize(
|
||||
app.config, args.os_cloud)
|
||||
|
||||
else:
|
||||
if not libvirtdriver.is_loaded:
|
||||
app.logger.error('libvirt driver not loaded')
|
||||
return 1
|
||||
|
||||
driver = libvirtdriver.LibvirtDriver(
|
||||
DRIVER = libvirtdriver.LibvirtDriver.initialize(
|
||||
app.config,
|
||||
args.libvirt_uri or
|
||||
app.config.get('SUSHY_EMULATOR_LIBVIRT_URI', '')
|
||||
)
|
||||
|
||||
app.logger.debug('Running with %s', driver.driver)
|
||||
app.logger.debug('Running with %s', DRIVER.driver)
|
||||
|
||||
ssl_context = None
|
||||
|
||||
|
@ -25,7 +25,8 @@ class LibvirtDriverTestCase(base.BaseTestCase):
|
||||
uuid = 'c7a5fdbd-cdaf-9455-926a-d65c16db1809'
|
||||
|
||||
def setUp(self):
|
||||
self.test_driver = LibvirtDriver({})
|
||||
test_driver_class = LibvirtDriver.initialize({})
|
||||
self.test_driver = test_driver_class()
|
||||
super(LibvirtDriverTestCase, self).setUp()
|
||||
|
||||
@mock.patch('libvirt.open', autospec=True)
|
||||
|
@ -16,23 +16,24 @@ from six.moves import mock
|
||||
from sushy_tools.emulator import main
|
||||
|
||||
|
||||
@mock.patch('sushy_tools.emulator.main.driver', autospec=True)
|
||||
@mock.patch.object(main, 'DRIVER') # This enables libvirt driver
|
||||
class EmulatorTestCase(base.BaseTestCase):
|
||||
|
||||
name = 'QEmu-fedora-i686'
|
||||
uuid = 'c7a5fdbd-cdaf-9455-926a-d65c16db1809'
|
||||
|
||||
def setUp(self):
|
||||
main.driver = None
|
||||
main.DRIVER = None
|
||||
self.app = main.app.test_client()
|
||||
|
||||
super(EmulatorTestCase, self).setUp()
|
||||
|
||||
def test_error(self, driver_mock):
|
||||
driver_mock.get_power_state.side_effect = Exception('Fish is dead')
|
||||
driver_mock.return_value.get_power_state.side_effect = (
|
||||
Exception('Fish is dead'))
|
||||
response = self.app.get('/redfish/v1/Systems/' + self.uuid)
|
||||
|
||||
self.assertEqual('500 INTERNAL SERVER ERROR', response.status)
|
||||
self.assertEqual(500, response.status_code)
|
||||
|
||||
def test_root_resource(self, driver_mock):
|
||||
response = self.app.get('/redfish/v1/')
|
||||
@ -40,7 +41,7 @@ class EmulatorTestCase(base.BaseTestCase):
|
||||
self.assertEqual('RedvirtService', response.json['Id'])
|
||||
|
||||
def test_collection_resource(self, driver_mock):
|
||||
type(driver_mock).systems = mock.PropertyMock(
|
||||
type(driver_mock.return_value).systems = mock.PropertyMock(
|
||||
return_value=['host0', 'host1'])
|
||||
response = self.app.get('/redfish/v1/Systems')
|
||||
self.assertEqual(200, response.status_code)
|
||||
@ -50,12 +51,12 @@ class EmulatorTestCase(base.BaseTestCase):
|
||||
response.json['Members'][1])
|
||||
|
||||
def test_system_resource_get(self, driver_mock):
|
||||
driver_mock.uuid.return_value = 'zzzz-yyyy-xxxx'
|
||||
driver_mock.get_power_state.return_value = 'On'
|
||||
driver_mock.get_total_memory.return_value = 1
|
||||
driver_mock.get_total_cpus.return_value = 2
|
||||
driver_mock.get_boot_device.return_value = 'Cd'
|
||||
driver_mock.get_boot_mode.return_value = 'Legacy'
|
||||
driver_mock.return_value.uuid.return_value = 'zzzz-yyyy-xxxx'
|
||||
driver_mock.return_value.get_power_state.return_value = 'On'
|
||||
driver_mock.return_value.get_total_memory.return_value = 1
|
||||
driver_mock.return_value.get_total_cpus.return_value = 2
|
||||
driver_mock.return_value.get_boot_device.return_value = 'Cd'
|
||||
driver_mock.return_value.get_boot_mode.return_value = 'Legacy'
|
||||
|
||||
response = self.app.get('/redfish/v1/Systems/xxxx-yyyy-zzzz')
|
||||
|
||||
@ -76,7 +77,7 @@ class EmulatorTestCase(base.BaseTestCase):
|
||||
response = self.app.patch('/redfish/v1/Systems/xxxx-yyyy-zzzz',
|
||||
json=data)
|
||||
self.assertEqual(204, response.status_code)
|
||||
set_boot_device = driver_mock.set_boot_device
|
||||
set_boot_device = driver_mock.return_value.set_boot_device
|
||||
set_boot_device.assert_called_once_with('xxxx-yyyy-zzzz', 'Cd')
|
||||
|
||||
def test_system_reset_action_on(self, driver_mock):
|
||||
@ -85,7 +86,7 @@ class EmulatorTestCase(base.BaseTestCase):
|
||||
'/redfish/v1/Systems/xxxx-yyyy-zzzz/Actions/ComputerSystem.Reset',
|
||||
json=data)
|
||||
self.assertEqual(204, response.status_code)
|
||||
set_power_state = driver_mock.set_power_state
|
||||
set_power_state = driver_mock.return_value.set_power_state
|
||||
set_power_state.assert_called_once_with('xxxx-yyyy-zzzz', 'On')
|
||||
|
||||
def test_system_reset_action_forceon(self, driver_mock):
|
||||
@ -94,7 +95,7 @@ class EmulatorTestCase(base.BaseTestCase):
|
||||
'/redfish/v1/Systems/xxxx-yyyy-zzzz/Actions/ComputerSystem.Reset',
|
||||
json=data)
|
||||
self.assertEqual(204, response.status_code)
|
||||
set_power_state = driver_mock.set_power_state
|
||||
set_power_state = driver_mock.return_value.set_power_state
|
||||
set_power_state.assert_called_once_with('xxxx-yyyy-zzzz', 'ForceOn')
|
||||
|
||||
def test_system_reset_action_forceoff(self, driver_mock):
|
||||
@ -103,7 +104,7 @@ class EmulatorTestCase(base.BaseTestCase):
|
||||
'/redfish/v1/Systems/xxxx-yyyy-zzzz/Actions/ComputerSystem.Reset',
|
||||
json=data)
|
||||
self.assertEqual(204, response.status_code)
|
||||
set_power_state = driver_mock.set_power_state
|
||||
set_power_state = driver_mock.return_value.set_power_state
|
||||
set_power_state.assert_called_once_with('xxxx-yyyy-zzzz', 'ForceOff')
|
||||
|
||||
def test_system_reset_action_shutdown(self, driver_mock):
|
||||
@ -112,7 +113,7 @@ class EmulatorTestCase(base.BaseTestCase):
|
||||
'/redfish/v1/Systems/xxxx-yyyy-zzzz/Actions/ComputerSystem.Reset',
|
||||
json=data)
|
||||
self.assertEqual(204, response.status_code)
|
||||
set_power_state = driver_mock.set_power_state
|
||||
set_power_state = driver_mock.return_value.set_power_state
|
||||
set_power_state.assert_called_once_with(
|
||||
'xxxx-yyyy-zzzz', 'GracefulShutdown')
|
||||
|
||||
@ -122,7 +123,7 @@ class EmulatorTestCase(base.BaseTestCase):
|
||||
'/redfish/v1/Systems/xxxx-yyyy-zzzz/Actions/ComputerSystem.Reset',
|
||||
json=data)
|
||||
self.assertEqual(204, response.status_code)
|
||||
set_power_state = driver_mock.set_power_state
|
||||
set_power_state = driver_mock.return_value.set_power_state
|
||||
set_power_state.assert_called_once_with(
|
||||
'xxxx-yyyy-zzzz', 'GracefulRestart')
|
||||
|
||||
@ -132,7 +133,7 @@ class EmulatorTestCase(base.BaseTestCase):
|
||||
'/redfish/v1/Systems/xxxx-yyyy-zzzz/Actions/ComputerSystem.Reset',
|
||||
json=data)
|
||||
self.assertEqual(204, response.status_code)
|
||||
set_power_state = driver_mock.set_power_state
|
||||
set_power_state = driver_mock.return_value.set_power_state
|
||||
set_power_state.assert_called_once_with(
|
||||
'xxxx-yyyy-zzzz', 'ForceRestart')
|
||||
|
||||
@ -142,7 +143,7 @@ class EmulatorTestCase(base.BaseTestCase):
|
||||
'/redfish/v1/Systems/xxxx-yyyy-zzzz/Actions/ComputerSystem.Reset',
|
||||
json=data)
|
||||
self.assertEqual(204, response.status_code)
|
||||
set_power_state = driver_mock.set_power_state
|
||||
set_power_state = driver_mock.return_value.set_power_state
|
||||
set_power_state.assert_called_once_with('xxxx-yyyy-zzzz', 'Nmi')
|
||||
|
||||
@mock.patch.dict(main.app.config, {}, clear=True)
|
||||
@ -170,19 +171,20 @@ class EmulatorTestCase(base.BaseTestCase):
|
||||
self.assertTrue(main.instance_denied(identity='b'))
|
||||
|
||||
def test_get_bios(self, driver_mock):
|
||||
driver_mock.get_bios.return_value = {"attribute 1": "value 1",
|
||||
"attribute 2": "value 2"}
|
||||
driver_mock.return_value.get_bios.return_value = {
|
||||
"attribute 1": "value 1",
|
||||
"attribute 2": "value 2"}
|
||||
response = self.app.get('/redfish/v1/Systems/' + self.uuid + '/BIOS')
|
||||
|
||||
self.assertEqual('200 OK', response.status)
|
||||
self.assertEqual(200, response.status_code)
|
||||
self.assertEqual('BIOS', response.json['Id'])
|
||||
self.assertEqual({"attribute 1": "value 1",
|
||||
"attribute 2": "value 2"},
|
||||
response.json['Attributes'])
|
||||
|
||||
def test_get_bios_existing(self, driver_mock):
|
||||
driver_mock.get_bios.return_value = {"attribute 1": "value 1",
|
||||
"attribute 2": "value 2"}
|
||||
driver_mock.return_value.get_bios.return_value = {
|
||||
"attribute 1": "value 1", "attribute 2": "value 2"}
|
||||
response = self.app.get(
|
||||
'/redfish/v1/Systems/' + self.uuid + '/BIOS/Settings')
|
||||
|
||||
@ -194,39 +196,33 @@ class EmulatorTestCase(base.BaseTestCase):
|
||||
|
||||
def test_bios_settings_patch(self, driver_mock):
|
||||
data = {'Attributes': {'key': 'value'}}
|
||||
self.app.driver = driver_mock
|
||||
response = self.app.patch(
|
||||
'/redfish/v1/Systems/xxxx-yyyy-zzzz/BIOS/Settings',
|
||||
json=data)
|
||||
|
||||
self.assertEqual(204, response.status_code)
|
||||
driver_mock.set_bios.assert_called_once_with('xxxx-yyyy-zzzz',
|
||||
{'key': 'value'})
|
||||
driver_mock.return_value.set_bios.assert_called_once_with(
|
||||
'xxxx-yyyy-zzzz', {'key': 'value'})
|
||||
|
||||
def test_set_bios(self, driver_mock):
|
||||
data = {'Attributes': {'key': 'value'}}
|
||||
self.app.driver = driver_mock
|
||||
response = self.app.patch(
|
||||
'/redfish/v1/Systems/xxxx-yyyy-zzzz/BIOS/Settings',
|
||||
json=data)
|
||||
|
||||
self.assertEqual(204, response.status_code)
|
||||
driver_mock.set_bios.assert_called_once_with(
|
||||
driver_mock.return_value.set_bios.assert_called_once_with(
|
||||
'xxxx-yyyy-zzzz', data['Attributes'])
|
||||
|
||||
def test_reset_bios(self, driver_mock):
|
||||
self.app.driver = driver_mock
|
||||
response = self.app.post('/redfish/v1/Systems/' + self.uuid +
|
||||
'/BIOS/Actions/Bios.ResetBios')
|
||||
|
||||
self.assertEqual(204, response.status_code)
|
||||
driver_mock.reset_bios.assert_called_once_with(self.uuid)
|
||||
driver_mock.return_value.reset_bios.assert_called_once_with(self.uuid)
|
||||
|
||||
def test_ethernet_interfaces_collection(self, driver_mock):
|
||||
driver_mock.get_nics.return_value = [{'id': 'nic1',
|
||||
'mac': '52:54:00:4e:5d:37'},
|
||||
{'id': 'nic2',
|
||||
'mac': '00:11:22:33:44:55'}]
|
||||
driver_mock.return_value.get_nics.return_value = [
|
||||
{'id': 'nic1', 'mac': '52:54:00:4e:5d:37'},
|
||||
{'id': 'nic2', 'mac': '00:11:22:33:44:55'}]
|
||||
response = self.app.get('redfish/v1/Systems/' + self.uuid +
|
||||
'/EthernetInterfaces')
|
||||
|
||||
@ -241,7 +237,7 @@ class EmulatorTestCase(base.BaseTestCase):
|
||||
[m['@odata.id'] for m in response.json['Members']])
|
||||
|
||||
def test_ethernet_interfaces_collection_empty(self, driver_mock):
|
||||
driver_mock.get_nics.return_value = []
|
||||
driver_mock.return_value.get_nics.return_value = []
|
||||
response = self.app.get('redfish/v1/Systems/' + self.uuid +
|
||||
'/EthernetInterfaces')
|
||||
|
||||
@ -252,10 +248,9 @@ class EmulatorTestCase(base.BaseTestCase):
|
||||
self.assertEqual([], response.json['Members'])
|
||||
|
||||
def test_ethernet_interface(self, driver_mock):
|
||||
driver_mock.get_nics.return_value = [{'id': 'nic1',
|
||||
'mac': '52:54:00:4e:5d:37'},
|
||||
{'id': 'nic2',
|
||||
'mac': '00:11:22:33:44:55'}]
|
||||
driver_mock.return_value.get_nics.return_value = [
|
||||
{'id': 'nic1', 'mac': '52:54:00:4e:5d:37'},
|
||||
{'id': 'nic2', 'mac': '00:11:22:33:44:55'}]
|
||||
response = self.app.get('/redfish/v1/Systems/' + self.uuid +
|
||||
'/EthernetInterfaces/nic2')
|
||||
|
||||
@ -271,10 +266,9 @@ class EmulatorTestCase(base.BaseTestCase):
|
||||
response.json['@odata.id'])
|
||||
|
||||
def test_ethernet_interface_not_found(self, driver_mock):
|
||||
driver_mock.get_nics.return_value = [{'id': 'nic1',
|
||||
'mac': '52:54:00:4e:5d:37'},
|
||||
{'id': 'nic2',
|
||||
'mac': '00:11:22:33:44:55'}]
|
||||
driver_mock.return_value.get_nics.return_value = [
|
||||
{'id': 'nic1', 'mac': '52:54:00:4e:5d:37'},
|
||||
{'id': 'nic2', 'mac': '00:11:22:33:44:55'}]
|
||||
response = self.app.get('/redfish/v1/Systems/' + self.uuid +
|
||||
'/EthernetInterfaces/nic3')
|
||||
|
||||
|
81
sushy_tools/tests/unit/emulator/test_memoize.py
Normal file
81
sushy_tools/tests/unit/emulator/test_memoize.py
Normal file
@ -0,0 +1,81 @@
|
||||
# Copyright 2018 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.
|
||||
|
||||
from oslotest import base
|
||||
|
||||
from sushy_tools.emulator.drivers import memoize
|
||||
|
||||
|
||||
class MemoizeTestCase(base.BaseTestCase):
|
||||
|
||||
def test_local_cache(self):
|
||||
|
||||
class Driver(object):
|
||||
call_count = 0
|
||||
|
||||
@memoize.memoize()
|
||||
def fun(self, *args, **kwargs):
|
||||
self.call_count += 1
|
||||
return args, kwargs
|
||||
|
||||
driver = Driver()
|
||||
|
||||
result = driver.fun(1, x=2)
|
||||
self.assertEqual(((1,), {'x': 2}), result)
|
||||
|
||||
result = driver.fun(1, x=2)
|
||||
self.assertEqual(((1,), {'x': 2}), result)
|
||||
|
||||
# Due to memoization, expect just one call
|
||||
self.assertEqual(1, driver.call_count)
|
||||
|
||||
driver = Driver()
|
||||
|
||||
result = driver.fun(1, x=2)
|
||||
self.assertEqual(((1,), {'x': 2}), result)
|
||||
|
||||
# Due to volatile cache, expect one more call
|
||||
self.assertEqual(1, driver.call_count)
|
||||
|
||||
def test_external_cache(self):
|
||||
|
||||
permanent_cache = {}
|
||||
|
||||
class Driver(object):
|
||||
call_count = 0
|
||||
|
||||
@memoize.memoize(permanent_cache=permanent_cache)
|
||||
def fun(self, *args, **kwargs):
|
||||
self.call_count += 1
|
||||
return args, kwargs
|
||||
|
||||
driver = Driver()
|
||||
|
||||
result = driver.fun(1, x=2)
|
||||
self.assertEqual(((1,), {'x': 2}), result)
|
||||
|
||||
result = driver.fun(1, x=2)
|
||||
self.assertEqual(((1,), {'x': 2}), result)
|
||||
|
||||
# Due to memoization, expect just one call
|
||||
self.assertEqual(1, driver.call_count)
|
||||
|
||||
driver = Driver()
|
||||
|
||||
result = driver.fun(1, x=2)
|
||||
self.assertEqual(((1,), {'x': 2}), result)
|
||||
|
||||
# Due to permanent cache, expect no more calls
|
||||
self.assertEqual(0, driver.call_count)
|
@ -21,6 +21,7 @@ from sushy_tools.emulator.drivers.novadriver import OpenStackDriver
|
||||
from sushy_tools import error
|
||||
|
||||
|
||||
@mock.patch.dict(OpenStackDriver.PERMANENT_CACHE)
|
||||
class NovaDriverTestCase(base.BaseTestCase):
|
||||
|
||||
name = 'QEmu-fedora-i686'
|
||||
@ -30,7 +31,8 @@ class NovaDriverTestCase(base.BaseTestCase):
|
||||
self.nova_patcher = mock.patch('openstack.connect', autospec=True)
|
||||
self.nova_mock = self.nova_patcher.start()
|
||||
|
||||
self.test_driver = OpenStackDriver({}, 'fake-cloud')
|
||||
test_driver_class = OpenStackDriver.initialize({}, 'fake-cloud')
|
||||
self.test_driver = test_driver_class()
|
||||
|
||||
super(NovaDriverTestCase, self).setUp()
|
||||
|
||||
@ -145,6 +147,7 @@ class NovaDriverTestCase(base.BaseTestCase):
|
||||
self.nova_mock.return_value.get_server.return_value = server
|
||||
|
||||
image = mock.Mock(hw_firmware_type='bios')
|
||||
|
||||
self.nova_mock.return_value.image.find_image.return_value = image
|
||||
|
||||
boot_mode = self.test_driver.get_boot_mode(self.uuid)
|
||||
@ -216,7 +219,9 @@ class NovaDriverTestCase(base.BaseTestCase):
|
||||
|
||||
server = mock.Mock(id=self.uuid, addresses=addresses)
|
||||
self.nova_mock.return_value.get_server.return_value = server
|
||||
|
||||
nics = self.test_driver.get_nics(self.uuid)
|
||||
|
||||
self.assertEqual([{'id': 'fa:16:3e:22:18:31',
|
||||
'mac': 'fa:16:3e:22:18:31'},
|
||||
{'id': 'fa:16:3e:46:e3:ac',
|
||||
|
Loading…
Reference in New Issue
Block a user