Remove api doc headers
[pyi2ncommon] / src / dial.py
CommitLineData
f49f6323 1# This Python file uses the following encoding: utf-8
11cbb815
PD
2
3# The software in this package is distributed under the GNU General
4# Public License version 2 (with a special exception described below).
5#
6# A copy of GNU General Public License (GPL) is included in this distribution,
7# in the file COPYING.GPL.
8#
9# As a special exception, if other files instantiate templates or use macros
10# or inline functions from this file, or you compile this file and link it
11# with other works to produce a work based on this file, this file
12# does not by itself cause the resulting work to be covered
13# by the GNU General Public License.
14#
15# However the source code for this file must still be made available
16# in accordance with section (3) of the GNU General Public License.
17#
18# This exception does not invalidate any other reasons why a work based
19# on this file might be covered by the GNU General Public License.
20#
21# Copyright (c) 2016-2018 Intra2net AG <info@intra2net.com>
22
f49f6323 23"""
f49f6323
PD
24Dialing, hangup, general provider online/offline state control.
25
26Copyright: 2017 Intra2net AG
27
28This used to be part of the sysmisc utilitiy initially which caused an include
29circle with the arnied wrapper.
30
fcec8a63
CH
31Contents:
32 * dialout
33 Generic access to the system’s dialing mode. Allows for requesting manual
34 or permanently online dial state.
35 * arnied_dial_permanent
36 Enter permantly online dialing state.
37 * arnied_dial_do
38 Enter dial on command state.
39 * arnied_dial_hangup
40 Terminate uplink unconditionally.
f49f6323
PD
41
42All API methods come with the optional argument ``block`` (bool) to request the
43call to block until the system completes the state transition successfully. The
44completion timeout is currently 10 seconds (see the definition of
45``DIALTOOLS_TIMEOUT`` below).
f49f6323
PD
46"""
47
48
49import re
50import io
51import time
52import logging
3de8b4d8 53log = logging.getLogger('pyi2ncommon.dial')
f49f6323 54
e5c2b9eb 55from . import cnfvar
30521dad 56from . import sysmisc
f49f6323
PD
57
58HAVE_IPADDRESS = True
59try:
60 import ipaddress
61except ImportError: # guest
62 HAVE_IPADDRESS = False
63
64__all__ = (
65 "arnied_dial_hangup", "arnied_dial_doc", "arnied_dial_permanent", "dialout", "get_wan_address", "DIALOUT_MODE_PERMANENT", "DIALOUT_MODE_MANUAL", "DIALOUT_MODE_DEFAULT", "DIALOUT_MODE_BY_NAME"
66)
67
f49f6323
PD
68DIALOUT_MODE_PERMANENT = 0
69DIALOUT_MODE_MANUAL = 1
70DIALOUT_MODE_DEFAULT = DIALOUT_MODE_PERMANENT
71DIALOUT_MODE_BY_NAME = {"permanent": DIALOUT_MODE_PERMANENT, "manual": DIALOUT_MODE_MANUAL}
72DIALOUT_MODE_CNF = {DIALOUT_MODE_PERMANENT: "ONLINE", DIALOUT_MODE_MANUAL: "MANUAL"}
73
74# compiling this regex needs the provider id and is postponed due to
75# the peculiar implementation of the connd online condition
76NEEDLE_MEMO = " \[%s\] :(.*connected online fsm<online>.*)"
77NEEDLE_OFFLINE = re.compile("connection map:\nend of connection map")
78
79DIALTOOLS_HANGUP_BIN = "/usr/intranator/bin/hangup"
7f66ff3e
CH
80
81#: binary for manual dialing (dial on command)
f49f6323
PD
82DIALTOOLS_DOC_BIN = "/usr/intranator/bin/doc"
83DIALTOOLS_TIMEOUT = 10 # s
7f66ff3e
CH
84
85#: client binary for talking to connd
f49f6323
PD
86TELL_CONND_BIN = "/usr/intranator/bin/tell-connd"
87
88
89def _connd_online(prid="P1"):
90 succ, out, _ = sysmisc.run_cmd_with_pipe([TELL_CONND_BIN, "--status"])
91 if succ is False:
92 return False
93 return re.search(NEEDLE_MEMO % prid, out) is not None
94
95
96def _connd_offline():
97 succ, out, _ = sysmisc.run_cmd_with_pipe([TELL_CONND_BIN, "--status"])
98 return succ and (NEEDLE_OFFLINE.search(out) is not None)
99
100
101def arnied_dial_hangup(block=False):
102 """
103 Take down any currently active provider. This leverages arnied to
104 accomplish the disconnect which is apparently more appropriate than
105 having connd do it directly.
106
107 :returns: Whether the ``hangup`` command succeeded.
108 :rtype: int (dial result as above)
109 """
110 log.debug("requested arnied_dial_hangup%s",
111 " (blocking)" if block else "")
112 if block is False:
113 succ, _, _ = sysmisc.run_cmd_with_pipe([DIALTOOLS_HANGUP_BIN])
114 return sysmisc.RUN_RESULT_OK if succ is True else sysmisc.RUN_RESULT_FAIL
115
116 res, err = sysmisc.cmd_block_till([DIALTOOLS_HANGUP_BIN],
117 DIALTOOLS_TIMEOUT, _connd_offline)
118 log.debug("arnied_dial_hangup → (%d, %r)", res, err)
119 return res
120
121
122def arnied_dial_doc(prid="P1", block=False):
123 """
7f66ff3e 124 Bring provider up via arnied manual dial (dial on command).
f49f6323
PD
125
126 :param prid: Provider id, default *P1*. It is up to the caller to ensure
127 this is a valid provider id.
128 :type prid: str
7628bc48
CH
129 :param block: block execution until system is online
130 :type block: bool
f49f6323
PD
131 :returns: Whether the ``doc`` command succeeded.
132 :rtype: int (dial result as above)
133 """
134 log.debug("requested arnied_dial_doc%s", " (blocking)" if block else "")
135 if block is False:
136 succ, _, _ = sysmisc.run_cmd_with_pipe([DIALTOOLS_DOC_BIN, prid])
137 return sysmisc.RUN_RESULT_OK if succ is True else sysmisc.RUN_RESULT_FAIL
138 res, err = sysmisc.cmd_block_till([DIALTOOLS_DOC_BIN, prid],
139 DIALTOOLS_TIMEOUT, _connd_online,
140 prid=prid)
141 log.debug("arnied_dial_doc → (%d, %r)", res, err)
142 return res
143
144
145def arnied_dial_permanent(prid="P1", block=False):
146 """
147 Set permanent online state. Since the arnied dial helpers cannot initiate a
148 permanent online state, achieve this via arnied.
149
150 :param prid: Provider id, default *P1*. It is up to the caller to ensure
151 this is a valid provider id.
152 :type prid: str
7628bc48
CH
153 :param block: block execution until system is online
154 :type block: bool
f49f6323
PD
155 :returns: Whether the ``tell-connd`` command succeeded.
156 :rtype: int (dial result as above)
4965c436
PD
157
158 ..todo:: This function uses old and deprecated methods of cnfvar usage, it
159 should therefore be converted and needs unit tests added to verify the
160 conversion and perhaps even to test the overall dial functionality.
f49f6323 161 """
14d257c1 162 log.debug("requested connd_dial_online%s" % " (blocking)" if block else "")
f49f6323 163
e5c2b9eb
PD
164 cnf = cnfvar.CnfList([("dialout_mode", DIALOUT_MODE_CNF[DIALOUT_MODE_PERMANENT]),
165 ("dialout_defaultprovider_ref", "1")])
f49f6323
PD
166
167 def aux():
46a11f18
PD
168 store = cnfvar.BinaryCnfStore()
169 store.commit(cnf)
170 return True, "", None
f49f6323
PD
171
172 if block is False:
46a11f18 173 succ, out, _ = aux()
f49f6323
PD
174 return sysmisc.RUN_RESULT_OK if succ is True else sysmisc.RUN_RESULT_FAIL
175
176 res, err = sysmisc.cmd_block_till(aux, DIALTOOLS_TIMEOUT, _connd_online,
177 prid=prid)
178 log.debug("arnied_dial_permanent: result (%d, %r)", res, err)
179 return res
180
181
182def dialout(mode=DIALOUT_MODE_DEFAULT, prid="P1", block=True):
183 """
184
185 :param mode: How to dial (permanent vs. manual).
186 :type mode: int (``DIALOUT_MODE``) | string
187 :param prid: Provider id, default *P1*. It is up to the caller to ensure
188 this is a valid provider id.
189 :type prid: str (constrained by available providers, obviously).
190 :param block: Whether to block until completion of the command.
191 :type block: bool
192 :returns: Whether the command succeeded.
193 :rtype: int (dial result)
194 :raises: :py:class:`ValueError` if invalid dial mode was selected
195 """
196 log.info("go online with provider")
197
198 dmode = None
199 if isinstance(mode, int) is True:
200 dmode = mode
201 elif isinstance(mode, str) is True:
202 try:
203 dmode = DIALOUT_MODE_BY_NAME[mode]
7628bc48 204 except Exception:
f49f6323
PD
205 log.error("invalid online mode name “%s” requested" % mode)
206 pass
207
208 if dmode is None:
209 raise ValueError("exiting due to invalid online mode %r" % mode)
210
211 log.debug("go online, mode=%d(%s), id=%r", dmode, mode, prid)
212
213 if dmode == DIALOUT_MODE_PERMANENT:
214 return arnied_dial_permanent(prid, block=block)
215
216 if dmode == DIALOUT_MODE_MANUAL:
217 return arnied_dial_doc(prid, block=block)
218
219 raise ValueError("invalid dialout mode %r/%r requested" % (mode, dmode))
220
221
222def get_wan_address(vm=None):
223 """
224 Retrieve the current WAN IP address of client ``vm`` or localhost.
225
226 :param vm: Guest (client) to query; will as local connd if left unspecified.
227 :type vm: virttest.qemu_vm.VM | None
228
229 :returns: The IPv4 address. For correctness, it will use the
230 ipaddress module if available. Otherwise it falls back
231 on untyped data.
232 :rtype: None | (ipaddress.IPv4Address | str)
233 """
234 log.info("query current lease")
235 if vm is None:
236 succ, connstat, _ = sysmisc.run_cmd_with_pipe([TELL_CONND_BIN, "--status"])
237 if succ is False:
238 return None
239 else:
240 connstat = vm.session.cmd_output("%s --status" % TELL_CONND_BIN)
241 astr = io.StringIO(connstat)
242
243 while True:
244 l = astr.readline()
245 if l == "":
246 break
247 if l.find("connected online fsm<online> IP:") != -1:
248 addr = l[l.find("IP:")+3:l.find(" )\n")] # beurk
249 if HAVE_IPADDRESS is True:
250 return ipaddress.IPv4Address(str(addr))
251 else:
252 return addr
253 return None