python-chi 1.1.0__tar.gz → 1.2.2__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.2.2/.github/workflows/linting.yml +25 -0
- python_chi-1.2.2/ChangeLog +7 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/PKG-INFO +1 -1
- {python_chi-1.1.0 → python_chi-1.2.2}/chi/__init__.py +2 -3
- {python_chi-1.1.0 → python_chi-1.2.2}/chi/clients.py +6 -6
- {python_chi-1.1.0 → python_chi-1.2.2}/chi/container.py +1 -2
- {python_chi-1.1.0 → python_chi-1.2.2}/chi/context.py +14 -10
- {python_chi-1.1.0 → python_chi-1.2.2}/chi/hardware.py +4 -5
- {python_chi-1.1.0 → python_chi-1.2.2}/chi/image.py +10 -7
- {python_chi-1.1.0 → python_chi-1.2.2}/chi/jupyterhub.py +1 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/chi/lease.py +55 -8
- {python_chi-1.1.0 → python_chi-1.2.2}/chi/magic.py +9 -9
- {python_chi-1.1.0 → python_chi-1.2.2}/chi/network.py +72 -15
- {python_chi-1.1.0 → python_chi-1.2.2}/chi/server.py +98 -23
- {python_chi-1.1.0 → python_chi-1.2.2}/chi/share.py +2 -3
- {python_chi-1.1.0 → python_chi-1.2.2}/chi/storage.py +108 -1
- {python_chi-1.1.0 → python_chi-1.2.2}/chi/util.py +6 -4
- python_chi-1.2.2/docs/_templates/page.html +13 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/docs/generate_notebook.py +40 -31
- {python_chi-1.1.0 → python_chi-1.2.2}/python_chi.egg-info/PKG-INFO +1 -1
- {python_chi-1.1.0 → python_chi-1.2.2}/python_chi.egg-info/SOURCES.txt +3 -0
- python_chi-1.2.2/python_chi.egg-info/pbr.json +1 -0
- python_chi-1.2.2/ruff.toml +15 -0
- python_chi-1.2.2/setup.py +5 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/test-requirements.txt +4 -1
- python_chi-1.2.2/tests/test_container.py +112 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/tests/test_context.py +17 -5
- {python_chi-1.1.0 → python_chi-1.2.2}/tests/test_lease.py +49 -91
- {python_chi-1.1.0 → python_chi-1.2.2}/tests/test_network.py +18 -12
- python_chi-1.2.2/tests/test_server.py +62 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/tests/test_share.py +6 -5
- {python_chi-1.1.0 → python_chi-1.2.2}/tests/test_ssh.py +3 -5
- python_chi-1.1.0/ChangeLog +0 -7
- python_chi-1.1.0/python_chi.egg-info/pbr.json +0 -1
- python_chi-1.1.0/setup.py +0 -9
- python_chi-1.1.0/tests/test_container.py +0 -72
- python_chi-1.1.0/tests/test_server.py +0 -123
- {python_chi-1.1.0 → python_chi-1.2.2}/.github/CODEOWNERS +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/.github/workflows/pypi-publish.yml +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/.github/workflows/test.yml +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/.mailmap +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/.readthedocs.yml +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/AUTHORS +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/DEVELOPMENT.rst +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/LICENSE +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/Makefile +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/README.rst +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/chi/exception.py +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/chi/keypair.py +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/chi/ssh.py +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/docs/__init__.py +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/docs/conf.py +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/docs/examples.rst +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/docs/index.rst +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/docs/modules/clients.rst +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/docs/modules/container.rst +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/docs/modules/context.rst +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/docs/modules/exception.rst +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/docs/modules/hardware.rst +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/docs/modules/image.rst +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/docs/modules/lease.rst +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/docs/modules/magic.rst +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/docs/modules/network.rst +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/docs/modules/server.rst +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/docs/modules/share.rst +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/docs/modules/ssh.rst +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/docs/modules/storage.rst +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/docs/requirements.txt +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/python_chi.egg-info/dependency_links.txt +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/python_chi.egg-info/not-zip-safe +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/python_chi.egg-info/requires.txt +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/python_chi.egg-info/top_level.txt +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/requirements.txt +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/setup.cfg +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/tests/__init__.py +0 -0
- {python_chi-1.1.0 → python_chi-1.2.2}/tox.ini +0 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
name: Linting
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
lint-with-ruff:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
- name: Set up Python
|
|
13
|
+
uses: actions/setup-python@v5
|
|
14
|
+
with:
|
|
15
|
+
python-version: '3.x'
|
|
16
|
+
- name: Install the code linting and formatting tool Ruff
|
|
17
|
+
run: pip install ruff
|
|
18
|
+
|
|
19
|
+
# run pass-fail test for any lint rule violations
|
|
20
|
+
- name: Lint code with Ruff
|
|
21
|
+
run: ruff check --output-format=github
|
|
22
|
+
|
|
23
|
+
# pas-fail test if any formatting would be applied, and output diff
|
|
24
|
+
- name: Check code formatting with Ruff
|
|
25
|
+
run: ruff format --diff
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from .clients import (
|
|
2
|
-
connection,
|
|
3
2
|
blazar,
|
|
4
3
|
cinder,
|
|
4
|
+
connection,
|
|
5
5
|
glance,
|
|
6
6
|
ironic,
|
|
7
7
|
keystone,
|
|
@@ -10,8 +10,7 @@ from .clients import (
|
|
|
10
10
|
nova,
|
|
11
11
|
zun,
|
|
12
12
|
)
|
|
13
|
-
from .context import
|
|
14
|
-
|
|
13
|
+
from .context import get, params, reset, session, set, use_site
|
|
15
14
|
|
|
16
15
|
__all__ = [
|
|
17
16
|
"get",
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
from .context import session
|
|
2
|
-
|
|
3
1
|
# Import all of the client classes for type annotations.
|
|
4
2
|
# We have to do this because we lazy-import the client definitions
|
|
5
3
|
# inside each function to reduce runtime dependencies.
|
|
6
4
|
from typing import TYPE_CHECKING
|
|
7
5
|
|
|
6
|
+
from .context import session
|
|
7
|
+
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
|
-
from openstack.connection import Connection
|
|
10
9
|
from blazarclient.client import Client as BlazarClient
|
|
11
10
|
from cinderclient.client import Client as CinderClient
|
|
12
11
|
from glanceclient.client import Client as GlanceClient
|
|
12
|
+
from ironicclient import client as IronicClient
|
|
13
|
+
from keystoneclient.v3.client import Client as KeystoneClient
|
|
13
14
|
from manilaclient.client import Client as ManilaClient
|
|
14
15
|
from neutronclient.v2_0.client import Client as NeutronClient
|
|
15
16
|
from novaclient.client import Client as NovaClient
|
|
16
|
-
from
|
|
17
|
-
from keystoneclient.v3.client import Client as KeystoneClient
|
|
17
|
+
from openstack.connection import Connection
|
|
18
18
|
from zunclient.client import Client as ZunClient
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
session_factory = session
|
|
22
22
|
|
|
23
|
-
NOVA_API_VERSION = "2.
|
|
23
|
+
NOVA_API_VERSION = "2.61"
|
|
24
24
|
ZUN_API_VERSION = "1.41"
|
|
25
25
|
|
|
26
26
|
|
|
@@ -24,13 +24,12 @@ from packaging.version import Version
|
|
|
24
24
|
from zunclient.exceptions import NotFound
|
|
25
25
|
|
|
26
26
|
from chi import context, util
|
|
27
|
+
from chi import network as chi_network
|
|
27
28
|
|
|
28
29
|
from .clients import connection, zun
|
|
29
30
|
from .context import session
|
|
30
31
|
from .exception import ResourceError, ServiceError
|
|
31
32
|
from .network import bind_floating_ip, get_free_floating_ip
|
|
32
|
-
from chi import network as chi_network
|
|
33
|
-
|
|
34
33
|
|
|
35
34
|
DEFAULT_IMAGE_DRIVER = "docker"
|
|
36
35
|
DEFAULT_NETWORK = "containernet1"
|
|
@@ -11,6 +11,7 @@ from IPython.display import display
|
|
|
11
11
|
from keystoneauth1 import loading, session
|
|
12
12
|
from keystoneauth1.identity.v3 import OidcAccessToken
|
|
13
13
|
from keystoneauth1.loading.conf import _AUTH_SECTION_OPT, _AUTH_TYPE_OPT
|
|
14
|
+
from keystoneclient import exceptions as keystone_exceptions
|
|
14
15
|
from keystoneclient.v3.client import Client as KeystoneClient
|
|
15
16
|
from oslo_config import cfg
|
|
16
17
|
|
|
@@ -72,8 +73,7 @@ _auth_plugin = None
|
|
|
72
73
|
_session = None
|
|
73
74
|
_sites = {}
|
|
74
75
|
|
|
75
|
-
|
|
76
|
-
version = "0.17.12"
|
|
76
|
+
version = "1.1"
|
|
77
77
|
|
|
78
78
|
|
|
79
79
|
def printerr(msg):
|
|
@@ -144,7 +144,7 @@ def _default_from_env(opts, group=None):
|
|
|
144
144
|
getattr(opt, "deprecated_opts", getattr(opt, "deprecated", None)) or []
|
|
145
145
|
)
|
|
146
146
|
for o in all_opts:
|
|
147
|
-
v = os.environ.get(f
|
|
147
|
+
v = os.environ.get(f"OS_{o.name.replace('-', '_').upper()}")
|
|
148
148
|
if v:
|
|
149
149
|
return v
|
|
150
150
|
|
|
@@ -384,19 +384,20 @@ def use_site(site_name: str) -> None:
|
|
|
384
384
|
if not site:
|
|
385
385
|
raise CHIValueError(
|
|
386
386
|
(
|
|
387
|
-
f'No site named "{site_name}" exists! Possible values: '
|
|
388
|
-
|
|
387
|
+
f'No site named "{site_name}" exists! Possible values: , '.join(
|
|
388
|
+
_sites.keys()
|
|
389
|
+
)
|
|
389
390
|
)
|
|
390
391
|
)
|
|
391
392
|
|
|
392
|
-
set("auth_url", f
|
|
393
|
+
set("auth_url", f"{site['web']}:5000/v3")
|
|
393
394
|
set("region_name", site["name"])
|
|
394
395
|
|
|
395
396
|
output = [
|
|
396
397
|
f"Now using {site_name}:",
|
|
397
|
-
f
|
|
398
|
-
f
|
|
399
|
-
f
|
|
398
|
+
f"URL: {site.get('web')}",
|
|
399
|
+
f"Location: {site.get('location')}",
|
|
400
|
+
f"Support contact: {site.get('user_support_contact')}",
|
|
400
401
|
]
|
|
401
402
|
print("\n".join(output))
|
|
402
403
|
|
|
@@ -465,7 +466,10 @@ def list_projects(show: str = None) -> List[str]:
|
|
|
465
466
|
region_name=getattr(keystone_session, "region_name", None),
|
|
466
467
|
)
|
|
467
468
|
|
|
468
|
-
|
|
469
|
+
try:
|
|
470
|
+
projects = keystone_client.projects.list(user=keystone_session.get_user_id())
|
|
471
|
+
except keystone_exceptions.Unauthorized:
|
|
472
|
+
raise ResourceError("Failed to retrieve projects. Check your credentials.")
|
|
469
473
|
project_names = [project.name for project in projects]
|
|
470
474
|
|
|
471
475
|
if show == "widget":
|
|
@@ -1,17 +1,16 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
from collections import defaultdict
|
|
2
3
|
from concurrent.futures import ThreadPoolExecutor
|
|
3
4
|
from dataclasses import dataclass
|
|
4
5
|
from datetime import datetime, timedelta, timezone
|
|
5
6
|
from typing import List, Optional, Set, Tuple
|
|
6
7
|
|
|
8
|
+
import requests
|
|
9
|
+
|
|
7
10
|
from chi import exception
|
|
8
11
|
|
|
9
12
|
from .clients import blazar, connection
|
|
10
|
-
from .context import
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
import requests
|
|
14
|
-
import logging
|
|
13
|
+
from .context import EDGE_RESOURCE_API_URL, RESOURCE_API_URL, get, session
|
|
15
14
|
|
|
16
15
|
LOG = logging.getLogger(__name__)
|
|
17
16
|
|
|
@@ -2,7 +2,7 @@ from dataclasses import dataclass
|
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
from typing import List, Optional
|
|
4
4
|
|
|
5
|
-
from glanceclient.exc import NotFound
|
|
5
|
+
from glanceclient.exc import HTTPBadRequest, NotFound
|
|
6
6
|
from packaging.version import Version
|
|
7
7
|
|
|
8
8
|
from chi import context
|
|
@@ -79,12 +79,15 @@ def get_image(name: str) -> Image:
|
|
|
79
79
|
ResourceError: If multiple images are found with the same name.
|
|
80
80
|
"""
|
|
81
81
|
if Version(context.version) >= Version("1.0"):
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
82
|
+
try:
|
|
83
|
+
glance_images = list(glance().images.list(filters={"name": name}))
|
|
84
|
+
if not glance_images:
|
|
85
|
+
raise CHIValueError(f'No images found matching name "{name}"')
|
|
86
|
+
elif len(glance_images) > 1:
|
|
87
|
+
raise ResourceError(f'Multiple images found matching name "{name}"')
|
|
88
|
+
return Image.from_glance_image(glance_images[0])
|
|
89
|
+
except HTTPBadRequest:
|
|
90
|
+
return Image(None, None, False, name)
|
|
88
91
|
try:
|
|
89
92
|
return glance().images.get(name)
|
|
90
93
|
except NotFound:
|
|
@@ -11,7 +11,7 @@ from IPython.display import display
|
|
|
11
11
|
from ipywidgets import HTML
|
|
12
12
|
from packaging.version import Version
|
|
13
13
|
|
|
14
|
-
from chi import context, util
|
|
14
|
+
from chi import context, server, util
|
|
15
15
|
|
|
16
16
|
from .clients import blazar
|
|
17
17
|
from .context import _is_ipynb
|
|
@@ -172,6 +172,7 @@ class Lease:
|
|
|
172
172
|
node_reservations (list): List of node reservations associated with the lease.
|
|
173
173
|
fip_reservations (list): List of floating IP reservations associated with the lease.
|
|
174
174
|
network_reservations (list): List of network reservations associated with the lease.
|
|
175
|
+
flavor_reservations (list): List of flavor reservations associated with the lease.
|
|
175
176
|
events (list): List of events associated with the lease.
|
|
176
177
|
"""
|
|
177
178
|
|
|
@@ -192,6 +193,7 @@ class Lease:
|
|
|
192
193
|
self.device_reservations = []
|
|
193
194
|
self.node_reservations = []
|
|
194
195
|
self.fip_reservations = []
|
|
196
|
+
self.flavor_reservations = []
|
|
195
197
|
self.network_reservations = []
|
|
196
198
|
self._events = []
|
|
197
199
|
|
|
@@ -239,6 +241,7 @@ class Lease:
|
|
|
239
241
|
self.device_reservations.clear()
|
|
240
242
|
self.node_reservations.clear()
|
|
241
243
|
self.fip_reservations.clear()
|
|
244
|
+
self.flavor_reservations.clear()
|
|
242
245
|
self.network_reservations.clear()
|
|
243
246
|
|
|
244
247
|
for reservation in lease_json.get("reservations", []):
|
|
@@ -249,6 +252,8 @@ class Lease:
|
|
|
249
252
|
self.node_reservations.append(reservation)
|
|
250
253
|
elif resource_type == "virtual:floatingip":
|
|
251
254
|
self.fip_reservations.append(reservation)
|
|
255
|
+
elif resource_type == "flavor:instance":
|
|
256
|
+
self.flavor_reservations.append(reservation)
|
|
252
257
|
elif resource_type == "network":
|
|
253
258
|
self.network_reservations.append(reservation)
|
|
254
259
|
|
|
@@ -347,6 +352,23 @@ class Lease:
|
|
|
347
352
|
"""
|
|
348
353
|
add_fip_reservation(reservation_list=self.fip_reservations, count=amount)
|
|
349
354
|
|
|
355
|
+
def add_flavor_reservation(self, id, amount=1):
|
|
356
|
+
"""
|
|
357
|
+
Add a reservation for a KVM flavor to the list of reservations.
|
|
358
|
+
|
|
359
|
+
Args:
|
|
360
|
+
id (str): The ID of the flavor to reserve
|
|
361
|
+
count (int): The number of floating IPs to reserve.
|
|
362
|
+
"""
|
|
363
|
+
self.flavor_reservations.append(
|
|
364
|
+
{
|
|
365
|
+
"resource_type": "flavor:instance",
|
|
366
|
+
"flavor_id": id,
|
|
367
|
+
"amount": amount,
|
|
368
|
+
"affinity": None,
|
|
369
|
+
}
|
|
370
|
+
)
|
|
371
|
+
|
|
350
372
|
def add_network_reservation(
|
|
351
373
|
self, network_name: str, usage_type: str = None, stitch_provider: str = None
|
|
352
374
|
):
|
|
@@ -404,6 +426,7 @@ class Lease:
|
|
|
404
426
|
self.device_reservations
|
|
405
427
|
+ self.node_reservations
|
|
406
428
|
+ self.fip_reservations
|
|
429
|
+
+ self.flavor_reservations
|
|
407
430
|
+ self.network_reservations
|
|
408
431
|
)
|
|
409
432
|
|
|
@@ -414,7 +437,6 @@ class Lease:
|
|
|
414
437
|
start_date=self.start_date,
|
|
415
438
|
end_date=self.end_date,
|
|
416
439
|
)
|
|
417
|
-
|
|
418
440
|
if response:
|
|
419
441
|
self._populate_from_json(response)
|
|
420
442
|
else:
|
|
@@ -505,12 +527,12 @@ class Lease:
|
|
|
505
527
|
<h2>Lease Details</h2>
|
|
506
528
|
<table>
|
|
507
529
|
<tr><th>Name</th><td>{self.name}</td></tr>
|
|
508
|
-
<tr><th>ID</th><td>{self.id or
|
|
509
|
-
<tr><th>Status</th><td>{self.status or
|
|
510
|
-
<tr><th>Start Date</th><td>{self.start_date or
|
|
511
|
-
<tr><th>End Date</th><td>{self.end_date or
|
|
512
|
-
<tr><th>User ID</th><td>{self.user_id or
|
|
513
|
-
<tr><th>Project ID</th><td>{self.project_id or
|
|
530
|
+
<tr><th>ID</th><td>{self.id or "N/A"}</td></tr>
|
|
531
|
+
<tr><th>Status</th><td>{self.status or "N/A"}</td></tr>
|
|
532
|
+
<tr><th>Start Date</th><td>{self.start_date or "N/A"}</td></tr>
|
|
533
|
+
<tr><th>End Date</th><td>{self.end_date or "N/A"}</td></tr>
|
|
534
|
+
<tr><th>User ID</th><td>{self.user_id or "N/A"}</td></tr>
|
|
535
|
+
<tr><th>Project ID</th><td>{self.project_id or "N/A"}</td></tr>
|
|
514
536
|
</table>
|
|
515
537
|
|
|
516
538
|
|
|
@@ -534,6 +556,11 @@ class Lease:
|
|
|
534
556
|
{"".join(f"<li>ID: {r.get('id', 'N/A')}, Status: {r.get('status', 'N/A')}, Resource type: {r.get('resource_type', 'N/A')}, Network Name: {r.get('network_name', 'N/A')}</li>" for r in self.network_reservations)}
|
|
535
557
|
</ul>
|
|
536
558
|
|
|
559
|
+
<h3>Flavor Reservations</h3>
|
|
560
|
+
<ul>
|
|
561
|
+
{"".join(f"<li>ID: {r.get('id', 'N/A')}, Status: {r.get('status', 'N/A')}, Resource type: {r.get('resource_type', 'N/A')}, Flavor: {r.get('flavor_id', 'N/A')}, Amount: {r.get('amount', 'N/A')}</li>" for r in self.flavor_reservations)}
|
|
562
|
+
</ul>
|
|
563
|
+
|
|
537
564
|
<h3>Events</h3>
|
|
538
565
|
<ul>
|
|
539
566
|
{"".join(f"<li>Type: {e.get('event_type', 'N/A')}, Time: {e.get('time', 'N/A')}, Status: {e.get('status', 'N/A')}</li>" for e in self.events)}
|
|
@@ -571,6 +598,12 @@ class Lease:
|
|
|
571
598
|
f"ID: {r.get('id', 'N/A')}, Status: {r.get('status', 'N/A')}, Network Name: {r.get('network_name', 'N/A')}"
|
|
572
599
|
)
|
|
573
600
|
|
|
601
|
+
print("\nFlavor Reservations:")
|
|
602
|
+
for r in self.flavor_reservations:
|
|
603
|
+
print(
|
|
604
|
+
f"ID: {r.get('id', 'N/A')}, Status: {r.get('status', 'N/A')}, Flavor: {r.get('flavor_id', 'N/A')}, Amount: {r.get('amount', 'N/A')}"
|
|
605
|
+
)
|
|
606
|
+
|
|
574
607
|
print("\nEvents:")
|
|
575
608
|
for e in self.events:
|
|
576
609
|
print(
|
|
@@ -609,6 +642,20 @@ class Lease:
|
|
|
609
642
|
)
|
|
610
643
|
]
|
|
611
644
|
|
|
645
|
+
def get_reserved_flavors(self):
|
|
646
|
+
"""Get flavors from flavor reservations in this lease. There will be one
|
|
647
|
+
flavor per flavor reservation.
|
|
648
|
+
|
|
649
|
+
Returns:
|
|
650
|
+
List[chi.server.Flavor] of flavor
|
|
651
|
+
"""
|
|
652
|
+
flavors = []
|
|
653
|
+
for res in self.flavor_reservations:
|
|
654
|
+
flavors.extend(
|
|
655
|
+
server.list_flavors(reservable=True, reservation_id=res.get("id"))
|
|
656
|
+
)
|
|
657
|
+
return flavors
|
|
658
|
+
|
|
612
659
|
|
|
613
660
|
def _format_resource_properties(user_constraints, extra_constraints):
|
|
614
661
|
if user_constraints:
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
from typing import Optional, List
|
|
2
1
|
from datetime import timedelta
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
|
|
4
|
+
import matplotlib.pyplot as plt
|
|
5
|
+
import networkx as nx
|
|
3
6
|
|
|
4
|
-
from .server import Server
|
|
5
7
|
from .container import Container
|
|
6
|
-
from .exception import ResourceError
|
|
7
|
-
from .lease import Lease, delete_lease
|
|
8
|
-
from .image import list_images
|
|
9
|
-
from .hardware import get_node_types
|
|
10
8
|
from .context import (
|
|
11
9
|
DEFAULT_NETWORK,
|
|
12
10
|
get,
|
|
13
11
|
)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
from .exception import ResourceError
|
|
13
|
+
from .hardware import get_node_types
|
|
14
|
+
from .image import list_images
|
|
15
|
+
from .lease import Lease, delete_lease
|
|
16
|
+
from .server import Server
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def visualize_resources(leases: List[Lease]):
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
from .clients import neutron
|
|
2
|
-
from .exception import CHIValueError, ResourceError
|
|
3
|
-
|
|
4
1
|
from neutronclient.common.exceptions import NotFound
|
|
5
2
|
|
|
6
|
-
import
|
|
3
|
+
from .clients import neutron
|
|
4
|
+
from .exception import CHIValueError, ResourceError
|
|
7
5
|
|
|
8
6
|
__all__ = [
|
|
9
7
|
"get_network",
|
|
@@ -141,7 +139,7 @@ def create_network(
|
|
|
141
139
|
desc_parts = []
|
|
142
140
|
if of_controller_ip and of_controller_port:
|
|
143
141
|
desc_parts.append(f"OFController={of_controller_ip}:{of_controller_port}")
|
|
144
|
-
if vswitch_name
|
|
142
|
+
if vswitch_name is not None:
|
|
145
143
|
desc_parts.append(f"VSwitchName={vswitch_name}")
|
|
146
144
|
|
|
147
145
|
network = neutron().create_network(
|
|
@@ -189,9 +187,7 @@ def list_networks() -> "list[dict]":
|
|
|
189
187
|
|
|
190
188
|
def set_network_tag(network_id, value):
|
|
191
189
|
_neutron = neutron()
|
|
192
|
-
_neutron.replace_tag(
|
|
193
|
-
'tags': [value]
|
|
194
|
-
})
|
|
190
|
+
_neutron.replace_tag("networks", network_id, {"tags": [value]})
|
|
195
191
|
|
|
196
192
|
|
|
197
193
|
##########
|
|
@@ -645,19 +641,16 @@ def remove_subnet_from_router(router_id, subnet_id):
|
|
|
645
641
|
# Floating IPs
|
|
646
642
|
###############
|
|
647
643
|
|
|
644
|
+
|
|
648
645
|
def set_floating_ip_tag(address, value):
|
|
649
646
|
ip_addr = get_floating_ip(address)
|
|
650
647
|
_neutron = neutron()
|
|
651
|
-
_neutron.replace_tag(
|
|
652
|
-
'tags': [value]
|
|
653
|
-
})
|
|
648
|
+
_neutron.replace_tag("floatingips", ip_addr["id"], {"tags": [value]})
|
|
654
649
|
|
|
655
650
|
|
|
656
651
|
def deallocate_floating_ip(address):
|
|
657
652
|
_neutron = neutron()
|
|
658
|
-
_neutron.delete_floatingip(
|
|
659
|
-
get_floating_ip(address)["id"]
|
|
660
|
-
)
|
|
653
|
+
_neutron.delete_floatingip(get_floating_ip(address)["id"])
|
|
661
654
|
|
|
662
655
|
|
|
663
656
|
def get_free_floating_ip(allocate=True) -> dict:
|
|
@@ -711,7 +704,7 @@ def get_or_create_floating_ip() -> "tuple[dict,bool]":
|
|
|
711
704
|
{"floatingip": {"floating_network_id": network_id}}
|
|
712
705
|
)["floatingip"]
|
|
713
706
|
created = True
|
|
714
|
-
print(f
|
|
707
|
+
print(f"Allocated new floating IP {fip['floating_ip_address']}")
|
|
715
708
|
return fip, created
|
|
716
709
|
|
|
717
710
|
|
|
@@ -819,6 +812,70 @@ def nuke_network(network_ref: str):
|
|
|
819
812
|
delete_network(network_id)
|
|
820
813
|
|
|
821
814
|
|
|
815
|
+
class SecurityGroup:
|
|
816
|
+
"""A wrapper class for Neutron security groups. Only supports creating."""
|
|
817
|
+
|
|
818
|
+
def __init__(self, security_group_data):
|
|
819
|
+
self.id = security_group_data.get("id")
|
|
820
|
+
self.name = security_group_data.get("name")
|
|
821
|
+
self.description = security_group_data.get("description")
|
|
822
|
+
self.rules = security_group_data.get("security_group_rules", [])
|
|
823
|
+
|
|
824
|
+
def submit(self, idempotent=False):
|
|
825
|
+
"""Create a new security group."""
|
|
826
|
+
if idempotent:
|
|
827
|
+
existing_groups = neutron().list_security_groups()["security_groups"]
|
|
828
|
+
for sg in existing_groups:
|
|
829
|
+
if sg["name"] == self.name:
|
|
830
|
+
self.id = sg["id"]
|
|
831
|
+
return
|
|
832
|
+
security_group = neutron().create_security_group(
|
|
833
|
+
{"security_group": {"name": self.name, "description": self.description}}
|
|
834
|
+
)["security_group"]
|
|
835
|
+
self.id = security_group["id"]
|
|
836
|
+
for rule in self.rules:
|
|
837
|
+
neutron().create_security_group_rule(
|
|
838
|
+
{
|
|
839
|
+
"security_group_rule": {
|
|
840
|
+
"direction": rule["direction"],
|
|
841
|
+
"port_range_min": rule.get("port_range_min"),
|
|
842
|
+
"port_range_max": rule.get("port_range_max"),
|
|
843
|
+
"protocol": rule["protocol"],
|
|
844
|
+
"security_group_id": self.id,
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
)
|
|
848
|
+
|
|
849
|
+
def add_rule(self, direction, protocol, port):
|
|
850
|
+
"""Add a rule to the security group."""
|
|
851
|
+
self.rules.append(
|
|
852
|
+
{
|
|
853
|
+
"direction": direction,
|
|
854
|
+
"protocol": protocol,
|
|
855
|
+
"port_range_min": port,
|
|
856
|
+
"port_range_max": port,
|
|
857
|
+
"security_group_id": self.id,
|
|
858
|
+
}
|
|
859
|
+
)
|
|
860
|
+
|
|
861
|
+
|
|
862
|
+
def list_security_groups(name_filter=None) -> "list[SecurityGroup]":
|
|
863
|
+
"""List all security groups.
|
|
864
|
+
|
|
865
|
+
Args:
|
|
866
|
+
name_filter (str, optional): Filter security groups containing name.
|
|
867
|
+
|
|
868
|
+
Returns:
|
|
869
|
+
list[SecurityGroup]: A list of SecurityGroup instances.
|
|
870
|
+
"""
|
|
871
|
+
security_groups = neutron().list_security_groups()["security_groups"]
|
|
872
|
+
return [
|
|
873
|
+
SecurityGroup(sg)
|
|
874
|
+
for sg in security_groups
|
|
875
|
+
if not name_filter or name_filter.lower() in sg["name"].lower()
|
|
876
|
+
]
|
|
877
|
+
|
|
878
|
+
|
|
822
879
|
###################
|
|
823
880
|
# Wizard functions
|
|
824
881
|
###################
|