atex 0.13__py3-none-any.whl → 0.15__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,6 +4,7 @@ import uuid
4
4
  import shlex
5
5
  import socket
6
6
  import random
7
+ import logging
7
8
  import textwrap
8
9
  import tempfile
9
10
  import threading
@@ -18,6 +19,8 @@ from . import locking
18
19
 
19
20
  libvirt = util.import_libvirt()
20
21
 
22
+ logger = logging.getLogger("atex.provisioner.libvirt")
23
+
21
24
  # thread-safe bool
22
25
  libvirt_needs_setup = threading.Semaphore(1)
23
26
 
@@ -34,7 +37,7 @@ def setup_event_loop():
34
37
  time.sleep(0.5)
35
38
  libvirt.virEventRunDefaultImpl()
36
39
 
37
- util.debug("starting libvirt event loop")
40
+ logger.debug("starting libvirt event loop")
38
41
  thread = threading.Thread(target=loop, name="libvirt_event_loop", daemon=True)
39
42
  thread.start()
40
43
 
@@ -46,17 +49,17 @@ class LibvirtCloningRemote(Remote, connection.ssh.ManagedSSHConnection):
46
49
 
47
50
  def __init__(self, ssh_options, host, domain, source_image, *, release_hook):
48
51
  """
49
- 'ssh_options' are a dict, passed to ManagedSSHConnection __init__().
52
+ - `ssh_options` are a dict, passed to ManagedSSHConnection `__init__()`.
50
53
 
51
- 'host' is a str of libvirt host name (used for repr()).
54
+ - `host` is a str of libvirt host name (used for `repr()`).
52
55
 
53
- 'domain' is a str of libvirt domain name (used for repr()).
56
+ - `domain` is a str of libvirt domain name (used for `repr()`).
54
57
 
55
- 'source_image' is a str of libvirt volume name that was cloned
56
- for the domain to boot from (used for repr()).
58
+ - `source_image` is a str of libvirt volume name that was cloned
59
+ for the domain to boot from (used for `repr()`).
57
60
 
58
- 'release_hook' is a callable called on .release() in addition
59
- to disconnecting the connection.
61
+ - `release_hook` is a callable called on `.release()` in addition
62
+ to disconnecting the connection.
60
63
  """
61
64
  # NOTE: self.lock inherited from ManagedSSHConnection
62
65
  super().__init__(options=ssh_options)
@@ -129,37 +132,42 @@ class LibvirtCloningProvisioner(Provisioner):
129
132
  reserve_delay=3, reserve_time=3600, start_event_loop=True,
130
133
  ):
131
134
  """
132
- 'host' is a ManagedSSHConnection class instance, connected to a libvirt host.
135
+ - `host` is a ManagedSSHConnection class instance, connected to
136
+ a libvirt host.
137
+
138
+ - `image` is a string with a libvirt storage volume name inside the
139
+ given storage `pool` that should be used as the source for cloning.
140
+
141
+ - `pool` is a libvirt storage pool used by all relevant domains on the
142
+ libvirt host **as well as** the would-be-cloned images.
133
143
 
134
- 'image' is a string with a libvirt storage volume name inside the
135
- given storage 'pool' that should be used as the source for cloning.
144
+ - `domain_filter` is a regex string matching libvirt domain names to
145
+ attempt reservation on. Useful for including only ie. `auto-.*`
146
+ domains while leaving other domains on the same libvirt host
147
+ untouched.
136
148
 
137
- 'pool' is a libvirt storage pool used by all relevant domains on the
138
- libvirt host **as well as** the would-be-cloned images.
149
+ - `domain_user` and `domain_sshkey` (strings) specify how to connect to
150
+ an OS booted from the pre-instaled `image`, as these credentials are
151
+ known only to the logic that created the `image` in the first place.
139
152
 
140
- 'domain_filter' is a regex string matching libvirt domain names to
141
- attempt reservation on. Useful for including only ie. 'auto-.*' domains
142
- while leaving other domains on the same libvirt host untouched.
153
+ - `reserve_delay` is an int of how many seconds to wait between trying
154
+ to lock libvirt domains, after every unsuccessful locking attempt.
143
155
 
144
- 'domain_user' and 'domain_sshkey' (strings) specify how to connect to
145
- an OS booted from the pre-instaled 'image', as these credentials are
146
- known only to the logic that created the 'image' in the first place.
156
+ Ie. with `delay=5` and 20 domains, the code will try to lock every
157
+ domain in `5*20=100` seconds before looping back to the first.
147
158
 
148
- 'reserve_delay' is an int of how many seconds to wait between trying to
149
- lock libvirt domains, after every unsuccessful locking attempt.
150
- Ie. with delay=5 and 20 domains, the code will try to lock every domain
151
- in 5*20=100 seconds before looping back to the first.
159
+ - `reserve_time` is an int of maximum seconds to reserve a libvirt
160
+ domain for before other users can steal it for themselves. Note that
161
+ there is no automatic timeout release logic, it's just a hint for
162
+ others.
152
163
 
153
- 'reserve_time' is an int of maximum seconds to reserve a libvirt domain
154
- for before other users can steal it for themselves. Note that there is
155
- no automatic timeout release logic, it's just a hint for others.
164
+ - `start_event_loop` set to `True` starts a global default libvirt event
165
+ loop as part of `.start()` (or context manager enter) in a background
166
+ daemon thread.
156
167
 
157
- 'start_event_loop' set to True starts a global default libvirt event
158
- loop as part of .start() (or context manager enter) in a background
159
- daemon thread.
160
- This is necessary to maintain connection keep-alives, but if you plan
161
- on managing the loop yourself (have custom uses for the libvirt module),
162
- setting False here avoids any meddling by this class.
168
+ This is necessary to maintain connection keep-alives, but if you plan
169
+ on managing the loop yourself (have custom uses for the libvirt
170
+ module), setting `False` here avoids any meddling by this class.
163
171
  """
