Clean up, remove compat with py < 3.6
[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
PD
23"""
24
25SUMMARY
26------------------------------------------------------
27Dialing, hangup, general provider online/offline state control.
28
29Copyright: 2017 Intra2net AG
30
31This used to be part of the sysmisc utilitiy initially which caused an include
32circle with the arnied wrapper.
33
34
35CONTENTS
36------------------------------------------------------
37dialout
38 Generic access to the system’s dialing mode. Allows for requesting manual
39 or permanently online dial state.
40
41arnied_dial_permanent
42 Enter permantly online dialing state.
43
44arnied_dial_do
45 Enter dial on command state.
46
47arnied_dial_hangup
48 Terminate uplink unconditionally.
49
50All API methods come with the optional argument ``block`` (bool) to request the
51call to block until the system completes the state transition successfully. The
52completion timeout is currently 10 seconds (see the definition of
53``DIALTOOLS_TIMEOUT`` below).
54
55
56IMPLEMENTATION
57------------------------------------------------------
58
59"""
60
61
62import re
63import io
64import time
65import logging
3de8b4d8 66log = logging.getLogger('pyi2ncommon.dial')
f49f6323 67
30521dad
PD
68from . import arnied_wrapper
69from . import simple_cnf
70from . import sysmisc
f49f6323
PD
71
72HAVE_IPADDRESS = True
73try:
74 import ipaddress
75except ImportError: # guest
76 HAVE_IPADDRESS = False
77
78__all__ = (
79 "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"
80)
81
f49f6323
PD
82DIALOUT_MODE_PERMANENT = 0
83DIALOUT_MODE_MANUAL = 1
84DIALOUT_MODE_DEFAULT = DIALOUT_MODE_PERMANENT
85DIALOUT_MODE_BY_NAME = {"permanent": DIALOUT_MODE_PERMANENT, "manual": DIALOUT_MODE_MANUAL}
86DIALOUT_MODE_CNF = {DIALOUT_MODE_PERMANENT: "ONLINE", DIALOUT_MODE_MANUAL: "MANUAL"}
87
88# compiling this regex needs the provider id and is postponed due to
89# the peculiar implementation of the connd online condition
90NEEDLE_MEMO = " \[%s\] :(.*connected online fsm<online>.*)"
91NEEDLE_OFFLINE = re.compile("connection map:\nend of connection map")
92
93DIALTOOLS_HANGUP_BIN = "/usr/intranator/bin/hangup"
7f66ff3e
CH
94
95#: binary for manual dialing (dial on command)
f49f6323
PD
96DIALTOOLS_DOC_BIN = "/usr/intranator/bin/doc"
97DIALTOOLS_TIMEOUT = 10 # s
7f66ff3e
CH
98
99#: client binary for talking to connd
f49f6323
PD
100TELL_CONND_BIN = "/usr/intranator/bin/tell-connd"
101
102
103def _connd_online(prid="P1"):
104 succ, out, _ = sysmisc.run_cmd_with_pipe([TELL_CONND_BIN, "--status"])
105 if succ is False:
106 return False
107 return re.search(NEEDLE_MEMO % prid, out) is not None
108
109
110def _connd_offline():
111 succ, out, _ = sysmisc.run_cmd_with_pipe([TELL_CONND_BIN, "--status"])
112 return succ and (NEEDLE_OFFLINE.search(out) is not None)
113
114
115def arnied_dial_hangup(block=False):
116 """
117 Take down any currently active provider. This leverages arnied to
118 accomplish the disconnect which is apparently more appropriate than
119 having connd do it directly.
120
121 :returns: Whether the ``hangup`` command succeeded.
122 :rtype: int (dial result as above)
123 """
124 log.debug("requested arnied_dial_hangup%s",
125 " (blocking)" if block else "")
126 if block is False:
127 succ, _, _ = sysmisc.run_cmd_with_pipe([DIALTOOLS_HANGUP_BIN])
128 return sysmisc.RUN_RESULT_OK if succ is True else sysmisc.RUN_RESULT_FAIL
129
130 res, err = sysmisc.cmd_block_till([DIALTOOLS_HANGUP_BIN],
131 DIALTOOLS_TIMEOUT, _connd_offline)
132 log.debug("arnied_dial_hangup → (%d, %r)", res, err)
133 return res
134
135
136def arnied_dial_doc(prid="P1", block=False):
137 """
7f66ff3e 138 Bring provider up via arnied manual dial (dial on command).
f49f6323
PD
139
140 :param prid: Provider id, default *P1*. It is up to the caller to ensure
141 this is a valid provider id.
142 :type prid: str
7628bc48
CH
143 :param block: block execution until system is online
144 :type block: bool
f49f6323
PD
145 :returns: Whether the ``doc`` command succeeded.
146 :rtype: int (dial result as above)
147 """
148 log.debug("requested arnied_dial_doc%s", " (blocking)" if block else "")
149 if block is False:
150 succ, _, _ = sysmisc.run_cmd_with_pipe([DIALTOOLS_DOC_BIN, prid])
151 return sysmisc.RUN_RESULT_OK if succ is True else sysmisc.RUN_RESULT_FAIL
152 res, err = sysmisc.cmd_block_till([DIALTOOLS_DOC_BIN, prid],
153 DIALTOOLS_TIMEOUT, _connd_online,
154 prid=prid)
155 log.debug("arnied_dial_doc → (%d, %r)", res, err)
156 return res
157
158
159def arnied_dial_permanent(prid="P1", block=False):
160 """
161 Set permanent online state. Since the arnied dial helpers cannot initiate a
162 permanent online state, achieve this via arnied.
163
164 :param prid: Provider id, default *P1*. It is up to the caller to ensure
165 this is a valid provider id.
166 :type prid: str
7628bc48
CH
167 :param block: block execution until system is online
168 :type block: bool
f49f6323
PD
169 :returns: Whether the ``tell-connd`` command succeeded.
170 :rtype: int (dial result as above)
171 """
14d257c1 172 log.debug("requested connd_dial_online%s" % " (blocking)" if block else "")
f49f6323
PD
173
174 cnf = simple_cnf.SimpleCnf()
175 cnf.add("DIALOUT_MODE", DIALOUT_MODE_CNF[DIALOUT_MODE_PERMANENT])
176 cnf.add("DIALOUT_DEFAULTPROVIDER_REF", "1")
177
178 def aux():
179 return arnied_wrapper.set_cnf_pipe(cnf, block=block), "", None
180
181 if block is False:
182 succ = aux()
183 return sysmisc.RUN_RESULT_OK if succ is True else sysmisc.RUN_RESULT_FAIL
184
185 res, err = sysmisc.cmd_block_till(aux, DIALTOOLS_TIMEOUT, _connd_online,
186 prid=prid)
187 log.debug("arnied_dial_permanent: result (%d, %r)", res, err)
188 return res
189
190
191def dialout(mode=DIALOUT_MODE_DEFAULT, prid="P1", block=True):
192 """
193
194 :param mode: How to dial (permanent vs. manual).
195 :type mode: int (``DIALOUT_MODE``) | string
196 :param prid: Provider id, default *P1*. It is up to the caller to ensure
197 this is a valid provider id.
198 :type prid: str (constrained by available providers, obviously).
199 :param block: Whether to block until completion of the command.
200 :type block: bool
201 :returns: Whether the command succeeded.
202 :rtype: int (dial result)
203 :raises: :py:class:`ValueError` if invalid dial mode was selected
204 """
205 log.info("go online with provider")
206
207 dmode = None
208 if isinstance(mode, int) is True:
209 dmode = mode
210 elif isinstance(mode, str) is True:
211 try:
212 dmode = DIALOUT_MODE_BY_NAME[mode]
7628bc48 213 except Exception:
f49f6323
PD
214 log.error("invalid online mode name “%s” requested" % mode)
215 pass
216
217 if dmode is None:
218 raise ValueError("exiting due to invalid online mode %r" % mode)
219
220 log.debug("go online, mode=%d(%s), id=%r", dmode, mode, prid)
221
222 if dmode == DIALOUT_MODE_PERMANENT:
223 return arnied_dial_permanent(prid, block=block)
224
225 if dmode == DIALOUT_MODE_MANUAL:
226 return arnied_dial_doc(prid, block=block)
227
228 raise ValueError("invalid dialout mode %r/%r requested" % (mode, dmode))
229
230
231def get_wan_address(vm=None):
232 """
233 Retrieve the current WAN IP address of client ``vm`` or localhost.
234
235 :param vm: Guest (client) to query; will as local connd if left unspecified.
236 :type vm: virttest.qemu_vm.VM | None
237
238 :returns: The IPv4 address. For correctness, it will use the
239 ipaddress module if available. Otherwise it falls back
240 on untyped data.
241 :rtype: None | (ipaddress.IPv4Address | str)
242 """
243 log.info("query current lease")
244 if vm is None:
245 succ, connstat, _ = sysmisc.run_cmd_with_pipe([TELL_CONND_BIN, "--status"])
246 if succ is False:
247 return None
248 else:
249 connstat = vm.session.cmd_output("%s --status" % TELL_CONND_BIN)
250 astr = io.StringIO(connstat)
251
252 while True:
253 l = astr.readline()
254 if l == "":
255 break
256 if l.find("connected online fsm<online> IP:") != -1:
257 addr = l[l.find("IP:")+3:l.find(" )\n")] # beurk
258 if HAVE_IPADDRESS is True:
259 return ipaddress.IPv4Address(str(addr))
260 else:
261 return addr
262 return None