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:
Ilya Etingof 2018-10-23 18:24:56 +02:00
parent b6ccaf6da8
commit b13ea4e9c7
9 changed files with 308 additions and 112 deletions

View File

@ -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

View File

@ -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'))

View 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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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')

View 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)

View File

@ -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',