164
172
  self.lock = threading.RLock()
165
173
  self.host = host
@@ -204,7 +212,7 @@ class LibvirtCloningProvisioner(Provisioner):
204
212
  xml_root = ET.fromstring(source_vol.XMLDesc())
205
213
  source_format = xml_root.find("target").find("format").get("type")
206
214
 
207
- util.debug(
215
+ logger.info(
208
216
  f"found volume {source_vol.name()} (format:{source_format}) in pool {pool.name()}",
209
217
  )
210
218
 
@@ -214,7 +222,7 @@ class LibvirtCloningProvisioner(Provisioner):
214
222
  already_reserving = {conn.lookupByName(name) for name in already_reserving}
215
223
 
216
224
  # acquire (lock) a domain on the libvirt host
217
- util.debug("attempting to acquire a domain")
225
+ logger.info("attempting to acquire a domain")
218
226
  acquired = None
219
227
  while not acquired:
220
228
  domains = []
@@ -229,7 +237,7 @@ class LibvirtCloningProvisioner(Provisioner):
229
237
  for domain in domains:
230
238
  if locking.lock(domain, self.signature, self.reserve_end):
231
239
  acquired = domain
232
- util.debug(f"acquired domain {acquired.name()}")
240
+ logger.info(f"acquired domain {acquired.name()}")
233
241
  break
234
242
  time.sleep(self.reserve_delay)
235
243
 
@@ -245,7 +253,7 @@ class LibvirtCloningProvisioner(Provisioner):
245
253
 
246
254
  # parse XML definition of the domain
247
255
  xmldesc = acquired.XMLDesc().rstrip("\n")
248
- util.extradebug(f"domain {acquired.name()} XML:\n{textwrap.indent(xmldesc, ' ')}")
256
+ logger.debug(f"domain {acquired.name()} XML:\n{textwrap.indent(xmldesc, ' ')}")
249
257
  xml_root = ET.fromstring(xmldesc)
250
258
  nvram_vol = nvram_path = None
251
259
 
@@ -279,7 +287,7 @@ class LibvirtCloningProvisioner(Provisioner):
279
287
  if "Storage volume not found" not in str(e):
280
288
  raise
281
289
  if nvram_vol:
282
- util.debug(f"deleting nvram volume {nvram_vol.name()}")
290
+ logger.info(f"deleting nvram volume {nvram_vol.name()}")
283
291
  nvram_vol.delete()
284
292
 
285
293
  # try to find a disk that is a volume in the specified storage pool
@@ -298,7 +306,7 @@ class LibvirtCloningProvisioner(Provisioner):
298
306
  if xml_disk_source.get("pool") != pool.name():
299
307
  continue
300
308
  disk_vol_name = xml_disk_source.get("volume")
301
- util.debug(f"found a domain disk in XML: {disk_vol_name} for pool {pool.name()}")
309
+ logger.info(f"found a domain disk in XML: {disk_vol_name} for pool {pool.name()}")
302
310
  break
303
311
  else:
304
312
  raise RuntimeError("could not find any <disk> in <devices>")
@@ -322,7 +330,7 @@ class LibvirtCloningProvisioner(Provisioner):
322
330
  pool.createXMLFrom(new_volume, source_vol)
323
331
 
324
332
  # start the domain up
325
- util.debug(f"starting up {acquired.name()}")
333
+ logger.info(f"starting up {acquired.name()}")
326
334
  acquired.create() # like 'virsh start' NOT 'virsh create'
327
335
 
328
336
  # wait for an IP address leased by libvirt host
@@ -332,7 +340,7 @@ class LibvirtCloningProvisioner(Provisioner):
332
340
  libvirt.VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LEASE,
333
341
  )
