1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

nmcli: Support GSM connections

* Add GSM support
* Add GSM unit test
This commit is contained in:
Manuel Gayer 2021-08-31 15:41:24 +02:00
parent d9dcdcbbe4
commit 184ab504e6
2 changed files with 251 additions and 3 deletions

View file

@ -55,7 +55,7 @@ options:
- Type C(generic) is added in Ansible 2.5. - Type C(generic) is added in Ansible 2.5.
- Type C(infiniband) is added in community.general 2.0.0. - Type C(infiniband) is added in community.general 2.0.0.
type: str 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: mode:
description: description:
- This is the type of device or network connection that you wish to create for a bond or bridge. - This is the type of device or network connection that you wish to create for a bond or bridge.
@ -183,7 +183,7 @@ options:
mtu: mtu:
description: 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. - 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. - This parameter defaults to C(1500) when unset.
type: int type: int
dhcp_client_id: dhcp_client_id:
@ -643,6 +643,98 @@ options:
type: bool type: bool
default: false default: false
version_added: 3.6.0 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
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: string
auto-config:
description: When C(true), the settings such as C(APN), username, or 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: string
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: string
number:
description: Legacy setting that used to help establishing PPP data sessions for GSM-based modems.
type: string
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: string
password-flags:
description:
- NMSettingSecretFlags indicating how to handle the I(password) property.
- Following choices are allowed:
C(0) I(NONE): The system is responsible for providing and storing this secret (default),
C(1) I(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) I(NOT_SAVED): This secret should not be saved, but should be requested from the user each time it is needed
C(4) I(NOT_REQUIRED): in situations where it cannot be automatically determined that the secret is required
(some VPNs and PPP providers don't 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: string
pin-flags:
description:
- NMSettingSecretFlags indicating how to handle the I(pin) property.
- See C(password-flags) for NMSettingSecretFlags coices
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(device-id) which contains a SIM card matching the given identifier.
type: string
sim-operator-id:
description:
- A MCC/MNC string like I(310260) or I(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(device-id) and I(sim-id) which contains a SIM card provisioned by the given operator.
type: string
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''' EXAMPLES = r'''
@ -979,6 +1071,19 @@ EXAMPLES = r'''
autoconnect: true autoconnect: true
state: present 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"""# RETURN = r"""#
@ -1086,6 +1191,7 @@ class Nmcli(object):
self.ssid = module.params['ssid'] self.ssid = module.params['ssid']
self.wifi = module.params['wifi'] self.wifi = module.params['wifi']
self.wifi_sec = module.params['wifi_sec'] self.wifi_sec = module.params['wifi_sec']
self.gsm = module.params['gsm']
if self.method4: if self.method4:
self.ipv4_method = self.method4 self.ipv4_method = self.method4
@ -1243,6 +1349,12 @@ class Nmcli(object):
options.update({ options.update({
'802-11-wireless-security.%s' % name: value '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. # Convert settings values based on the situation.
for setting, value in options.items(): for setting, value in options.items():
setting_type = self.settings_type(setting) setting_type = self.settings_type(setting)
@ -1280,7 +1392,8 @@ class Nmcli(object):
'sit', 'sit',
'team', 'team',
'vlan', 'vlan',
'wifi' 'wifi',
'gsm',
) )
@property @property
@ -1630,6 +1743,7 @@ def main():
'vlan', 'vlan',
'vxlan', 'vxlan',
'wifi', 'wifi',
'gsm',
]), ]),
ip4=dict(type='str'), ip4=dict(type='str'),
gw4=dict(type='str'), gw4=dict(type='str'),
@ -1700,6 +1814,7 @@ def main():
ssid=dict(type='str'), ssid=dict(type='str'),
wifi=dict(type='dict'), wifi=dict(type='dict'),
wifi_sec=dict(type='dict', no_log=True), wifi_sec=dict(type='dict', no_log=True),
gsm=dict(type='dict'),
), ),
mutually_exclusive=[['never_default4', 'gw4']], mutually_exclusive=[['never_default4', 'gw4']],
required_if=[("type", "wifi", [("ssid")])], required_if=[("type", "wifi", [("ssid")])],

View file

@ -86,6 +86,12 @@ TESTCASE_CONNECTION = [
'state': 'absent', 'state': 'absent',
'_ansible_check_mode': True, '_ansible_check_mode': True,
}, },
{
'type': 'gsm',
'conn_name': 'non_existent_nw_device',
'state': 'absent',
'_ansible_check_mode': True,
},
] ]
TESTCASE_GENERIC = [ TESTCASE_GENERIC = [
@ -603,6 +609,7 @@ TESTCASE_DEFAULT_SECURE_WIRELESS_SHOW_OUTPUT = \
802-11-wireless-security.fils: 0 (default) 802-11-wireless-security.fils: 0 (default)
""" """
TESTCASE_DUMMY_STATIC = [ TESTCASE_DUMMY_STATIC = [
{ {
'type': 'dummy', 'type': 'dummy',
@ -638,6 +645,44 @@ 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
gsm.number: *99#
gsm.username: tm-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
gsm.auto-config: no
"""
def mocker_set(mocker, def mocker_set(mocker,
connection_exists=False, connection_exists=False,
execute_return=(0, "", ""), execute_return=(0, "", ""),
@ -789,6 +834,13 @@ def mocked_ethernet_connection_static_unchanged(mocker):
execute_return=(0, TESTCASE_ETHERNET_STATIC_SHOW_OUTPUT, "")) execute_return=(0, TESTCASE_ETHERNET_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.fixture @pytest.fixture
def mocked_ethernet_connection_dhcp_to_static(mocker): def mocked_ethernet_connection_dhcp_to_static(mocker):
mocker_set(mocker, mocker_set(mocker,
@ -2162,3 +2214,84 @@ def test_dummy_connection_static_unchanged(mocked_dummy_connection_static_unchan
results = json.loads(out) results = json.loads(out)
assert not results.get('failed') assert not results.get('failed')
assert not results['changed'] 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.apn', 'web.vodafone.de',
'gsm.username', '',
'gsm.password', '',
]:
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']