Modify Ironic driver to support multi-podm arch

This commit adds support for Ironic driver to work in
multi-podm architecture

Change-Id: I09e2be5eb3a2651d6f960ac5610fca89335e5b5b
This commit is contained in:
Anusha Ramineni 2017-08-30 11:00:49 +05:30 committed by akhiljain23
parent 0104b783ba
commit ecd965149b
9 changed files with 116 additions and 88 deletions

View File

@ -45,12 +45,12 @@ class Node(Resource):
def get(self, node_uuid):
return utils.make_response(
http_client.OK,
nodes.Node(node_id=node_uuid).get_composed_node_by_uuid(node_uuid))
nodes.Node(node_id=node_uuid).get_composed_node_by_uuid())
def delete(self, node_uuid):
return utils.make_response(
http_client.OK,
nodes.Node(node_id=node_uuid).delete_composed_node(node_uuid))
nodes.Node(node_id=node_uuid).delete_composed_node())
class NodeAction(Resource):
@ -59,8 +59,7 @@ class NodeAction(Resource):
def post(self, node_uuid):
return utils.make_response(
http_client.NO_CONTENT,
nodes.Node(node_id=node_uuid).node_action(node_uuid,
request.get_json()))
nodes.Node(node_id=node_uuid).node_action(request.get_json()))
class NodeManage(Resource):

View File

@ -151,7 +151,7 @@ class Node(object):
return self._show_node_brief_info(composed_node)
def get_composed_node_by_uuid(self, node_uuid):
def get_composed_node_by_uuid(self):
"""Get composed node details
Get the detail of specific composed node. In some cases db data may be
@ -159,7 +159,6 @@ class Node(object):
through valence api. So compare it with node info from redfish, and
update db if it's inconsistent.
param node_uuid: uuid of composed node
return: detail of this composed node
"""
@ -169,15 +168,14 @@ class Node(object):
node_hw.update(self.node)
return node_hw
def delete_composed_node(self, node_uuid):
def delete_composed_node(self):
"""Delete a composed node
param node_uuid: uuid of composed node
return: message of this deletion
"""
# Call podmanager to delete node, and delete corresponding entry in db
message = self.connection.delete_composed_node(self.node['index'])
db_api.Connection.delete_composed_node(node_uuid)
db_api.Connection.delete_composed_node(self.node['uuid'])
return message
@ -190,7 +188,7 @@ class Node(object):
return [cls._show_node_brief_info(node.as_dict())
for node in db_api.Connection.list_composed_nodes(filters)]
def node_action(self, node_uuid, request_body):
def node_action(self, request_body):
"""Post action to a composed node
param node_uuid: uuid of composed node

View File

@ -17,13 +17,15 @@ from valence.redfish.sushy import sushy_instance
class PodManagerBase(object):
def __init__(self, username, password, podm_url):
def __init__(self, username, password, podm_url, **kwargs):
self.podm_url = podm_url
self.username = username
self.password = password
self.driver = sushy_instance.RedfishInstance(username=username,
password=password,
base_url=podm_url)
# TODO(ramineni): rebase on nate's patch
# TODO(): use rsd_lib here
def get_status(self):
pass
@ -62,6 +64,28 @@ class PodManagerBase(object):
def get_all_devices(self):
pass
def get_ironic_node_params(self, node_info, **param):
# TODO(): change to 'rsd' once ironic driver is implemented.
driver_info = {
'redfish_address': self.podm_url,
'redfish_username': self.username,
'redfish_password': self.password,
'redfish_system_id': node_info['computer_system']
}
node_args = {}
if param and param.get('driver_info', None):
driver_info.update(param.pop('driver_info'))
node_args.update({'driver': 'redfish', 'name': node_info['name'],
'driver_info': driver_info})
port_args = {'address': node_info['metadata']['network'][0]['mac']}
# update any remaining params passed
if param:
node_args.update(param)
return node_args, port_args
def get_resource_info_by_url(self, resource_url):
return self.driver.get_resources_by_url(resource_url)

View File

@ -33,48 +33,37 @@ class IronicDriver(driver.ProvisioningDriver):
def __init__(self):
super(IronicDriver, self).__init__()
self.ironic = utils.create_ironicclient()
def node_register(self, node_uuid, param):
LOG.debug('Registering node %s with ironic' % node_uuid)
node_info = nodes.Node.get_composed_node_by_uuid(node_uuid)
node_controller = nodes.Node(node_id=node_uuid)
try:
ironic = utils.create_ironicclient()
except Exception as e:
message = ('Error occurred while communicating to '
'Ironic: %s' % six.text_type(e))
LOG.error(message)
raise exception.ValenceException(message)
try:
# NOTE(mkrai): Below implementation will be changed in future to
# support the multiple pod manager in which we access pod managers'
# detail from podm object associated with a node.
driver_info = {
'redfish_address': CONF.podm.url,
'redfish_username': CONF.podm.username,
'redfish_password': CONF.podm.password,
'redfish_verify_ca': CONF.podm.verify_ca,
'redfish_system_id': node_info['computer_system']}
node_args = {}
if param:
if param.get('driver_info', None):
driver_info.update(param.get('driver_info'))
del param['driver_info']
node_args.update({'driver': 'redfish', 'name': node_info['name'],
'driver_info': driver_info})
if param:
node_args.update(param)
ironic_node = ironic.node.create(**node_args)
port_args = {'node_uuid': ironic_node.uuid,
'address': node_info['metadata']['network'][0]['mac']}
ironic.port.create(**port_args)
db_api.Connection.update_composed_node(node_uuid,
{'managed_by': 'ironic'})
return exception.confirmation(
confirm_code="Node Registered",
confirm_detail="The composed node {0} has been registered "
"with Ironic successfully.".format(node_uuid))
node_info = node_controller.get_composed_node_by_uuid()
node_args, port_args = (
node_controller.connection.get_ironic_node_params(node_info,
**param))
ironic_node = self.ironic.node.create(**node_args)
except Exception as e:
message = ('Unexpected error while registering node with '
'Ironic: %s' % six.text_type(e))
LOG.error(message)
raise exception.ValenceException(message)
node_info = db_api.Connection.update_composed_node(
node_uuid, {'managed_by': 'ironic'}).as_dict()
if port_args:
# If MAC provided, create ports, else skip
port_args['node_uuid'] = ironic_node.uuid
self.ironic_port_create(**port_args)
return node_info
def ironic_port_create(self, **port):
try:
self.ironic.port.create(**port)
LOG.debug('Successfully created ironic ports %s', port)
except Exception as e:
LOG.debug("Ironic port creation failed with error %s", str(e))

View File

@ -13,7 +13,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
from valence.common import clients
from valence.common import exception
LOG = logging.getLogger(__name__)
def create_ironicclient():
@ -21,5 +26,10 @@ def create_ironicclient():
:returns: Ironic client object
"""
osc = clients.OpenStackClients()
return osc.ironic()
try:
osc = clients.OpenStackClients()
return osc.ironic()
except Exception:
message = ('Error occurred while communicating to Ironic')
LOG.exception(message)
raise exception.ValenceException(message)

