diff --git a/README.md b/README.md index 8b0b2728..789f7f66 100644 --- a/README.md +++ b/README.md @@ -301,6 +301,7 @@ deployed then see file `actions.yaml`. * `disable` * `enable` * `hugepagereport` +* `list-compute-nodes` * `node-name` * `openstack-upgrade` * `pause` diff --git a/actions.yaml b/actions.yaml index 2c885164..934468b9 100644 --- a/actions.yaml +++ b/actions.yaml @@ -12,6 +12,8 @@ register-to-cloud: README.md, section 'Cloud downscaling'. openstack-upgrade: description: Perform openstack upgrades. Config option action-managed-upgrade must be set to True. +list-compute-nodes: + description: List all nova-compute nodes registered in the Openstack cloud. node-name: description: Return nova-compute node name. This can be used to identify this unit in the list of nova-compute services. pause: diff --git a/actions/cloud.py b/actions/cloud.py index f02fb78e..b704d595 100755 --- a/actions/cloud.py +++ b/actions/cloud.py @@ -14,6 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import json import os import sys from enum import Enum @@ -136,7 +137,17 @@ def register_to_cloud(): }) +def list_computes(): + """Implementation of `list-compute-nodes` action.""" + nova = cloud_utils.nova_client() + function_set({'node-name': cloud_utils.service_hostname()}) + computes = [service.to_dict() + for service in nova.services.list(binary='nova-compute')] + function_set({'compute-nodes': json.dumps(computes)}) + + def node_name(): + """Implementation of 'node-name' action.""" function_set({'node-name': cloud_utils.service_hostname()}) @@ -145,6 +156,7 @@ ACTIONS = { 'enable': enable, 'remove-from-cloud': remove_from_cloud, 'register-to-cloud': register_to_cloud, + 'list-compute-nodes': list_computes, 'node-name': node_name, } diff --git a/actions/list-compute-nodes b/actions/list-compute-nodes new file mode 120000 index 00000000..126acea1 --- /dev/null +++ b/actions/list-compute-nodes @@ -0,0 +1 @@ +cloud.py \ No newline at end of file diff --git a/unit_tests/test_actions_cloud.py b/unit_tests/test_actions_cloud.py index 81d89da7..77426636 100644 --- a/unit_tests/test_actions_cloud.py +++ b/unit_tests/test_actions_cloud.py @@ -11,15 +11,29 @@ # 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 json import sys from unittest import TestCase -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock, patch, call sys.modules['nova_compute_hooks'] = MagicMock() import cloud del sys.modules['nova_compute_hooks'] +class _MockComputeHost: + + def __init__(self, node_name, state='enabled', binary='nova-compute'): + self.node_name = node_name + self.state = state + self.binary = binary + + def to_dict(self): + return {'node_name': self.node_name, + 'state': self.state, + 'binary': self.binary} + + class _ActionTestCase(TestCase): NAME = '' @@ -233,6 +247,37 @@ class TestRegisterToCloud(_ActionTestCase): cloud.function_fail.assert_not_called() +class TestListComputeNodes(_ActionTestCase): + NAME = 'list-compute-nodes' + + MOCK_LIST = [_MockComputeHost('compute0'), + _MockComputeHost('compute1')] + + def setUp(self, to_mock=None): + super(TestListComputeNodes, self).setUp() + self.nova_client = MagicMock() + services = MagicMock() + services.list.return_value = self.MOCK_LIST + self.nova_client.services = services + + def test_list_compute_nodes(self): + """Test listing nov-compute services.""" + cloud.cloud_utils.nova_client.return_value = self.nova_client + expected_identity = 'compute-0' + cloud.cloud_utils.service_hostname.return_value = expected_identity + self.call_action() + + expected_nodes = [host.to_dict() for host in self.MOCK_LIST + if host.binary == 'nova-compute'] + + expected_calls = [call({'node-name': expected_identity}), + call({'compute-nodes': json.dumps(expected_nodes)})] + + self.nova_client.services.list.assert_called_with( + binary='nova-compute') + cloud.function_set.assert_has_calls(expected_calls) + + class TestNodeName(_ActionTestCase): NAME = 'node-name'