Move the convert_requirements method to the utils namespace

The convert_requirements method is used by host_plugin and
instance_plugin. But this method is implemented as an instance method of
host_plugin. The instance_plugin can't use this method without an
instance of host_plugin.

This patch moves the convert_requirements method to the utils namespace.
All plugins which query the db data can use this method.

Partially implements: blueprint new-instance-reservation
Change-Id: Ib365a794b45a1a1bf0c1b0a8a750171d44d25b8a
This commit is contained in:
Masahito Muroi 2017-06-23 13:40:45 +09:00
parent a20b29b179
commit 50e730f060
4 changed files with 148 additions and 105 deletions

View File

@ -15,10 +15,8 @@
# under the License.
import datetime
import json
from oslo_config import cfg
import six
from blazar.db import api as db_api
from blazar.db import exceptions as db_ex
@ -27,6 +25,7 @@ from blazar.manager import exceptions as manager_ex
from blazar.plugins import base
from blazar.plugins import oshosts as plugin
from blazar.utils.openstack import nova
from blazar.utils import plugins as plugins_utils
from blazar.utils import trusts
plugin_opts = [
@ -348,10 +347,10 @@ class PhysicalHostPlugin(base.BasePlugin, nova.NovaClientWrapper):
filter_array = []
# TODO(frossigneux) support "or" operator
if hypervisor_properties:
filter_array = self._convert_requirements(
filter_array = plugins_utils.convert_requirements(
hypervisor_properties)
if resource_properties:
filter_array += self._convert_requirements(
filter_array += plugins_utils.convert_requirements(
resource_properties)
for host in db_api.host_get_all_by_queries(filter_array):
if not db_api.host_allocation_get_all_by_values(
@ -373,53 +372,3 @@ class PhysicalHostPlugin(base.BasePlugin, nova.NovaClientWrapper):
return all_host_ids[:int(max_host)]
else:
return []
def _convert_requirements(self, requirements):
"""Convert the requirements to an array of strings
Convert the requirements to an array of strings.
["key op value", "key op value", ...]
"""
# TODO(frossigneux) Support the "or" operator
# Convert text to json
if isinstance(requirements, six.string_types):
try:
requirements = json.loads(requirements)
except ValueError:
raise manager_ex.MalformedRequirements(rqrms=requirements)
# Requirement list looks like ['<', '$ram', '1024']
if self._requirements_with_three_elements(requirements):
result = []
if requirements[0] == '=':
requirements[0] = '=='
string = (requirements[1][1:] + " " + requirements[0] + " " +
requirements[2])
result.append(string)
return result
# Remove the 'and' element at the head of the requirement list
elif self._requirements_with_and_keyword(requirements):
return [self._convert_requirements(x)[0]
for x in requirements[1:]]
# Empty requirement list0
elif isinstance(requirements, list) and not requirements:
return requirements
else:
raise manager_ex.MalformedRequirements(rqrms=requirements)
def _requirements_with_three_elements(self, requirements):
"""Return true if requirement list looks like ['<', '$ram', '1024']."""
return (isinstance(requirements, list) and
len(requirements) == 3 and
isinstance(requirements[0], six.string_types) and
isinstance(requirements[1], six.string_types) and
isinstance(requirements[2], six.string_types) and
requirements[0] in ['==', '=', '!=', '>=', '<=', '>', '<'] and
len(requirements[1]) > 1 and requirements[1][0] == '$' and
len(requirements[2]) > 0)
def _requirements_with_and_keyword(self, requirements):
return (len(requirements) > 1 and
isinstance(requirements[0], six.string_types) and
requirements[0] == 'and' and
all(self._convert_requirements(x) for x in requirements[1:]))

View File

@ -771,54 +771,3 @@ class PhysicalHostPluginTestCase(tests.TestCase):
datetime.datetime(2013, 12, 19, 20, 00),
datetime.datetime(2013, 12, 19, 21, 00))
self.assertEqual([], result)
def test_convert_requirements_empty(self):
request = '[]'
result = self.fake_phys_plugin._convert_requirements(request)
self.assertEqual([], result)
def test_convert_requirements_small(self):
request = '["=", "$memory", "4096"]'
result = self.fake_phys_plugin._convert_requirements(request)
self.assertEqual(['memory == 4096'], result)
def test_convert_requirements_with_incorrect_syntax_1(self):
self.assertRaises(
manager_exceptions.MalformedRequirements,
self.fake_phys_plugin._convert_requirements,
'["a", "$memory", "4096"]')
def test_convert_requirements_with_incorrect_syntax_2(self):
self.assertRaises(
manager_exceptions.MalformedRequirements,
self.fake_phys_plugin._convert_requirements,
'["=", "memory", "4096"]')
def test_convert_requirements_with_incorrect_syntax_3(self):
self.assertRaises(
manager_exceptions.MalformedRequirements,
self.fake_phys_plugin._convert_requirements,
'["=", "$memory", 4096]')
def test_convert_requirements_complex(self):
request = '["and", [">", "$memory", "4096"], [">", "$disk", "40"]]'
result = self.fake_phys_plugin._convert_requirements(request)
self.assertEqual(['memory > 4096', 'disk > 40'], result)
def test_convert_requirements_complex_with_incorrect_syntax_1(self):
self.assertRaises(
manager_exceptions.MalformedRequirements,
self.fake_phys_plugin._convert_requirements,
'["and", [">", "memory", "4096"], [">", "$disk", "40"]]')
def test_convert_requirements_complex_with_incorrect_syntax_2(self):
self.assertRaises(
manager_exceptions.MalformedRequirements,
self.fake_phys_plugin._convert_requirements,
'["fail", [">", "$memory", "4096"], [">", "$disk", "40"]]')
def test_convert_requirements_complex_with_not_json_value(self):
self.assertRaises(
manager_exceptions.MalformedRequirements,
self.fake_phys_plugin._convert_requirements,
'something')

View File

@ -0,0 +1,72 @@
# Copyright (c) 2017 NTT.
#
# 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 blazar.manager import exceptions as manager_exceptions
from blazar import tests
from blazar.utils import plugins as plugins_utils
class TestPluginsUtils(tests.TestCase):
def setUp(self):
super(TestPluginsUtils, self).setUp()
def test_convert_requirements_empty(self):
request = '[]'
result = plugins_utils.convert_requirements(request)
self.assertEqual([], result)
def test_convert_requirements_small(self):
request = '["=", "$memory", "4096"]'
result = plugins_utils.convert_requirements(request)
self.assertEqual(['memory == 4096'], result)
def test_convert_requirements_with_incorrect_syntax_1(self):
self.assertRaises(
manager_exceptions.MalformedRequirements,
plugins_utils.convert_requirements, '["a", "$memory", "4096"]')
def test_convert_requirements_with_incorrect_syntax_2(self):
self.assertRaises(
manager_exceptions.MalformedRequirements,
plugins_utils.convert_requirements, '["=", "memory", "4096"]')
def test_convert_requirements_with_incorrect_syntax_3(self):
self.assertRaises(
manager_exceptions.MalformedRequirements,
plugins_utils.convert_requirements, '["=", "$memory", 4096]')
def test_convert_requirements_complex(self):
request = '["and", [">", "$memory", "4096"], [">", "$disk", "40"]]'
result = plugins_utils.convert_requirements(request)
self.assertEqual(['memory > 4096', 'disk > 40'], result)
def test_convert_requirements_complex_with_incorrect_syntax_1(self):
self.assertRaises(
manager_exceptions.MalformedRequirements,
plugins_utils.convert_requirements,
'["and", [">", "memory", "4096"], [">", "$disk", "40"]]')
def test_convert_requirements_complex_with_incorrect_syntax_2(self):
self.assertRaises(
manager_exceptions.MalformedRequirements,
plugins_utils.convert_requirements,
'["fail", [">", "$memory", "4096"], [">", "$disk", "40"]]')
def test_convert_requirements_complex_with_not_json_value(self):
self.assertRaises(
manager_exceptions.MalformedRequirements,
plugins_utils.convert_requirements, 'something')

73
blazar/utils/plugins.py Normal file
View File

@ -0,0 +1,73 @@
# Copyright (c) 2017 NTT.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import six
from blazar.manager import exceptions as manager_ex
def convert_requirements(requirements):
"""Convert the requirements to an array of strings
Convert the requirements to an array of strings.
["key op value", "key op value", ...]
"""
# TODO(frossigneux) Support the "or" operator
# Convert text to json
if isinstance(requirements, six.string_types):
try:
requirements = json.loads(requirements)
except ValueError:
raise manager_ex.MalformedRequirements(rqrms=requirements)
# Requirement list looks like ['<', '$ram', '1024']
if _requirements_with_three_elements(requirements):
result = []
if requirements[0] == '=':
requirements[0] = '=='
string = (requirements[1][1:] + " " + requirements[0] + " " +
requirements[2])
result.append(string)
return result
# Remove the 'and' element at the head of the requirement list
elif _requirements_with_and_keyword(requirements):
return [convert_requirements(x)[0] for x in requirements[1:]]
# Empty requirement list0
elif isinstance(requirements, list) and not requirements:
return requirements
else:
raise manager_ex.MalformedRequirements(rqrms=requirements)
def _requirements_with_three_elements(requirements):
"""Return true if requirement list looks like ['<', '$ram', '1024']."""
return (isinstance(requirements, list) and
len(requirements) == 3 and
isinstance(requirements[0], six.string_types) and
isinstance(requirements[1], six.string_types) and
isinstance(requirements[2], six.string_types) and
requirements[0] in ['==', '=', '!=', '>=', '<=', '>', '<'] and
len(requirements[1]) > 1 and requirements[1][0] == '$' and
len(requirements[2]) > 0)
def _requirements_with_and_keyword(requirements):
return (len(requirements) > 1 and
isinstance(requirements[0], six.string_types) and
requirements[0] == 'and' and
all(convert_requirements(x) for x in requirements[1:]))