View File

@ -30,6 +30,7 @@ class TestAPINodes(unittest.TestCase):
@mock.patch('valence.redfish.sushy.sushy_instance.RedfishInstance')
def setUp(self, mock_redfish, mock_connection):
self.node_controller = nodes.Node(podm_id='test-podm-1')
self.node_controller.node = test_utils.get_test_composed_node_db_info()
self.node_controller.connection = podm_base.PodManagerBase(
'fake', 'fake-pass', 'http://fake-url')
@ -182,7 +183,7 @@ class TestAPINodes(unittest.TestCase):
self.node_controller.node = node_db
mock_redfish_get_node.return_value = node_hw
result = self.node_controller.get_composed_node_by_uuid("fake_uuid")
result = self.node_controller.get_composed_node_by_uuid()
expected = copy.deepcopy(node_hw)
expected.update(node_db)
self.assertEqual(expected, result)
@ -192,7 +193,7 @@ class TestAPINodes(unittest.TestCase):
"""Test delete composed node"""
node_db = test_utils.get_test_composed_node_db_info()
self.node_controller.node = node_db
self.node_controller.delete_composed_node(node_db["uuid"])
self.node_controller.delete_composed_node()
mock_db_delete_composed_node.assert_called_once_with(
node_db["uuid"])
@ -216,8 +217,8 @@ class TestAPINodes(unittest.TestCase):
"""Test reset composed node status"""
action = {"Reset": {"Type": "On"}}
self.node_controller.node = {'index': '1', 'name': 'test-node'}
self.node_controller.node_action("fake_uuid", action)
mock_node_action.assert_called_once_with("1", action)
self.node_controller.node_action(action)
mock_node_action.assert_called_once_with('1', action)
@mock.patch("valence.provision.driver.node_register")
def test_node_register(self, mock_node_register):

View File

