Add a Blazar Host resource
Add a OS::Blazar::Host resource plugin to support Blazar which is a resource reservation services in OpenStack. Co-author: Asmita Singh <Asmita.Singh@nttdata.com> Change-Id: Ie5b9373681943222268eb9144740f5733ffef750 Task: 22881 Story: 2002085
This commit is contained in:
parent
809ac97439
commit
8c46dacd6a
167
heat/engine/resources/openstack/blazar/host.py
Normal file
167
heat/engine/resources/openstack/blazar/host.py
Normal file
@ -0,0 +1,167 @@
|
||||
#
|
||||
# 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 heat.common import exception
|
||||
from heat.common.i18n import _
|
||||
from heat.engine import attributes
|
||||
from heat.engine import properties
|
||||
from heat.engine import resource
|
||||
from heat.engine import support
|
||||
|
||||
|
||||
class Host(resource.Resource):
|
||||
"""A resource to manage Blazar hosts.
|
||||
|
||||
Host resource manages the physical hosts for the lease/reservation
|
||||
within OpenStack.
|
||||
|
||||
# TODO(asmita): Based on an agreement with Blazar team, this resource
|
||||
class does not support updating host resource as currently Blazar does
|
||||
not support to delete existing extra_capability keys while updating host.
|
||||
Also, in near future, when Blazar team will come up with a new alternative
|
||||
API to resolve this issue, we will need to modify this class.
|
||||
"""
|
||||
|
||||
support_status = support.SupportStatus(version='12.0.0')
|
||||
|
||||
PROPERTIES = (
|
||||
NAME, EXTRA_CAPABILITY,
|
||||
) = (
|
||||
'name', 'extra_capability',
|
||||
)
|
||||
|
||||
ATTRIBUTES = (
|
||||
HYPERVISOR_HOSTNAME, HYPERVISOR_TYPE, HYPERVISOR_VERSION,
|
||||
VCPUS, CPU_INFO, MEMORY_MB, LOCAL_GB,
|
||||
SERVICE_NAME, RESERVABLE, STATUS, TRUST_ID,
|
||||
EXTRA_CAPABILITY_ATTR, CREATED_AT, UPDATED_AT,
|
||||
) = (
|
||||
'hypervisor_hostname', 'hypervisor_type', 'hypervisor_version',
|
||||
'vcpus', 'cpu_info', 'memory_mb', 'local_gb',
|
||||
'service_name', 'reservable', 'status', 'trust_id',
|
||||
'extra_capability', 'created_at', 'updated_at',
|
||||
)
|
||||
|
||||
properties_schema = {
|
||||
NAME: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('The name of the host.'),
|
||||
required=True,
|
||||
),
|
||||
EXTRA_CAPABILITY: properties.Schema(
|
||||
properties.Schema.MAP,
|
||||
_('The extra capability of the host.'),
|
||||
)
|
||||
}
|
||||
|
||||
attributes_schema = {
|
||||
HYPERVISOR_HOSTNAME: attributes.Schema(
|
||||
_('The hypervisor name of the host.'),
|
||||
type=attributes.Schema.STRING,
|
||||
),
|
||||
HYPERVISOR_TYPE: attributes.Schema(
|
||||
_('The hypervisor type the host.'),
|
||||
type=attributes.Schema.STRING,
|
||||
),
|
||||
HYPERVISOR_VERSION: attributes.Schema(
|
||||
_('The hypervisor version of the host.'),
|
||||
type=attributes.Schema.INTEGER,
|
||||
),
|
||||
VCPUS: attributes.Schema(
|
||||
_('The number of the VCPUs of the host.'),
|
||||
type=attributes.Schema.INTEGER,
|
||||
),
|
||||
CPU_INFO: attributes.Schema(
|
||||
_('Information of the CPU of the host.'),
|
||||
type=attributes.Schema.MAP,
|
||||
),
|
||||
MEMORY_MB: attributes.Schema(
|
||||
_('Megabytes of the memory of the host.'),
|
||||
type=attributes.Schema.INTEGER,
|
||||
),
|
||||
LOCAL_GB: attributes.Schema(
|
||||
_('Gigabytes of the disk of the host.'),
|
||||
type=attributes.Schema.INTEGER,
|
||||
),
|
||||
SERVICE_NAME: attributes.Schema(
|
||||
_('The compute service name of the host.'),
|
||||
type=attributes.Schema.STRING,
|
||||
),
|
||||
RESERVABLE: attributes.Schema(
|
||||
_('The flag which represents whether the host is reservable '
|
||||
'or not.'),
|
||||
type=attributes.Schema.BOOLEAN,
|
||||
),
|
||||
STATUS: attributes.Schema(
|
||||
_('The status of the host.'),
|
||||
type=attributes.Schema.STRING,
|
||||
),
|
||||
TRUST_ID: attributes.Schema(
|
||||
_('The UUID of the trust of the host operator.'),
|
||||
type=attributes.Schema.STRING,
|
||||
),
|
||||
EXTRA_CAPABILITY_ATTR: attributes.Schema(
|
||||
_('The extra capability of the host.'),
|
||||
type=attributes.Schema.MAP,
|
||||
),
|
||||
CREATED_AT: attributes.Schema(
|
||||
_('The date and time when the host was created. '
|
||||
'The date and time format must be "CCYY-MM-DD hh:mm".'),
|
||||
type=attributes.Schema.STRING,
|
||||
),
|
||||
UPDATED_AT: attributes.Schema(
|
||||
_('The date and time when the host was updated. '
|
||||
'The date and time format must be "CCYY-MM-DD hh:mm".'),
|
||||
type=attributes.Schema.STRING
|
||||
),
|
||||
}
|
||||
|
||||
default_client_name = 'blazar'
|
||||
|
||||
entity = 'host'
|
||||
|
||||
def _parse_extra_capability(self, args):
|
||||
if self.NAME in args[self.EXTRA_CAPABILITY]:
|
||||
# Remove "name" key if present in the extra_capability property.
|
||||
del args[self.EXTRA_CAPABILITY][self.NAME]
|
||||
args.update(args[self.EXTRA_CAPABILITY])
|
||||
args.pop(self.EXTRA_CAPABILITY)
|
||||
return args
|
||||
|
||||
def handle_create(self):
|
||||
args = dict((k, v) for k, v in self.properties.items()
|
||||
if v is not None)
|
||||
|
||||
if self.EXTRA_CAPABILITY in args:
|
||||
args = self._parse_extra_capability(args)
|
||||
|
||||
host = self.client_plugin().create_host(**args)
|
||||
self.resource_id_set(host['id'])
|
||||
|
||||
return host['id']
|
||||
|
||||
def _resolve_attribute(self, name):
|
||||
if self.resource_id is None:
|
||||
return
|
||||
host = self.client_plugin().get_host(self.resource_id)
|
||||
try:
|
||||
return host[name]
|
||||
except KeyError:
|
||||
raise exception.InvalidTemplateAttribute(resource=self.name,
|
||||
key=name)
|
||||
|
||||
|
||||
def resource_mapping():
|
||||
return {
|
||||
'OS::Blazar::Host': Host
|
||||
}
|
@ -61,6 +61,9 @@ resource_types_policies = [
|
||||
check_str=base.RULE_PROJECT_ADMIN),
|
||||
policy.RuleDefault(
|
||||
name=POLICY_ROOT % 'OS::Keystone::*',
|
||||
check_str=base.RULE_PROJECT_ADMIN),
|
||||
policy.RuleDefault(
|
||||
name=POLICY_ROOT % 'OS::Blazar::Host',
|
||||
check_str=base.RULE_PROJECT_ADMIN)
|
||||
]
|
||||
|
||||
|
167
heat/tests/openstack/blazar/test_host.py
Normal file
167
heat/tests/openstack/blazar/test_host.py
Normal file
@ -0,0 +1,167 @@
|
||||
#
|
||||
# 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 blazarclient import exception as client_exception
|
||||
import mock
|
||||
from oslo_utils.fixture import uuidsentinel as uuids
|
||||
|
||||
from heat.common import exception
|
||||
from heat.common import template_format
|
||||
from heat.engine.clients.os import blazar
|
||||
from heat.engine.resources.openstack.blazar import host
|
||||
from heat.engine import scheduler
|
||||
from heat.tests import common
|
||||
from heat.tests import utils
|
||||
|
||||
|
||||
blazar_host_template = '''
|
||||
heat_template_version: rocky
|
||||
|
||||
resources:
|
||||
test-host:
|
||||
type: OS::Blazar::Host
|
||||
properties:
|
||||
name: test-host
|
||||
extra_capability:
|
||||
gpu: true
|
||||
'''
|
||||
|
||||
blazar_host_template_extra_capability = '''
|
||||
heat_template_version: rocky
|
||||
|
||||
resources:
|
||||
test-host:
|
||||
type: OS::Blazar::Host
|
||||
properties:
|
||||
name: test-host
|
||||
extra_capability:
|
||||
gpu: true
|
||||
name: test-name
|
||||
'''
|
||||
|
||||
|
||||
class BlazarHostTestCase(common.HeatTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(BlazarHostTestCase, self).setUp()
|
||||
|
||||
self.host = {
|
||||
"id": uuids.id,
|
||||
"name": "test-host",
|
||||
"gpu": True,
|
||||
"hypervisor_hostname": "compute-1",
|
||||
"hypervisor_type": "QEMU",
|
||||
"hypervisor_version": 2010001,
|
||||
"cpu_info": "{"
|
||||
"'arch': 'x86_64', 'model': 'cpu64-rhel6', "
|
||||
"'vendor': 'Intel', 'features': "
|
||||
"['pge', 'clflush', 'sep', 'syscall', 'msr', "
|
||||
"'vmx', 'cmov', 'nx', 'pat', 'lm', 'tsc', "
|
||||
"'fpu', 'fxsr', 'pae', 'mmx', 'cx8', 'mce', "
|
||||
"'de', 'mca', 'pse', 'pni', 'apic', 'sse', "
|
||||
"'lahf_lm', 'sse2', 'hypervisor', 'cx16', "
|
||||
"'pse36', 'mttr', 'x2apic'], "
|
||||
"'topology': {'cores': 1, 'cells': 1, 'threads': 1, "
|
||||
"'sockets': 4}}",
|
||||
"memory_mb": 8192,
|
||||
"local_gb": 100,
|
||||
"vcpus": 2,
|
||||
"service_name": "compute-1",
|
||||
"reservable": True,
|
||||
"trust_id": uuids.trust_id,
|
||||
"created_at": "2020-01-01 08:00",
|
||||
"updated_at": "2020-01-01 12:00",
|
||||
"extra_capability": "foo"
|
||||
}
|
||||
t = template_format.parse(blazar_host_template)
|
||||
self.stack = utils.parse_stack(t)
|
||||
resource_defns = self.stack.t.resource_definitions(self.stack)
|
||||
self.rsrc_defn = resource_defns['test-host']
|
||||
self.client = mock.Mock()
|
||||
self.patchobject(blazar.BlazarClientPlugin, 'client',
|
||||
return_value=self.client)
|
||||
|
||||
def _create_resource(self, name, snippet, stack):
|
||||
self.client.host.create.return_value = self.host
|
||||
return host.Host(name, snippet, stack)
|
||||
|
||||
def test_host_create(self):
|
||||
host_resource = self._create_resource('host', self.rsrc_defn,
|
||||
self.stack)
|
||||
|
||||
self.assertEqual(self.host['name'],
|
||||
host_resource.properties.get(host.Host.NAME))
|
||||
|
||||
scheduler.TaskRunner(host_resource.create)()
|
||||
self.assertEqual(uuids.id, host_resource.resource_id)
|
||||
self.assertEqual((host_resource.CREATE, host_resource.COMPLETE),
|
||||
host_resource.state)
|
||||
self.assertEqual('host', host_resource.entity)
|
||||
self.client.host.create.assert_called_once_with(
|
||||
name=self.host['name'], gpu=self.host['gpu'])
|
||||
|
||||
def test_host_delete(self):
|
||||
host_resource = self._create_resource('host', self.rsrc_defn,
|
||||
self.stack)
|
||||
|
||||
scheduler.TaskRunner(host_resource.create)()
|
||||
self.client.host.delete.return_value = None
|
||||
self.client.host.get.side_effect = [
|
||||
'host_obj', client_exception.BlazarClientException(code=404)]
|
||||
scheduler.TaskRunner(host_resource.delete)()
|
||||
self.assertEqual((host_resource.DELETE, host_resource.COMPLETE),
|
||||
host_resource.state)
|
||||
self.client.host.delete.assert_called_once_with(uuids.id)
|
||||
|
||||
def test_host_delete_not_found(self):
|
||||
host_resource = self._create_resource('host', self.rsrc_defn,
|
||||
self.stack)
|
||||
|
||||
scheduler.TaskRunner(host_resource.create)()
|
||||
self.client.host.delete.side_effect = client_exception.\
|
||||
BlazarClientException(code=404)
|
||||
self.client.host.get.side_effect = client_exception.\
|
||||
BlazarClientException(code=404)
|
||||
scheduler.TaskRunner(host_resource.delete)()
|
||||
self.assertEqual((host_resource.DELETE, host_resource.COMPLETE),
|
||||
host_resource.state)
|
||||
|
||||
def test_parse_extra_capability(self):
|
||||
t = template_format.parse(blazar_host_template_extra_capability)
|
||||
stack = utils.parse_stack(t)
|
||||
resource_defns = self.stack.t.resource_definitions(stack)
|
||||
rsrc_defn = resource_defns['test-host']
|
||||
host_resource = self._create_resource('host', rsrc_defn, stack)
|
||||
args = dict((k, v) for k, v in host_resource.properties.items()
|
||||
if v is not None)
|
||||
parsed_args = host_resource._parse_extra_capability(args)
|
||||
self.assertEqual({'gpu': True, 'name': 'test-host'}, parsed_args)
|
||||
|
||||
def test_resolve_attributes(self):
|
||||
host_resource = self._create_resource('host', self.rsrc_defn,
|
||||
self.stack)
|
||||
|
||||
scheduler.TaskRunner(host_resource.create)()
|
||||
self.client.host.get.return_value = self.host
|
||||
self.assertEqual(self.host['vcpus'],
|
||||
host_resource._resolve_attribute(host.Host.VCPUS))
|
||||
|
||||
def test_resolve_attributes_not_found(self):
|
||||
host_resource = self._create_resource('host', self.rsrc_defn,
|
||||
self.stack)
|
||||
|
||||
scheduler.TaskRunner(host_resource.create)()
|
||||
self.client.host.get.return_value = self.host
|
||||
self.assertRaises(exception.InvalidTemplateAttribute,
|
||||
host_resource._resolve_attribute,
|
||||
'invalid_attribute')
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
A new ``OS::Blazar::Host`` resource is added to manage compute hosts for
|
||||
the lease/reservation in OpenStack.
|
Loading…
Reference in New Issue
Block a user