Add ironic client plugin support

Change-Id: I2f3ee94424c4dab75fbcef6f8b32e565b45684e4
Task: 36285
This commit is contained in:
ricolin 2019-08-15 12:42:34 +08:00
parent 6a2a4000b9
commit 5b35225d16
9 changed files with 178 additions and 0 deletions

View File

@ -76,3 +76,4 @@ We have integration with
* https://opendev.org/openstack/python-octaviaclient.git (Load-balancer service) * https://opendev.org/openstack/python-octaviaclient.git (Load-balancer service)
* https://opendev.org/openstack/python-senlinclient (Clustering service) * https://opendev.org/openstack/python-senlinclient (Clustering service)
* https://opendev.org/openstack/python-vitrageclient.git (RCA service) * https://opendev.org/openstack/python-vitrageclient.git (RCA service)
* https://opendev.org/openstack/python-ironicclient (baremetal provisioning service)

View File

@ -183,6 +183,11 @@ engine_opts = [
'this limitation, any nova feature supported with ' 'this limitation, any nova feature supported with '
'microversion number above max_nova_api_microversion ' 'microversion number above max_nova_api_microversion '
'will not be available.')), 'will not be available.')),
cfg.FloatOpt('max_ironic_api_microversion',
help=_('Maximum ironic API version for client plugin. With '
'this limitation, any ironic feature supported with '
'microversion number above '
'max_ironic_api_microversion will not be available.')),
cfg.IntOpt('event_purge_batch_size', cfg.IntOpt('event_purge_batch_size',
min=1, min=1,
default=200, default=200,

View File

@ -0,0 +1,82 @@
#
# 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 ironicclient.common.apiclient import exceptions as ic_exc
from ironicclient.v1 import client as ironic_client
from oslo_config import cfg
from heat.common import exception
from heat.engine.clients import client_plugin
from heat.engine import constraints
CLIENT_NAME = 'ironic'
class IronicClientPlugin(client_plugin.ClientPlugin):
service_types = [BAREMETAL] = ['baremetal']
IRONIC_API_VERSION = 1.58
max_ironic_api_microversion = cfg.CONF.max_ironic_api_microversion
max_microversion = max_ironic_api_microversion if (
max_ironic_api_microversion is not None and (
IRONIC_API_VERSION > max_ironic_api_microversion)
) else IRONIC_API_VERSION
def _create(self):
interface = self._get_client_option(CLIENT_NAME, 'endpoint_type')
args = {
'interface': interface,
'service_type': self.BAREMETAL,
'session': self.context.keystone_session,
'region_name': self._get_region_name(),
'os_ironic_api_version': self.max_microversion
}
client = ironic_client.Client(**args)
return client
def is_not_found(self, ex):
return isinstance(ex, ic_exc.NotFound)
def is_over_limit(self, ex):
return isinstance(ex, ic_exc.RequestEntityTooLarge)
def is_conflict(self, ex):
return isinstance(ex, ic_exc.Conflict)
def _get_rsrc_name_or_id(self, value, entity, entity_msg):
entity_client = getattr(self.client(), entity)
try:
return entity_client.get(value).uuid
except ic_exc.NotFound:
# Ironic cli will find the value either is name or id,
# so no need to call list() here.
raise exception.EntityNotFound(entity=entity_msg,
name=value)
def get_portgroup(self, value):
return self._get_rsrc_name_or_id(value, entity='portgroup',
entity_msg='PortGroup')
def get_node(self, value):
return self._get_rsrc_name_or_id(value, entity='node',
entity_msg='Node')
class PortGroupConstraint(constraints.BaseCustomConstraint):
resource_client_name = CLIENT_NAME
resource_getter_name = 'get_portgroup'
class NodeConstraint(constraints.BaseCustomConstraint):
resource_client_name = CLIENT_NAME
resource_getter_name = 'get_node'

View File

@ -0,0 +1,77 @@
#
# 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 ironicclient import exceptions as ic_exc
import mock
from heat.engine.clients.os import ironic as ic
from heat.tests import common
from heat.tests import utils
class IronicClientPluginTest(common.HeatTestCase):
def test_create(self):
context = utils.dummy_context()
plugin = context.clients.client_plugin('ironic')
client = plugin.client()
self.assertEqual('http://server.test:5000/v3',
client.port.api.session.auth.endpoint)
class fake_resource(object):
def __init__(self, id=None, name=None):
self.uuid = id
self.name = name
class PortGroupConstraintTest(common.HeatTestCase):
def setUp(self):
super(PortGroupConstraintTest, self).setUp()
self.ctx = utils.dummy_context()
self.mock_port_group_get = mock.Mock()
self.ctx.clients.client_plugin(
'ironic').client().portgroup.get = self.mock_port_group_get
self.constraint = ic.PortGroupConstraint()
def test_validate(self):
self.mock_port_group_get.return_value = fake_resource(
id='my_port_group')
self.assertTrue(self.constraint.validate(
'my_port_group', self.ctx))
def test_validate_fail(self):
self.mock_port_group_get.side_effect = ic_exc.NotFound()
self.assertFalse(self.constraint.validate(
"bad_port_group", self.ctx))
class NodeConstraintTest(common.HeatTestCase):
def setUp(self):
super(NodeConstraintTest, self).setUp()
self.ctx = utils.dummy_context()
self.mock_node_get = mock.Mock()
self.ctx.clients.client_plugin(
'ironic').client().node.get = self.mock_node_get
self.constraint = ic.NodeConstraint()
def test_validate(self):
self.mock_node_get.return_value = fake_resource(
id='my_node')
self.assertTrue(self.constraint.validate(
'my_node', self.ctx))
def test_validate_fail(self):
self.mock_node_get.side_effect = ic_exc.NotFound()
self.assertFalse(self.constraint.validate(
"bad_node", self.ctx))

View File

View File

@ -111,6 +111,7 @@ python-designateclient==2.7.0
python-editor==1.0.3 python-editor==1.0.3
python-glanceclient==2.8.0 python-glanceclient==2.8.0
python-heatclient==1.10.0 python-heatclient==1.10.0
python-ironicclient==2.8.0
python-keystoneclient==3.8.0 python-keystoneclient==3.8.0
python-magnumclient==2.3.0 python-magnumclient==2.3.0
python-manilaclient==1.16.0 python-manilaclient==1.16.0

View File

@ -0,0 +1,7 @@
---
features:
- |
Introduce a Ironic client plugin module that will be used by the Ironic's
resources.
Support only ironicclient version >=2.8.0 to get allocation functionality
support.

View File

@ -38,6 +38,7 @@ python-cinderclient>=3.3.0 # Apache-2.0
python-designateclient>=2.7.0 # Apache-2.0 python-designateclient>=2.7.0 # Apache-2.0
python-glanceclient>=2.8.0 # Apache-2.0 python-glanceclient>=2.8.0 # Apache-2.0
python-heatclient>=1.10.0 # Apache-2.0 python-heatclient>=1.10.0 # Apache-2.0
python-ironicclient>=2.8.0 # Apache-2.0
python-keystoneclient>=3.8.0 # Apache-2.0 python-keystoneclient>=3.8.0 # Apache-2.0
python-magnumclient>=2.3.0 # Apache-2.0 python-magnumclient>=2.3.0 # Apache-2.0
python-manilaclient>=1.16.0 # Apache-2.0 python-manilaclient>=1.16.0 # Apache-2.0

View File

@ -74,6 +74,7 @@ heat.clients =
designate = heat.engine.clients.os.designate:DesignateClientPlugin designate = heat.engine.clients.os.designate:DesignateClientPlugin
glance = heat.engine.clients.os.glance:GlanceClientPlugin glance = heat.engine.clients.os.glance:GlanceClientPlugin
heat = heat.engine.clients.os.heat_plugin:HeatClientPlugin heat = heat.engine.clients.os.heat_plugin:HeatClientPlugin
ironic = heat.engine.clients.os.ironic:IronicClientPlugin
keystone = heat.engine.clients.os.keystone:KeystoneClientPlugin keystone = heat.engine.clients.os.keystone:KeystoneClientPlugin
magnum = heat.engine.clients.os.magnum:MagnumClientPlugin magnum = heat.engine.clients.os.magnum:MagnumClientPlugin
manila = heat.engine.clients.os.manila:ManilaClientPlugin manila = heat.engine.clients.os.manila:ManilaClientPlugin
@ -174,6 +175,9 @@ heat.constraints =
senlin.profile_type = heat.engine.clients.os.senlin:ProfileTypeConstraint senlin.profile_type = heat.engine.clients.os.senlin:ProfileTypeConstraint
trove.flavor = heat.engine.clients.os.trove:FlavorConstraint trove.flavor = heat.engine.clients.os.trove:FlavorConstraint
zaqar.queue = heat.engine.clients.os.zaqar:QueueConstraint zaqar.queue = heat.engine.clients.os.zaqar:QueueConstraint
#ironic
ironic.portgroup = heat.engine.clients.os.ironic:PortGroupConstraint
ironic.node = heat.engine.clients.os.ironic:NodeConstraint
heat.stack_lifecycle_plugins = heat.stack_lifecycle_plugins =