Merge "Merge of container_facts modules"
This commit is contained in:
commit
9dd323e6f2
@ -1,4 +1,5 @@
|
|||||||
# Copyright 2016 99cloud
|
# Copyright 2016 99cloud
|
||||||
|
# Copyright 2023 StackHPC
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@ -45,7 +46,7 @@ options:
|
|||||||
- The action to perform
|
- The action to perform
|
||||||
required: True
|
required: True
|
||||||
type: str
|
type: str
|
||||||
author: Jeffrey Zhang
|
author: Jeffrey Zhang, Michal Nasiadka
|
||||||
'''
|
'''
|
||||||
|
|
||||||
EXAMPLES = '''
|
EXAMPLES = '''
|
||||||
@ -76,6 +77,18 @@ EXAMPLES = '''
|
|||||||
name:
|
name:
|
||||||
- glance_api
|
- glance_api
|
||||||
action: get_containers_env
|
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'])
|
envs = self._remap_envs(cont['Config']['Env'])
|
||||||
self.result['envs'][name] = envs
|
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):
|
class DockerFactsWorker(ContainerFactsWorker):
|
||||||
def __init__(self, module):
|
def __init__(self, module):
|
||||||
@ -188,7 +215,8 @@ def main():
|
|||||||
action=dict(required=True, type='str',
|
action=dict(required=True, type='str',
|
||||||
choices=['get_containers',
|
choices=['get_containers',
|
||||||
'get_containers_env',
|
'get_containers_env',
|
||||||
'get_containers_state']),
|
'get_containers_state',
|
||||||
|
'get_volumes']),
|
||||||
)
|
)
|
||||||
|
|
||||||
required_if = [
|
required_if = [
|
||||||
|
@ -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()
|
|
@ -12,8 +12,9 @@
|
|||||||
|
|
||||||
- name: Get container volume facts
|
- name: Get container volume facts
|
||||||
become: true
|
become: true
|
||||||
kolla_container_volume_facts:
|
kolla_container_facts:
|
||||||
container_engine: "{{ kolla_container_engine }}"
|
container_engine: "{{ kolla_container_engine }}"
|
||||||
|
action: get_volumes
|
||||||
name:
|
name:
|
||||||
- ovn_nb_db
|
- ovn_nb_db
|
||||||
- ovn_sb_db
|
- ovn_sb_db
|
||||||
@ -30,7 +31,7 @@
|
|||||||
assert:
|
assert:
|
||||||
that:
|
that:
|
||||||
- neutron_plugin_agent == 'openvswitch'
|
- neutron_plugin_agent == 'openvswitch'
|
||||||
- container_volume_facts['ovn_nb_db'] is not defined
|
- container_volume_facts.volumes['ovn_nb_db'] is not defined
|
||||||
- container_volume_facts['ovn_sb_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."
|
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
|
when: container_facts.containers['neutron_openvswitch_agent'] is defined
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
---
|
---
|
||||||
- name: Checking for any existing OVN DB container volumes
|
- name: Checking for any existing OVN DB container volumes
|
||||||
become: true
|
become: true
|
||||||
kolla_container_volume_facts:
|
kolla_container_facts:
|
||||||
container_engine: "{{ kolla_container_engine }}"
|
container_engine: "{{ kolla_container_engine }}"
|
||||||
|
action: get_volumes
|
||||||
name:
|
name:
|
||||||
- ovn_nb_db
|
- ovn_nb_db
|
||||||
- ovn_sb_db
|
- ovn_sb_db
|
||||||
@ -10,12 +11,12 @@
|
|||||||
|
|
||||||
- name: Divide hosts by their OVN NB volume availability
|
- name: Divide hosts by their OVN NB volume availability
|
||||||
group_by:
|
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
|
changed_when: false
|
||||||
|
|
||||||
- name: Divide hosts by their OVN SB volume availability
|
- name: Divide hosts by their OVN SB volume availability
|
||||||
group_by:
|
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
|
changed_when: false
|
||||||
|
|
||||||
- name: Establish whether the OVN NB cluster has already existed
|
- name: Establish whether the OVN NB cluster has already existed
|
||||||
|
@ -65,7 +65,23 @@ FAKE_DATA = {
|
|||||||
'Volumes': {'/var/lib/kolla/config_files/': {}}},
|
'Volumes': {'/var/lib/kolla/config_files/': {}}},
|
||||||
'Mounts': {},
|
'Mounts': {},
|
||||||
'NetworkSettings': {}
|
'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
|
return dfw
|
||||||
|
|
||||||
|
|
||||||
def construct_container(cont_dict):
|
def construct_container(cont_dict) -> mock.Mock:
|
||||||
container = mock.Mock()
|
container = mock.Mock()
|
||||||
container.name = cont_dict['Name']
|
container.name = cont_dict['Name']
|
||||||
container.attrs = copy.deepcopy(cont_dict)
|
container.attrs = copy.deepcopy(cont_dict)
|
||||||
@ -85,6 +101,14 @@ def construct_container(cont_dict):
|
|||||||
return container
|
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):
|
def get_containers(override=None):
|
||||||
if override:
|
if override:
|
||||||
cont_dicts = override
|
cont_dicts = override
|
||||||
@ -100,6 +124,15 @@ def get_containers(override=None):
|
|||||||
return containers
|
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):
|
class TestContainerFacts(base.BaseTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestContainerFacts, self).setUp()
|
super(TestContainerFacts, self).setUp()
|
||||||
@ -202,3 +235,78 @@ class TestContainerFacts(base.BaseTestCase):
|
|||||||
'fake_container')
|
'fake_container')
|
||||||
self.dfw.module.fail_json.assert_called_once_with(
|
self.dfw.module.fail_json.assert_called_once_with(
|
||||||
msg="No such container: fake_container")
|
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'])
|
||||||
|
Loading…
x
Reference in New Issue
Block a user