1 # The software in this package is distributed under the GNU General
2 # Public License version 2 (with a special exception described below).
4 # A copy of GNU General Public License (GPL) is included in this distribution,
5 # in the file COPYING.GPL.
7 # As a special exception, if other files instantiate templates or use macros
8 # or inline functions from this file, or you compile this file and link it
9 # with other works to produce a work based on this file, this file
10 # does not by itself cause the resulting work to be covered
11 # by the GNU General Public License.
13 # However the source code for this file must still be made available
14 # in accordance with section (3) of the GNU General Public License.
16 # This exception does not invalidate any other reasons why a work based
17 # on this file might be covered by the GNU General Public License.
19 # Copyright (c) 2016-2022 Intra2net AG <info@intra2net.com>
22 arnied_api: wrappers around the arnied varlink API.
25 - Arnied: stateless class with methods as exposed in the varlink API.
27 For documentation of Exceptions, methods and their arguments, refer to arnied
28 source code (arnied/client/arnieclient.hxx). For compatibility, argument names
29 are the same here as they are there (defeating python naming rules).
31 See package `cnfvar` for a higher-level interface to this functionality.
33 .. codeauthor:: Intra2net
36 from contextlib import contextmanager
37 from dataclasses import dataclass
38 from types import SimpleNamespace
39 from enum import Enum, auto
45 #: socket where the varlink interface is exposed
46 ARNIED_SOCKET = "/var/intranator/arnied-varlink.sock"
58 children: typing.List['CnfVar']
74 instance: typing.Optional[int] = None
77 class ProgramStatus(Enum):
83 # Method return types (not present in the interface)
85 class IsQueueActiveRet(SimpleNamespace):
89 class IsScheduledOrRunningRet(SimpleNamespace):
91 timestamp: typing.Optional[int]
94 class GetCnfRet(SimpleNamespace):
95 vars: typing.List[CnfVar]
98 class SetCnfRet(SimpleNamespace):
99 change_numbers: typing.List[int]
102 class SetCommitCnf(SimpleNamespace):
103 results: typing.List[ChangeCnfVar]
106 class CommitCnfRet(SimpleNamespace):
107 results: typing.List[ChangeCnfVar]
113 class InternalBridgeError(Exception):
115 super().__init__("An error occurred (InternalBridgeError)")
118 class EmptyInputError(Exception):
120 super().__init__("An error occurred (EmptyInputError)")
123 class BadCharError(Exception):
124 def __init__(self, results: str) -> None:
125 super().__init__(f"[BadCharError] Error in the arnied API (results={results})")
126 self.results = results
129 class ChildError(Exception):
130 def __init__(self, results: str) -> None:
131 super().__init__(f"[ChildError] Error in the arnied API (results={results})")
132 self.results = results
135 class CnfCommitError(Exception):
136 def __init__(self, results: typing.List[ChangeCnfVar]) -> None:
140 # the type does not seem to be converted correctly
141 if isinstance(r, dict):
142 r = ChangeCnfVar(**r)
143 self.results.append(r)
144 msgs.append(f"{r.name},{r.instance}: \"{r.data}\": {r.result_msg}")
145 super().__init__("Error committing cnfvars:\n" + "\n".join(msgs))
148 class NotFoundError(Exception):
149 def __init__(self, results: str) -> None:
150 super().__init__(f"[NotFoundError] Error in the arnied API (results={results})")
151 self.results = results
154 class SchedulerProgError(Exception):
156 super().__init__("An error occurred (SchedulerProgError)")
159 class ShowerrVarDataErr(Exception):
160 def __init__(self, msg_id: int) -> None:
161 super().__init__(f"[ShowerrVarDataErr] Error in the arnied API (msg_id={msg_id})")
165 class ShowerrVarMissing(Exception):
166 def __init__(self, params_count: int) -> None:
168 f"[ShowerrVarMissing] Error in the arnied API (params_count={params_count})")
169 self.params_count = params_count
172 class ProviderNotFound(Exception):
173 def __init__(self, provider_id: int) -> None:
175 f"[ProviderNotFound] Could not find provider (provider_id={provider_id})")
176 self.provider_id = provider_id
179 # Arnied varlink proxy
184 Expose methods of the arnied varlink interface in python.
186 As described in module doc, documentation of exceptions, methods, and
187 their arguments can be found in arnied source code.
189 VARLINK_ADDRESS = f"unix:{ARNIED_SOCKET}"
192 def _send_msg_wrapper(cls, send_msg):
194 Wrap the _send_message method of the client to always send parameters.
196 HACK: this is as hackish as it comes, but at the moment we do not
197 have another workaround. The underlying varlink package will only
198 send a dictionary with a "parameters" key when we pass parameters,
199 however methods with nullable arguments require this key, in which
200 case the API errors out. One example is the `GetCnf` method: if
201 we want to get *all* cnfvars we should not provide it with any args,
202 but since the varlink module will not pass a dictionary with a
203 "parameters" key, it will fail. This has been reported upstream:
204 https://github.com/varlink/python/issues/10#issuecomment-1067232162
207 data = json.loads(msg.decode("utf8"))
208 if not data.get("method", "").startswith("io.arnied"):
210 if data.get("parameters", {}):
212 data["parameters"] = {}
213 return send_msg(json.dumps(data).encode("utf8"))
218 def new_connection(cls):
219 # lazy import to reduce the scope of this dependency
221 client = varlink.Client.new_with_address(cls.VARLINK_ADDRESS)
222 conn = client.open("io.arnied", namespaced=True)
224 sm = conn._send_message
225 setattr(conn, "_send_message", Arnied._send_msg_wrapper(sm))
227 except varlink.VarlinkError as ex:
228 # not an exception from this module
229 if not ex.error().startswith("io.arnied"):
232 # raise the corresponding exception
233 error_class_name = ex.error().split(".")[-1]
234 self_mod = sys.modules[__name__]
235 exception_class = self_mod.__dict__[error_class_name]
236 raise exception_class(**ex.parameters())
242 def is_queue_active(cls) -> IsQueueActiveRet:
243 with cls.new_connection() as conn:
244 return conn.IsQueueActive()
247 def is_scheduled_or_running(cls, prog: str) -> IsScheduledOrRunningRet:
248 with cls.new_connection() as conn:
249 return conn.IsScheduledOrRunning(prog)
252 def noop(cls) -> None:
253 with cls.new_connection() as conn:
257 def no_timeout(cls) -> None:
258 with cls.new_connection() as conn:
259 return conn.NoTimeout()
262 def get_cnf(cls, query: typing.Optional[GetCnfQuery]) -> GetCnfRet:
263 with cls.new_connection() as conn:
264 return conn.GetCnf(query)
267 def set_cnf(cls, vars: typing.List[CnfVar]) -> SetCnfRet:
268 with cls.new_connection() as conn:
269 return conn.SetCnf(vars)
272 def set_commit_cnf(cls, vars: typing.List[CnfVar], username: typing.Optional[str],
273 nogenerate: bool, fix_commit: bool) -> SetCommitCnf:
274 with cls.new_connection() as conn:
275 return conn.SetCommitCnf(vars, username, nogenerate, fix_commit)
278 def commit_cnf(cls, change_numbers: typing.List[int], username: typing.Optional[str],
279 nogenerate: bool, fix_commit: bool) -> CommitCnfRet:
280 with cls.new_connection() as conn:
281 return conn.CommitCnf(change_numbers, username, nogenerate, fix_commit)
284 def showerr_add(cls, msg_id: int, params: typing.List[str]) -> None:
285 with cls.new_connection() as conn:
286 return conn.ShowerrAdd(msg_id, params)
289 def now_online(cls, provider: int) -> None:
290 with cls.new_connection() as conn:
291 return conn.NowOnline(provider)
294 def now_offline(cls) -> None:
295 with cls.new_connection() as conn:
296 return conn.NowOffline()
299 def now_dialing(cls, provider: int) -> None:
300 with cls.new_connection() as conn:
301 return conn.NowDialing(provider)
304 def dial_taking_ages(cls) -> None:
305 with cls.new_connection() as conn:
306 return conn.DialTakingAges()
309 def barrier_executed(cls, barrier_nr: int) -> None:
310 with cls.new_connection() as conn:
311 return conn.BarrierExecuted(barrier_nr)