python-chi 1.0.2__tar.gz → 1.0.4__tar.gz
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.
- python_chi-1.0.4/ChangeLog +7 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/PKG-INFO +1 -1
- {python_chi-1.0.2 → python_chi-1.0.4}/chi/container.py +2 -1
- {python_chi-1.0.2 → python_chi-1.0.4}/chi/hardware.py +3 -1
- {python_chi-1.0.2 → python_chi-1.0.4}/chi/network.py +21 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/chi/server.py +11 -2
- {python_chi-1.0.2 → python_chi-1.0.4}/chi/util.py +11 -0
- python_chi-1.0.4/docs/examples.rst +92 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/docs/index.rst +5 -1
- {python_chi-1.0.2 → python_chi-1.0.4}/python_chi.egg-info/PKG-INFO +1 -1
- {python_chi-1.0.2 → python_chi-1.0.4}/python_chi.egg-info/SOURCES.txt +1 -0
- python_chi-1.0.4/python_chi.egg-info/pbr.json +1 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/setup.py +1 -1
- python_chi-1.0.2/ChangeLog +0 -7
- python_chi-1.0.2/python_chi.egg-info/pbr.json +0 -1
- {python_chi-1.0.2 → python_chi-1.0.4}/.github/CODEOWNERS +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/.github/workflows/pypi-publish.yml +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/.github/workflows/test.yml +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/.mailmap +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/.readthedocs.yml +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/AUTHORS +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/DEVELOPMENT.rst +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/LICENSE +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/Makefile +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/README.rst +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/chi/__init__.py +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/chi/clients.py +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/chi/context.py +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/chi/exception.py +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/chi/image.py +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/chi/jupyterhub.py +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/chi/keypair.py +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/chi/lease.py +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/chi/magic.py +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/chi/share.py +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/chi/ssh.py +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/chi/storage.py +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/docs/__init__.py +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/docs/conf.py +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/docs/generate_notebook.py +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/docs/modules/clients.rst +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/docs/modules/container.rst +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/docs/modules/context.rst +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/docs/modules/exception.rst +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/docs/modules/hardware.rst +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/docs/modules/image.rst +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/docs/modules/lease.rst +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/docs/modules/magic.rst +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/docs/modules/network.rst +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/docs/modules/server.rst +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/docs/modules/share.rst +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/docs/modules/ssh.rst +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/docs/modules/storage.rst +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/docs/requirements.txt +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/python_chi.egg-info/dependency_links.txt +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/python_chi.egg-info/not-zip-safe +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/python_chi.egg-info/requires.txt +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/python_chi.egg-info/top_level.txt +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/requirements.txt +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/setup.cfg +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/test-requirements.txt +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/tests/__init__.py +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/tests/test_container.py +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/tests/test_context.py +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/tests/test_lease.py +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/tests/test_network.py +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/tests/test_server.py +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/tests/test_share.py +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/tests/test_ssh.py +0 -0
- {python_chi-1.0.2 → python_chi-1.0.4}/tox.ini +0 -0
|
@@ -515,7 +515,8 @@ def download(container_ref: "str", source: "str", dest: "str"):
|
|
|
515
515
|
res = zun().containers.get_archive(container_ref, source)
|
|
516
516
|
fd = io.BytesIO(res["data"])
|
|
517
517
|
with tarfile.open(fileobj=fd, mode="r") as tar:
|
|
518
|
-
tar.
|
|
518
|
+
tar.extraction_filter = (lambda member, path: member)
|
|
519
|
+
tar.extractall(dest)
|
|
519
520
|
|
|
520
521
|
|
|
521
522
|
def wait_for_active(container_ref: "str", timeout: int = (60 * 2)) -> "Container":
|
|
@@ -53,7 +53,7 @@ class Node:
|
|
|
53
53
|
|
|
54
54
|
def get_host_id(items, target_uid):
|
|
55
55
|
for item in items:
|
|
56
|
-
if item.get("uid") == target_uid:
|
|
56
|
+
if item.get("uid") == target_uid or item.get("hypervisor_hostname") == target_uid:
|
|
57
57
|
return item["id"]
|
|
58
58
|
return None
|
|
59
59
|
|
|
@@ -62,6 +62,8 @@ class Node:
|
|
|
62
62
|
# Get allocation for this specific host
|
|
63
63
|
host_id = get_host_id(blazarclient.host.list(), self.uid)
|
|
64
64
|
|
|
65
|
+
if not host_id:
|
|
66
|
+
raise exception.ServiceError(f"Host for {self.uid} not found in Blazar")
|
|
65
67
|
allocation = blazarclient.host.get_allocation(host_id)
|
|
66
68
|
|
|
67
69
|
now = datetime.now(timezone.utc)
|
|
@@ -187,6 +187,13 @@ def list_networks() -> "list[dict]":
|
|
|
187
187
|
return neutron().list_networks()["networks"]
|
|
188
188
|
|
|
189
189
|
|
|
190
|
+
def set_network_tag(network_id, value):
|
|
191
|
+
_neutron = neutron()
|
|
192
|
+
_neutron.replace_tag('networks', network_id, {
|
|
193
|
+
'tags': [value]
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
|
|
190
197
|
##########
|
|
191
198
|
# Subnets
|
|
192
199
|
##########
|
|
@@ -638,6 +645,20 @@ def remove_subnet_from_router(router_id, subnet_id):
|
|
|
638
645
|
# Floating IPs
|
|
639
646
|
###############
|
|
640
647
|
|
|
648
|
+
def set_floating_ip_tag(address, value):
|
|
649
|
+
ip_addr = get_floating_ip(address)
|
|
650
|
+
_neutron = neutron()
|
|
651
|
+
_neutron.replace_tag('floatingips', ip_addr['id'], {
|
|
652
|
+
'tags': [value]
|
|
653
|
+
})
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
def deallocate_floating_ip(address):
|
|
657
|
+
_neutron = neutron()
|
|
658
|
+
_neutron.delete_floatingip(
|
|
659
|
+
get_floating_ip(address)["id"]
|
|
660
|
+
)
|
|
661
|
+
|
|
641
662
|
|
|
642
663
|
def get_free_floating_ip(allocate=True) -> dict:
|
|
643
664
|
"""Get the first unallocated floating IP available to your project.
|
|
@@ -155,6 +155,7 @@ class Server:
|
|
|
155
155
|
wait_for_active: bool = True,
|
|
156
156
|
show: str = "widget",
|
|
157
157
|
idempotent: bool = False,
|
|
158
|
+
**kwargs,
|
|
158
159
|
) -> "Server":
|
|
159
160
|
"""
|
|
160
161
|
Submits a server creation request to the Nova API.
|
|
@@ -187,6 +188,7 @@ class Server:
|
|
|
187
188
|
flavor=self.flavor_name,
|
|
188
189
|
key=self.keypair.name,
|
|
189
190
|
net_ids=[get_network_id(DEFAULT_NETWORK)],
|
|
191
|
+
**kwargs,
|
|
190
192
|
)
|
|
191
193
|
try:
|
|
192
194
|
nova_server = self.conn.compute.create_server(**server_args)
|
|
@@ -274,6 +276,8 @@ class Server:
|
|
|
274
276
|
return server
|
|
275
277
|
|
|
276
278
|
def delete(self) -> None:
|
|
279
|
+
"""Deletes the server.
|
|
280
|
+
"""
|
|
277
281
|
delete_server(self.id)
|
|
278
282
|
|
|
279
283
|
def refresh(self):
|
|
@@ -422,7 +426,7 @@ class Server:
|
|
|
422
426
|
|
|
423
427
|
def associate_floating_ip(self, fip: Optional[str] = None) -> None:
|
|
424
428
|
"""
|
|
425
|
-
Associates a floating IP with the server.
|
|
429
|
+
Associates a floating IP with the server.
|
|
426
430
|
|
|
427
431
|
Args:
|
|
428
432
|
fip (str, optional): The floating IP to associate with the server. If not provided, a new floating IP will be allocated.
|
|
@@ -435,7 +439,7 @@ class Server:
|
|
|
435
439
|
|
|
436
440
|
def detach_floating_ip(self, fip: str) -> None:
|
|
437
441
|
"""
|
|
438
|
-
Detaches a floating IP from the server.
|
|
442
|
+
Detaches a floating IP from the server.
|
|
439
443
|
|
|
440
444
|
Args:
|
|
441
445
|
fip (str): The floating IP to detach.
|
|
@@ -562,6 +566,11 @@ class Server:
|
|
|
562
566
|
with self.ssh_connection(**kwargs) as conn:
|
|
563
567
|
return conn.run(command)
|
|
564
568
|
|
|
569
|
+
def get_metadata(self):
|
|
570
|
+
return nova().servers.list_meta(self.id)["metadata"]
|
|
571
|
+
|
|
572
|
+
def set_metadata_item(self, key, value):
|
|
573
|
+
return nova().servers.set_meta_item(self.id, key, value)
|
|
565
574
|
|
|
566
575
|
##########
|
|
567
576
|
# Flavors
|
|
@@ -36,6 +36,17 @@ def utcnow():
|
|
|
36
36
|
return datetime.now(tz=tz.tzutc())
|
|
37
37
|
|
|
38
38
|
|
|
39
|
+
def date_string_in_future(days=1):
|
|
40
|
+
return (utcnow() + timedelta(days=days)).isoformat()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def should_delete(delete_at):
|
|
44
|
+
try:
|
|
45
|
+
return datetime.fromisoformat(delete_at) < utcnow()
|
|
46
|
+
except ValueError:
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
|
|
39
50
|
class TimerProgressBar:
|
|
40
51
|
def __init__(self):
|
|
41
52
|
self.progress = widgets.IntProgress(
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
===================
|
|
2
|
+
Python-chi Examples
|
|
3
|
+
===================
|
|
4
|
+
|
|
5
|
+
.. _examples:
|
|
6
|
+
|
|
7
|
+
Cleanup Non-leased Resources
|
|
8
|
+
============================
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
Opt-in Cleanup using tags
|
|
12
|
+
-------------------------
|
|
13
|
+
|
|
14
|
+
While baremetal nodes on Chameleon require leases, other resources like networks, and KVM instances
|
|
15
|
+
are persistent, and require manual cleanup. For large projects (e.g. for educational courses), it
|
|
16
|
+
may be useful to have some automatic process to clean up resources after a certain time. Below is
|
|
17
|
+
a suggested method for managing this. First, when creating the resource, or to prolong the resource,
|
|
18
|
+
you can set a "delete_at" tag. This is simply an date string in the future, which can be checked.
|
|
19
|
+
|
|
20
|
+
.. code-block:: python
|
|
21
|
+
|
|
22
|
+
# Set a Server metadata item, which will be read leader
|
|
23
|
+
my_server.set_metadata_item("delete_at", util.date_string_in_future(days=3))
|
|
24
|
+
|
|
25
|
+
# Similarly, use "tag" for network or floating ip
|
|
26
|
+
network.set_floating_ip_tag(MY_FIP_ADDRESS, f"delete_at={util.date_string_in_future(days=3)}")
|
|
27
|
+
network.set_network_tag(MY_NETWORK_ID, f"delete_at={util.date_string_in_future(days=3)}")
|
|
28
|
+
|
|
29
|
+
Then run the following script. You'll need to set authentication variables (e.g. source an openrc file),
|
|
30
|
+
and set the site/project at the beginning. This script iterates over the resources in your project, and
|
|
31
|
+
deletes them if the "delete_at" tag is in the past, if the user manually enters "yes" to confirm.
|
|
32
|
+
You can remove this confirmation in order to automatically run periodically with e.g. cron.
|
|
33
|
+
|
|
34
|
+
.. code-block:: python
|
|
35
|
+
|
|
36
|
+
from chi import util, server, network
|
|
37
|
+
|
|
38
|
+
# Find servers with "delete_at" metadata
|
|
39
|
+
for s in server.list_servers():
|
|
40
|
+
if util.should_delete(my_servers.get_metadata().get("delete_at", "")):
|
|
41
|
+
confirm = input(f"Delete server {s.name}? (yes/no): ").strip().lower()
|
|
42
|
+
if confirm == "yes":
|
|
43
|
+
s.delete()
|
|
44
|
+
|
|
45
|
+
# Networks, FIP tags set via tags as a string
|
|
46
|
+
for net in network.list_networks():
|
|
47
|
+
try:
|
|
48
|
+
delete_tag = next(s for s in net["tags"] if s.startswith("delete_at="))
|
|
49
|
+
if util.should_delete(delete_tag.split("=")[1]):
|
|
50
|
+
confirm = input(f"Delete the network {net['name']}? (yes/no): ").strip().lower()
|
|
51
|
+
if confirm == "yes":
|
|
52
|
+
# nuke_network deletes subnets, routers, and the network itself
|
|
53
|
+
network.nuke_network(net_id)
|
|
54
|
+
except StopIteration:
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
for fip in network.list_floating_ips():
|
|
58
|
+
try:
|
|
59
|
+
delete_tag = next(s for s in fip["tags"] if s.startswith("delete_at="))
|
|
60
|
+
if util.should_delete(delete_tag.split("=")[1]):
|
|
61
|
+
confirm = input(f"Delete the floating ip {fip['floating_ip_address']}? (yes/no): ").strip().lower()
|
|
62
|
+
if confirm == "yes":
|
|
63
|
+
network.deallocate_floating_ip(fip["floating_ip_address"])
|
|
64
|
+
except StopIteration:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
Opt-in Cleanup using `created_at`
|
|
68
|
+
---------------------------------
|
|
69
|
+
|
|
70
|
+
The above examples are explicitly opt-in, requring the user to set the "delete_at" tag to ensure that
|
|
71
|
+
nothing is deleted unknowlingly. Alternatively, you can monitor the "created_at" field of resources to
|
|
72
|
+
check for long-running resources such as in the following example.
|
|
73
|
+
|
|
74
|
+
.. code-block:: python
|
|
75
|
+
|
|
76
|
+
from datetime import datetime, timedelta
|
|
77
|
+
from chi import util, server, network
|
|
78
|
+
|
|
79
|
+
MAX_AGE = 3 # days
|
|
80
|
+
for s in server.list_servers():
|
|
81
|
+
age = util.utcnow() - datetime.fromisoformat(s.created_at)
|
|
82
|
+
if age > timedelta(days=MAX_AGE):
|
|
83
|
+
confirm = input(f"Delete server {s.name}? Age is {age}. (yes/no): ").strip().lower()
|
|
84
|
+
if confirm == "yes":
|
|
85
|
+
s.delete()
|
|
86
|
+
for net in network.list_networks():
|
|
87
|
+
age = util.utcnow() - datetime.fromisoformat(net["created_at"])
|
|
88
|
+
if age > timedelta(days=MAX_AGE):
|
|
89
|
+
confirm = input(f"Delete the network {net['name']}? Age is {age}. (yes/no): ").strip().lower()
|
|
90
|
+
if confirm == "yes":
|
|
91
|
+
# nuke_network deletes subnets, routers, and the network itself
|
|
92
|
+
network.nuke_network(net_id)
|
|
@@ -61,6 +61,9 @@ For more details about the modules available refer to their respective pages.
|
|
|
61
61
|
chi.lease.create_lease(
|
|
62
62
|
lease_name, reservations, start_date=start_date, end_date=end_date)
|
|
63
63
|
|
|
64
|
+
For further examples, see the `examples <examples>`_ section.
|
|
65
|
+
|
|
66
|
+
|
|
64
67
|
Versions
|
|
65
68
|
========
|
|
66
69
|
In python-chi version 1.0, we've deprecated older versions of functions that return
|
|
@@ -103,7 +106,8 @@ version of python-chi, this will no longer be needed.
|
|
|
103
106
|
.. toctree::
|
|
104
107
|
:caption: Examples
|
|
105
108
|
:glob:
|
|
106
|
-
|
|
109
|
+
|
|
110
|
+
examples
|
|
107
111
|
notebooks/*
|
|
108
112
|
|
|
109
113
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"git_version": "fa6238a", "is_release": false}
|
python_chi-1.0.2/ChangeLog
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"git_version": "b0c1918", "is_release": false}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|