Add Ironic datasource driver
This patch adds a datasource driver to congress that integrates with ironic. This driver exports the following tables with ironic data: chassises, nodes, node_properties, ports, drivers, active_hosts Implements blueprint: ironic-datasource-driver Change-Id: I55046d2bd1d483c05a4760343d9a5af30dfb0c94
This commit is contained in:
parent
f72698b397
commit
229ad26ff8
|
@ -4,6 +4,7 @@ Congress.tokens
|
|||
/congress/policy/CongressParser.py
|
||||
subunit.log
|
||||
congress/tests/policy_engines/snapshot/test
|
||||
congress/tests/policy/snapshot/test
|
||||
/doc/html
|
||||
|
||||
*.py[cod]
|
||||
|
|
|
@ -0,0 +1,199 @@
|
|||
# Copyright (c) 2015 Intel 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.
|
||||
#
|
||||
from ironicclient import client
|
||||
|
||||
from congress.datasources.datasource_driver import DataSourceDriver
|
||||
from congress.datasources import datasource_utils
|
||||
from congress.openstack.common import log as logging
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def d6service(name, keys, inbox, datapath, args):
|
||||
"""This method is called by d6cage to create a dataservice instance."""
|
||||
return IronicDriver(name, keys, inbox, datapath, args)
|
||||
|
||||
|
||||
class IronicDriver(DataSourceDriver):
|
||||
CHASSISES = "chassises"
|
||||
NODES = "nodes"
|
||||
NODEPROPERTIES = "node_properties"
|
||||
PORTS = "ports"
|
||||
DRIVERS = "drivers"
|
||||
ACTIVEHOSTS = "active_hosts"
|
||||
|
||||
# This is the most common per-value translator, so define it once here.
|
||||
value_trans = {'translation-type': 'VALUE'}
|
||||
|
||||
def safe_id(x):
|
||||
if isinstance(x, basestring):
|
||||
return x
|
||||
try:
|
||||
return x['id']
|
||||
except KeyError:
|
||||
return str(x)
|
||||
|
||||
def safe_port_extra(x):
|
||||
try:
|
||||
return x['vif_port_id']
|
||||
except KeyError:
|
||||
return ""
|
||||
|
||||
chassises_translator = {
|
||||
'translation-type': 'HDICT',
|
||||
'table-name': CHASSISES,
|
||||
'selector-type': 'DOT_SELECTOR',
|
||||
'field-translators':
|
||||
({'fieldname': 'uuid', 'col': 'id', 'translator': value_trans},
|
||||
{'fieldname': 'created_at', 'translator': value_trans},
|
||||
{'fieldname': 'updated_at', 'translator': value_trans})}
|
||||
|
||||
nodes_translator = {
|
||||
'translation-type': 'HDICT',
|
||||
'table-name': NODES,
|
||||
'selector-type': 'DOT_SELECTOR',
|
||||
'field-translators':
|
||||
({'fieldname': 'uuid', 'col': 'id', 'translator': value_trans},
|
||||
{'fieldname': 'chassis_uuid', 'col': 'owner_chassis',
|
||||
'translator': value_trans},
|
||||
{'fieldname': 'power_state', 'translator': value_trans},
|
||||
{'fieldname': 'maintenance', 'translator': value_trans},
|
||||
{'fieldname': 'properties', 'translator':
|
||||
{'translation-type': 'HDICT',
|
||||
'table-name': 'node_properties',
|
||||
'parent-key': 'id',
|
||||
'parent-col-name': 'properties',
|
||||
'selector-type': 'DICT_SELECTOR',
|
||||
'in-list': False,
|
||||
'field-translators':
|
||||
({'fieldname': 'memory_mb',
|
||||
'translator': value_trans},
|
||||
{'fieldname': 'cpu_arch',
|
||||
'translator': value_trans},
|
||||
{'fieldname': 'local_gb',
|
||||
'translator': value_trans},
|
||||
{'fieldname': 'cpus',
|
||||
'translator': value_trans})}},
|
||||
{'fieldname': 'driver', 'translator': value_trans},
|
||||
{'fieldname': 'instance_uuid', 'col': 'running_instance',
|
||||
'translator': value_trans},
|
||||
{'fieldname': 'created_at', 'translator': value_trans},
|
||||
{'fieldname': 'provision_updated_at', 'translator': value_trans},
|
||||
{'fieldname': 'updated_at', 'translator': value_trans})}
|
||||
|
||||
ports_translator = {
|
||||
'translation-type': 'HDICT',
|
||||
'table-name': PORTS,
|
||||
'selector-type': 'DOT_SELECTOR',
|
||||
'field-translators':
|
||||
({'fieldname': 'uuid', 'col': 'id', 'translator': value_trans},
|
||||
{'fieldname': 'node_uuid', 'col': 'owner_node',
|
||||
'translator': value_trans},
|
||||
{'fieldname': 'address', 'col': 'mac_address',
|
||||
'translator': value_trans},
|
||||
{'fieldname': 'extra', 'col': 'vif_port_id', 'translator':
|
||||
{'translation-type': 'VALUE',
|
||||
'extract-fn': safe_port_extra}},
|
||||
{'fieldname': 'created_at', 'translator': value_trans},
|
||||
{'fieldname': 'updated_at', 'translator': value_trans})}
|
||||
|
||||
drivers_translator = {
|
||||
'translation-type': 'HDICT',
|
||||
'table-name': DRIVERS,
|
||||
'selector-type': 'DOT_SELECTOR',
|
||||
'field-translators':
|
||||
({'fieldname': 'name', 'translator': value_trans},
|
||||
{'fieldname': 'hosts', 'translator':
|
||||
{'translation-type': 'LIST',
|
||||
'table-name': 'active_hosts',
|
||||
'parent-key': 'name',
|
||||
'parent-col-name': 'name',
|
||||
'val-col': 'hosts',
|
||||
'translator':
|
||||
{'translation-type': 'VALUE'}}})}
|
||||
|
||||
TRANSLATORS = [chassises_translator, nodes_translator, ports_translator,
|
||||
drivers_translator]
|
||||
|
||||
def __init__(self, name='', keys='', inbox=None, datapath=None, args=None):
|
||||
super(IronicDriver, self).__init__(name, keys, inbox, datapath, args)
|
||||
creds = datasource_utils.get_credentials(name, args)
|
||||
self.creds = self.get_ironic_credentials(name, creds)
|
||||
self.ironic_client = client.get_client(**self.creds)
|
||||
|
||||
self.initialized = True
|
||||
|
||||
@staticmethod
|
||||
def get_datasource_info():
|
||||
result = {}
|
||||
result['id'] = 'ironic'
|
||||
result['description'] = ('Datasource driver that interfaces with '
|
||||
'OpenStack bare metal aka ironic.')
|
||||
result['config'] = datasource_utils.get_openstack_required_config()
|
||||
return result
|
||||
|
||||
def get_ironic_credentials(self, name, args):
|
||||
creds = datasource_utils.get_credentials(name, args)
|
||||
d = {}
|
||||
d['api_version'] = '1'
|
||||
d['os_username'] = creds['username']
|
||||
d['os_password'] = creds['password']
|
||||
d['os_auth_url'] = creds['auth_url']
|
||||
d['os_tenant_name'] = creds['tenant_name']
|
||||
d['insecure'] = False
|
||||
return d
|
||||
|
||||
def update_from_datasource(self):
|
||||
self.state = {}
|
||||
chassises = self.ironic_client.chassis.list(
|
||||
detail=True, limit=0)
|
||||
self._translate_chassises(chassises)
|
||||
self._translate_nodes(self.ironic_client.node.list(detail=True,
|
||||
limit=0))
|
||||
self._translate_ports(self.ironic_client.port.list(detail=True,
|
||||
limit=0))
|
||||
self._translate_drivers(self.ironic_client.driver.list())
|
||||
|
||||
def _translate_chassises(self, obj):
|
||||
row_data = IronicDriver.convert_objs(obj,
|
||||
IronicDriver.chassises_translator)
|
||||
self.state[self.CHASSISES] = set()
|
||||
for table, row in row_data:
|
||||
assert table == self.CHASSISES
|
||||
self.state[table].add(row)
|
||||
|
||||
def _translate_nodes(self, obj):
|
||||
row_data = IronicDriver.convert_objs(obj,
|
||||
IronicDriver.nodes_translator)
|
||||
self.state[self.NODES] = set()
|
||||
self.state[self.NODEPROPERTIES] = set()
|
||||
for table, row in row_data:
|
||||
self.state[table].add(row)
|
||||
|
||||
def _translate_ports(self, obj):
|
||||
row_data = IronicDriver.convert_objs(obj,
|
||||
IronicDriver.ports_translator)
|
||||
self.state[self.PORTS] = set()
|
||||
for table, row in row_data:
|
||||
assert table == self.PORTS
|
||||
self.state[table].add(row)
|
||||
|
||||
def _translate_drivers(self, obj):
|
||||
row_data = IronicDriver.convert_objs(obj,
|
||||
IronicDriver.drivers_translator)
|
||||
self.state[self.DRIVERS] = set()
|
||||
self.state[self.ACTIVEHOSTS] = set()
|
||||
for table, row in row_data:
|
||||
self.state[table].add(row)
|
|
@ -0,0 +1,187 @@
|
|||
# Copyright (c) 2014 VMware, Inc. 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.
|
||||
#
|
||||
import contextlib
|
||||
|
||||
import ironicclient.v1.chassis as IrChassis
|
||||
import ironicclient.v1.driver as IrDriver
|
||||
import ironicclient.v1.node as IrNode
|
||||
import ironicclient.v1.port as IrPort
|
||||
import mock
|
||||
|
||||
from congress.datasources import ironic_driver
|
||||
from congress.tests import base
|
||||
from congress.tests.datasources import test_datasource_driver_config
|
||||
from congress.tests import helper
|
||||
|
||||
|
||||
class TestIronicDataSourceDriverConfig(
|
||||
base.TestCase,
|
||||
test_datasource_driver_config.TestDataSourceDriverConfig):
|
||||
def setUp(self):
|
||||
super(TestIronicDataSourceDriverConfig, self).setUp()
|
||||
self.driver_obj = ironic_driver.IronicDriver
|
||||
|
||||
|
||||
class TestIronicDriver(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestIronicDriver, self).setUp()
|
||||
self.ironic_client_p = mock.patch("ironicclient.client.get_client")
|
||||
self.ironic_client_p.start()
|
||||
|
||||
args = helper.datasource_openstack_args()
|
||||
args['poll_time'] = 0
|
||||
args['client'] = mock.MagicMock()
|
||||
|
||||
self.driver = ironic_driver.IronicDriver(args=args)
|
||||
|
||||
self.mock_chassis = {"chassis": [
|
||||
{"uuid": "89a15e07-5c80-48a4-b440-9c61ddb7e652",
|
||||
"extra": {},
|
||||
"created_at": "2015-01-13T06:52:01+00:00",
|
||||
"updated_at": None,
|
||||
"description": "ironic test chassis"}]}
|
||||
|
||||
self.mock_nodes = {"nodes": [
|
||||
{"instance_uuid": "2520745f-b4da-4e10-9d32-84451cfa8b33",
|
||||
"uuid": "9cf035f0-351c-43d5-8968-f9fe2c41787b",
|
||||
"chassis_uuid": "89a15e07-5c80-48a4-b440-9c61ddb7e652",
|
||||
"properties": {"memory_mb": "512", "cpu_arch": "x86_64",
|
||||
"local_gb": "10", "cpus": "1"},
|
||||
"driver": "pxe_ssh",
|
||||
"maintenance": False,
|
||||
"console_enabled": False,
|
||||
"created_at": "2015-01-13T06:52:02+00:00",
|
||||
"updated_at": "2015-02-10T07:55:23+00:00",
|
||||
"provision_updated_at": "2015-01-13T07:55:24+00:00",
|
||||
"provision_state": "active",
|
||||
"power_state": "power on"},
|
||||
{"instance_uuid": None,
|
||||
"uuid": "7a95ebf5-f213-4427-b669-010438f43e87",
|
||||
"chassis_uuid": "89a15e07-5c80-48a4-b440-9c61ddb7e652",
|
||||
"properties": {"memory_mb": "512", "cpu_arch": "x86_64",
|
||||
"local_gb": "10", "cpus": "1"},
|
||||
"driver": "pxe_ssh",
|
||||
"maintenance": False,
|
||||
"console_enabled": False,
|
||||
"created_at": "2015-01-13T06:52:04+00:00",
|
||||
"updated_at": "2015-02-10T07:55:24+00:00",
|
||||
"provision_updated_at": None,
|
||||
"provision_state": None,
|
||||
"power_state": "power off"}]}
|
||||
|
||||
self.mock_ports = {"ports": [
|
||||
{"uuid": "43190aae-d5fe-444f-9d50-155fca4bad82",
|
||||
"node_uuid": "9cf035f0-351c-43d5-8968-f9fe2c41787b",
|
||||
"extra": {"vif_port_id": "9175f72b-5783-4cea-8ae0-55df69fee568"},
|
||||
"created_at": "2015-01-13T06:52:03+00:00",
|
||||
"updated_at": "2015-01-30T03:17:23+00:00",
|
||||
"address": "52:54:00:7f:e7:2e"},
|
||||
{"uuid": "49f3205a-db1e-4497-9371-6011ef572981",
|
||||
"node_uuid": "7a95ebf5-f213-4427-b669-010438f43e87",
|
||||
"extra": {},
|
||||
"created_at": "2015-01-13T06:52:05+00:00",
|
||||
"updated_at": None,
|
||||
"address": "52:54:00:98:f2:4e"}]}
|
||||
|
||||
self.mock_drivers = {"drivers": [
|
||||
{"hosts": ["localhost"], "name": "pxe_ssh"},
|
||||
{"hosts": ["localhost"], "name": "pxe_ipmitool"},
|
||||
{"hosts": ["localhost"], "name": "fake"}]}
|
||||
|
||||
self.expected_state = {
|
||||
'drivers': set([
|
||||
('pxe_ipmitool',),
|
||||
('fake',),
|
||||
('pxe_ssh',)]),
|
||||
'node_properties': set([
|
||||
('7a95ebf5-f213-4427-b669-010438f43e87',
|
||||
'512', 'x86_64', '10', '1'),
|
||||
('9cf035f0-351c-43d5-8968-f9fe2c41787b',
|
||||
'512', 'x86_64', '10', '1')]),
|
||||
'chassises': set([
|
||||
('89a15e07-5c80-48a4-b440-9c61ddb7e652',
|
||||
'2015-01-13T06:52:01+00:00', 'None')]),
|
||||
'active_hosts': set([
|
||||
('pxe_ipmitool', 'localhost'),
|
||||
('pxe_ssh', 'localhost'),
|
||||
('fake', 'localhost')]),
|
||||
'nodes': set([
|
||||
('9cf035f0-351c-43d5-8968-f9fe2c41787b',
|
||||
'89a15e07-5c80-48a4-b440-9c61ddb7e652',
|
||||
'power on',
|
||||
'False',
|
||||
'pxe_ssh',
|
||||
'2520745f-b4da-4e10-9d32-84451cfa8b33',
|
||||
'2015-01-13T06:52:02+00:00',
|
||||
'2015-01-13T07:55:24+00:00',
|
||||
'2015-02-10T07:55:23+00:00'),
|
||||
('7a95ebf5-f213-4427-b669-010438f43e87',
|
||||
'89a15e07-5c80-48a4-b440-9c61ddb7e652',
|
||||
'power off',
|
||||
'False',
|
||||
'pxe_ssh',
|
||||
'None',
|
||||
'2015-01-13T06:52:04+00:00',
|
||||
'None',
|
||||
'2015-02-10T07:55:24+00:00')]),
|
||||
'ports': set([
|
||||
('49f3205a-db1e-4497-9371-6011ef572981',
|
||||
'7a95ebf5-f213-4427-b669-010438f43e87',
|
||||
'52:54:00:98:f2:4e', '',
|
||||
'2015-01-13T06:52:05+00:00', 'None'),
|
||||
('43190aae-d5fe-444f-9d50-155fca4bad82',
|
||||
'9cf035f0-351c-43d5-8968-f9fe2c41787b',
|
||||
'52:54:00:7f:e7:2e',
|
||||
'9175f72b-5783-4cea-8ae0-55df69fee568',
|
||||
'2015-01-13T06:52:03+00:00',
|
||||
'2015-01-30T03:17:23+00:00')])
|
||||
}
|
||||
|
||||
def mock_value(self, mock_data, key, obj_class):
|
||||
data = mock_data[key]
|
||||
return [obj_class(self, res, loaded=True) for res in data if res]
|
||||
|
||||
def test_driver_called(self):
|
||||
self.assertIsNotNone(self.driver.ironic_client)
|
||||
|
||||
def test_update_from_datasource(self):
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.driver.ironic_client.chassis,
|
||||
"list",
|
||||
return_value=self.mock_value(self.mock_chassis,
|
||||
"chassis",
|
||||
IrChassis.Chassis)),
|
||||
mock.patch.object(self.driver.ironic_client.node,
|
||||
"list",
|
||||
return_value=self.mock_value(self.mock_nodes,
|
||||
"nodes",
|
||||
IrNode.Node)),
|
||||
mock.patch.object(self.driver.ironic_client.port,
|
||||
"list",
|
||||
return_value=self.mock_value(self.mock_ports,
|
||||
"ports",
|
||||
IrPort.Port)),
|
||||
mock.patch.object(self.driver.ironic_client.driver,
|
||||
"list",
|
||||
return_value=self.mock_value(self.mock_drivers,
|
||||
"drivers",
|
||||
IrDriver.Driver)),
|
||||
) as (self.driver.ironic_client.chassis.list,
|
||||
self.driver.ironic_client.node.list,
|
||||
self.driver.ironic_client.port.list,
|
||||
self.driver.ironic_client.driver.list):
|
||||
self.driver.update_from_datasource()
|
||||
self.assertEqual(self.driver.state, self.expected_state)
|
|
@ -121,7 +121,8 @@ function configure_congress {
|
|||
CONGRESS_DRIVERS+="congress.datasources.cinder_driver.CinderDriver,"
|
||||
CONGRESS_DRIVERS+="congress.datasources.swift_driver.SwiftDriver,"
|
||||
CONGRESS_DRIVERS+="congress.datasources.plexxi_driver.PlexxiDriver,"
|
||||
CONGRESS_DRIVERS+="congress.datasources.vCenter_driver.VCenterDriver"
|
||||
CONGRESS_DRIVERS+="congress.datasources.vCenter_driver.VCenterDriver,"
|
||||
CONGRESS_DRIVERS+="congress.datasources.ironic_driver.IronicDriver"
|
||||
# FIXME(arosen): congress does not yet have the murano client in requirements.txt
|
||||
# so we can't yet load it.
|
||||
#CONGRESS_DRIVERS+="congress.datasources.murano_driver.MuranoDriver"
|
||||
|
@ -148,6 +149,7 @@ function configure_congress_datasources {
|
|||
#_configure_service swift swift
|
||||
_configure_service glance glancev2
|
||||
_configure_service murano murano
|
||||
_configure_service ironic ironic
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ python-neutronclient>=2.3.6,<3
|
|||
python-ceilometerclient>=1.0.6
|
||||
python-cinderclient>=1.1.0
|
||||
python-swiftclient>=2.2.0
|
||||
python-ironicclient>=0.2.1
|
||||
alembic>=0.7.2
|
||||
python-glanceclient>=0.15.0
|
||||
Routes>=1.12.3,!=2.0
|
||||
|
|
Loading…
Reference in New Issue