syneto_api 0.1.0__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.
- syneto_api-0.1.0/PKG-INFO +15 -0
- syneto_api-0.1.0/pyproject.toml +19 -0
- syneto_api-0.1.0/setup.py +34 -0
- syneto_api-0.1.0/src/syneto_api/__init__.py +4 -0
- syneto_api-0.1.0/src/syneto_api/api_client.py +177 -0
- syneto_api-0.1.0/src/syneto_api/auth_api.py +11 -0
- syneto_api-0.1.0/src/syneto_api/protection_api.py +37 -0
- syneto_api-0.1.0/src/syneto_api/settings.py +39 -0
- syneto_api-0.1.0/src/syneto_api/storage_api.py +130 -0
- syneto_api-0.1.0/src/syneto_api/virt_api.py +21 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: syneto-api
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Syneto Client API library
|
|
5
|
+
License: Proprietary
|
|
6
|
+
Author: Your Name
|
|
7
|
+
Author-email: you@example.com
|
|
8
|
+
Requires-Python: >=3.6,<4.0
|
|
9
|
+
Classifier: License :: Other/Proprietary License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Requires-Dist: python-dotenv (>=0.16.0,<0.17.0)
|
|
15
|
+
Requires-Dist: requests[secure] (>=2.25.1,<3.0.0)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "syneto_api"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Syneto Client API library"
|
|
5
|
+
authors = ["Your Name <you@example.com>"]
|
|
6
|
+
license = "Proprietary"
|
|
7
|
+
|
|
8
|
+
[tool.poetry.dependencies]
|
|
9
|
+
python = "^3.6"
|
|
10
|
+
python-dotenv = "^0.16.0"
|
|
11
|
+
requests = {extras = ["secure"], version = "^2.25.1"}
|
|
12
|
+
|
|
13
|
+
[tool.poetry.dev-dependencies]
|
|
14
|
+
pytest = "^6.2.2"
|
|
15
|
+
ipdb = "^0.13.7"
|
|
16
|
+
|
|
17
|
+
[build-system]
|
|
18
|
+
requires = ["poetry>=0.12"]
|
|
19
|
+
build-backend = "poetry.masonry.api"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from setuptools import setup
|
|
3
|
+
|
|
4
|
+
package_dir = \
|
|
5
|
+
{'': 'src'}
|
|
6
|
+
|
|
7
|
+
packages = \
|
|
8
|
+
['syneto_api']
|
|
9
|
+
|
|
10
|
+
package_data = \
|
|
11
|
+
{'': ['*']}
|
|
12
|
+
|
|
13
|
+
install_requires = \
|
|
14
|
+
['python-dotenv>=0.16.0,<0.17.0', 'requests[secure]>=2.25.1,<3.0.0']
|
|
15
|
+
|
|
16
|
+
setup_kwargs = {
|
|
17
|
+
'name': 'syneto-api',
|
|
18
|
+
'version': '0.1.0',
|
|
19
|
+
'description': 'Syneto Client API library',
|
|
20
|
+
'long_description': None,
|
|
21
|
+
'author': 'Your Name',
|
|
22
|
+
'author_email': 'you@example.com',
|
|
23
|
+
'maintainer': None,
|
|
24
|
+
'maintainer_email': None,
|
|
25
|
+
'url': None,
|
|
26
|
+
'package_dir': package_dir,
|
|
27
|
+
'packages': packages,
|
|
28
|
+
'package_data': package_data,
|
|
29
|
+
'install_requires': install_requires,
|
|
30
|
+
'python_requires': '>=3.6,<4.0',
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
setup(**setup_kwargs)
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import requests
|
|
3
|
+
import urllib.parse
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
from .settings import get_env_flag
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def make_url(url_base, *path_args, query_args: dict = None, **path_kwargs):
|
|
12
|
+
path_args = [urllib.parse.quote(v, safe="") for v in path_args]
|
|
13
|
+
path_kwargs = {k: urllib.parse.quote(v, safe="") for k, v in path_kwargs.items()}
|
|
14
|
+
path = url_base.format(*path_args, **path_kwargs)
|
|
15
|
+
|
|
16
|
+
if query_args is None or len(query_args) == 0:
|
|
17
|
+
return path
|
|
18
|
+
query_string = urllib.parse.urlencode(
|
|
19
|
+
query_args, doseq=True, quote_via=urllib.parse.quote
|
|
20
|
+
)
|
|
21
|
+
return f"{path}?{query_string}"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class APIException(Exception):
|
|
25
|
+
def __init__(
|
|
26
|
+
self, url: str, status_code: int = 0, request_body: dict = None, response=None
|
|
27
|
+
):
|
|
28
|
+
self.url = url
|
|
29
|
+
self.status_code = status_code
|
|
30
|
+
self.request_body = request_body
|
|
31
|
+
self.response = response
|
|
32
|
+
message = f"API Error: {self.url}"
|
|
33
|
+
if self.status_code and self.response is not None:
|
|
34
|
+
message = (
|
|
35
|
+
f"API {self.url} returned HTTP {self.status_code}: {self.response.text}"
|
|
36
|
+
)
|
|
37
|
+
super().__init__(message)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class APIClientBase:
|
|
41
|
+
SUCCESS_CODES = {
|
|
42
|
+
"GET": [200],
|
|
43
|
+
"POST": [200, 201, 202],
|
|
44
|
+
"PUT": [200, 202],
|
|
45
|
+
"PATCH": [200, 202],
|
|
46
|
+
"DELETE": [200, 204],
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
def __init__(self, url_base=None, default_protocol="http", insecure_ssl=None):
|
|
50
|
+
self.url_base = url_base
|
|
51
|
+
protocols = ["http://", "https://"]
|
|
52
|
+
if not any(self.url_base.lower().startswith(p) for p in protocols):
|
|
53
|
+
self.url_base = f"{default_protocol}://{self.url_base}"
|
|
54
|
+
|
|
55
|
+
self.url_base = self.url_base.rstrip("/")
|
|
56
|
+
|
|
57
|
+
self.headers = {}
|
|
58
|
+
self._verify_ssl_cert = True
|
|
59
|
+
if insecure_ssl is not None:
|
|
60
|
+
self.set_insecure_ssl(insecure_ssl)
|
|
61
|
+
elif get_env_flag("ALLOW_INSECURE_SSL"):
|
|
62
|
+
self.set_insecure_ssl(True)
|
|
63
|
+
|
|
64
|
+
def _request(
|
|
65
|
+
self,
|
|
66
|
+
method: str,
|
|
67
|
+
relative_url: str,
|
|
68
|
+
*args,
|
|
69
|
+
query_args: dict = None,
|
|
70
|
+
body: dict = None,
|
|
71
|
+
success_codes: list = None,
|
|
72
|
+
**kwargs,
|
|
73
|
+
):
|
|
74
|
+
relative_url = relative_url.lstrip("/")
|
|
75
|
+
url = make_url(
|
|
76
|
+
self.url_base + "/" + relative_url, *args, query_args=query_args, **kwargs
|
|
77
|
+
)
|
|
78
|
+
logger.debug(f"Calling {method} {url}")
|
|
79
|
+
|
|
80
|
+
if not success_codes:
|
|
81
|
+
success_codes = APIClientBase.SUCCESS_CODES[method]
|
|
82
|
+
|
|
83
|
+
method_func = self._get_method_func(method)
|
|
84
|
+
method_args = {"verify": self._verify_ssl_cert}
|
|
85
|
+
if self.headers is not None and len(self.headers) > 0:
|
|
86
|
+
method_args["headers"] = self.headers
|
|
87
|
+
|
|
88
|
+
if body is not None:
|
|
89
|
+
method_args["json"] = body
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
response = method_func(url, timeout=20, **method_args)
|
|
93
|
+
if response.status_code in success_codes:
|
|
94
|
+
logger.debug("Received {response.status_code} from {method} {url}")
|
|
95
|
+
return response.json()
|
|
96
|
+
|
|
97
|
+
logger.info("Received {response.status_code} from {method} {url}")
|
|
98
|
+
raise APIException(
|
|
99
|
+
url=url,
|
|
100
|
+
status_code=response.status_code,
|
|
101
|
+
request_body=body,
|
|
102
|
+
response=response,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
except APIException as e:
|
|
106
|
+
raise
|
|
107
|
+
|
|
108
|
+
except TypeError:
|
|
109
|
+
raise
|
|
110
|
+
|
|
111
|
+
except Exception as e:
|
|
112
|
+
logger.warning(f"Failed {method} request to {url}: {str(e)}")
|
|
113
|
+
raise APIException(url=url, request_body=body,) from e
|
|
114
|
+
|
|
115
|
+
def _get_method_func(self, method):
|
|
116
|
+
requests_methods = {
|
|
117
|
+
"GET": requests.get,
|
|
118
|
+
"POST": requests.post,
|
|
119
|
+
"PUT": requests.put,
|
|
120
|
+
"PATCH": requests.patch,
|
|
121
|
+
"DELETE": requests.delete,
|
|
122
|
+
}
|
|
123
|
+
return requests_methods[method]
|
|
124
|
+
|
|
125
|
+
def get_request(
|
|
126
|
+
self,
|
|
127
|
+
relative_url: str,
|
|
128
|
+
*url_path_args,
|
|
129
|
+
query_args: dict = None,
|
|
130
|
+
success_codes: list = None,
|
|
131
|
+
**url_path_kwargs,
|
|
132
|
+
):
|
|
133
|
+
return self._request(
|
|
134
|
+
"GET",
|
|
135
|
+
relative_url,
|
|
136
|
+
*url_path_args,
|
|
137
|
+
query_args=query_args,
|
|
138
|
+
success_codes=success_codes,
|
|
139
|
+
**url_path_kwargs,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
def delete_request(self, *args, **kwargs):
|
|
143
|
+
# Same args as get_request
|
|
144
|
+
return self._request("DELETE", *args, **kwargs)
|
|
145
|
+
|
|
146
|
+
def post_request(
|
|
147
|
+
self,
|
|
148
|
+
relative_url: str,
|
|
149
|
+
*url_path_args,
|
|
150
|
+
query_args: dict = None,
|
|
151
|
+
body: dict = None,
|
|
152
|
+
success_codes: list = None,
|
|
153
|
+
**url_path_kwargs,
|
|
154
|
+
):
|
|
155
|
+
return self._request(
|
|
156
|
+
"POST",
|
|
157
|
+
relative_url,
|
|
158
|
+
*url_path_args,
|
|
159
|
+
query_args=query_args,
|
|
160
|
+
body=body,
|
|
161
|
+
success_codes=success_codes,
|
|
162
|
+
**url_path_kwargs,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
def put_request(self, *args, **kwargs):
|
|
166
|
+
# Same args as post_request
|
|
167
|
+
return self._request("PUT", *args, **kwargs)
|
|
168
|
+
|
|
169
|
+
def patch_request(self, *args, **kwargs):
|
|
170
|
+
# Same args as post_request
|
|
171
|
+
return self._request("PATCH", *args, **kwargs)
|
|
172
|
+
|
|
173
|
+
def set_insecure_ssl(self, insecure=True):
|
|
174
|
+
self._verify_ssl_cert = not insecure
|
|
175
|
+
|
|
176
|
+
def set_auth_jwt(self, jwt):
|
|
177
|
+
self.headers["Authorization"] = f"Bearer {jwt}"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from .api_client import APIClientBase
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Authentication(APIClientBase):
|
|
6
|
+
def __init__(self, url_base=None, **kwargs):
|
|
7
|
+
super().__init__(url_base or os.environ.get("AUTH_SERVICE", ""), **kwargs)
|
|
8
|
+
|
|
9
|
+
def login(self, username: str, password: str):
|
|
10
|
+
body = {"username": username, "password": password}
|
|
11
|
+
return self.post_request("/login", body=body)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from .api_client import APIClientBase
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Protection(APIClientBase):
|
|
6
|
+
def __init__(self, url_base=None, **kwargs):
|
|
7
|
+
super().__init__(url_base or os.environ.get("PROTECTION_SERVICE", ""), **kwargs)
|
|
8
|
+
|
|
9
|
+
def get_policies(self):
|
|
10
|
+
return self.get_request("/policies/")
|
|
11
|
+
|
|
12
|
+
def get_protected_vms(self):
|
|
13
|
+
return self.get_request("/vms/")
|
|
14
|
+
|
|
15
|
+
def get_jobs(self):
|
|
16
|
+
return self.get_request("/jobs/")
|
|
17
|
+
|
|
18
|
+
def get_job(self, id):
|
|
19
|
+
return self.get_request("/jobs/{}", id)
|
|
20
|
+
|
|
21
|
+
def patch_job(self, id, payload):
|
|
22
|
+
return self.patch_request("/jobs/", query_args={"id": id}, body=payload)
|
|
23
|
+
|
|
24
|
+
def remove_job(self, id):
|
|
25
|
+
return self.delete_request("/jobs/", query_args={"id": id})
|
|
26
|
+
|
|
27
|
+
def create_external_vm_protection_job(self, config):
|
|
28
|
+
body = {"config": config}
|
|
29
|
+
return self.post_request("/jobs/protect-external-vm", body=body)
|
|
30
|
+
|
|
31
|
+
def create_local_vm_protection_job(self, config):
|
|
32
|
+
body = {"config": config}
|
|
33
|
+
return self.post_request("/jobs/protect-local-vm", body=body)
|
|
34
|
+
|
|
35
|
+
def create_local_vm_replication_job(self, config):
|
|
36
|
+
body = {"config": config}
|
|
37
|
+
return self.post_request("/jobs/replicate-local-vm", body=body)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from dotenv import load_dotenv
|
|
3
|
+
|
|
4
|
+
load_dotenv()
|
|
5
|
+
|
|
6
|
+
bool_values = {"true": True, "yes": True, "false": False, "no": False}
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_env_flag(env_key: str) -> bool:
|
|
10
|
+
"""
|
|
11
|
+
Returns the parsed value of the given env. var (assumed of boolean type).
|
|
12
|
+
The values "True" and "Yes" (case-insensitive) are parsed as True while
|
|
13
|
+
everything else (including non-existent env. var) parses as False.
|
|
14
|
+
"""
|
|
15
|
+
env_value = os.environ.get(env_key, "")
|
|
16
|
+
return bool_values.get(env_value.lower()) == True
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_env_int(env_key, default=0) -> int:
|
|
20
|
+
"""
|
|
21
|
+
Returns the parsed value of the given env. var (assumed of integer type).
|
|
22
|
+
If the env. var does not exist, the given default is returned. If the env. var value
|
|
23
|
+
fails to parse, a ValueError is raised.
|
|
24
|
+
"""
|
|
25
|
+
env_value = os.environ.get(env_key, f"{default}")
|
|
26
|
+
return int(env_value)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_env_string(env_key, default="") -> str:
|
|
30
|
+
"""
|
|
31
|
+
Returns the associated os.environ value for the specified value as a string.
|
|
32
|
+
"""
|
|
33
|
+
env_value = os.environ.get(env_key, f"{default}")
|
|
34
|
+
return env_value
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_root_path():
|
|
38
|
+
root_path_env = os.environ.get("ROOT_PATH", "") or ""
|
|
39
|
+
return "/" + root_path_env.strip("/")
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from .api_client import APIClientBase
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Storage(APIClientBase):
|
|
6
|
+
def __init__(self, url_base=None, **kwargs):
|
|
7
|
+
super().__init__(url_base or os.environ.get("STORAGE_SERVICE", ""), **kwargs)
|
|
8
|
+
|
|
9
|
+
def get_pools(self):
|
|
10
|
+
return self.get_request("/pools")
|
|
11
|
+
|
|
12
|
+
def get_importable_pools(self):
|
|
13
|
+
return self.get_request("/pools/importable")
|
|
14
|
+
|
|
15
|
+
def get_pool(self, pool_name: str):
|
|
16
|
+
return self.get_request("/pools/{pool_name}", pool_name=pool_name)
|
|
17
|
+
|
|
18
|
+
def clear_pool(self, pool_name: str):
|
|
19
|
+
return self.put_request("/pools/{pool_name}/clear", pool_name=pool_name)
|
|
20
|
+
|
|
21
|
+
def upgrade_pool(self, pool_name: str):
|
|
22
|
+
return self.put_request("/pools/{pool_name}/upgrade", pool_name=pool_name)
|
|
23
|
+
|
|
24
|
+
def export_pool(self, pool_name: str):
|
|
25
|
+
return self.put_request("/pools/{pool_name}/export", pool_name=pool_name)
|
|
26
|
+
|
|
27
|
+
def get_pool_periodic_scrub(self, pool_name: str):
|
|
28
|
+
return self.get_request(
|
|
29
|
+
"/pools/{pool_name}/scrub/interval", pool_name=pool_name
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
def set_pool_periodic_scrub(self, pool_name: str, interval: str):
|
|
33
|
+
return self.post_request(
|
|
34
|
+
"/pools/{pool_name}/scrub/interval",
|
|
35
|
+
pool_name=pool_name,
|
|
36
|
+
query_args={"interval": interval},
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def delete_pool_periodic_scrub(self, pool_name: str):
|
|
40
|
+
return self.delete_request(
|
|
41
|
+
"/pools/{pool_name}/scrub/interval", pool_name=pool_name
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def start_pool_scrub(self, pool_name: str):
|
|
45
|
+
return self.put_request("/pools/{pool_name}/scrub/start", pool_name=pool_name)
|
|
46
|
+
|
|
47
|
+
def stop_pool_scrub(self, pool_name: str):
|
|
48
|
+
return self.put_request("/pools/{pool_name}/scrub/stop", pool_name=pool_name)
|
|
49
|
+
|
|
50
|
+
def get_volumes(self):
|
|
51
|
+
return self.get_request("/volumes")
|
|
52
|
+
|
|
53
|
+
def create_volume(
|
|
54
|
+
self, pool_name: str, ips: list, volume_type: str = "NFS", quota: str = "none"
|
|
55
|
+
):
|
|
56
|
+
hosts = [{"ipNetwork": ip, "accessType": "rw"} for ip in ips]
|
|
57
|
+
body = {
|
|
58
|
+
"type": volume_type,
|
|
59
|
+
"pool": pool_name,
|
|
60
|
+
"nfsShareConfig": {"rootAccess": "true", "hosts": hosts},
|
|
61
|
+
"quota": quota,
|
|
62
|
+
}
|
|
63
|
+
return self.post_request("/volumes", body=body,)
|
|
64
|
+
|
|
65
|
+
def get_volume(self, volume_id: str):
|
|
66
|
+
return self.get_request("/volumes/{volume_id}", volume_id=volume_id,)
|
|
67
|
+
|
|
68
|
+
def delete_volume(self, volume_id: str):
|
|
69
|
+
return self.delete_request(
|
|
70
|
+
"/volumes/{volume_id}", query_args={"volume_id": volume_id},
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def promote_volume(self, volume_id: str):
|
|
74
|
+
return self.put_request("/volumes/{volume_id}/promote", volume_id=volume_id)
|
|
75
|
+
|
|
76
|
+
def get_volume_meta(self, volume_id: str, key: str):
|
|
77
|
+
return self.put_request(
|
|
78
|
+
"/volumes/{volume_id}/meta", volume_id=volume_id, query_args={"key": key}
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
def set_volume_meta(self, volume_id: str, key: str, value: dict):
|
|
82
|
+
body = {"key": key, "value": value}
|
|
83
|
+
return self.put_request(
|
|
84
|
+
"/volumes/{volume_id}/meta", volume_id=volume_id, body=body,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def get_volume_snapshots(self, volume_id: str):
|
|
88
|
+
return self.get_request("/volumes/{volume_id}/snapshots", volume_id=volume_id,)
|
|
89
|
+
|
|
90
|
+
def create_volume_snapshot(self, volume_id: str, snapshot_name: str):
|
|
91
|
+
return self.post_request(
|
|
92
|
+
"/volumes/{volume_id}/snapshots",
|
|
93
|
+
volume_id=volume_id,
|
|
94
|
+
query_args={"snapshot_name": snapshot_name},
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
def get_volume_snapshot(self, volume_id: str, snapshot_name: str):
|
|
98
|
+
return self.get_request(
|
|
99
|
+
"/volumes/{volume_id}/snapshots/{snapshot_name}",
|
|
100
|
+
volume_id=volume_id,
|
|
101
|
+
snapshot_name=snapshot_name,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def remove_volume_snapshot(self, volume_id: str, snapshot_name: str):
|
|
105
|
+
return self.delete_request(
|
|
106
|
+
"/volumes/{volume_id}/snapshots",
|
|
107
|
+
volume_id=volume_id,
|
|
108
|
+
query_args={"snapshot_name": snapshot_name},
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
def rollback_volume_snapshot(self, volume_id: str, snapshot_name: str):
|
|
112
|
+
return self.put_request(
|
|
113
|
+
"/volumes/{volume_id}/snapshots/{snapshot_name}/rollback",
|
|
114
|
+
volume_id=volume_id,
|
|
115
|
+
snapshot_name=snapshot_name,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def get_volume_snapshot_clones(self, volume_id: str, snapshot_name: str):
|
|
119
|
+
return self.get_request(
|
|
120
|
+
"/volumes/{volume_id}/snapshots/{snapshot_name}/clones",
|
|
121
|
+
volume_id=volume_id,
|
|
122
|
+
snapshot_name=snapshot_name,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
def create_volume_snapshot_clone(self, volume_id: str, snapshot_name: str):
|
|
126
|
+
return self.post_request(
|
|
127
|
+
"/volumes/{volume_id}/snapshots/{snapshot_name}/clones",
|
|
128
|
+
volume_id=volume_id,
|
|
129
|
+
snapshot_name=snapshot_name,
|
|
130
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from .api_client import APIClientBase
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Virtualization(APIClientBase):
|
|
6
|
+
def __init__(self, url_base=None, **kwargs):
|
|
7
|
+
super().__init__(
|
|
8
|
+
url_base or os.environ.get("VIRTUALIZATION_SERVICE", ""), **kwargs
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
def get_hypervisors(self):
|
|
12
|
+
return self.get_request("/hypervisors/")
|
|
13
|
+
|
|
14
|
+
def get_vms(self):
|
|
15
|
+
return self.get_request("/hypervisors/vms")
|
|
16
|
+
|
|
17
|
+
def get_hosts(self):
|
|
18
|
+
return self.get_request("/hypervisors/hosts")
|
|
19
|
+
|
|
20
|
+
def get_datastores(self):
|
|
21
|
+
return self.get_request("/hypervisors/datastores")
|