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 | 23 | """ |
f49f6323 PD |
24 | Dialing, hangup, general provider online/offline state control. |
25 | ||
26 | Copyright: 2017 Intra2net AG | |
27 | ||
28 | This used to be part of the sysmisc utilitiy initially which caused an include | |
29 | circle with the arnied wrapper. | |
30 | ||
fcec8a63 CH |
31 | Contents: |
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 | |
42 | All API methods come with the optional argument ``block`` (bool) to request the | |
43 | call to block until the system completes the state transition successfully. The | |
44 | completion timeout is currently 10 seconds (see the definition of | |
45 | ``DIALTOOLS_TIMEOUT`` below). | |
f49f6323 PD |
46 | """ |
47 | ||
48 | ||
49 | import re | |
50 | import io | |
51 | import time | |
52 | import logging | |
3de8b4d8 | 53 | log = logging.getLogger('pyi2ncommon.dial') |
f49f6323 | 54 | |
e5c2b9eb | 55 | from . import cnfvar |
30521dad | 56 | from . import sysmisc |
f49f6323 PD |
57 | |
58 | HAVE_IPADDRESS = True | |
59 | try: | |
60 | import ipaddress | |
61 | except 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 |
68 | DIALOUT_MODE_PERMANENT = 0 |
69 | DIALOUT_MODE_MANUAL = 1 | |
70 | DIALOUT_MODE_DEFAULT = DIALOUT_MODE_PERMANENT | |
71 | DIALOUT_MODE_BY_NAME = {"permanent": DIALOUT_MODE_PERMANENT, "manual": DIALOUT_MODE_MANUAL} | |
72 | DIALOUT_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 | |
76 | NEEDLE_MEMO = " \[%s\] :(.*connected online fsm<online>.*)" | |
77 | NEEDLE_OFFLINE = re.compile("connection map:\nend of connection map") | |
78 | ||
79 | DIALTOOLS_HANGUP_BIN = "/usr/intranator/bin/hangup" | |
7f66ff3e CH |
80 | |
81 | #: binary for manual dialing (dial on command) | |
f49f6323 PD |
82 | DIALTOOLS_DOC_BIN = "/usr/intranator/bin/doc" |
83 | DIALTOOLS_TIMEOUT = 10 # s | |
7f66ff3e CH |
84 | |
85 | #: client binary for talking to connd | |
f49f6323 PD |
86 | TELL_CONND_BIN = "/usr/intranator/bin/tell-connd" |
87 | ||
88 | ||
89 | def _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 | ||
96 | def _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 | ||
101 | def 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 | ||
122 | def 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 | ||
145 | def 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 | ||
182 | def 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 | ||
222 | def 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 |