Merge branch 'cnfvar-deprecations'
[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
e5c2b9eb 68from . import cnfvar
30521dad 69from . import sysmisc
f49f6323
PD
70
71HAVE_IPADDRESS = True
72try:
73 import ipaddress
74except 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
81DIALOUT_MODE_PERMANENT = 0
82DIALOUT_MODE_MANUAL = 1
83DIALOUT_MODE_DEFAULT = DIALOUT_MODE_PERMANENT
84DIALOUT_MODE_BY_NAME = {"permanent": DIALOUT_MODE_PERMANENT, "manual": DIALOUT_MODE_MANUAL}
85DIALOUT_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
89NEEDLE_MEMO = " \[%s\] :(.*connected online fsm<online>.*)"
90NEEDLE_OFFLINE = re.compile("connection map:\nend of connection map")
91
92DIALTOOLS_HANGUP_BIN = "/usr/intranator/bin/hangup"
7f66ff3e
CH
93
94#: binary for manual dialing (dial on command)
f49f6323
PD
95DIALTOOLS_DOC_BIN = "/usr/intranator/bin/doc"
96DIALTOOLS_TIMEOUT = 10 # s
7f66ff3e
CH
97
98#: client binary for talking to connd
f49f6323
PD
99TELL_CONND_BIN = "/usr/intranator/bin/tell-connd"
100
101
102def _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
109def _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
114def 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
135def 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
158def 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)
4965c436
PD
170
171 ..todo:: This function uses old and deprecated methods of cnfvar usage, it
172 should therefore be converted and needs unit tests added to verify the
173 conversion and perhaps even to test the overall dial functionality.
f49f6323 174 """
14d257c1 175 log.debug("requested connd_dial_online%s" % " (blocking)" if block else "")
f49f6323 176
e5c2b9eb
PD
177 cnf = cnfvar.CnfList([("dialout_mode", DIALOUT_MODE_CNF[DIALOUT_MODE_PERMANENT]),
178 ("dialout_defaultprovider_ref", "1")])
f49f6323
PD
179
180 def aux():
46a11f18
PD
181 store = cnfvar.BinaryCnfStore()
182 store.commit(cnf)
183 return True, "", None
f49f6323
PD
184
185 if block is False:
46a11f18 186 succ, out, _ = aux()
f49f6323
PD
187 return sysmisc.RUN_RESULT_OK if succ is True else sysmisc.RUN_RESULT_FAIL
188
189 res, err = sysmisc.cmd_block_till(aux, DIALTOOLS_TIMEOUT, _connd_online,
190 prid=prid)
191 log.debug("arnied_dial_permanent: result (%d, %r)", res, err)
192 return res
193
194
195def dialout(mode=DIALOUT_MODE_DEFAULT, prid="P1", block=True):
196 """
197
198 :param mode: How to dial (permanent vs. manual).
199 :type mode: int (``DIALOUT_MODE``) | string
200 :param prid: Provider id, default *P1*. It is up to the caller to ensure
201 this is a valid provider id.
202 :type prid: str (constrained by available providers, obviously).
203 :param block: Whether to block until completion of the command.
204 :type block: bool
205 :returns: Whether the command succeeded.
206 :rtype: int (dial result)
207 :raises: :py:class:`ValueError` if invalid dial mode was selected
208 """
209 log.info("go online with provider")
210
211 dmode = None
212 if isinstance(mode, int) is True:
213 dmode = mode
214 elif isinstance(mode, str) is True:
215 try:
216 dmode = DIALOUT_MODE_BY_NAME[mode]
7628bc48 217 except Exception:
f49f6323
PD
218 log.error("invalid online mode name “%s” requested" % mode)
219 pass
220
221 if dmode is None:
222 raise ValueError("exiting due to invalid online mode %r" % mode)
223
224 log.debug("go online, mode=%d(%s), id=%r", dmode, mode, prid)
225
226 if dmode == DIALOUT_MODE_PERMANENT:
227 return arnied_dial_permanent(prid, block=block)
228
229 if dmode == DIALOUT_MODE_MANUAL:
230 return arnied_dial_doc(prid, block=block)
231
232 raise ValueError("invalid dialout mode %r/%r requested" % (mode, dmode))
233
234
235def get_wan_address(vm=None):
236 """
237 Retrieve the current WAN IP address of client ``vm`` or localhost.
238
239 :param vm: Guest (client) to query; will as local connd if left unspecified.
240 :type vm: virttest.qemu_vm.VM | None
241
242 :returns: The IPv4 address. For correctness, it will use the
243 ipaddress module if available. Otherwise it falls back
244 on untyped data.
245 :rtype: None | (ipaddress.IPv4Address | str)
246 """
247 log.info("query current lease")
248 if vm is None:
249 succ, connstat, _ = sysmisc.run_cmd_with_pipe([TELL_CONND_BIN, "--status"])
250 if succ is False:
251 return None
252 else:
253 connstat = vm.session.cmd_output("%s --status" % TELL_CONND_BIN)
254 astr = io.StringIO(connstat)
255
256 while True:
257 l = astr.readline()
258 if l == "":
259 break
260 if l.find("connected online fsm<online> IP:") != -1:
261 addr = l[l.find("IP:")+3:l.find(" )\n")] # beurk
262 if HAVE_IPADDRESS is True:
263 return ipaddress.IPv4Address(str(addr))
264 else:
265 return addr
266 return None