mirror of
				https://github.com/ansible-collections/community.general.git
				synced 2025-10-25 05:23:58 -07:00 
			
		
		
		
	* Initial Commit
* Adding changelog fragment
* Ensured params are present during verbose output and enhanced check_mode
* Making specific to builtins
* Removing unneccessary external call
* Acutal bugfix
(cherry picked from commit d180390dbc)
Co-authored-by: Ajpantuso <ajpantuso@gmail.com>
	
	
This commit is contained in:
		
					parent
					
						
							
								48b1bc7d47
							
						
					
				
			
			
				commit
				
					
						0760f60ca5
					
				
			
		
					 3 changed files with 263 additions and 53 deletions
				
			
		|  | @ -0,0 +1,3 @@ | ||||||
|  | --- | ||||||
|  | bugfixes: | ||||||
|  |   - modprobe - added additional checks to ensure module load/unload is effective (https://github.com/ansible-collections/community.general/issues/1608). | ||||||
|  | @ -50,11 +50,90 @@ EXAMPLES = ''' | ||||||
| ''' | ''' | ||||||
| 
 | 
 | ||||||
| import os.path | import os.path | ||||||
|  | import platform | ||||||
| import shlex | import shlex | ||||||
| import traceback | import traceback | ||||||
| 
 | 
 | ||||||
| from ansible.module_utils.basic import AnsibleModule | from ansible.module_utils.basic import AnsibleModule | ||||||
| from ansible.module_utils._text import to_native | from ansible.module_utils.common.text.converters import to_native | ||||||
|  | 
 | ||||||
|  | RELEASE_VER = platform.release() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Modprobe(object): | ||||||
|  |     def __init__(self, module): | ||||||
|  |         self.module = module | ||||||
|  |         self.modprobe_bin = module.get_bin_path('modprobe', True) | ||||||
|  | 
 | ||||||
|  |         self.check_mode = module.check_mode | ||||||
|  |         self.desired_state = module.params['state'] | ||||||
|  |         self.name = module.params['name'] | ||||||
|  |         self.params = module.params['params'] | ||||||
|  | 
 | ||||||
|  |         self.changed = False | ||||||
|  | 
 | ||||||
|  |     def load_module(self): | ||||||
|  |         command = [self.modprobe_bin] | ||||||
|  |         if self.check_mode: | ||||||
|  |             command.append('-n') | ||||||
|  |         command.extend([self.name] + shlex.split(self.params)) | ||||||
|  | 
 | ||||||
|  |         rc, out, err = self.module.run_command(command) | ||||||
|  | 
 | ||||||
|  |         if rc != 0: | ||||||
|  |             return self.module.fail_json(msg=err, rc=rc, stdout=out, stderr=err, **self.result) | ||||||
|  | 
 | ||||||
|  |         if self.check_mode or self.module_loaded(): | ||||||
|  |             self.changed = True | ||||||
|  |         else: | ||||||
|  |             rc, stdout, stderr = self.module.run_command( | ||||||
|  |                 [self.modprobe_bin, '-n', '--first-time', self.name] + shlex.split(self.params) | ||||||
|  |             ) | ||||||
|  |             if rc != 0: | ||||||
|  |                 self.module.warn(stderr) | ||||||
|  | 
 | ||||||
|  |     def module_loaded(self): | ||||||
|  |         is_loaded = False | ||||||
|  |         try: | ||||||
|  |             with open('/proc/modules') as modules: | ||||||
|  |                 module_name = self.name.replace('-', '_') + ' ' | ||||||
|  |                 for line in modules: | ||||||
|  |                     if line.startswith(module_name): | ||||||
|  |                         is_loaded = True | ||||||
|  |                         break | ||||||
|  | 
 | ||||||
|  |             if not is_loaded: | ||||||
|  |                 module_file = '/' + self.name + '.ko' | ||||||
|  |                 builtin_path = os.path.join('/lib/modules/', RELEASE_VER, 'modules.builtin') | ||||||
|  |                 with open(builtin_path) as builtins: | ||||||
|  |                     for line in builtins: | ||||||
|  |                         if line.rstrip().endswith(module_file): | ||||||
|  |                             is_loaded = True | ||||||
|  |                             break | ||||||
|  |         except (IOError, OSError) as e: | ||||||
|  |             self.module.fail_json(msg=to_native(e), exception=traceback.format_exc(), **self.result) | ||||||
|  | 
 | ||||||
|  |         return is_loaded | ||||||
|  | 
 | ||||||
|  |     def unload_module(self): | ||||||
|  |         command = [self.modprobe_bin, '-r', self.name] | ||||||
|  |         if self.check_mode: | ||||||
|  |             command.append('-n') | ||||||
|  | 
 | ||||||
|  |         rc, out, err = self.module.run_command(command) | ||||||
|  |         if rc != 0: | ||||||
|  |             return self.module.fail_json(msg=err, rc=rc, stdout=out, stderr=err, **self.result) | ||||||
|  | 
 | ||||||
|  |         self.changed = True | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def result(self): | ||||||
|  |         return { | ||||||
|  |             'changed': self.changed, | ||||||
|  |             'name': self.name, | ||||||
|  |             'params': self.params, | ||||||
|  |             'state': self.desired_state, | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def main(): | def main(): | ||||||
|  | @ -67,60 +146,14 @@ def main(): | ||||||
|         supports_check_mode=True, |         supports_check_mode=True, | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     name = module.params['name'] |     modprobe = Modprobe(module) | ||||||
|     params = module.params['params'] |  | ||||||
|     state = module.params['state'] |  | ||||||
| 
 | 
 | ||||||
|     # FIXME: Adding all parameters as result values is useless |     if modprobe.desired_state == 'present' and not modprobe.module_loaded(): | ||||||
|     result = dict( |         modprobe.load_module() | ||||||
|         changed=False, |     elif modprobe.desired_state == 'absent' and modprobe.module_loaded(): | ||||||
|         name=name, |         modprobe.unload_module() | ||||||
|         params=params, |  | ||||||
|         state=state, |  | ||||||
|     ) |  | ||||||
| 
 | 
 | ||||||
|     # Check if module is present |     module.exit_json(**modprobe.result) | ||||||
|     try: |  | ||||||
|         present = False |  | ||||||
|         with open('/proc/modules') as modules: |  | ||||||
|             module_name = name.replace('-', '_') + ' ' |  | ||||||
|             for line in modules: |  | ||||||
|                 if line.startswith(module_name): |  | ||||||
|                     present = True |  | ||||||
|                     break |  | ||||||
|         if not present: |  | ||||||
|             command = [module.get_bin_path('uname', True), '-r'] |  | ||||||
|             rc, uname_kernel_release, err = module.run_command(command) |  | ||||||
|             module_file = '/' + name + '.ko' |  | ||||||
|             builtin_path = os.path.join('/lib/modules/', uname_kernel_release.strip(), |  | ||||||
|                                         'modules.builtin') |  | ||||||
|             with open(builtin_path) as builtins: |  | ||||||
|                 for line in builtins: |  | ||||||
|                     if line.endswith(module_file): |  | ||||||
|                         present = True |  | ||||||
|                         break |  | ||||||
|     except IOError as e: |  | ||||||
|         module.fail_json(msg=to_native(e), exception=traceback.format_exc(), **result) |  | ||||||
| 
 |  | ||||||
|     # Add/remove module as needed |  | ||||||
|     if state == 'present': |  | ||||||
|         if not present: |  | ||||||
|             if not module.check_mode: |  | ||||||
|                 command = [module.get_bin_path('modprobe', True), name] |  | ||||||
|                 command.extend(shlex.split(params)) |  | ||||||
|                 rc, out, err = module.run_command(command) |  | ||||||
|                 if rc != 0: |  | ||||||
|                     module.fail_json(msg=err, rc=rc, stdout=out, stderr=err, **result) |  | ||||||
|             result['changed'] = True |  | ||||||
|     elif state == 'absent': |  | ||||||
|         if present: |  | ||||||
|             if not module.check_mode: |  | ||||||
|                 rc, out, err = module.run_command([module.get_bin_path('modprobe', True), '-r', name]) |  | ||||||
|                 if rc != 0: |  | ||||||
|                     module.fail_json(msg=err, rc=rc, stdout=out, stderr=err, **result) |  | ||||||
|             result['changed'] = True |  | ||||||
| 
 |  | ||||||
|     module.exit_json(**result) |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == '__main__': | ||||||
|  |  | ||||||
							
								
								
									
										174
									
								
								tests/unit/plugins/modules/system/test_modprobe.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								tests/unit/plugins/modules/system/test_modprobe.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,174 @@ | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) | ||||||
|  | 
 | ||||||
|  | from __future__ import (absolute_import, division, print_function) | ||||||
|  | __metaclass__ = type | ||||||
|  | 
 | ||||||
|  | from ansible_collections.community.general.tests.unit.plugins.modules.utils import ModuleTestCase, set_module_args | ||||||
|  | from ansible_collections.community.general.tests.unit.compat.mock import patch | ||||||
|  | from ansible_collections.community.general.tests.unit.compat.mock import Mock | ||||||
|  | from ansible.module_utils.basic import AnsibleModule | ||||||
|  | from ansible_collections.community.general.plugins.modules.system.modprobe import Modprobe | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class TestLoadModule(ModuleTestCase): | ||||||
|  |     def setUp(self): | ||||||
|  |         super(TestLoadModule, self).setUp() | ||||||
|  | 
 | ||||||
|  |         self.mock_module_loaded = patch( | ||||||
|  |             'ansible_collections.community.general.plugins.modules.system.modprobe.Modprobe.module_loaded' | ||||||
|  |         ) | ||||||
|  |         self.mock_run_command = patch('ansible.module_utils.basic.AnsibleModule.run_command') | ||||||
|  |         self.mock_get_bin_path = patch('ansible.module_utils.basic.AnsibleModule.get_bin_path') | ||||||
|  | 
 | ||||||
|  |         self.module_loaded = self.mock_module_loaded.start() | ||||||
|  |         self.run_command = self.mock_run_command.start() | ||||||
|  |         self.get_bin_path = self.mock_get_bin_path.start() | ||||||
|  | 
 | ||||||
|  |     def tearDown(self): | ||||||
|  |         """Teardown.""" | ||||||
|  |         super(TestLoadModule, self).tearDown() | ||||||
|  |         self.mock_module_loaded.stop() | ||||||
|  |         self.mock_run_command.stop() | ||||||
|  |         self.mock_get_bin_path.stop() | ||||||
|  | 
 | ||||||
|  |     def test_load_module_success(self): | ||||||
|  |         set_module_args(dict( | ||||||
|  |             name='test', | ||||||
|  |             state='present', | ||||||
|  |         )) | ||||||
|  | 
 | ||||||
|  |         module = AnsibleModule( | ||||||
|  |             argument_spec=dict( | ||||||
|  |                 name=dict(type='str', required=True), | ||||||
|  |                 state=dict(type='str', default='present', choices=['absent', 'present']), | ||||||
|  |                 params=dict(type='str', default=''), | ||||||
|  |             ), | ||||||
|  |             supports_check_mode=True, | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         self.get_bin_path.side_effect = ['modprobe'] | ||||||
|  |         self.module_loaded.side_effect = [True] | ||||||
|  |         self.run_command.side_effect = [(0, '', '')] | ||||||
|  | 
 | ||||||
|  |         modprobe = Modprobe(module) | ||||||
|  |         modprobe.load_module() | ||||||
|  | 
 | ||||||
|  |         assert modprobe.result == { | ||||||
|  |             'changed': True, | ||||||
|  |             'name': 'test', | ||||||
|  |             'params': '', | ||||||
|  |             'state': 'present', | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     def test_load_module_unchanged(self): | ||||||
|  |         set_module_args(dict( | ||||||
|  |             name='test', | ||||||
|  |             state='present', | ||||||
|  |         )) | ||||||
|  | 
 | ||||||
|  |         module = AnsibleModule( | ||||||
|  |             argument_spec=dict( | ||||||
|  |                 name=dict(type='str', required=True), | ||||||
|  |                 state=dict(type='str', default='present', choices=['absent', 'present']), | ||||||
|  |                 params=dict(type='str', default=''), | ||||||
|  |             ), | ||||||
|  |             supports_check_mode=True, | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         module.warn = Mock() | ||||||
|  | 
 | ||||||
|  |         self.get_bin_path.side_effect = ['modprobe'] | ||||||
|  |         self.module_loaded.side_effect = [False] | ||||||
|  |         self.run_command.side_effect = [(0, '', ''), (1, '', '')] | ||||||
|  | 
 | ||||||
|  |         modprobe = Modprobe(module) | ||||||
|  |         modprobe.load_module() | ||||||
|  | 
 | ||||||
|  |         module.warn.assert_called_once_with('') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class TestUnloadModule(ModuleTestCase): | ||||||
|  |     def setUp(self): | ||||||
|  |         super(TestUnloadModule, self).setUp() | ||||||
|  | 
 | ||||||
|  |         self.mock_module_loaded = patch( | ||||||
|  |             'ansible_collections.community.general.plugins.modules.system.modprobe.Modprobe.module_loaded' | ||||||
|  |         ) | ||||||
|  |         self.mock_run_command = patch('ansible.module_utils.basic.AnsibleModule.run_command') | ||||||
|  |         self.mock_get_bin_path = patch('ansible.module_utils.basic.AnsibleModule.get_bin_path') | ||||||
|  | 
 | ||||||
|  |         self.module_loaded = self.mock_module_loaded.start() | ||||||
|  |         self.run_command = self.mock_run_command.start() | ||||||
|  |         self.get_bin_path = self.mock_get_bin_path.start() | ||||||
|  | 
 | ||||||
|  |     def tearDown(self): | ||||||
|  |         """Teardown.""" | ||||||
|  |         super(TestUnloadModule, self).tearDown() | ||||||
|  |         self.mock_module_loaded.stop() | ||||||
|  |         self.mock_run_command.stop() | ||||||
|  |         self.mock_get_bin_path.stop() | ||||||
|  | 
 | ||||||
|  |     def test_unload_module_success(self): | ||||||
|  |         set_module_args(dict( | ||||||
|  |             name='test', | ||||||
|  |             state='absent', | ||||||
|  |         )) | ||||||
|  | 
 | ||||||
|  |         module = AnsibleModule( | ||||||
|  |             argument_spec=dict( | ||||||
|  |                 name=dict(type='str', required=True), | ||||||
|  |                 state=dict(type='str', default='present', choices=['absent', 'present']), | ||||||
|  |                 params=dict(type='str', default=''), | ||||||
|  |             ), | ||||||
|  |             supports_check_mode=True, | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         self.get_bin_path.side_effect = ['modprobe'] | ||||||
|  |         self.module_loaded.side_effect = [False] | ||||||
|  |         self.run_command.side_effect = [(0, '', '')] | ||||||
|  | 
 | ||||||
|  |         modprobe = Modprobe(module) | ||||||
|  |         modprobe.unload_module() | ||||||
|  | 
 | ||||||
|  |         assert modprobe.result == { | ||||||
|  |             'changed': True, | ||||||
|  |             'name': 'test', | ||||||
|  |             'params': '', | ||||||
|  |             'state': 'absent', | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     def test_unload_module_failure(self): | ||||||
|  |         set_module_args(dict( | ||||||
|  |             name='test', | ||||||
|  |             state='absent', | ||||||
|  |         )) | ||||||
|  | 
 | ||||||
|  |         module = AnsibleModule( | ||||||
|  |             argument_spec=dict( | ||||||
|  |                 name=dict(type='str', required=True), | ||||||
|  |                 state=dict(type='str', default='present', choices=['absent', 'present']), | ||||||
|  |                 params=dict(type='str', default=''), | ||||||
|  |             ), | ||||||
|  |             supports_check_mode=True, | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         module.fail_json = Mock() | ||||||
|  | 
 | ||||||
|  |         self.get_bin_path.side_effect = ['modprobe'] | ||||||
|  |         self.module_loaded.side_effect = [True] | ||||||
|  |         self.run_command.side_effect = [(1, '', '')] | ||||||
|  | 
 | ||||||
|  |         modprobe = Modprobe(module) | ||||||
|  |         modprobe.unload_module() | ||||||
|  | 
 | ||||||
|  |         dummy_result = { | ||||||
|  |             'changed': False, | ||||||
|  |             'name': 'test', | ||||||
|  |             'state': 'absent', | ||||||
|  |             'params': '', | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         module.fail_json.assert_called_once_with( | ||||||
|  |             msg='', rc=1, stdout='', stderr='', **dummy_result | ||||||
|  |         ) | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue