From 32cdbd23d5dee8dbfe22570b61981f3e7000aa68 Mon Sep 17 00:00:00 2001 From: Ivan Halomi Date: Mon, 11 Mar 2024 15:29:17 +0100 Subject: [PATCH] Merge of container_facts modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge container_facts and container_volume_facts into a single module for retrieving all information about containers and volumes. Change-Id: I5d321b8326edd7f3b7a11dbdc821e534f457f9d7 Signed-off-by: Ivan Halomi Signed-off-by: Roman KrĨek --- ansible/library/kolla_container_facts.py | 32 ++++- .../library/kolla_container_volume_facts.py | 108 ----------------- .../tasks/neutron_plugin_agent_check.yml | 7 +- ansible/roles/ovn-db/tasks/lookup_cluster.yml | 7 +- tests/test_kolla_container_facts.py | 112 +++++++++++++++++- 5 files changed, 148 insertions(+), 118 deletions(-) delete mode 100644 ansible/library/kolla_container_volume_facts.py diff --git a/ansible/library/kolla_container_facts.py b/ansible/library/kolla_container_facts.py index 041aa39044..18c194c9fd 100644 --- a/ansible/library/kolla_container_facts.py +++ b/ansible/library/kolla_container_facts.py @@ -1,4 +1,5 @@ # Copyright 2016 99cloud +# Copyright 2023 StackHPC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -45,7 +46,7 @@ options: - The action to perform required: True type: str -author: Jeffrey Zhang +author: Jeffrey Zhang, Michal Nasiadka ''' EXAMPLES = ''' @@ -76,6 +77,18 @@ EXAMPLES = ''' name: - glance_api action: get_containers_env + + - name: Gather glance volume facts + kolla_container_facts: + container_engine: docker + name: + - glance_api + action: get_volumes + + - name: Gather all volume facts + kolla_container_facts: + container_engine: docker + action: get_volumes ''' @@ -150,6 +163,20 @@ class ContainerFactsWorker(): envs = self._remap_envs(cont['Config']['Env']) self.result['envs'][name] = envs + def get_volumes(self): + """Handles when module is called with action get_volumes.""" + names = self.params.get('name') + self.result['volumes'] = dict() + + if isinstance(names, str): + names = [names] + + volumes = self.client.volumes.list() + for volume in volumes: + if names and volume.name not in names: + continue + self.result['volumes'][volume.name] = volume.attrs + class DockerFactsWorker(ContainerFactsWorker): def __init__(self, module): @@ -188,7 +215,8 @@ def main(): action=dict(required=True, type='str', choices=['get_containers', 'get_containers_env', - 'get_containers_state']), + 'get_containers_state', + 'get_volumes']), ) required_if = [ diff --git a/ansible/library/kolla_container_volume_facts.py b/ansible/library/kolla_container_volume_facts.py deleted file mode 100644 index a5aba0c9e1..0000000000 --- a/ansible/library/kolla_container_volume_facts.py +++ /dev/null @@ -1,108 +0,0 @@ -# Copyright 2023 StackHPC -# -# 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 ansible.module_utils.basic import AnsibleModule - -DOCUMENTATION = ''' ---- -module: kolla_container_volume_facts -short_description: Module for collecting container volume facts -description: - - A module targeted at collecting container volume facts. It is used - for detecting whether the container volume exists on a host. -options: - container_engine: - description: - - Name of container engine to use - required: True - type: str - api_version: - description: - - The version of the api for docker-py to use when contacting docker - required: False - type: str - default: auto - name: - description: - - Name or names of the container volumes - required: False - type: str or list -author: Jeffrey Zhang / Michal Nasiadka -''' - -EXAMPLES = ''' -- hosts: all - tasks: - - name: Gather docker facts - kolla_container_volume_facts: - - - name: Gather glance container facts - kolla_container_volume_facts: - container_engine: docker - name: - - glance_api - - glance_registry -''' - - -def get_docker_client(): - import docker - return docker.APIClient - - -def get_docker_volumes(api_version): - client = get_docker_client()(version=api_version) - return client.volumes()['Volumes'] - - -def get_podman_volumes(): - from podman import PodmanClient - - client = PodmanClient(base_url="http+unix:/run/podman/podman.sock") - volumes = [] - for volume in client.volumes.list(): - volumes.append(volume.attrs) - return volumes - - -def main(): - argument_spec = dict( - name=dict(required=False, type='list', default=[]), - api_version=dict(required=False, type='str', default='auto'), - container_engine=dict(required=True, type='str') - ) - - module = AnsibleModule(argument_spec=argument_spec) - - results = dict(changed=False, _volumes=[]) - if module.params.get('container_engine') == 'docker': - volumes = get_docker_volumes(module.params.get('api_version')) - else: - volumes = get_podman_volumes() - - names = module.params.get('name') - if names and not isinstance(names, list): - names = [names] - for volume in volumes: - volume_name = volume['Name'] - if names and volume_name not in names: - continue - results['_volumes'].append(volume) - results[volume_name] = volume - module.exit_json(**results) - - -if __name__ == "__main__": - main() diff --git a/ansible/roles/neutron/tasks/neutron_plugin_agent_check.yml b/ansible/roles/neutron/tasks/neutron_plugin_agent_check.yml index 76b8e440df..0f89a07890 100644 --- a/ansible/roles/neutron/tasks/neutron_plugin_agent_check.yml +++ b/ansible/roles/neutron/tasks/neutron_plugin_agent_check.yml @@ -12,8 +12,9 @@ - name: Get container volume facts become: true - kolla_container_volume_facts: + kolla_container_facts: container_engine: "{{ kolla_container_engine }}" + action: get_volumes name: - ovn_nb_db - ovn_sb_db @@ -30,7 +31,7 @@ assert: that: - neutron_plugin_agent == 'openvswitch' - - container_volume_facts['ovn_nb_db'] is not defined - - container_volume_facts['ovn_sb_db'] is not defined + - container_volume_facts.volumes['ovn_nb_db'] is not defined + - container_volume_facts.volumes['ovn_sb_db'] is not defined fail_msg: "ML2/OVS agent detected, neutron_plugin_agent is not set to 'openvswitch', Kolla-Ansible does not support this migration operation." when: container_facts.containers['neutron_openvswitch_agent'] is defined diff --git a/ansible/roles/ovn-db/tasks/lookup_cluster.yml b/ansible/roles/ovn-db/tasks/lookup_cluster.yml index d97d358c77..86b4aa13af 100644 --- a/ansible/roles/ovn-db/tasks/lookup_cluster.yml +++ b/ansible/roles/ovn-db/tasks/lookup_cluster.yml @@ -1,8 +1,9 @@ --- - name: Checking for any existing OVN DB container volumes become: true - kolla_container_volume_facts: + kolla_container_facts: container_engine: "{{ kolla_container_engine }}" + action: get_volumes name: - ovn_nb_db - ovn_sb_db @@ -10,12 +11,12 @@ - name: Divide hosts by their OVN NB volume availability group_by: - key: "ovn-nb-db_had_volume_{{ ovn_db_container_volume_facts['ovn_nb_db'] is defined }}" + key: "ovn-nb-db_had_volume_{{ ovn_db_container_volume_facts.volumes['ovn_nb_db'] is defined }}" changed_when: false - name: Divide hosts by their OVN SB volume availability group_by: - key: "ovn-sb-db_had_volume_{{ ovn_db_container_volume_facts['ovn_sb_db'] is defined }}" + key: "ovn-sb-db_had_volume_{{ ovn_db_container_volume_facts.volumes['ovn_sb_db'] is defined }}" changed_when: false - name: Establish whether the OVN NB cluster has already existed diff --git a/tests/test_kolla_container_facts.py b/tests/test_kolla_container_facts.py index 81e6a50095..f1fc59f3e1 100644 --- a/tests/test_kolla_container_facts.py +++ b/tests/test_kolla_container_facts.py @@ -65,7 +65,23 @@ FAKE_DATA = { 'Volumes': {'/var/lib/kolla/config_files/': {}}}, 'Mounts': {}, 'NetworkSettings': {} - } + }, + 'volumes': [ + {'CreatedAt': '2024-10-10T12:05:46+02:00', + 'Driver': 'local', + 'Labels': None, + 'Mountpoint': '/var/lib/docker/volumes/my_volume/_data', + 'Name': 'my_volume', + 'Options': None, + 'Scope': 'local'}, + {'CreatedAt': '2023-05-01T14:55:36+02:00', + 'Driver': 'local', + 'Labels': None, + 'Mountpoint': '/var/lib/docker/volumes/another_volume/_data', + 'Name': 'another_volume', + 'Options': None, + 'Scope': 'local'} + ] } @@ -77,7 +93,7 @@ def get_DockerFactsWorker(mod_param, mock_client): return dfw -def construct_container(cont_dict): +def construct_container(cont_dict) -> mock.Mock: container = mock.Mock() container.name = cont_dict['Name'] container.attrs = copy.deepcopy(cont_dict) @@ -85,6 +101,14 @@ def construct_container(cont_dict): return container +def contruct_volume(vol_dict: dict) -> mock.Mock: + """Creates volume object that mimics the output of a container client.""" + volume = mock.Mock() + volume.name = vol_dict['Name'] + volume.attrs = copy.deepcopy(vol_dict) + return volume + + def get_containers(override=None): if override: cont_dicts = override @@ -100,6 +124,15 @@ def get_containers(override=None): return containers +def get_volumes(override=None): + if override: + vol_dicts = override + else: + vol_dicts = copy.deepcopy(FAKE_DATA['volumes']) + + return [contruct_volume(v) for v in vol_dicts] + + class TestContainerFacts(base.BaseTestCase): def setUp(self): super(TestContainerFacts, self).setUp() @@ -202,3 +235,78 @@ class TestContainerFacts(base.BaseTestCase): 'fake_container') self.dfw.module.fail_json.assert_called_once_with( msg="No such container: fake_container") + + def test_get_volumes_single(self): + """Test fetching a single volume""" + self.dfw = get_DockerFactsWorker( + {'name': ['my_volume'], 'action': 'get_volumes'}) + full_vol_list = get_volumes(self.fake_data['volumes']) + self.dfw.client.volumes.list.return_value = full_vol_list + + self.dfw.get_volumes() + + self.assertFalse(self.dfw.result['changed']) + self.dfw.client.volumes.list.assert_called_once_with() + self.assertIn('my_volume', self.dfw.result['volumes']) + self.assertNotIn('another_volume', self.dfw.result['volumes']) + self.assertEqual(len(self.dfw.result['volumes']), 1) + self.assertDictEqual( + self.dfw.result['volumes']['my_volume'], + self.fake_data['volumes'][0]) + + def test_get_volumes_multiple(self): + """Test fetching multiple volumes""" + self.dfw = get_DockerFactsWorker({ + 'name': ['my_volume', 'another_volume'], + 'action': 'get_volumes'}) + full_vol_list = get_volumes(self.fake_data['volumes']) + self.dfw.client.volumes.list.return_value = full_vol_list + + self.dfw.get_volumes() + + self.assertFalse(self.dfw.result['changed']) + self.dfw.client.volumes.list.assert_called_once_with() + self.assertIn('my_volume', self.dfw.result['volumes']) + self.assertIn('another_volume', self.dfw.result['volumes']) + self.assertEqual(len(self.dfw.result['volumes']), 2) + self.assertDictEqual( + self.dfw.result['volumes']['my_volume'], + self.fake_data['volumes'][0]) + self.assertDictEqual( + self.dfw.result['volumes']['another_volume'], + self.fake_data['volumes'][1]) + + def test_get_volumes_all(self): + """Test fetching all volumes when no specific names are provided""" + self.dfw = get_DockerFactsWorker({'name': [], + 'action': 'get_volumes'}) + full_vol_list = get_volumes(self.fake_data['volumes']) + self.dfw.client.volumes.list.return_value = full_vol_list + + self.dfw.get_volumes() + + self.assertFalse(self.dfw.result['changed']) + self.dfw.client.volumes.list.assert_called_once_with() + self.assertIn('my_volume', self.dfw.result['volumes']) + self.assertIn('another_volume', self.dfw.result['volumes']) + self.assertDictEqual( + self.dfw.result['volumes']['my_volume'], + self.fake_data['volumes'][0]) + self.assertDictEqual( + self.dfw.result['volumes']['another_volume'], + self.fake_data['volumes'][1]) + + def test_get_volumes_negative(self): + """Test fetching a volume that doesn't exist""" + self.dfw = get_DockerFactsWorker({'name': ['nonexistent_volume'], + 'action': 'get_volumes'}) + full_vol_list = get_volumes(self.fake_data['volumes']) + self.dfw.client.volumes.list.return_value = full_vol_list + + self.dfw.get_volumes() + + self.assertFalse(self.dfw.result['changed']) + self.dfw.client.volumes.list.assert_called_once_with() + self.assertIn('volumes', self.dfw.result) + self.assertEqual(len(self.dfw.result['volumes']), 0) + self.assertNotIn('nonexistent_volume', self.dfw.result['volumes'])