1 # This Python file uses the following encoding: utf-8
3 # The software in this package is distributed under the GNU General
4 # Public License version 2 (with a special exception described below).
6 # A copy of GNU General Public License (GPL) is included in this distribution,
7 # in the file COPYING.GPL.
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.
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.
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.
21 # Copyright (c) 2016-2018 Intra2net AG <info@intra2net.com>
26 ------------------------------------------------------
27 Dialing, hangup, general provider online/offline state control.
29 Copyright: 2017 Intra2net AG
31 This used to be part of the sysmisc utilitiy initially which caused an include
32 circle with the arnied wrapper.
36 ------------------------------------------------------
38 Generic access to the system’s dialing mode. Allows for requesting manual
39 or permanently online dial state.
42 Enter permantly online dialing state.
45 Enter dial on command state.
48 Terminate uplink unconditionally.
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).
57 ------------------------------------------------------
66 log = logging.getLogger('pyi2ncommon.dial')
68 from . import arnied_wrapper
69 from . import simple_cnf
75 except ImportError: # guest
76 HAVE_IPADDRESS = False
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"
82 DIALOUT_MODE_PERMANENT = 0
83 DIALOUT_MODE_MANUAL = 1
84 DIALOUT_MODE_DEFAULT = DIALOUT_MODE_PERMANENT
85 DIALOUT_MODE_BY_NAME = {"permanent": DIALOUT_MODE_PERMANENT, "manual": DIALOUT_MODE_MANUAL}
86 DIALOUT_MODE_CNF = {DIALOUT_MODE_PERMANENT: "ONLINE", DIALOUT_MODE_MANUAL: "MANUAL"}
88 # compiling this regex needs the provider id and is postponed due to
89 # the peculiar implementation of the connd online condition
90 NEEDLE_MEMO = " \[%s\] :(.*connected online fsm<online>.*)"
91 NEEDLE_OFFLINE = re.compile("connection map:\nend of connection map")
93 DIALTOOLS_HANGUP_BIN = "/usr/intranator/bin/hangup"
95 #: binary for manual dialing (dial on command)
96 DIALTOOLS_DOC_BIN = "/usr/intranator/bin/doc"
97 DIALTOOLS_TIMEOUT = 10 # s
99 #: client binary for talking to connd
100 TELL_CONND_BIN = "/usr/intranator/bin/tell-connd"
103 def _connd_online(prid="P1"):
104 succ, out, _ = sysmisc.run_cmd_with_pipe([TELL_CONND_BIN, "--status"])
107 return re.search(NEEDLE_MEMO % prid, out) is not None
110 def _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)
115 def arnied_dial_hangup(block=False):
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.
121 :returns: Whether the ``hangup`` command succeeded.
122 :rtype: int (dial result as above)
124 log.debug("requested arnied_dial_hangup%s",
125 " (blocking)" if block else "")
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
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)
136 def arnied_dial_doc(prid="P1", block=False):
138 Bring provider up via arnied manual dial (dial on command).
140 :param prid: Provider id, default *P1*. It is up to the caller to ensure
141 this is a valid provider id.
143 :param block: block execution until system is online
145 :returns: Whether the ``doc`` command succeeded.
146 :rtype: int (dial result as above)
148 log.debug("requested arnied_dial_doc%s", " (blocking)" if block else "")
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,
155 log.debug("arnied_dial_doc → (%d, %r)", res, err)
159 def arnied_dial_permanent(prid="P1", block=False):
161 Set permanent online state. Since the arnied dial helpers cannot initiate a
162 permanent online state, achieve this via arnied.
164 :param prid: Provider id, default *P1*. It is up to the caller to ensure
165 this is a valid provider id.
167 :param block: block execution until system is online
169 :returns: Whether the ``tell-connd`` command succeeded.
170 :rtype: int (dial result as above)
172 log.debug("requested connd_dial_online%s" % " (blocking)" if block else "")
174 cnf = simple_cnf.SimpleCnf()
175 cnf.add("DIALOUT_MODE", DIALOUT_MODE_CNF[DIALOUT_MODE_PERMANENT])
176 cnf.add("DIALOUT_DEFAULTPROVIDER_REF", "1")
179 return arnied_wrapper.set_cnf_pipe(cnf, block=block), "", None
183 return sysmisc.RUN_RESULT_OK if succ is True else sysmisc.RUN_RESULT_FAIL
185 res, err = sysmisc.cmd_block_till(aux, DIALTOOLS_TIMEOUT, _connd_online,
187 log.debug("arnied_dial_permanent: result (%d, %r)", res, err)
191 def dialout(mode=DIALOUT_MODE_DEFAULT, prid="P1", block=True):
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.
201 :returns: Whether the command succeeded.
202 :rtype: int (dial result)
203 :raises: :py:class:`ValueError` if invalid dial mode was selected
205 log.info("go online with provider")
208 if isinstance(mode, int) is True:
210 elif isinstance(mode, str) is True:
212 dmode = DIALOUT_MODE_BY_NAME[mode]
214 log.error("invalid online mode name “%s” requested" % mode)
218 raise ValueError("exiting due to invalid online mode %r" % mode)
220 log.debug("go online, mode=%d(%s), id=%r", dmode, mode, prid)
222 if dmode == DIALOUT_MODE_PERMANENT:
223 return arnied_dial_permanent(prid, block=block)
225 if dmode == DIALOUT_MODE_MANUAL:
226 return arnied_dial_doc(prid, block=block)
228 raise ValueError("invalid dialout mode %r/%r requested" % (mode, dmode))
231 def get_wan_address(vm=None):
233 Retrieve the current WAN IP address of client ``vm`` or localhost.
235 :param vm: Guest (client) to query; will as local connd if left unspecified.
236 :type vm: virttest.qemu_vm.VM | None
238 :returns: The IPv4 address. For correctness, it will use the
239 ipaddress module if available. Otherwise it falls back
241 :rtype: None | (ipaddress.IPv4Address | str)
243 log.info("query current lease")
245 succ, connstat, _ = sysmisc.run_cmd_with_pipe([TELL_CONND_BIN, "--status"])
249 connstat = vm.session.cmd_output("%s --status" % TELL_CONND_BIN)
250 astr = io.StringIO(connstat)
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))