334
342
  time.sleep(1)
335
- util.debug(f"found iface addrs: {addrs}")
343
+ logger.info(f"found iface addrs: {addrs}")
336
344
  first_iface = next(iter(addrs.values()))
337
345
  first_addr = next(iter(first_iface.values()))[0]["addr"]
338
346
 
@@ -414,7 +422,7 @@ class LibvirtCloningProvisioner(Provisioner):
414
422
  name = Path(f.name)
415
423
  name.chmod(0o0500) # r-x------
416
424
  uri = f"qemu+ext:///system?command={urllib.parse.quote(str(name.absolute()))}"
417
- util.debug(f"opening libvirt conn to {uri}")
425
+ logger.info(f"opening libvirt conn to {uri}")
418
426
  conn = libvirt.open(uri)
419
427
  conn.setKeepAlive(5, 3)
420
428
  return conn
@@ -429,7 +437,6 @@ class LibvirtCloningProvisioner(Provisioner):
429
437
 
430
438
  def stop(self):
431
439
  with self.lock:
432
- #util.debug(f"SELF.RESERVING: {self.reserving} // SELF.REMOTES: {self.remotes}")
433
440
  # close reserving libvirt host connection
434
441
  # - this stops _reserve_one() from doing anything bad
435
442
  if self.reserve_conn:
@@ -445,8 +452,7 @@ class LibvirtCloningProvisioner(Provisioner):
445
452
  try:
446
453
  domain = self.manage_conn.lookupByName(self.reserving.pop())
447
454
  locking.unlock(domain, self.signature)
448
- except libvirt.libvirtError as e:
449
- util.debug(f"GOT ERROR: {str(e)}")
455
+ except libvirt.libvirtError:
450
456
  pass
451
457
  # cancel/release all Remotes ever created by us
452
458
  while self.remotes:
@@ -9,8 +9,8 @@ Lock safety is ensured by libvirt retaining <metadata> content (tag) order,
9
9
  so each user can re-check (after locking) whether the race was won or lost,
10
10
  depending on whether the user's signature is on top of <metadata>.
11
11
 
12
- A timestamp (meaning now()+duration) is used as the lock tag value/text,
13
- and any expired timestamps (now() > timestamp) are ignored by the locking
12
+ A timestamp (meaning `now()+duration`) is used as the lock tag value/text,
13
+ and any expired timestamps (`now() > timestamp`) are ignored by the locking
14
14
  logic.
15
15
  """
16
16
 
@@ -26,11 +26,11 @@ libvirt = util.import_libvirt()
26
26
 
27
27
  def get_locks(domain, expired=False):
28
28
  """
29
- Yield (signature,timestamp) tuples of atex-lock entries for a 'domain'.
29
+ Yield (signature,timestamp) tuples of atex-lock entries for a `domain`.
30
30
 
31
- If 'expired' is True, yield also locks with an expired timestamp.
31
+ - If `expired` is `True`, yield also locks with an expired timestamp.
32
32
 
33
- If a timestamp is missing, it is substituted with 0.
33
+ If a timestamp is missing, it is substituted with `0`.
34
34
  """
35
35
  xml_dump = ET.fromstring(domain.XMLDesc(libvirt.VIR_DOMAIN_XML_INACTIVE))
36
36
  metadata = xml_dump.find("metadata")
@@ -49,7 +49,7 @@ def get_locks(domain, expired=False):
49
49
 
50
50
  def unlock(domain, signature):
51
51
  """
52
- Unlock a domain previously locked by lock().
52
+ Unlock a domain previously locked by `lock()`.
53
53
  """
54
54
  domain.setMetadata(
55
55
  libvirt.VIR_DOMAIN_METADATA_ELEMENT,
@@ -62,10 +62,10 @@ def unlock(domain, signature):
62
62
 
63
63
  def lock(domain, signature, timestamp):
64
64
  """
65
- Attempt to lock a domain under 'signature' ownership,
66
- writing out 'timestamp' as the lock tag content.
65
+ Attempt to lock a domain under `signature` ownership,
66
+ writing out `timestamp` as the lock tag content.
67
67
 
68
- Returns True if the domain was successfully locked, False otherwise.
68
+ Returns `True` if the domain was successfully locked, `False` otherwise.
69
69
  """
70
70
  signature = str(signature)
71
71
  timestamp = int(timestamp)
@@ -102,17 +102,19 @@ def lock(domain, signature, timestamp):
102
102
 
103
103
  def lock_any(connection, signature, duration, filter_domains=lambda _: True):
104
104
  """