@ -21,52 +21,52 @@ from valence.provision.ironic import driver
class TestDriver(base.BaseTestCase):
def setUp(self):
@mock.patch("valence.provision.ironic.utils.create_ironicclient")
def setUp(self, mock_ironic_client):
super(TestDriver, self).setUp()
self.ironic = driver.IronicDriver()
self.driver = driver.IronicDriver()
self.driver.ironic = mock.MagicMock()
def tearDown(self):
super(TestDriver, self).tearDown()
@mock.patch("valence.controller.nodes.Node.get_composed_node_by_uuid")
@mock.patch("valence.db.api.Connection.get_composed_node_by_uuid")
def test_node_register_node_not_found(self, mock_db):
mock_db.side_effect = exception.NotFound
mock_db.side_effect = exception.NotFound("node not found")
self.assertRaises(exception.NotFound,
self.ironic.node_register,
'fake-uuid', {})
@mock.patch("valence.controller.nodes.Node.get_composed_node_by_uuid")
@mock.patch("valence.provision.ironic.utils.create_ironicclient")
def test_node_register_ironic_client_failure(self, mock_client,
mock_db):
mock_client.side_effect = Exception()
self.assertRaises(exception.ValenceException,
self.ironic.node_register,
self.driver.node_register,
'fake-uuid', {})
@mock.patch('valence.redfish.sushy.sushy_instance.RedfishInstance')
@mock.patch("valence.db.api.Connection.update_composed_node")
@mock.patch("valence.controller.nodes.Node.get_composed_node_by_uuid")
@mock.patch("valence.provision.ironic.utils.create_ironicclient")
def test_node_register(self, mock_client,
mock_node_get, mock_node_update):
ironic = mock.MagicMock()
mock_client.return_value = ironic
mock_node_get.return_value = {
@mock.patch('valence.controller.nodes.Node')
def test_node_register(self, node_mock, mock_node_update, mock_sushy):
ironic = self.driver.ironic
node_mock.return_value = mock.MagicMock()
node_info = {
'id': 'fake-uuid', 'podm_id': 'fake-podm_id',
'index': '1',
'name': 'test', 'metadata':
{'network': [{'mac': 'fake-mac'}]},
'computer_system': '/redfish/v1/Systems/437XR1138R2'}
node_controller = node_mock.return_value
node_controller.get_composed_node_by_uuid.return_value = node_info
node_info.update({'managed_by': 'ironic'})
mock_node_update.return_value.as_dict.return_value = node_info
node_controller.connection = mock.MagicMock()
ironic.node.create.return_value = mock.MagicMock(uuid='ironic-uuid')
port_arg = {'node_uuid': 'ironic-uuid', 'address': 'fake-mac'}
resp = self.ironic.node_register('fake-uuid',
n_args = ({'driver_info': {'username': 'fake1'}}, {})
node_controller.connection.get_ironic_node_params.return_value = n_args
resp = self.driver.node_register('fake-uuid',
{"extra": {"foo": "bar"}})
self.assertEqual({
'code': 'Node Registered',
'detail': 'The composed node fake-uuid has been '
'registered with Ironic successfully.',
'request_id': '00000000-0000-0000-0000-000000000000'}, resp)
mock_client.assert_called_once()
mock_node_get.assert_called_once_with('fake-uuid')
self.assertEqual(node_info, resp)
node_mock.assert_called_once_with(node_id='fake-uuid')
mock_node_update.assert_called_once_with('fake-uuid',
{'managed_by': 'ironic'})
ironic.node.create.assert_called_once()
ironic.port.create.assert_called_once_with(**port_arg)
def test_ironic_port_create(self):
port_args = {'mac_address': 'fake-mac'}
self.driver.ironic_port_create(**port_args)
self.driver.ironic.port.create.assert_called_once()

View File

@ -15,6 +15,7 @@ import mock
from oslotest import base
from valence.common import exception
from valence.provision.ironic import utils
@ -27,3 +28,9 @@ class TestUtils(base.BaseTestCase):
ironic = utils.create_ironicclient()
self.assertTrue(ironic)
mock_ironic.assert_called_once_with()
@mock.patch('valence.common.clients.OpenStackClients.ironic')
def test_create_ironic_client_failure(self, mock_client):
mock_client.side_effect = Exception()
self.assertRaises(exception.ValenceException,
utils.create_ironicclient)

View File

@ -219,7 +219,7 @@ class TestNodeApi(TestApiValidation):
resp = self.app.post('/v1/nodes/fake-node/action',
content_type='application/json',
data=json.dumps(req))
mock_action.assert_called_once_with('fake-node', req)
mock_action.assert_called_once_with(req)
self.assertEqual(http_client.NO_CONTENT, resp.status_code)
@mock.patch('valence.controller.nodes.Node.node_action')
@ -235,7 +235,7 @@ class TestNodeApi(TestApiValidation):
resp = self.app.post('/v1/nodes/fake-node/action',
content_type='application/json',
data=json.dumps(req))
mock_action.assert_called_once_with('fake-node', req)
mock_action.assert_called_once_with(req)
self.assertEqual(http_client.NO_CONTENT, resp.status_code)
@mock.patch('valence.controller.nodes.Node.node_action')
@ -251,7 +251,7 @@ class TestNodeApi(TestApiValidation):
resp = self.app.post('/v1/nodes/fake-node/action',
content_type='application/json',
data=json.dumps(req))
mock_action.assert_called_once_with('fake-node', req)
mock_action.assert_called_once_with(req)
self.assertEqual(http_client.NO_CONTENT, resp.status_code)
@mock.patch('valence.db.api.Connection.get_composed_node_by_uuid')