Porting baremetal_nodes extension to v2.1/v3
This patch ports baremetal-nodes(include baremetal-stat-ext) to v2.1 and make v2 and v2.1 share unit test cases. In v2.1/v3,baremetal-nodes will not depend on baremetal-stat-ext. Partially implements blueprint v2-on-v3-api Change-Id: I0f6a968897975ee91e76538d2ce7d2538044613e
This commit is contained in:
@@ -65,6 +65,8 @@
|
||||
"compute_extension:v3:os-attach-interfaces": "",
|
||||
"compute_extension:v3:os-attach-interfaces:discoverable": "",
|
||||
"compute_extension:baremetal_nodes": "rule:admin_api",
|
||||
"compute_extension:v3:os-baremetal-nodes": "rule:admin_api",
|
||||
"compute_extension:v3:os-baremetal-nodes:discoverable": "",
|
||||
"compute_extension:v3:os-block-device-mapping-v1:discoverable": "",
|
||||
"compute_extension:cells": "rule:admin_api",
|
||||
"compute_extension:cells:create": "rule:admin_api",
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
# Copyright (c) 2013 NTT DOCOMO, INC.
|
||||
# Copyright 2014 IBM Corporation.
|
||||
# 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.
|
||||
|
||||
"""The bare-metal admin extension."""
|
||||
|
||||
from oslo.config import cfg
|
||||
from oslo.utils import importutils
|
||||
import webob
|
||||
|
||||
from nova.api.openstack import extensions
|
||||
from nova.api.openstack import wsgi
|
||||
from nova.i18n import _
|
||||
|
||||
ironic_client = importutils.try_import('ironicclient.client')
|
||||
|
||||
CONF = cfg.CONF
|
||||
ALIAS = "os-baremetal-nodes"
|
||||
authorize = extensions.extension_authorizer('compute', 'v3:' + ALIAS)
|
||||
|
||||
node_fields = ['id', 'cpus', 'local_gb', 'memory_mb', 'pm_address',
|
||||
'pm_user', 'service_host', 'terminal_port', 'instance_uuid']
|
||||
|
||||
node_ext_fields = ['uuid', 'task_state', 'updated_at', 'pxe_config_path']
|
||||
|
||||
interface_fields = ['id', 'address', 'datapath_id', 'port_no']
|
||||
|
||||
CONF.import_opt('api_version',
|
||||
'nova.virt.ironic.driver',
|
||||
group='ironic')
|
||||
CONF.import_opt('api_endpoint',
|
||||
'nova.virt.ironic.driver',
|
||||
group='ironic')
|
||||
CONF.import_opt('admin_username',
|
||||
'nova.virt.ironic.driver',
|
||||
group='ironic')
|
||||
CONF.import_opt('admin_password',
|
||||
'nova.virt.ironic.driver',
|
||||
group='ironic')
|
||||
CONF.import_opt('admin_tenant_name',
|
||||
'nova.virt.ironic.driver',
|
||||
group='ironic')
|
||||
CONF.import_opt('compute_driver', 'nova.virt.driver')
|
||||
|
||||
|
||||
def _interface_dict(interface_ref):
|
||||
d = {}
|
||||
for f in interface_fields:
|
||||
d[f] = interface_ref.get(f)
|
||||
return d
|
||||
|
||||
|
||||
def _get_ironic_client():
|
||||
"""return an Ironic client."""
|
||||
# TODO(NobodyCam): Fix insecure setting
|
||||
kwargs = {'os_username': CONF.ironic.admin_username,
|
||||
'os_password': CONF.ironic.admin_password,
|
||||
'os_auth_url': CONF.ironic.admin_url,
|
||||
'os_tenant_name': CONF.ironic.admin_tenant_name,
|
||||
'os_service_type': 'baremetal',
|
||||
'os_endpoint_type': 'public',
|
||||
'insecure': 'true',
|
||||
'ironic_url': CONF.ironic.api_endpoint}
|
||||
icli = ironic_client.get_client(CONF.ironic.api_version, **kwargs)
|
||||
return icli
|
||||
|
||||
|
||||
def _no_ironic_proxy(cmd):
|
||||
raise webob.exc.HTTPBadRequest(
|
||||
explanation=_("Command Not supported. Please use Ironic "
|
||||
"command %(cmd)s to perform this "
|
||||
"action.") % {'cmd': cmd})
|
||||
|
||||
|
||||
class BareMetalNodeController(wsgi.Controller):
|
||||
"""The Bare-Metal Node API controller for the OpenStack API."""
|
||||
|
||||
def _node_dict(self, node_ref):
|
||||
d = {}
|
||||
for f in node_fields:
|
||||
d[f] = node_ref.get(f)
|
||||
for f in node_ext_fields:
|
||||
d[f] = node_ref.get(f)
|
||||
return d
|
||||
|
||||
@extensions.expected_errors(404)
|
||||
def index(self, req):
|
||||
context = req.environ['nova.context']
|
||||
authorize(context)
|
||||
nodes = []
|
||||
# proxy command to Ironic
|
||||
icli = _get_ironic_client()
|
||||
ironic_nodes = icli.node.list(detail=True)
|
||||
for inode in ironic_nodes:
|
||||
node = {'id': inode.uuid,
|
||||
'interfaces': [],
|
||||
'host': 'IRONIC MANAGED',
|
||||
'task_state': inode.provision_state,
|
||||
'cpus': inode.properties['cpus'],
|
||||
'memory_mb': inode.properties['memory_mb'],
|
||||
'disk_gb': inode.properties['local_gb']}
|
||||
nodes.append(node)
|
||||
return {'nodes': nodes}
|
||||
|
||||
@extensions.expected_errors(404)
|
||||
def show(self, req, id):
|
||||
context = req.environ['nova.context']
|
||||
authorize(context)
|
||||
# proxy command to Ironic
|
||||
icli = _get_ironic_client()
|
||||
inode = icli.node.get(id)
|
||||
iports = icli.node.list_ports(id)
|
||||
node = {'id': inode.uuid,
|
||||
'interfaces': [],
|
||||
'host': 'IRONIC MANAGED',
|
||||
'task_state': inode.provision_state,
|
||||
'cpus': inode.properties['cpus'],
|
||||
'memory_mb': inode.properties['memory_mb'],
|
||||
'disk_gb': inode.properties['local_gb'],
|
||||
'instance_uuid': inode.instance_uuid}
|
||||
for port in iports:
|
||||
node['interfaces'].append({'address': port.address})
|
||||
return {'node': node}
|
||||
|
||||
@extensions.expected_errors(400)
|
||||
def create(self, req, body):
|
||||
_no_ironic_proxy("port-create")
|
||||
|
||||
@extensions.expected_errors(400)
|
||||
def delete(self, req, id):
|
||||
_no_ironic_proxy("port-create")
|
||||
|
||||
@wsgi.action('add_interface')
|
||||
@extensions.expected_errors(400)
|
||||
def _add_interface(self, req, id, body):
|
||||
_no_ironic_proxy("port-create")
|
||||
|
||||
@wsgi.action('remove_interface')
|
||||
@extensions.expected_errors(400)
|
||||
def _remove_interface(self, req, id, body):
|
||||
_no_ironic_proxy("port-delete")
|
||||
|
||||
|
||||
class BareMetalNodes(extensions.V3APIExtensionBase):
|
||||
"""Admin-only bare-metal node administration."""
|
||||
|
||||
name = "BareMetalNodes"
|
||||
alias = ALIAS
|
||||
version = 1
|
||||
|
||||
def get_resources(self):
|
||||
resource = [extensions.ResourceExtension(ALIAS,
|
||||
BareMetalNodeController(),
|
||||
member_actions={"action": "POST"})]
|
||||
return resource
|
||||
|
||||
def get_controller_extensions(self):
|
||||
"""It's an abstract function V3APIExtensionBase and the extension
|
||||
will not be loaded without it.
|
||||
"""
|
||||
return []
|
||||
@@ -100,3 +100,9 @@ metadata = {
|
||||
},
|
||||
'additionalProperties': False
|
||||
}
|
||||
|
||||
|
||||
mac_address = {
|
||||
'type': 'string',
|
||||
'pattern': '^([0-9a-fA-F]{2})(:[0-9a-fA-F]{2}){5}$'
|
||||
}
|
||||
|
||||
@@ -14,17 +14,16 @@
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
from oslo.config import cfg
|
||||
from webob import exc
|
||||
|
||||
from nova.api.openstack.compute.contrib import baremetal_nodes
|
||||
from nova.api.openstack.compute.contrib import baremetal_nodes as b_nodes_v2
|
||||
from nova.api.openstack.compute.plugins.v3 import baremetal_nodes \
|
||||
as b_nodes_v21
|
||||
from nova.api.openstack import extensions
|
||||
from nova import context
|
||||
from nova import test
|
||||
from nova.tests.virt.ironic import utils as ironic_utils
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class FakeRequest(object):
|
||||
|
||||
@@ -64,18 +63,19 @@ def fake_node_ext_status(**updates):
|
||||
FAKE_IRONIC_CLIENT = ironic_utils.FakeClient()
|
||||
|
||||
|
||||
@mock.patch.object(baremetal_nodes, '_get_ironic_client',
|
||||
@mock.patch.object(b_nodes_v21, '_get_ironic_client',
|
||||
lambda *_: FAKE_IRONIC_CLIENT)
|
||||
class BareMetalNodesTest(test.NoDBTestCase):
|
||||
|
||||
class BareMetalNodesTestV21(test.NoDBTestCase):
|
||||
def setUp(self):
|
||||
super(BareMetalNodesTest, self).setUp()
|
||||
super(BareMetalNodesTestV21, self).setUp()
|
||||
|
||||
self.ext_mgr = self.mox.CreateMock(extensions.ExtensionManager)
|
||||
self._setup()
|
||||
self.context = context.get_admin_context()
|
||||
self.controller = baremetal_nodes.BareMetalNodeController(self.ext_mgr)
|
||||
self.request = FakeRequest(self.context)
|
||||
|
||||
def _setup(self):
|
||||
self.controller = b_nodes_v21.BareMetalNodeController()
|
||||
|
||||
@mock.patch.object(FAKE_IRONIC_CLIENT.node, 'list')
|
||||
def test_index_ironic(self, mock_list):
|
||||
properties = {'cpus': 2, 'memory_mb': 1024, 'local_gb': 20}
|
||||
@@ -149,3 +149,11 @@ class BareMetalNodesTest(test.NoDBTestCase):
|
||||
self.assertRaises(exc.HTTPBadRequest,
|
||||
self.controller._remove_interface,
|
||||
self.request, 'fake-id', 'fake-body')
|
||||
|
||||
|
||||
@mock.patch.object(b_nodes_v2, '_get_ironic_client',
|
||||
lambda *_: FAKE_IRONIC_CLIENT)
|
||||
class BareMetalNodesTestV2(BareMetalNodesTestV21):
|
||||
def _setup(self):
|
||||
self.ext_mgr = self.mox.CreateMock(extensions.ExtensionManager)
|
||||
self.controller = b_nodes_v2.BareMetalNodeController(self.ext_mgr)
|
||||
|
||||
@@ -138,6 +138,7 @@ policy_data = """
|
||||
"compute_extension:attach_interfaces": "",
|
||||
"compute_extension:v3:os-attach-interfaces": "",
|
||||
"compute_extension:baremetal_nodes": "",
|
||||
"compute_extension:v3:os-baremetal-nodes": "",
|
||||
"compute_extension:cells": "",
|
||||
"compute_extension:cells:create": "rule:admin_api",
|
||||
"compute_extension:cells:delete": "rule:admin_api",
|
||||
|
||||
@@ -63,6 +63,7 @@ nova.api.v3.extensions =
|
||||
aggregates = nova.api.openstack.compute.plugins.v3.aggregates:Aggregates
|
||||
attach_interfaces = nova.api.openstack.compute.plugins.v3.attach_interfaces:AttachInterfaces
|
||||
availability_zone = nova.api.openstack.compute.plugins.v3.availability_zone:AvailabilityZone
|
||||
baremetal_nodes = nova.api.openstack.compute.plugins.v3.baremetal_nodes:BareMetalNodes
|
||||
block_device_mapping = nova.api.openstack.compute.plugins.v3.block_device_mapping:BlockDeviceMapping
|
||||
cells = nova.api.openstack.compute.plugins.v3.cells:Cells
|
||||
certificates = nova.api.openstack.compute.plugins.v3.certificates:Certificates
|
||||
|
||||
Reference in New Issue
Block a user