Merge "Add a Blazar Host resource"

This commit is contained in:
Zuul 2019-03-07 10:20:04 +00:00 committed by Gerrit Code Review
commit 404ca3fad1
4 changed files with 342 additions and 0 deletions

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

View File

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

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

View File

@ -0,0 +1,5 @@
---
features:
- |
A new ``OS::Blazar::Host`` resource is added to manage compute hosts for
the lease/reservation in OpenStack.