mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Merge pull request #50657 from terminalmage/issue49392
Rewrite host.present state to allow for removal of entries not matching IP
This commit is contained in:
commit
a5c7639ecb
6 changed files with 401 additions and 48 deletions
|
@ -4,3 +4,11 @@ In Progress: Salt 2018.3.4 Release Notes
|
|||
|
||||
Version 2018.3.4 is an **unreleased** bugfix release for :ref:`2018.3.0 <release-2018-3-0>`.
|
||||
This release is still in progress and has not been released yet.
|
||||
|
||||
|
||||
State Changes
|
||||
=============
|
||||
|
||||
- The :py:func:`host.present <salt.states.host.present>` state can now remove
|
||||
the specified hostname from IPs not specified in the state. This can be done
|
||||
by setting the newly-added ``clean`` argument to ``True``.
|
||||
|
|
|
@ -5,6 +5,7 @@ Manage the information in the hosts file
|
|||
|
||||
# Import Python libs
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
import errno
|
||||
import os
|
||||
|
||||
# Import salt libs
|
||||
|
@ -22,7 +23,12 @@ def __get_hosts_filename():
|
|||
'''
|
||||
Return the path to the appropriate hosts file
|
||||
'''
|
||||
return __salt__['config.option']('hosts.file')
|
||||
try:
|
||||
return __context__['hosts.__get_hosts_filename']
|
||||
except KeyError:
|
||||
__context__['hosts.__get_hosts_filename'] = \
|
||||
__salt__['config.option']('hosts.file')
|
||||
return __context__['hosts.__get_hosts_filename']
|
||||
|
||||
|
||||
def _get_or_create_hostfile():
|
||||
|
@ -43,26 +49,35 @@ def _list_hosts():
|
|||
'''
|
||||
Return the hosts found in the hosts file in as an OrderedDict
|
||||
'''
|
||||
count = 0
|
||||
hfn = __get_hosts_filename()
|
||||
ret = odict.OrderedDict()
|
||||
if not os.path.isfile(hfn):
|
||||
try:
|
||||
return __context__['hosts._list_hosts']
|
||||
except KeyError:
|
||||
count = 0
|
||||
hfn = __get_hosts_filename()
|
||||
ret = odict.OrderedDict()
|
||||
try:
|
||||
with salt.utils.files.fopen(hfn) as ifile:
|
||||
for line in ifile:
|
||||
line = salt.utils.stringutils.to_unicode(line).strip()
|
||||
if not line:
|
||||
continue
|
||||
if line.startswith('#'):
|
||||
ret.setdefault('comment-{0}'.format(count), []).append(line)
|
||||
count += 1
|
||||
continue
|
||||
if '#' in line:
|
||||
line = line[:line.index('#')].strip()
|
||||
comps = line.split()
|
||||
ip = comps.pop(0)
|
||||
ret.setdefault(ip, []).extend(comps)
|
||||
except (IOError, OSError) as exc:
|
||||
salt.utils.files.process_read_exception(exc, hfn, ignore=errno.ENOENT)
|
||||
# Don't set __context__ since we weren't able to read from the
|
||||
# hosts file.
|
||||
return ret
|
||||
|
||||
__context__['hosts._list_hosts'] = ret
|
||||
return ret
|
||||
with salt.utils.files.fopen(hfn) as ifile:
|
||||
for line in ifile:
|
||||
line = salt.utils.stringutils.to_unicode(line).strip()
|
||||
if not line:
|
||||
continue
|
||||
if line.startswith('#'):
|
||||
ret.setdefault('comment-{0}'.format(count), []).append(line)
|
||||
count += 1
|
||||
continue
|
||||
if '#' in line:
|
||||
line = line[:line.index('#')].strip()
|
||||
comps = line.split()
|
||||
ip = comps.pop(0)
|
||||
ret.setdefault(ip, []).extend(comps)
|
||||
return ret
|
||||
|
||||
|
||||
def list_hosts():
|
||||
|
@ -133,7 +148,10 @@ def has_pair(ip, alias):
|
|||
salt '*' hosts.has_pair <ip> <alias>
|
||||
'''
|
||||
hosts = _list_hosts()
|
||||
return ip in hosts and alias in hosts[ip]
|
||||
try:
|
||||
return alias in hosts[ip]
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
|
||||
def set_host(ip, alias):
|
||||
|
@ -157,6 +175,9 @@ def set_host(ip, alias):
|
|||
if not os.path.isfile(hfn):
|
||||
return False
|
||||
|
||||
# Make sure future calls to _list_hosts() will re-read the file
|
||||
__context__.pop('hosts._list_hosts', None)
|
||||
|
||||
line_to_add = salt.utils.stringutils.to_bytes(
|
||||
ip + '\t\t' + alias + os.linesep
|
||||
)
|
||||
|
@ -203,6 +224,8 @@ def rm_host(ip, alias):
|
|||
'''
|
||||
if not has_pair(ip, alias):
|
||||
return True
|
||||
# Make sure future calls to _list_hosts() will re-read the file
|
||||
__context__.pop('hosts._list_hosts', None)
|
||||
hfn = _get_or_create_hostfile()
|
||||
with salt.utils.files.fopen(hfn, 'rb') as fp_:
|
||||
lines = fp_.readlines()
|
||||
|
@ -251,6 +274,10 @@ def add_host(ip, alias):
|
|||
return True
|
||||
|
||||
hosts = _list_hosts()
|
||||
|
||||
# Make sure future calls to _list_hosts() will re-read the file
|
||||
__context__.pop('hosts._list_hosts', None)
|
||||
|
||||
inserted = False
|
||||
for i, h in six.iteritems(hosts):
|
||||
for j in range(len(h)):
|
||||
|
|
|
@ -67,7 +67,7 @@ from salt.ext import six
|
|||
import salt.utils.validate.net
|
||||
|
||||
|
||||
def present(name, ip): # pylint: disable=C0103
|
||||
def present(name, ip, clean=False): # pylint: disable=C0103
|
||||
'''
|
||||
Ensures that the named host is present with the given ip
|
||||
|
||||
|
@ -75,36 +75,92 @@ def present(name, ip): # pylint: disable=C0103
|
|||
The host to assign an ip to
|
||||
|
||||
ip
|
||||
The ip addr(s) to apply to the host
|
||||
The ip addr(s) to apply to the host. Can be a single IP or a list of IP
|
||||
addresses.
|
||||
|
||||
clean : False
|
||||
Remove any entries which don't match those configured in the ``ip``
|
||||
option.
|
||||
|
||||
.. versionadded:: 2018.3.4
|
||||
'''
|
||||
ret = {'name': name,
|
||||
'changes': {},
|
||||
'result': None,
|
||||
'result': None if __opts__['test'] else True,
|
||||
'comment': ''}
|
||||
|
||||
if not isinstance(ip, list):
|
||||
ip = [ip]
|
||||
|
||||
all_hosts = __salt__['hosts.list_hosts']()
|
||||
comments = []
|
||||
for _ip in ip:
|
||||
if __salt__['hosts.has_pair'](_ip, name):
|
||||
ret['result'] = True
|
||||
comments.append('Host {0} ({1}) already present'.format(name, _ip))
|
||||
to_add = set()
|
||||
to_remove = set()
|
||||
|
||||
# First check for IPs not currently in the hosts file
|
||||
to_add.update([(addr, name) for addr in ip if addr not in all_hosts])
|
||||
|
||||
# Now sweep through the hosts file and look for entries matching either the
|
||||
# IP address(es) or hostname.
|
||||
for addr, aliases in six.iteritems(all_hosts):
|
||||
if addr not in ip:
|
||||
if name in aliases:
|
||||
# Found match for hostname, but the corresponding IP is not in
|
||||
# our list, so we need to remove it.
|
||||
if clean:
|
||||
to_remove.add((addr, name))
|
||||
else:
|
||||
ret.setdefault('warnings', []).append(
|
||||
'Host {0} present for IP address {1}. To get rid of '
|
||||
'this warning, either run this state with \'clean\' '
|
||||
'set to True to remove {0} from {1}, or add {1} to '
|
||||
'the \'ip\' argument.'.format(name, addr)
|
||||
)
|
||||
else:
|
||||
if __opts__['test']:
|
||||
comments.append('Host {0} ({1}) needs to be added/updated'.format(name, _ip))
|
||||
if name in aliases:
|
||||
# No changes needed for this IP address and hostname
|
||||
comments.append(
|
||||
'Host {0} ({1}) already present'.format(name, addr)
|
||||
)
|
||||
else:
|
||||
if salt.utils.validate.net.ipv4_addr(_ip) or salt.utils.validate.net.ipv6_addr(_ip):
|
||||
if __salt__['hosts.add_host'](_ip, name):
|
||||
ret['changes'] = {'host': name}
|
||||
ret['result'] = True
|
||||
comments.append('Added host {0} ({1})'.format(name, _ip))
|
||||
else:
|
||||
ret['result'] = False
|
||||
comments.append('Failed to set host')
|
||||
# IP address listed in hosts file, but hostname is not present.
|
||||
# We will need to add it.
|
||||
if salt.utils.validate.net.ip_addr(addr):
|
||||
to_add.add((addr, name))
|
||||
else:
|
||||
ret['result'] = False
|
||||
comments.append('Invalid IP Address for {0} ({1})'.format(name, _ip))
|
||||
comments.append(
|
||||
'Invalid IP Address for {0} ({1})'.format(name, addr)
|
||||
)
|
||||
|
||||
for addr, name in to_add:
|
||||
if __opts__['test']:
|
||||
comments.append(
|
||||
'Host {0} ({1}) would be added'.format(name, addr)
|
||||
)
|
||||
else:
|
||||
if __salt__['hosts.add_host'](addr, name):
|
||||
comments.append('Added host {0} ({1})'.format(name, addr))
|
||||
else:
|
||||
ret['result'] = False
|
||||
comments.append('Failed to add host {0} ({1})'.format(name, addr))
|
||||
continue
|
||||
ret['changes'].setdefault('added', {}).setdefault(addr, []).append(name)
|
||||
|
||||
for addr, name in to_remove:
|
||||
if __opts__['test']:
|
||||
comments.append(
|
||||
'Host {0} ({1}) would be removed'.format(name, addr)
|
||||
)
|
||||
else:
|
||||
if __salt__['hosts.rm_host'](addr, name):
|
||||
comments.append('Removed host {0} ({1})'.format(name, addr))
|
||||
else:
|
||||
ret['result'] = False
|
||||
comments.append('Failed to remove host {0} ({1})'.format(name, addr))
|
||||
continue
|
||||
ret['changes'].setdefault('removed', {}).setdefault(addr, []).append(name)
|
||||
|
||||
ret['comment'] = '\n'.join(comments)
|
||||
return ret
|
||||
|
||||
|
|
|
@ -205,10 +205,22 @@ def rename(src, dst):
|
|||
os.rename(src, dst)
|
||||
|
||||
|
||||
def process_read_exception(exc, path):
|
||||
def process_read_exception(exc, path, ignore=None):
|
||||
'''
|
||||
Common code for raising exceptions when reading a file fails
|
||||
|
||||
The ignore argument can be an iterable of integer error codes (or a single
|
||||
integer error code) that should be ignored.
|
||||
'''
|
||||
if ignore is not None:
|
||||
if isinstance(ignore, six.integer_types):
|
||||
ignore = (ignore,)
|
||||
else:
|
||||
ignore = ()
|
||||
|
||||
if exc.errno in ignore:
|
||||
return
|
||||
|
||||
if exc.errno == errno.ENOENT:
|
||||
raise CommandExecutionError('{0} does not exist'.format(path))
|
||||
elif exc.errno == errno.EACCES:
|
||||
|
|
|
@ -81,6 +81,14 @@ def ipv6_addr(addr):
|
|||
return __ip_addr(addr, socket.AF_INET6)
|
||||
|
||||
|
||||
def ip_addr(addr):
|
||||
'''
|
||||
Returns True if the IPv4 or IPv6 address (and optional subnet) are valid,
|
||||
otherwise returns False.
|
||||
'''
|
||||
return ipv4_addr(addr) or ipv6_addr(addr)
|
||||
|
||||
|
||||
def netmask(mask):
|
||||
'''
|
||||
Returns True if the value passed is a valid netmask, otherwise return False
|
||||
|
|
|
@ -15,7 +15,8 @@ from tests.support.mock import (
|
|||
NO_MOCK,
|
||||
NO_MOCK_REASON,
|
||||
MagicMock,
|
||||
patch
|
||||
call,
|
||||
patch,
|
||||
)
|
||||
|
||||
|
||||
|
@ -25,19 +26,260 @@ class HostTestCase(TestCase, LoaderModuleMockMixin):
|
|||
Validate the host state
|
||||
'''
|
||||
def setup_loader_modules(self):
|
||||
return {host: {}}
|
||||
return {
|
||||
host: {
|
||||
'__opts__': {
|
||||
'test': False,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
def test_present(self):
|
||||
'''
|
||||
Test to ensures that the named host is present with the given ip
|
||||
'''
|
||||
ret = {'changes': {},
|
||||
'comment': 'Host salt (127.0.0.1) already present',
|
||||
'name': 'salt', 'result': True}
|
||||
add_host = MagicMock(return_value=True)
|
||||
rm_host = MagicMock(return_value=True)
|
||||
hostname = 'salt'
|
||||
ip_str = '127.0.0.1'
|
||||
ip_list = ['10.1.2.3', '10.4.5.6']
|
||||
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(host.__salt__, {'hosts.has_pair': mock}):
|
||||
self.assertDictEqual(host.present("salt", "127.0.0.1"), ret)
|
||||
# Case 1: No match for hostname. Single IP address passed to the state.
|
||||
list_hosts = MagicMock(return_value={
|
||||
'127.0.0.1': ['localhost'],
|
||||
})
|
||||
with patch.dict(host.__salt__,
|
||||
{'hosts.list_hosts': list_hosts,
|
||||
'hosts.add_host': add_host,
|
||||
'hosts.rm_host': rm_host}):
|
||||
ret = host.present(hostname, ip_str)
|
||||
assert ret['result'] is True
|
||||
assert ret['comment'] == 'Added host {0} ({1})'.format(hostname, ip_str), ret['comment']
|
||||
assert ret['changes'] == {
|
||||
'added': {
|
||||
ip_str: [hostname],
|
||||
}
|
||||
}, ret['changes']
|
||||
expected = [call(ip_str, hostname)]
|
||||
assert add_host.mock_calls == expected, add_host.mock_calls
|
||||
assert rm_host.mock_calls == [], rm_host.mock_calls
|
||||
|
||||
# Case 2: No match for hostname. Multiple IP addresses passed to the
|
||||
# state.
|
||||
list_hosts = MagicMock(return_value={
|
||||
'127.0.0.1': ['localhost'],
|
||||
})
|
||||
add_host.reset_mock()
|
||||
rm_host.reset_mock()
|
||||
with patch.dict(host.__salt__,
|
||||
{'hosts.list_hosts': list_hosts,
|
||||
'hosts.add_host': add_host,
|
||||
'hosts.rm_host': rm_host}):
|
||||
ret = host.present(hostname, ip_list)
|
||||
assert ret['result'] is True
|
||||
assert 'Added host {0} ({1})'.format(hostname, ip_list[0]) in ret['comment']
|
||||
assert 'Added host {0} ({1})'.format(hostname, ip_list[1]) in ret['comment']
|
||||
assert ret['changes'] == {
|
||||
'added': {
|
||||
ip_list[0]: [hostname],
|
||||
ip_list[1]: [hostname],
|
||||
}
|
||||
}, ret['changes']
|
||||
expected = sorted([call(x, hostname) for x in ip_list])
|
||||
assert sorted(add_host.mock_calls) == expected, add_host.mock_calls
|
||||
assert rm_host.mock_calls == [], rm_host.mock_calls
|
||||
|
||||
# Case 3: Match for hostname, but no matching IP. Single IP address
|
||||
# passed to the state.
|
||||
list_hosts = MagicMock(return_value={
|
||||
'127.0.0.1': ['localhost'],
|
||||
ip_list[0]: [hostname],
|
||||
})
|
||||
add_host.reset_mock()
|
||||
rm_host.reset_mock()
|
||||
with patch.dict(host.__salt__,
|
||||
{'hosts.list_hosts': list_hosts,
|
||||
'hosts.add_host': add_host,
|
||||
'hosts.rm_host': rm_host}):
|
||||
ret = host.present(hostname, ip_str)
|
||||
assert ret['result'] is True
|
||||
assert 'Added host {0} ({1})'.format(hostname, ip_str) in ret['comment']
|
||||
assert 'Host {0} present for IP address {1}'.format(hostname, ip_list[0]) in ret['warnings'][0]
|
||||
assert ret['changes'] == {
|
||||
'added': {
|
||||
ip_str: [hostname],
|
||||
},
|
||||
}, ret['changes']
|
||||
expected = [call(ip_str, hostname)]
|
||||
assert add_host.mock_calls == expected, add_host.mock_calls
|
||||
assert rm_host.mock_calls == [], rm_host.mock_calls
|
||||
|
||||
# Case 3a: Repeat the above with clean=True
|
||||
add_host.reset_mock()
|
||||
rm_host.reset_mock()
|
||||
with patch.dict(host.__salt__,
|
||||
{'hosts.list_hosts': list_hosts,
|
||||
'hosts.add_host': add_host,
|
||||
'hosts.rm_host': rm_host}):
|
||||
ret = host.present(hostname, ip_str, clean=True)
|
||||
assert ret['result'] is True
|
||||
assert 'Added host {0} ({1})'.format(hostname, ip_str) in ret['comment']
|
||||
assert 'Removed host {0} ({1})'.format(hostname, ip_list[0]) in ret['comment']
|
||||
assert ret['changes'] == {
|
||||
'added': {
|
||||
ip_str: [hostname],
|
||||
},
|
||||
'removed': {
|
||||
ip_list[0]: [hostname],
|
||||
}
|
||||
}, ret['changes']
|
||||
expected = [call(ip_str, hostname)]
|
||||
assert add_host.mock_calls == expected, add_host.mock_calls
|
||||
expected = [call(ip_list[0], hostname)]
|
||||
assert rm_host.mock_calls == expected, rm_host.mock_calls
|
||||
|
||||
# Case 4: Match for hostname, but no matching IP. Multiple IP addresses
|
||||
# passed to the state.
|
||||
cur_ip = '1.2.3.4'
|
||||
list_hosts = MagicMock(return_value={
|
||||
'127.0.0.1': ['localhost'],
|
||||
cur_ip: [hostname],
|
||||
})
|
||||
add_host.reset_mock()
|
||||
rm_host.reset_mock()
|
||||
with patch.dict(host.__salt__,
|
||||
{'hosts.list_hosts': list_hosts,
|
||||
'hosts.add_host': add_host,
|
||||
'hosts.rm_host': rm_host}):
|
||||
ret = host.present(hostname, ip_list)
|
||||
assert ret['result'] is True
|
||||
assert 'Added host {0} ({1})'.format(hostname, ip_list[0]) in ret['comment']
|
||||
assert 'Added host {0} ({1})'.format(hostname, ip_list[1]) in ret['comment']
|
||||
assert ret['changes'] == {
|
||||
'added': {
|
||||
ip_list[0]: [hostname],
|
||||
ip_list[1]: [hostname],
|
||||
},
|
||||
}, ret['changes']
|
||||
expected = sorted([call(x, hostname) for x in ip_list])
|
||||
assert sorted(add_host.mock_calls) == expected, add_host.mock_calls
|
||||
assert rm_host.mock_calls == [], rm_host.mock_calls
|
||||
|
||||
# Case 4a: Repeat the above with clean=True
|
||||
add_host.reset_mock()
|
||||
rm_host.reset_mock()
|
||||
with patch.dict(host.__salt__,
|
||||
{'hosts.list_hosts': list_hosts,
|
||||
'hosts.add_host': add_host,
|
||||
'hosts.rm_host': rm_host}):
|
||||
ret = host.present(hostname, ip_list, clean=True)
|
||||
assert ret['result'] is True
|
||||
assert 'Added host {0} ({1})'.format(hostname, ip_list[0]) in ret['comment']
|
||||
assert 'Added host {0} ({1})'.format(hostname, ip_list[1]) in ret['comment']
|
||||
assert 'Removed host {0} ({1})'.format(hostname, cur_ip) in ret['comment']
|
||||
assert ret['changes'] == {
|
||||
'added': {
|
||||
ip_list[0]: [hostname],
|
||||
ip_list[1]: [hostname],
|
||||
},
|
||||
'removed': {
|
||||
cur_ip: [hostname],
|
||||
}
|
||||
}, ret['changes']
|
||||
expected = sorted([call(x, hostname) for x in ip_list])
|
||||
assert sorted(add_host.mock_calls) == expected, add_host.mock_calls
|
||||
expected = [call(cur_ip, hostname)]
|
||||
assert rm_host.mock_calls == expected, rm_host.mock_calls
|
||||
|
||||
# Case 5: Multiple IP addresses passed to the state. One of them
|
||||
# matches, the other does not. There is also a non-matching IP that
|
||||
# must be removed.
|
||||
cur_ip = '1.2.3.4'
|
||||
list_hosts = MagicMock(return_value={
|
||||
'127.0.0.1': ['localhost'],
|
||||
cur_ip: [hostname],
|
||||
ip_list[0]: [hostname],
|
||||
})
|
||||
add_host.reset_mock()
|
||||
rm_host.reset_mock()
|
||||
with patch.dict(host.__salt__,
|
||||
{'hosts.list_hosts': list_hosts,
|
||||
'hosts.add_host': add_host,
|
||||
'hosts.rm_host': rm_host}):
|
||||
ret = host.present(hostname, ip_list)
|
||||
assert ret['result'] is True
|
||||
assert 'Added host {0} ({1})'.format(hostname, ip_list[1]) in ret['comment']
|
||||
assert ret['changes'] == {
|
||||
'added': {
|
||||
ip_list[1]: [hostname],
|
||||
},
|
||||
}, ret['changes']
|
||||
expected = [call(ip_list[1], hostname)]
|
||||
assert add_host.mock_calls == expected, add_host.mock_calls
|
||||
assert rm_host.mock_calls == [], rm_host.mock_calls
|
||||
|
||||
# Case 5a: Repeat the above with clean=True
|
||||
add_host.reset_mock()
|
||||
rm_host.reset_mock()
|
||||
with patch.dict(host.__salt__,
|
||||
{'hosts.list_hosts': list_hosts,
|
||||
'hosts.add_host': add_host,
|
||||
'hosts.rm_host': rm_host}):
|
||||
ret = host.present(hostname, ip_list, clean=True)
|
||||
assert ret['result'] is True
|
||||
assert 'Added host {0} ({1})'.format(hostname, ip_list[1]) in ret['comment']
|
||||
assert 'Removed host {0} ({1})'.format(hostname, cur_ip) in ret['comment']
|
||||
assert ret['changes'] == {
|
||||
'added': {
|
||||
ip_list[1]: [hostname],
|
||||
},
|
||||
'removed': {
|
||||
cur_ip: [hostname],
|
||||
}
|
||||
}, ret['changes']
|
||||
expected = [call(ip_list[1], hostname)]
|
||||
assert add_host.mock_calls == expected, add_host.mock_calls
|
||||
expected = [call(cur_ip, hostname)]
|
||||
assert rm_host.mock_calls == expected, rm_host.mock_calls
|
||||
|
||||
# Case 6: Single IP address passed to the state, which matches the
|
||||
# current configuration for that hostname. No changes should be made.
|
||||
list_hosts = MagicMock(return_value={
|
||||
ip_str: [hostname],
|
||||
})
|
||||
add_host.reset_mock()
|
||||
rm_host.reset_mock()
|
||||
with patch.dict(host.__salt__,
|
||||
{'hosts.list_hosts': list_hosts,
|
||||
'hosts.add_host': add_host,
|
||||
'hosts.rm_host': rm_host}):
|
||||
ret = host.present(hostname, ip_str)
|
||||
assert ret['result'] is True
|
||||
assert ret['comment'] == 'Host {0} ({1}) already present'.format(hostname, ip_str) in ret['comment']
|
||||
assert ret['changes'] == {}, ret['changes']
|
||||
assert add_host.mock_calls == [], add_host.mock_calls
|
||||
assert rm_host.mock_calls == [], rm_host.mock_calls
|
||||
|
||||
# Case 7: Multiple IP addresses passed to the state, which both match
|
||||
# the current configuration for that hostname. No changes should be
|
||||
# made.
|
||||
list_hosts = MagicMock(return_value={
|
||||
ip_list[0]: [hostname],
|
||||
ip_list[1]: [hostname],
|
||||
})
|
||||
add_host.reset_mock()
|
||||
rm_host.reset_mock()
|
||||
with patch.dict(host.__salt__,
|
||||
{'hosts.list_hosts': list_hosts,
|
||||
'hosts.add_host': add_host,
|
||||
'hosts.rm_host': rm_host}):
|
||||
ret = host.present(hostname, ip_list)
|
||||
assert ret['result'] is True
|
||||
assert 'Host {0} ({1}) already present'.format(hostname, ip_list[0]) in ret['comment']
|
||||
assert 'Host {0} ({1}) already present'.format(hostname, ip_list[1]) in ret['comment']
|
||||
assert ret['changes'] == {}, ret['changes']
|
||||
assert add_host.mock_calls == [], add_host.mock_calls
|
||||
assert rm_host.mock_calls == [], rm_host.mock_calls
|
||||
|
||||
def test_absent(self):
|
||||
'''
|
||||
|
|
Loading…
Add table
Reference in a new issue