Merge of container_facts modules

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 <ivan.halomi@tietoevry.com>
Signed-off-by: Roman Krček <roman.krcek@tietoevry.com>
This commit is contained in:
Ivan Halomi 2024-03-11 15:29:17 +01:00 committed by Roman Krcek
parent 8f2781f25e
commit 32cdbd23d5
5 changed files with 148 additions and 118 deletions

View File

@ -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 = [

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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'])