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>
24 Dialing, hangup, general provider online/offline state control.
26 Copyright: 2017 Intra2net AG
28 This used to be part of the sysmisc utilitiy initially which caused an include
29 circle with the arnied wrapper.
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.
38 Enter dial on command state.
40 Terminate uplink unconditionally.
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).
53 log = logging.getLogger('pyi2ncommon.dial')
61 except ImportError: # guest
62 HAVE_IPADDRESS = False
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"
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"}
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")
79 DIALTOOLS_HANGUP_BIN = "/usr/intranator/bin/hangup"
81 #: binary for manual dialing (dial on command)
82 DIALTOOLS_DOC_BIN = "/usr/intranator/bin/doc"
83 DIALTOOLS_TIMEOUT = 10 # s
85 #: client binary for talking to connd
86 TELL_CONND_BIN = "/usr/intranator/bin/tell-connd"
89 def _connd_online(prid="P1"):
90 succ, out, _ = sysmisc.run_cmd_with_pipe([TELL_CONND_BIN, "--status"])
93 return re.search(NEEDLE_MEMO % prid, out) is not None
97 succ, out, _ = sysmisc.run_cmd_with_pipe([TELL_CONND_BIN, "--status"])
98 return succ and (NEEDLE_OFFLINE.search(out) is not None)
101 def arnied_dial_hangup(block=False):
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.
107 :returns: Whether the ``hangup`` command succeeded.
108 :rtype: int (dial result as above)
110 log.debug("requested arnied_dial_hangup%s",
111 " (blocking)" if block else "")
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
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)
122 def arnied_dial_doc(prid="P1", block=False):
124 Bring provider up via arnied manual dial (dial on command).
126 :param prid: Provider id, default *P1*. It is up to the caller to ensure
127 this is a valid provider id.
129 :param block: block execution until system is online
131 :returns: Whether the ``doc`` command succeeded.
132 :rtype: int (dial result as above)
134 log.debug("requested arnied_dial_doc%s", " (blocking)" if block else "")
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,
141 log.debug("arnied_dial_doc → (%d, %r)", res, err)
145 def arnied_dial_permanent(prid="P1", block=False):
147 Set permanent online state. Since the arnied dial helpers cannot initiate a
148 permanent online state, achieve this via arnied.
150 :param prid: Provider id, default *P1*. It is up to the caller to ensure
151 this is a valid provider id.
153 :param block: block execution until system is online
155 :returns: Whether the ``tell-connd`` command succeeded.
156 :rtype: int (dial result as above)
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.
162 log.debug("requested connd_dial_online%s" % " (blocking)" if block else "")
164 cnf = cnfvar.CnfList([("dialout_mode", DIALOUT_MODE_CNF[DIALOUT_MODE_PERMANENT]),
165 ("dialout_defaultprovider_ref", "1")])
168 store = cnfvar.BinaryCnfStore()
170 return True, "", None
174 return sysmisc.RUN_RESULT_OK if succ is True else sysmisc.RUN_RESULT_FAIL
176 res, err = sysmisc.cmd_block_till(aux, DIALTOOLS_TIMEOUT, _connd_online,
178 log.debug("arnied_dial_permanent: result (%d, %r)", res, err)
182 def dialout(mode=DIALOUT_MODE_DEFAULT, prid="P1", block=True):
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.
192 :returns: Whether the command succeeded.
193 :rtype: int (dial result)
194 :raises: :py:class:`ValueError` if invalid dial mode was selected
196 log.info("go online with provider")
199 if isinstance(mode, int) is True:
201 elif isinstance(mode, str) is True:
203 dmode = DIALOUT_MODE_BY_NAME[mode]
205 log.error("invalid online mode name “%s” requested" % mode)
209 raise ValueError("exiting due to invalid online mode %r" % mode)
211 log.debug("go online, mode=%d(%s), id=%r", dmode, mode, prid)
213 if dmode == DIALOUT_MODE_PERMANENT:
214 return arnied_dial_permanent(prid, block=block)
216 if dmode == DIALOUT_MODE_MANUAL:
217 return arnied_dial_doc(prid, block=block)
219 raise ValueError("invalid dialout mode %r/%r requested" % (mode, dmode))
222 def get_wan_address(vm=None):
224 Retrieve the current WAN IP address of client ``vm`` or localhost.
226 :param vm: Guest (client) to query; will as local connd if left unspecified.
227 :type vm: virttest.qemu_vm.VM | None
229 :returns: The IPv4 address. For correctness, it will use the
230 ipaddress module if available. Otherwise it falls back
232 :rtype: None | (ipaddress.IPv4Address | str)
234 log.info("query current lease")
236 succ, connstat, _ = sysmisc.run_cmd_with_pipe([TELL_CONND_BIN, "--status"])
240 connstat = vm.session.cmd_output("%s --status" % TELL_CONND_BIN)
241 astr = io.StringIO(connstat)
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))