Skip to content

Commit

Permalink
Porting baremetal_nodes extension to v2.1/v3
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Eli Qiao committed Oct 21, 2014
1 parent 9fd059b commit dd2f76a
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 10 deletions.
2 changes: 2 additions & 0 deletions etc/nova/policy.json
Expand Up @@ -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",
Expand Down
173 changes: 173 additions & 0 deletions nova/api/openstack/compute/plugins/v3/baremetal_nodes.py
@@ -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 []
6 changes: 6 additions & 0 deletions nova/api/validation/parameter_types.py
Expand Up @@ -100,3 +100,9 @@
},
'additionalProperties': False
}


mac_address = {
'type': 'string',
'pattern': '^([0-9a-fA-F]{2})(:[0-9a-fA-F]{2}){5}$'
}
28 changes: 18 additions & 10 deletions nova/tests/api/openstack/compute/contrib/test_baremetal_nodes.py
Expand Up @@ -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):

Expand Down Expand Up @@ -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}
Expand Down Expand Up @@ -149,3 +149,11 @@ def test_remove_interface_ironic_not_supported(self):
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)
1 change: 1 addition & 0 deletions nova/tests/fake_policy.py
Expand Up @@ -138,6 +138,7 @@
"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",
Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Expand Up @@ -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
Expand Down

0 comments on commit dd2f76a

Please sign in to comment.