Preserve other existing IP configurations when apply()
* With this fix `netapplier.apply()` will not discard all IP configurations(DNS, route, etc) but keep existing one except IP addresses. * Unit test case and integration test case included. Signed-off-by: Gris Ge <fge@redhat.com>
This commit is contained in:
parent
230d53f282
commit
09c50b5693
@ -226,8 +226,8 @@ def _build_connection_profile(iface_desired_state, base_con_profile=None):
|
||||
iface_desired_state['name'])
|
||||
|
||||
settings = [
|
||||
ipv4.create_setting(iface_desired_state.get('ipv4')),
|
||||
ipv6.create_setting(iface_desired_state.get('ipv6')),
|
||||
ipv4.create_setting(iface_desired_state.get('ipv4'), base_con_profile),
|
||||
ipv6.create_setting(iface_desired_state.get('ipv6'), base_con_profile),
|
||||
]
|
||||
if base_con_profile:
|
||||
con_setting = connection.duplicate_settings(base_con_profile)
|
||||
|
@ -20,8 +20,17 @@ import socket
|
||||
from . import nmclient
|
||||
|
||||
|
||||
def create_setting(config):
|
||||
setting_ipv4 = nmclient.NM.SettingIP4Config.new()
|
||||
def create_setting(config, base_con_profile):
|
||||
setting_ipv4 = None
|
||||
if base_con_profile:
|
||||
setting_ipv4 = base_con_profile.get_setting_ip4_config()
|
||||
if setting_ipv4:
|
||||
setting_ipv4 = setting_ipv4.duplicate()
|
||||
setting_ipv4.clear_addresses()
|
||||
|
||||
if not setting_ipv4:
|
||||
setting_ipv4 = nmclient.NM.SettingIP4Config.new()
|
||||
|
||||
if config and config.get('enabled') and config.get('address'):
|
||||
setting_ipv4.props.method = (
|
||||
nmclient.NM.SETTING_IP4_CONFIG_METHOD_MANUAL)
|
||||
|
@ -46,8 +46,16 @@ def get_info(active_connection):
|
||||
return info
|
||||
|
||||
|
||||
def create_setting(config):
|
||||
setting_ip = nmclient.NM.SettingIP6Config.new()
|
||||
def create_setting(config, base_con_profile):
|
||||
setting_ip = None
|
||||
if base_con_profile:
|
||||
setting_ip = base_con_profile.get_setting_ip6_config()
|
||||
if setting_ip:
|
||||
setting_ip = setting_ip.duplicate()
|
||||
setting_ip.clear_addresses()
|
||||
|
||||
if not setting_ip:
|
||||
setting_ip = nmclient.NM.SettingIP6Config.new()
|
||||
if config and config.get('enabled'):
|
||||
for address in config.get('address', ()):
|
||||
if iplib.is_ipv6_link_local_addr(address['ip'],
|
||||
|
@ -61,8 +61,8 @@ def _create_vlan(vlan_desired_state):
|
||||
)
|
||||
vlan_setting = nm.vlan.create_setting(vlan_desired_state,
|
||||
base_con_profile=None)
|
||||
ipv4_setting = nm.ipv4.create_setting({})
|
||||
ipv6_setting = nm.ipv6.create_setting({})
|
||||
ipv4_setting = nm.ipv4.create_setting({}, None)
|
||||
ipv6_setting = nm.ipv6.create_setting({}, None)
|
||||
with mainloop():
|
||||
con_profile = nm.connection.create_profile(
|
||||
(con_setting, vlan_setting, ipv4_setting, ipv6_setting))
|
||||
|
117
tests/integration/preserve_ip_config_test.py
Normal file
117
tests/integration/preserve_ip_config_test.py
Normal file
@ -0,0 +1,117 @@
|
||||
#
|
||||
# Copyright 2018 Red Hat, Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from contextlib import contextmanager
|
||||
|
||||
from libnmstate import netapplier
|
||||
from libnmstate.nm import nmclient
|
||||
|
||||
from .testlib import statelib
|
||||
from .testlib import cmd as libcmd
|
||||
from .testlib.statelib import INTERFACES
|
||||
|
||||
_IPV4_EXTRA_CONFIG = 'ipv4.dad-timeout'
|
||||
_IPV4_EXTRA_VALUE = '1000'
|
||||
_IPV6_EXTRA_CONFIG = 'ipv6.dhcp-hostname'
|
||||
_IPV6_EXTRA_VALUE = 'libnmstate.example.com'
|
||||
|
||||
IPV4_ADDRESS1 = '192.0.2.251'
|
||||
IPV6_ADDRESS1 = '2001:db8:1::1'
|
||||
|
||||
|
||||
def test_reapply_preserve_ip_config(eth1_up):
|
||||
netapplier.apply(
|
||||
{
|
||||
'interfaces': [
|
||||
{
|
||||
'name': 'eth1',
|
||||
'type': 'ethernet',
|
||||
'state': 'up',
|
||||
'ipv4': {
|
||||
'address': [
|
||||
{
|
||||
'ip': IPV4_ADDRESS1,
|
||||
'prefix-length': 24
|
||||
}
|
||||
],
|
||||
'enabled': True
|
||||
},
|
||||
'ipv6': {
|
||||
'address': [
|
||||
{
|
||||
'ip': IPV6_ADDRESS1,
|
||||
'prefix-length': 64
|
||||
}
|
||||
],
|
||||
'enabled': True
|
||||
},
|
||||
'mtu': 1500
|
||||
},
|
||||
]
|
||||
})
|
||||
cur_state = statelib.show_only(('eth1',))
|
||||
iface_name = cur_state[INTERFACES][0]['name']
|
||||
|
||||
uuid = _get_nm_profile_uuid(iface_name)
|
||||
|
||||
for key, value in ((_IPV4_EXTRA_CONFIG, _IPV4_EXTRA_VALUE),
|
||||
(_IPV6_EXTRA_CONFIG, _IPV6_EXTRA_VALUE)):
|
||||
with _extra_ip_config(uuid, key, value):
|
||||
netapplier.apply(cur_state)
|
||||
_assert_extra_ip_config(uuid, key, value)
|
||||
|
||||
|
||||
def _get_nm_profile_uuid(iface_name):
|
||||
nmcli = nmclient.client()
|
||||
cur_dev = None
|
||||
for dev in nmcli.get_all_devices():
|
||||
if dev.get_iface() == iface_name:
|
||||
cur_dev = dev
|
||||
break
|
||||
|
||||
active_conn = cur_dev.get_active_connection()
|
||||
return active_conn.get_uuid()
|
||||
|
||||
|
||||
def _get_cur_extra_ip_config(uuid, key):
|
||||
rc, output, _ = libcmd.exec_cmd(['nmcli', '--get-values', key,
|
||||
'connection', 'show', uuid])
|
||||
assert rc == 0
|
||||
return output.split('\n')[0]
|
||||
|
||||
|
||||
@contextmanager
|
||||
def _extra_ip_config(uuid, key, value):
|
||||
old_value = _get_cur_extra_ip_config(uuid, key)
|
||||
_apply_extra_ip_config(uuid, key, value)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
_apply_extra_ip_config(uuid, key, old_value)
|
||||
|
||||
|
||||
def _apply_extra_ip_config(uuid, key, value):
|
||||
assert libcmd.exec_cmd(['nmcli', 'connection', 'modify', uuid,
|
||||
key, value])[0] == 0
|
||||
|
||||
|
||||
def _assert_extra_ip_config(uuid, key, value):
|
||||
"""
|
||||
Check whether extra config is touched by libnmstate.
|
||||
"""
|
||||
cur_value = _get_cur_extra_ip_config(uuid, key)
|
||||
assert cur_value == value
|
@ -21,6 +21,8 @@ from lib.compat import mock
|
||||
|
||||
from libnmstate import nm
|
||||
|
||||
IPV4_ADDRESS1 = '192.0.2.251'
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def NM_mock():
|
||||
@ -29,7 +31,7 @@ def NM_mock():
|
||||
|
||||
|
||||
def test_create_setting_without_config(NM_mock):
|
||||
ipv4_setting = nm.ipv4.create_setting(config=None)
|
||||
ipv4_setting = nm.ipv4.create_setting(config=None, base_con_profile=None)
|
||||
|
||||
assert ipv4_setting == NM_mock.SettingIP4Config.new.return_value
|
||||
assert (ipv4_setting.props.method ==
|
||||
@ -37,7 +39,8 @@ def test_create_setting_without_config(NM_mock):
|
||||
|
||||
|
||||
def test_create_setting_with_ipv4_disabled(NM_mock):
|
||||
ipv4_setting = nm.ipv4.create_setting(config={'enabled': False})
|
||||
ipv4_setting = nm.ipv4.create_setting(config={'enabled': False},
|
||||
base_con_profile=None)
|
||||
|
||||
assert (ipv4_setting.props.method ==
|
||||
NM_mock.SETTING_IP4_CONFIG_METHOD_DISABLED)
|
||||
@ -48,7 +51,8 @@ def test_create_setting_without_addresses(NM_mock):
|
||||
config={
|
||||
'enabled': True,
|
||||
'address': [],
|
||||
}
|
||||
},
|
||||
base_con_profile=None
|
||||
)
|
||||
|
||||
assert (ipv4_setting.props.method ==
|
||||
@ -63,9 +67,7 @@ def test_create_setting_with_static_addresses(NM_mock):
|
||||
{'ip': '10.10.20.1', 'prefix-length': 24},
|
||||
],
|
||||
}
|
||||
ipv4_setting = nm.ipv4.create_setting(
|
||||
config=config
|
||||
)
|
||||
ipv4_setting = nm.ipv4.create_setting(config=config, base_con_profile=None)
|
||||
|
||||
assert (ipv4_setting.props.method ==
|
||||
NM_mock.SETTING_IP4_CONFIG_METHOD_MANUAL)
|
||||
@ -117,3 +119,22 @@ def test_get_info_with_ipv4_config():
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def test_create_setting_with_base_con_profile(NM_mock):
|
||||
config = {
|
||||
'enabled': True,
|
||||
'address': [
|
||||
{'ip': IPV4_ADDRESS1, 'prefix-length': 24},
|
||||
],
|
||||
}
|
||||
base_con_profile_mock = mock.MagicMock()
|
||||
config_mock = base_con_profile_mock.get_setting_ip4_config.return_value
|
||||
config_dup_mock = config_mock.duplicate.return_value
|
||||
|
||||
nm.ipv4.create_setting(config=config,
|
||||
base_con_profile=base_con_profile_mock)
|
||||
|
||||
base_con_profile_mock.get_setting_ip4_config.assert_called_once_with()
|
||||
config_mock.duplicate.assert_called_once_with()
|
||||
config_dup_mock.clear_addresses.assert_called_once_with()
|
||||
|
@ -36,7 +36,7 @@ def NM_mock():
|
||||
def test_create_setting_without_config(NM_mock):
|
||||
NM_mock.SettingIP6Config.new().props.addresses = []
|
||||
|
||||
ipv6_setting = nm.ipv6.create_setting(config=None)
|
||||
ipv6_setting = nm.ipv6.create_setting(config=None, base_con_profile=None)
|
||||
|
||||
assert ipv6_setting == NM_mock.SettingIP6Config.new.return_value
|
||||
assert (ipv6_setting.props.method ==
|
||||
@ -46,7 +46,8 @@ def test_create_setting_without_config(NM_mock):
|
||||
def test_create_setting_with_ipv6_disabled(NM_mock):
|
||||
NM_mock.SettingIP6Config.new().props.addresses = []
|
||||
|
||||
ipv6_setting = nm.ipv6.create_setting(config={'enabled': False})
|
||||
ipv6_setting = nm.ipv6.create_setting(config={'enabled': False},
|
||||
base_con_profile=None)
|
||||
|
||||
assert (ipv6_setting.props.method ==
|
||||
NM_mock.SETTING_IP6_CONFIG_METHOD_IGNORE)
|
||||
@ -59,7 +60,8 @@ def test_create_setting_without_addresses(NM_mock):
|
||||
config={
|
||||
'enabled': True,
|
||||
'address': [],
|
||||
}
|
||||
},
|
||||
base_con_profile=None
|
||||
)
|
||||
|
||||
assert (ipv6_setting.props.method ==
|
||||
@ -74,9 +76,7 @@ def test_create_setting_with_static_addresses(NM_mock):
|
||||
{'ip': 'fd12:3456:789a:2::1', 'prefix-length': 24},
|
||||
],
|
||||
}
|
||||
ipv6_setting = nm.ipv6.create_setting(
|
||||
config=config
|
||||
)
|
||||
ipv6_setting = nm.ipv6.create_setting(config=config, base_con_profile=None)
|
||||
|
||||
assert (ipv6_setting.props.method ==
|
||||
NM_mock.SETTING_IP6_CONFIG_METHOD_MANUAL)
|
||||
@ -138,7 +138,7 @@ def test_create_setting_with_link_local_addresses(NM_mock):
|
||||
{'ip': IPV6_ADDRESS1, 'prefix-length': 64},
|
||||
],
|
||||
}
|
||||
ipv6_setting = nm.ipv6.create_setting(config=config)
|
||||
ipv6_setting = nm.ipv6.create_setting(config=config, base_con_profile=None)
|
||||
|
||||
assert (ipv6_setting.props.method ==
|
||||
NM_mock.SETTING_IP6_CONFIG_METHOD_MANUAL)
|
||||
@ -152,3 +152,22 @@ def test_create_setting_with_link_local_addresses(NM_mock):
|
||||
NM_mock.SettingIP6Config.new.return_value.add_address.assert_has_calls(
|
||||
[mock.call(NM_mock.IPAddress.new.return_value)]
|
||||
)
|
||||
|
||||
|
||||
def test_create_setting_with_base_con_profile(NM_mock):
|
||||
config = {
|
||||
'enabled': True,
|
||||
'address': [
|
||||
{'ip': IPV6_ADDRESS1, 'prefix-length': 24},
|
||||
],
|
||||
}
|
||||
base_con_profile_mock = mock.MagicMock()
|
||||
config_mock = base_con_profile_mock.get_setting_ip6_config.return_value
|
||||
config_dup_mock = config_mock.duplicate.return_value
|
||||
|
||||
nm.ipv6.create_setting(config=config,
|
||||
base_con_profile=base_con_profile_mock)
|
||||
|
||||
base_con_profile_mock.get_setting_ip6_config.assert_called_once_with()
|
||||
config_mock.duplicate.assert_called_once_with()
|
||||
config_dup_mock.clear_addresses.assert_called_once_with()
|
||||
|
Loading…
x
Reference in New Issue
Block a user