105
- Given a libvirt 'connection', attempt to lock (reserve) any one
106
- domain under 'signature' ownership for 'duration' seconds.
105
+ Given a libvirt `connection`, attempt to lock (reserve) any one
106
+ domain under `signature` ownership for `duration` seconds.
107
107
 
108
- If 'filter_domain' is given as a callable, it is used to filter
109
- domains considered for locking. It takes one argument (libvirt
110
- domain object) and must return True (domain is eligible for locking)
111
- or False (domain should be skipped).
112
- For example: lambda dom: dom.name().startswith("foo-")
108
+ - If `filter_domain` is given as a callable, it is used to filter
109
+ domains considered for locking.
110
+
111
+ It takes one argument (libvirt domain object) and must return `True`
112
+ (domain is eligible for locking) or `False` (domain should be skipped).
113
+
114
+ For example: `lambda dom: dom.name().startswith("foo-")`
113
115
 
114
116
  Returns a libvirt domain object of a successfully locked domain,
115
- or None if no domain could be locked.
117
+ or `None` if no domain could be locked.
116
118
  """
117
119
  domains = connection.listAllDomains(libvirt.VIR_CONNECT_LIST_DOMAINS_PERSISTENT)
118
120
  # try to avoid lock conflicts
@@ -129,11 +131,11 @@ def unlock_all(connection, signature=None, shutdown=False, filter_domains=lambda
129
131
  """
130
132
  Remove all locks for all domains.
131
133
 
132
- If 'signature' is given, remove only locks matching the signature.
134
+ - If `signature` is given, remove only locks matching the signature.
133
135
 
134
- If 'shutdown' is True, also forcibly shutdown (destroy) all domains.
136
+ - If `shutdown` is `True`, also forcibly shutdown (destroy) all domains.
135
137
 
136
- If 'filter_domains' is given, it behaves like for lock_any().
138
+ - If `filter_domains` is given, it behaves like for `lock_any()`.
137
139
  """
138
140
  domains = connection.listAllDomains(libvirt.VIR_CONNECT_LIST_DOMAINS_PERSISTENT)
139
141
  for domain in filter(filter_domains, domains):
@@ -156,11 +158,11 @@ def cleanup_expired(connection, timestamp=None, filter_domains=lambda _: True):
156
158
  for the given signature, it is never removed, unless this function is used
157
159
  (by some maintenance service).
158
160
 
159
- Note that unlock_all() cleans up all locks, incl. expired ones.
161
+ Note that `unlock_all()` cleans up all locks, incl. expired ones.
160
162
 
161
- If 'timestamp' is given, it is used instead of the now() UTC timestamp.
163
+ - If `timestamp` is given, it is used instead of the `now()` UTC timestamp.
162
164
 
163
- If 'filter_domains' is given, it behaves like for lock_any().
165
+ - If `filter_domains` is given, it behaves like for `lock_any()`.
164
166
  """
165
167
  now = int(timestamp) if timestamp is not None else int(time.time())
166
168
  domains = connection.listAllDomains(libvirt.VIR_CONNECT_LIST_DOMAINS_PERSISTENT)
@@ -14,12 +14,12 @@ class PodmanRemote(Remote, connection.podman.PodmanConnection):
14
14
 
15
15
  def __init__(self, image, container, *, release_hook):
16
16
  """
17
- 'image' is an image tag (used for repr()).
17
+ - `image` is an image tag (used for `repr()`).
18
18
 
19
- 'container' is a podman container id / name.
19
+ - `container` is a podman container ID / name.
20
20
 
21
- 'release_hook' is a callable called on .release() in addition
22
- to disconnecting the connection.
21
+ - `release_hook` is a callable called on `.release()` in addition
22
+ to disconnecting the connection.
23
23
  """
24
24
  super().__init__(container=container)
25
25
  self.lock = threading.RLock()
@@ -61,11 +61,11 @@ class PodmanRemote(Remote, connection.podman.PodmanConnection):
61
61
  class PodmanProvisioner(Provisioner):
62
62
  def __init__(self, image, run_options=None):
63
63
  """
64
- 'image' is a string of image tag/id to create containers from.
65
- It can be a local identifier or an URL.
64
+ - `image` is a string of image tag/ID to create containers from.
65
+ It can be a local identifier or an URL.
66
66
 
67
- 'run_options' is an iterable with additional CLI options passed
68
- to 'podman container run'.
67
+ - `run_options` is an iterable with additional CLI options passed
68
+ to `podman container run`.
69
69
  """
70
70
  self.lock = threading.RLock()
71
71
  self.image = image