diff --git a/valence/api/v1/nodes.py b/valence/api/v1/nodes.py index 1cd95ba..90d9c5a 100644 --- a/valence/api/v1/nodes.py +++ b/valence/api/v1/nodes.py @@ -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): diff --git a/valence/controller/nodes.py b/valence/controller/nodes.py index 8906f7f..efad262 100644 --- a/valence/controller/nodes.py +++ b/valence/controller/nodes.py @@ -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 diff --git a/valence/podmanagers/podm_base.py b/valence/podmanagers/podm_base.py index 7492bc8..e131628 100644 --- a/valence/podmanagers/podm_base.py +++ b/valence/podmanagers/podm_base.py @@ -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) diff --git a/valence/provision/ironic/driver.py b/valence/provision/ironic/driver.py index 1e09edb..01ad733 100644 --- a/valence/provision/ironic/driver.py +++ b/valence/provision/ironic/driver.py @@ -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)) diff --git a/valence/provision/ironic/utils.py b/valence/provision/ironic/utils.py index ce118bf..e44dd63 100644 --- a/valence/provision/ironic/utils.py +++ b/valence/provision/ironic/utils.py @@ -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) diff --git a/valence/tests/unit/controller/test_nodes.py b/valence/tests/unit/controller/test_nodes.py index aff71a3..db863a4 100644 --- a/valence/tests/unit/controller/test_nodes.py +++ b/valence/tests/unit/controller/test_nodes.py @@ -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): diff --git a/valence/tests/unit/provision/ironic/test_driver.py b/valence/tests/unit/provision/ironic/test_driver.py index f4254e9..39f71c8 100644 --- a/valence/tests/unit/provision/ironic/test_driver.py +++ b/valence/tests/unit/provision/ironic/test_driver.py @@ -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() diff --git a/valence/tests/unit/provision/ironic/test_utils.py b/valence/tests/unit/provision/ironic/test_utils.py index 207a7b2..a2d4a93 100644 --- a/valence/tests/unit/provision/ironic/test_utils.py +++ b/valence/tests/unit/provision/ironic/test_utils.py @@ -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) diff --git a/valence/tests/unit/validation/test_validation.py b/valence/tests/unit/validation/test_validation.py index 852cdd6..8bbfc18 100644 --- a/valence/tests/unit/validation/test_validation.py +++ b/valence/tests/unit/validation/test_validation.py @@ -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')