Commit | Line | Data |
---|---|---|
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 | ||
25 | SUMMARY | |
26 | ------------------------------------------------------ | |
27 | Dialing, hangup, general provider online/offline state control. | |
28 | ||
29 | Copyright: 2017 Intra2net AG | |
30 | ||
31 | This used to be part of the sysmisc utilitiy initially which caused an include | |
32 | circle with the arnied wrapper. | |
33 | ||
34 | ||
35 | CONTENTS | |
36 | ------------------------------------------------------ | |
37 | dialout | |
38 | Generic access to the system’s dialing mode. Allows for requesting manual | |
39 | or permanently online dial state. | |
40 | ||
41 | arnied_dial_permanent | |
42 | Enter permantly online dialing state. | |
43 | ||
44 | arnied_dial_do | |
45 | Enter dial on command state. | |
46 | ||
47 | arnied_dial_hangup | |
48 | Terminate uplink unconditionally. | |
49 | ||
50 | All API methods come with the optional argument ``block`` (bool) to request the | |
51 | call to block until the system completes the state transition successfully. The | |
52 | completion timeout is currently 10 seconds (see the definition of | |
53 | ``DIALTOOLS_TIMEOUT`` below). | |
54 | ||
55 | ||
56 | IMPLEMENTATION | |
57 | ------------------------------------------------------ | |
58 | ||
59 | """ | |
60 | ||
61 | ||
62 | import re | |
63 | import io | |
64 | import time | |
65 | import logging | |
3de8b4d8 | 66 | log = logging.getLogger('pyi2ncommon.dial') |
f49f6323 | 67 | |
e5c2b9eb | 68 | from . import cnfvar |
30521dad | 69 | from . import sysmisc |
f49f6323 PD |
70 | |
71 | HAVE_IPADDRESS = True | |
72 | try: | |
73 | import ipaddress | |
74 | except ImportError: # guest | |
75 | HAVE_IPADDRESS = False | |
76 | ||
77 | __all__ = ( | |
78 | "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" | |
79 | ) | |
80 | ||
f49f6323 PD |
81 | DIALOUT_MODE_PERMANENT = 0 |
82 | DIALOUT_MODE_MANUAL = 1 | |
83 | DIALOUT_MODE_DEFAULT = DIALOUT_MODE_PERMANENT | |
84 | DIALOUT_MODE_BY_NAME = {"permanent": DIALOUT_MODE_PERMANENT, "manual": DIALOUT_MODE_MANUAL} | |
85 | DIALOUT_MODE_CNF = {DIALOUT_MODE_PERMANENT: "ONLINE", DIALOUT_MODE_MANUAL: "MANUAL"} | |
86 | ||
87 | # compiling this regex needs the provider id and is postponed due to | |
88 | # the peculiar implementation of the connd online condition | |
89 | NEEDLE_MEMO = " \[%s\] :(.*connected online fsm<online>.*)" | |
90 | NEEDLE_OFFLINE = re.compile("connection map:\nend of connection map") | |
91 | ||
92 | DIALTOOLS_HANGUP_BIN = "/usr/intranator/bin/hangup" | |
7f66ff3e CH |
93 | |
94 | #: binary for manual dialing (dial on command) | |
f49f6323 PD |
95 | DIALTOOLS_DOC_BIN = "/usr/intranator/bin/doc" |
96 | DIALTOOLS_TIMEOUT = 10 # s | |
7f66ff3e CH |
97 | |
98 | #: client binary for talking to connd | |
f49f6323 PD |
99 | TELL_CONND_BIN = "/usr/intranator/bin/tell-connd" |
100 | ||
101 | ||
102 | def _connd_online(prid="P1"): | |
103 | succ, out, _ = sysmisc.run_cmd_with_pipe([TELL_CONND_BIN, "--status"]) | |
104 | if succ is False: | |
105 | return False | |
106 | return re.search(NEEDLE_MEMO % prid, out) is not None | |
107 | ||
108 | ||
109 | def _connd_offline(): | |
110 | succ, out, _ = sysmisc.run_cmd_with_pipe([TELL_CONND_BIN, "--status"]) | |
111 | return succ and (NEEDLE_OFFLINE.search(out) is not None) | |
112 | ||
113 | ||
114 | def arnied_dial_hangup(block=False): | |
115 | """ | |
116 | Take down any currently active provider. This leverages arnied to | |
117 | accomplish the disconnect which is apparently more appropriate than | |
118 | having connd do it directly. | |
119 | ||
120 | :returns: Whether the ``hangup`` command succeeded. | |
121 | :rtype: int (dial result as above) | |
122 | """ | |
123 | log.debug("requested arnied_dial_hangup%s", | |
124 | " (blocking)" if block else "") | |
125 | if block is False: | |
126 | succ, _, _ = sysmisc.run_cmd_with_pipe([DIALTOOLS_HANGUP_BIN]) | |
127 | return sysmisc.RUN_RESULT_OK if succ is True else sysmisc.RUN_RESULT_FAIL | |
128 | ||
129 | res, err = sysmisc.cmd_block_till([DIALTOOLS_HANGUP_BIN], | |
130 | DIALTOOLS_TIMEOUT, _connd_offline) | |
131 | log.debug("arnied_dial_hangup → (%d, %r)", res, err) | |
132 | return res | |
133 | ||
134 | ||
135 | def arnied_dial_doc(prid="P1", block=False): | |
136 | """ | |
7f66ff3e | 137 | Bring provider up via arnied manual dial (dial on command). |
f49f6323 PD |
138 | |
139 | :param prid: Provider id, default *P1*. It is up to the caller to ensure | |
140 | this is a valid provider id. | |
141 | :type prid: str | |
7628bc48 CH |
142 | :param block: block execution until system is online |
143 | :type block: bool | |
f49f6323 PD |
144 | :returns: Whether the ``doc`` command succeeded. |
145 | :rtype: int (dial result as above) | |
146 | """ | |
147 | log.debug("requested arnied_dial_doc%s", " (blocking)" if block else "") | |
148 | if block is False: | |
149 | succ, _, _ = sysmisc.run_cmd_with_pipe([DIALTOOLS_DOC_BIN, prid]) | |
150 | return sysmisc.RUN_RESULT_OK if succ is True else sysmisc.RUN_RESULT_FAIL | |
151 | res, err = sysmisc.cmd_block_till([DIALTOOLS_DOC_BIN, prid], | |
152 | DIALTOOLS_TIMEOUT, _connd_online, | |
153 | prid=prid) | |
154 | log.debug("arnied_dial_doc → (%d, %r)", res, err) | |
155 | return res | |
156 | ||
157 | ||
158 | def arnied_dial_permanent(prid="P1", block=False): | |
159 | """ | |
160 | Set permanent online state. Since the arnied dial helpers cannot initiate a | |
161 | permanent online state, achieve this via arnied. | |
162 | ||
163 | :param prid: Provider id, default *P1*. It is up to the caller to ensure | |
164 | this is a valid provider id. | |
165 | :type prid: str | |
7628bc48 CH |
166 | :param block: block execution until system is online |
167 | :type block: bool | |
f49f6323 PD |
168 | :returns: Whether the ``tell-connd`` command succeeded. |
169 | :rtype: int (dial result as above) | |
170 | """ | |
14d257c1 | 171 | log.debug("requested connd_dial_online%s" % " (blocking)" if block else "") |
f49f6323 | 172 | |
e5c2b9eb PD |
173 | cnf = cnfvar.CnfList([("dialout_mode", DIALOUT_MODE_CNF[DIALOUT_MODE_PERMANENT]), |
174 | ("dialout_defaultprovider_ref", "1")]) | |
f49f6323 PD |
175 | |
176 | def aux(): | |
46a11f18 PD |
177 | store = cnfvar.BinaryCnfStore() |
178 | store.commit(cnf) | |
179 | return True, "", None | |
f49f6323 PD |
180 | |
181 | if block is False: | |
46a11f18 | 182 | succ, out, _ = aux() |
f49f6323 PD |
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 | ||
191 | def 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 | ||
231 | def 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 |