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:
parent
0104b783ba
commit
ecd965149b
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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')
|
||||
|
Loading…
Reference in New Issue
Block a user