mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
nmcli: Support GSM connections (#3313)
* nmcli: Support GSM connections
* Add GSM support
* Add GSM unit test
* nmcli: Add changelog fragment
* nmcli: Fix GSM unit test
* Fix copy-paste error in test_gsm_mod
* nmcli: Fix yaml formatting
* nmcli: Fix yaml formatting
* nmcli: Fix typeerror
* type must be str not string
* nmcli: Fix gsm_show_output
* gsm.username did not match input
* nmcli: Fix gsm_show_output
* doublechecked generated output with test-client
* nmcli: GSM fix unit test
* Removed `mocked_gsm_connection_unchanged`
* Revert "nmcli: GSM fix unit test"
This reverts commit 2d112b779a
.
* nmcli: gsm fix unit test
* Add needed output to `TESTCASE_GSM_SHOW_OUTPUT`
* Move `mocked_gsm_connection_unchanged`to sort correctly
* nmcli: gsm fix _compare_conn_params
* Strip double-qoutes of gsm.apn if exist
* nmcli: GSM apply suggestions from code review
Co-authored-by: Ajpantuso <ajpantuso@gmail.com>
* nmcli: GSM: Fix documentation
* Shorten too long lines
* nmcli: GSM apply suggestions from code review
Co-authored-by: Ajpantuso <ajpantuso@gmail.com>
* nmcli: GSM add version
Co-authored-by: Felix Fontein <felix@fontein.de>
Co-authored-by: Ajpantuso <ajpantuso@gmail.com>
Co-authored-by: Felix Fontein <felix@fontein.de>
This commit is contained in:
parent
3502f3b486
commit
76317d1f64
3 changed files with 265 additions and 3 deletions
2
changelogs/fragments/3313-nmcli-add_gsm_support.yml
Normal file
2
changelogs/fragments/3313-nmcli-add_gsm_support.yml
Normal file
|
@ -0,0 +1,2 @@
|
|||
minor_changes:
|
||||
- "nmcli - add ``gsm`` support (https://github.com/ansible-collections/community.general/pull/3313)."
|
|
@ -54,8 +54,9 @@ options:
|
|||
- Type C(dummy) is added in community.general 3.5.0.
|
||||
- Type C(generic) is added in Ansible 2.5.
|
||||
- Type C(infiniband) is added in community.general 2.0.0.
|
||||
- Type C(gsm) is added in community.general 3.7.0.
|
||||
type: str
|
||||
choices: [ bond, bond-slave, bridge, bridge-slave, dummy, ethernet, generic, gre, infiniband, ipip, sit, team, team-slave, vlan, vxlan, wifi ]
|
||||
choices: [ bond, bond-slave, bridge, bridge-slave, dummy, ethernet, generic, gre, infiniband, ipip, sit, team, team-slave, vlan, vxlan, wifi, gsm ]
|
||||
mode:
|
||||
description:
|
||||
- This is the type of device or network connection that you wish to create for a bond or bridge.
|
||||
|
@ -183,7 +184,7 @@ options:
|
|||
mtu:
|
||||
description:
|
||||
- The connection MTU, e.g. 9000. This can't be applied when creating the interface and is done once the interface has been created.
|
||||
- Can be used when modifying Team, VLAN, Ethernet (Future plans to implement wifi, pppoe, infiniband)
|
||||
- Can be used when modifying Team, VLAN, Ethernet (Future plans to implement wifi, gsm, pppoe, infiniband)
|
||||
- This parameter defaults to C(1500) when unset.
|
||||
type: int
|
||||
dhcp_client_id:
|
||||
|
@ -643,6 +644,101 @@ options:
|
|||
type: bool
|
||||
default: false
|
||||
version_added: 3.6.0
|
||||
gsm:
|
||||
description:
|
||||
- The configuration of the GSM connection.
|
||||
- Note the list of suboption attributes may vary depending on which version of NetworkManager/nmcli is installed on the host.
|
||||
- 'An up-to-date list of supported attributes can be found here:
|
||||
U(https://networkmanager.dev/docs/api/latest/settings-gsm.html).'
|
||||
- 'For instance to use apn, pin, username and password:
|
||||
C({apn: provider.apn, pin: 1234, username: apn.username, password: apn.password}).'
|
||||
type: dict
|
||||
version_added: 3.7.0
|
||||
suboptions:
|
||||
apn:
|
||||
description:
|
||||
- The GPRS Access Point Name specifying the APN used when establishing a data session with the GSM-based network.
|
||||
- The APN often determines how the user will be billed for their network usage and whether the user has access to the Internet or
|
||||
just a provider-specific walled-garden, so it is important to use the correct APN for the user's mobile broadband plan.
|
||||
- The APN may only be composed of the characters a-z, 0-9, ., and - per GSM 03.60 Section 14.9.
|
||||
type: str
|
||||
auto-config:
|
||||
description: When C(true), the settings such as I(gsm.apn), I(gsm.username), or I(gsm.password) will default to values that match the network
|
||||
the modem will register to in the Mobile Broadband Provider database.
|
||||
type: bool
|
||||
default: false
|
||||
device-id:
|
||||
description:
|
||||
- The device unique identifier (as given by the C(WWAN) management service) which this connection applies to.
|
||||
- If given, the connection will only apply to the specified device.
|
||||
type: str
|
||||
home-only:
|
||||
description:
|
||||
- When C(true), only connections to the home network will be allowed.
|
||||
- Connections to roaming networks will not be made.
|
||||
type: bool
|
||||
default: false
|
||||
mtu:
|
||||
description: If non-zero, only transmit packets of the specified size or smaller, breaking larger packets up into multiple Ethernet frames.
|
||||
type: int
|
||||
default: 0
|
||||
network-id:
|
||||
description:
|
||||
- The Network ID (GSM LAI format, ie MCC-MNC) to force specific network registration.
|
||||
- If the Network ID is specified, NetworkManager will attempt to force the device to register only on the specified network.
|
||||
- This can be used to ensure that the device does not roam when direct roaming control of the device is not otherwise possible.
|
||||
type: str
|
||||
number:
|
||||
description: Legacy setting that used to help establishing PPP data sessions for GSM-based modems.
|
||||
type: str
|
||||
password:
|
||||
description:
|
||||
- The password used to authenticate with the network, if required.
|
||||
- Many providers do not require a password, or accept any password.
|
||||
- But if a password is required, it is specified here.
|
||||
type: str
|
||||
password-flags:
|
||||
description:
|
||||
- NMSettingSecretFlags indicating how to handle the I(password) property.
|
||||
- 'Following choices are allowed:
|
||||
C(0) B(NONE): The system is responsible for providing and storing this secret (default),
|
||||
C(1) B(AGENT_OWNED): A user secret agent is responsible for providing and storing this secret; when it is required agents will be
|
||||
asked to retrieve it
|
||||
C(2) B(NOT_SAVED): This secret should not be saved, but should be requested from the user each time it is needed
|
||||
C(4) B(NOT_REQUIRED): In situations where it cannot be automatically determined that the secret is required
|
||||
(some VPNs and PPP providers do not require all secrets) this flag indicates that the specific secret is not required.'
|
||||
type: int
|
||||
choices: [ 0, 1, 2 , 4 ]
|
||||
default: 0
|
||||
pin:
|
||||
description:
|
||||
- If the SIM is locked with a PIN it must be unlocked before any other operations are requested.
|
||||
- Specify the PIN here to allow operation of the device.
|
||||
type: str
|
||||
pin-flags:
|
||||
description:
|
||||
- NMSettingSecretFlags indicating how to handle the I(gsm.pin) property.
|
||||
- See I(gsm.password-flags) for NMSettingSecretFlags choices.
|
||||
type: int
|
||||
choices: [ 0, 1, 2 , 4 ]
|
||||
default: 0
|
||||
sim-id:
|
||||
description:
|
||||
- The SIM card unique identifier (as given by the C(WWAN) management service) which this connection applies to.
|
||||
- 'If given, the connection will apply to any device also allowed by I(gsm.device-id) which contains a SIM card matching
|
||||
the given identifier.'
|
||||
type: str
|
||||
sim-operator-id:
|
||||
description:
|
||||
- A MCC/MNC string like C(310260) or C(21601I) identifying the specific mobile network operator which this connection applies to.
|
||||
- 'If given, the connection will apply to any device also allowed by I(gsm.device-id) and I(gsm.sim-id) which contains a SIM card
|
||||
provisioned by the given operator.'
|
||||
type: str
|
||||
username:
|
||||
description:
|
||||
- The username used to authenticate with the network, if required.
|
||||
- Many providers do not require a username, or accept any username.
|
||||
- But if a username is required, it is specified here.
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
|
@ -979,6 +1075,19 @@ EXAMPLES = r'''
|
|||
autoconnect: true
|
||||
state: present
|
||||
|
||||
- name: Create a gsm connection
|
||||
community.general.nmcli:
|
||||
type: gsm
|
||||
conn_name: my-gsm-provider
|
||||
ifname: cdc-wdm0
|
||||
gsm:
|
||||
apn: my.provider.apn
|
||||
username: my-provider-username
|
||||
password: my-provider-password
|
||||
pin: my-sim-pin
|
||||
autoconnect: true
|
||||
state: present
|
||||
|
||||
'''
|
||||
|
||||
RETURN = r"""#
|
||||
|
@ -1086,6 +1195,7 @@ class Nmcli(object):
|
|||
self.ssid = module.params['ssid']
|
||||
self.wifi = module.params['wifi']
|
||||
self.wifi_sec = module.params['wifi_sec']
|
||||
self.gsm = module.params['gsm']
|
||||
|
||||
if self.method4:
|
||||
self.ipv4_method = self.method4
|
||||
|
@ -1243,6 +1353,12 @@ class Nmcli(object):
|
|||
options.update({
|
||||
'802-11-wireless-security.%s' % name: value
|
||||
})
|
||||
elif self.type == 'gsm':
|
||||
if self.gsm:
|
||||
for name, value in self.gsm.items():
|
||||
options.update({
|
||||
'gsm.%s' % name: value,
|
||||
})
|
||||
# Convert settings values based on the situation.
|
||||
for setting, value in options.items():
|
||||
setting_type = self.settings_type(setting)
|
||||
|
@ -1280,7 +1396,8 @@ class Nmcli(object):
|
|||
'sit',
|
||||
'team',
|
||||
'vlan',
|
||||
'wifi'
|
||||
'wifi',
|
||||
'gsm',
|
||||
)
|
||||
|
||||
@property
|
||||
|
@ -1573,6 +1690,10 @@ class Nmcli(object):
|
|||
value = value.upper()
|
||||
# ensure current_value is also converted to uppercase in case nmcli changes behaviour
|
||||
current_value = current_value.upper()
|
||||
if key == 'gsm.apn':
|
||||
# Depending on version nmcli adds double-qoutes to gsm.apn
|
||||
# Need to strip them in order to compare both
|
||||
current_value = current_value.strip('"')
|
||||
else:
|
||||
# parameter does not exist
|
||||
current_value = None
|
||||
|
@ -1630,6 +1751,7 @@ def main():
|
|||
'vlan',
|
||||
'vxlan',
|
||||
'wifi',
|
||||
'gsm',
|
||||
]),
|
||||
ip4=dict(type='str'),
|
||||
gw4=dict(type='str'),
|
||||
|
@ -1700,6 +1822,7 @@ def main():
|
|||
ssid=dict(type='str'),
|
||||
wifi=dict(type='dict'),
|
||||
wifi_sec=dict(type='dict', no_log=True),
|
||||
gsm=dict(type='dict'),
|
||||
),
|
||||
mutually_exclusive=[['never_default4', 'gw4']],
|
||||
required_if=[("type", "wifi", [("ssid")])],
|
||||
|
|
|
@ -86,6 +86,12 @@ TESTCASE_CONNECTION = [
|
|||
'state': 'absent',
|
||||
'_ansible_check_mode': True,
|
||||
},
|
||||
{
|
||||
'type': 'gsm',
|
||||
'conn_name': 'non_existent_nw_device',
|
||||
'state': 'absent',
|
||||
'_ansible_check_mode': True,
|
||||
},
|
||||
]
|
||||
|
||||
TESTCASE_GENERIC = [
|
||||
|
@ -603,6 +609,7 @@ TESTCASE_DEFAULT_SECURE_WIRELESS_SHOW_OUTPUT = \
|
|||
802-11-wireless-security.fils: 0 (default)
|
||||
"""
|
||||
|
||||
|
||||
TESTCASE_DUMMY_STATIC = [
|
||||
{
|
||||
'type': 'dummy',
|
||||
|
@ -638,6 +645,53 @@ ipv6.addresses: 2001:db8::1/128
|
|||
"""
|
||||
|
||||
|
||||
TESTCASE_GSM = [
|
||||
{
|
||||
'type': 'gsm',
|
||||
'conn_name': 'non_existent_nw_device',
|
||||
'ifname': 'gsm_non_existant',
|
||||
'gsm': {
|
||||
'apn': 'internet.telekom',
|
||||
'username': 't-mobile',
|
||||
'password': 'tm',
|
||||
'pin': '1234',
|
||||
},
|
||||
'method4': 'auto',
|
||||
'state': 'present',
|
||||
'_ansible_check_mode': False,
|
||||
}
|
||||
]
|
||||
|
||||
TESTCASE_GSM_SHOW_OUTPUT = """\
|
||||
connection.id: non_existent_nw_device
|
||||
connection.type: gsm
|
||||
connection.interface-name: gsm_non_existant
|
||||
connection.autoconnect: yes
|
||||
ipv4.method: auto
|
||||
ipv4.ignore-auto-dns: no
|
||||
ipv4.ignore-auto-routes: no
|
||||
ipv4.never-default: no
|
||||
ipv4.may-fail: yes
|
||||
ipv6.method: auto
|
||||
ipv6.ignore-auto-dns: no
|
||||
ipv6.ignore-auto-routes: no
|
||||
gsm.auto-config: no
|
||||
gsm.number: --
|
||||
gsm.username: t-mobile
|
||||
gsm.password: tm
|
||||
gsm.password-flags: 0 (none)
|
||||
gsm.apn: "internet.telekom"
|
||||
gsm.network-id: --
|
||||
gsm.pin: 1234
|
||||
gsm.pin-flags: 0 (none)
|
||||
gsm.home-only: no
|
||||
gsm.device-id: --
|
||||
gsm.sim-id: --
|
||||
gsm.sim-operator-id: --
|
||||
gsm.mtu: auto
|
||||
"""
|
||||
|
||||
|
||||
def mocker_set(mocker,
|
||||
connection_exists=False,
|
||||
execute_return=(0, "", ""),
|
||||
|
@ -863,6 +917,13 @@ def mocked_dummy_connection_static_unchanged(mocker):
|
|||
execute_return=(0, TESTCASE_DUMMY_STATIC_SHOW_OUTPUT, ""))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mocked_gsm_connection_unchanged(mocker):
|
||||
mocker_set(mocker,
|
||||
connection_exists=True,
|
||||
execute_return=(0, TESTCASE_GSM_SHOW_OUTPUT, ""))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('patch_ansible_module', TESTCASE_BOND, indirect=['patch_ansible_module'])
|
||||
def test_bond_connection_create(mocked_generic_connection_create, capfd):
|
||||
"""
|
||||
|
@ -2162,3 +2223,79 @@ def test_dummy_connection_static_unchanged(mocked_dummy_connection_static_unchan
|
|||
results = json.loads(out)
|
||||
assert not results.get('failed')
|
||||
assert not results['changed']
|
||||
|
||||
|
||||
@pytest.mark.parametrize('patch_ansible_module', TESTCASE_GSM, indirect=['patch_ansible_module'])
|
||||
def test_create_gsm(mocked_generic_connection_create, capfd):
|
||||
"""
|
||||
Test if gsm created
|
||||
"""
|
||||
with pytest.raises(SystemExit):
|
||||
nmcli.main()
|
||||
|
||||
assert nmcli.Nmcli.execute_command.call_count == 1
|
||||
arg_list = nmcli.Nmcli.execute_command.call_args_list
|
||||
args, kwargs = arg_list[0]
|
||||
|
||||
assert args[0][0] == '/usr/bin/nmcli'
|
||||
assert args[0][1] == 'con'
|
||||
assert args[0][2] == 'add'
|
||||
assert args[0][3] == 'type'
|
||||
assert args[0][4] == 'gsm'
|
||||
assert args[0][5] == 'con-name'
|
||||
assert args[0][6] == 'non_existent_nw_device'
|
||||
|
||||
args_text = list(map(to_text, args[0]))
|
||||
for param in ['connection.interface-name', 'gsm_non_existant',
|
||||
'gsm.apn', 'internet.telekom',
|
||||
'gsm.username', 't-mobile',
|
||||
'gsm.password', 'tm',
|
||||
'gsm.pin', '1234']:
|
||||
assert param in args_text
|
||||
|
||||
out, err = capfd.readouterr()
|
||||
results = json.loads(out)
|
||||
assert not results.get('failed')
|
||||
assert results['changed']
|
||||
|
||||
|
||||
@pytest.mark.parametrize('patch_ansible_module', TESTCASE_GSM, indirect=['patch_ansible_module'])
|
||||
def test_gsm_mod(mocked_generic_connection_modify, capfd):
|
||||
"""
|
||||
Test if gsm modified
|
||||
"""
|
||||
with pytest.raises(SystemExit):
|
||||
nmcli.main()
|
||||
|
||||
assert nmcli.Nmcli.execute_command.call_count == 1
|
||||
arg_list = nmcli.Nmcli.execute_command.call_args_list
|
||||
args, kwargs = arg_list[0]
|
||||
|
||||
assert args[0][0] == '/usr/bin/nmcli'
|
||||
assert args[0][1] == 'con'
|
||||
assert args[0][2] == 'modify'
|
||||
assert args[0][3] == 'non_existent_nw_device'
|
||||
|
||||
args_text = list(map(to_text, args[0]))
|
||||
for param in ['gsm.username', 't-mobile',
|
||||
'gsm.password', 'tm']:
|
||||
assert param in args_text
|
||||
|
||||
out, err = capfd.readouterr()
|
||||
results = json.loads(out)
|
||||
assert not results.get('failed')
|
||||
assert results['changed']
|
||||
|
||||
|
||||
@pytest.mark.parametrize('patch_ansible_module', TESTCASE_GSM, indirect=['patch_ansible_module'])
|
||||
def test_gsm_connection_unchanged(mocked_gsm_connection_unchanged, capfd):
|
||||
"""
|
||||
Test if gsm connection unchanged
|
||||
"""
|
||||
with pytest.raises(SystemExit):
|
||||
nmcli.main()
|
||||
|
||||
out, err = capfd.readouterr()
|
||||
results = json.loads(out)
|
||||
assert not results.get('failed')
|
||||
assert not results['changed']
|
||||
|
|
Loading…
Reference in a new issue