syneto_api 0.2.37__tar.gz → 0.2.38__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.
@@ -1,15 +1,14 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: syneto-api
3
- Version: 0.2.37
3
+ Version: 0.2.38
4
4
  Summary: Syneto Client API library
5
5
  Home-page: https://github.com/SynetoNet/syneto-api
6
6
  License: Proprietary
7
7
  Author: Your Name
8
8
  Author-email: you@example.com
9
- Requires-Python: >=3.6,<4.0
9
+ Requires-Python: >=3.6.2,<4.0.0
10
10
  Classifier: License :: Other/Proprietary License
11
11
  Classifier: Programming Language :: Python :: 3
12
- Classifier: Programming Language :: Python :: 3.6
13
12
  Classifier: Programming Language :: Python :: 3.7
14
13
  Classifier: Programming Language :: Python :: 3.8
15
14
  Requires-Dist: aiodns (>=3,<4)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "syneto_api"
3
- version = "0.2.37"
3
+ version = "0.2.38"
4
4
  description = "Syneto Client API library"
5
5
  authors = ["Your Name <you@example.com>"]
6
6
  license = "Proprietary"
@@ -9,7 +9,7 @@ repository = "https://github.com/SynetoNet/syneto-api"
9
9
  readme = "README.md"
10
10
 
11
11
  [tool.poetry.dependencies]
12
- python = "^3.6"
12
+ python = "^3.6.2"
13
13
  python-dotenv = "^0.16"
14
14
  requests = {extras = ["secure"], version = "^2.25"}
15
15
  inflection = "^0.5"
@@ -21,6 +21,7 @@ cchardet = "^2.1"
21
21
  pytest = "^6.2.2"
22
22
  ipdb = "^0.13.7"
23
23
  pytest-asyncio = "^0.15"
24
+ black = {version = "^21.6b0", allow-prereleases = true}
24
25
 
25
26
  [build-system]
26
27
  requires = ["poetry>=0.12"]
@@ -20,7 +20,7 @@ install_requires = \
20
20
 
21
21
  setup_kwargs = {
22
22
  'name': 'syneto-api',
23
- 'version': '0.2.37',
23
+ 'version': '0.2.38',
24
24
  'description': 'Syneto Client API library',
25
25
  'long_description': '# Syneto API\n\nSyneto Client API library: authentication, storage, virtualization and protection\n\n# Installation\n\n```\n$ pip install syneto-api\n```\n\n# Basic Usage\n\n```\nfrom syneto_api import Authentication, Virtualization, Storage, Protection\n\nauth_api = Authentication(url_base="https://syneto-instance-ip-address/api/auth", insecure_ssl=True)\nresponse = auth_api.login(username="admin", password="admin")\njwt = response[\'jwt\']\n\nvirt_api = Virtualization(url_base="https://syneto-instance-ip-address/api/virtualization", insecure_ssl=True)\nvirt_api.set_auth_jwt(jwt)\nprint(virt_api.get_vms())\n\nstorage_api = Storage(url_base="https://syneto-instance-ip-address/api/storage", insecure_ssl=True)\nstorage_api.set_auth_jwt(jwt)\nprint(storage_api.get_pools())\n```\n\n# Environment Variables\n\nFor conveninence, the base urls for the api endpoints are also accepted as environment variables, please see below.\n\n```\nAUTH_SERVICE=https://syneto-instance-ip-address/api/auth\nVIRTUALIZATION_SERVICE=https://syneto-instance-ip-address/api/virtualization\nSTORAGE_SERVICE=https://syneto-instance-ip-address/api/storage\nPROTECTION_SERVICE=https://syneto-instance-ip-address/api/protection\n```\n\nIf you are using self-signed SSL certificates, set the following env. so that the http request library does not perform ssl verification. \n\n```\nALLOW_INSECURE_SSL=True\n```\n',
26
26
  'author': 'Your Name',
@@ -32,7 +32,7 @@ setup_kwargs = {
32
32
  'packages': packages,
33
33
  'package_data': package_data,
34
34
  'install_requires': install_requires,
35
- 'python_requires': '>=3.6,<4.0',
35
+ 'python_requires': '>=3.6.2,<4.0.0',
36
36
  }
37
37
 
38
38
 
@@ -25,9 +25,7 @@ def make_url(url_base, *path_args, query_args: dict = None, **path_kwargs):
25
25
 
26
26
 
27
27
  class APIException(Exception):
28
- def __init__(
29
- self, url: str, status_code: int = 0, message: str = None, request_body: dict = None, response=None,
30
- ):
28
+ def __init__(self, url: str, status_code: int = 0, message: str = None, request_body: dict = None, response=None):
31
29
  self.url = url
32
30
  self.status_code = status_code
33
31
  self.request_body = request_body
@@ -50,8 +48,9 @@ class APIClientBase:
50
48
  "DELETE": [200, 202, 204],
51
49
  }
52
50
 
53
- def __init__(self, url_base=None, default_protocol="http", insecure_ssl=None):
51
+ def __init__(self, url_base=None, default_protocol="http", insecure_ssl=None, timeout=5 * 60):
54
52
  self.url_base = url_base
53
+ self.timeout = timeout
55
54
  protocols = ["http://", "https://"]
56
55
  if not any(self.url_base.lower().startswith(p) for p in protocols):
57
56
  self.url_base = f"{default_protocol}://{self.url_base}"
@@ -105,37 +104,39 @@ class APIClientBase:
105
104
  logger.debug(f"Calling {method} {url} with aiohttp lib")
106
105
  # loop = asyncio.get_event_loop()
107
106
  # return loop.run_until_complete(self._async_request(method, url, body, success_codes, error_subject))
108
- return await self._unsafe_async_request(
109
- method, url, body, success_codes, error_subject, on_response_callback
107
+ print(self.timeout)
108
+ return await asyncio.wait_for(
109
+ self._unsafe_async_request(method, url, body, success_codes, error_subject, on_response_callback),
110
+ timeout=self.timeout,
110
111
  )
111
112
 
112
113
  except APIException as e:
113
114
  raise
114
115
 
115
- except (aiohttp.ServerTimeoutError, requests.Timeout) as e:
116
+ except (aiohttp.ServerTimeoutError, requests.Timeout, asyncio.TimeoutError) as e:
116
117
  logger.warning(f"Failed {method} request to {url}, network timeout: {str(e)}")
117
118
  error = self._append_url_if_not_local_service("network timeout")
118
- raise APIException(url=url, message=f"{error_subject} has failed ({error})", request_body=body,) from e
119
+ raise APIException(url=url, message=f"{error_subject} has failed ({error})", request_body=body) from e
119
120
 
120
121
  except (aiohttp.ClientSSLError, requests.exceptions.SSLError) as e:
121
122
  logger.warning(f"Failed {method} request to {url}, ssl error: {str(e)}")
122
123
  error = self._append_url_if_not_local_service("ssl error")
123
- raise APIException(url=url, message=f"{error_subject} has failed ({error})", request_body=body,) from e
124
+ raise APIException(url=url, message=f"{error_subject} has failed ({error})", request_body=body) from e
124
125
 
125
126
  except (aiohttp.ClientConnectionError, requests.ConnectionError) as e:
126
127
  logger.warning(f"Failed {method} request to {url}, connect error: {str(e)}")
127
128
  error = self._append_url_if_not_local_service("network connect error")
128
- raise APIException(url=url, message=f"{error_subject} has failed ({error})", request_body=body,) from e
129
+ raise APIException(url=url, message=f"{error_subject} has failed ({error})", request_body=body) from e
129
130
 
130
131
  except (aiohttp.ClientError, requests.RequestException) as e:
131
132
  logger.warning(f"Failed {method} request to {url}, network error: {str(e)}")
132
133
  error = self._append_url_if_not_local_service("network error")
133
- raise APIException(url=url, message=f"{error_subject} has failed ({error})", request_body=body,) from e
134
+ raise APIException(url=url, message=f"{error_subject} has failed ({error})", request_body=body) from e
134
135
 
135
136
  except Exception as e:
136
137
  logger.warning(f"Failed {method} request to {url}: {str(e)}")
137
138
  error = self._append_url_if_not_local_service(str(e) or type(e).__name__)
138
- raise APIException(url=url, message=f"{error_subject} has failed ({error})", request_body=body,) from e
139
+ raise APIException(url=url, message=f"{error_subject} has failed ({error})", request_body=body) from e
139
140
 
140
141
  async def _unsafe_async_request(
141
142
  self, method: str, url: str, body: dict, success_codes: list, error_subject: str, on_response_callback
@@ -161,7 +162,6 @@ class APIClientBase:
161
162
  request_method = request_methods[method]
162
163
  async with request_method(url, **method_args) as response:
163
164
  is_json = response.headers.get("content-type") == "application/json"
164
-
165
165
  if response.status in success_codes:
166
166
  logger.debug(f"Received {response.status} from {method} {url}")
167
167
  if is_json or self._force_json:
@@ -278,10 +278,15 @@ class APIClientBase:
278
278
  return requests_methods[method]
279
279
 
280
280
  def get_request(
281
- self, relative_url: str, *url_path_args, query_args: dict = None, success_codes: list = None, **url_path_kwargs,
281
+ self, relative_url: str, *url_path_args, query_args: dict = None, success_codes: list = None, **url_path_kwargs
282
282
  ):
283
283
  return self._request(
284
- "GET", relative_url, *url_path_args, query_args=query_args, success_codes=success_codes, **url_path_kwargs,
284
+ "GET",
285
+ relative_url,
286
+ *url_path_args,
287
+ query_args=query_args,
288
+ success_codes=success_codes,
289
+ **url_path_kwargs,
285
290
  )
286
291
 
287
292
  def delete_request(self, *args, **kwargs):
@@ -24,6 +24,11 @@ class Authentication(APIClientBase):
24
24
  return self.get_request("/public-key", query_args={"username": username})
25
25
 
26
26
  def add_authorized_key(self, username: str, public_key: str):
27
- return self.post_request(
28
- "/authorized-keys", query_args={"username": username}, body={"public_key": public_key},
27
+ return self.post_request("/authorized-keys", query_args={"username": username}, body={"public_key": public_key})
28
+
29
+ def remove_authorized_key(self, username: str, public_key: str):
30
+ return self.delete_request(
31
+ "/authorized-keys",
32
+ query_args={"username": username},
33
+ body={"public_key": public_key},
29
34
  )
@@ -12,13 +12,7 @@ class Protection(APIClientBase):
12
12
  def get_policy(self, id):
13
13
  return self.get_request("/policies/{id}", id=id)
14
14
 
15
- def create_policy(
16
- self,
17
- policy_name: str,
18
- pool_name: str = None,
19
- alert_enabled: bool = True,
20
- rules: list = None,
21
- ):
15
+ def create_policy(self, policy_name: str, pool_name: str = None, alert_enabled: bool = True, rules: list = None):
22
16
  body = {
23
17
  "policyName": policy_name,
24
18
  "poolName": pool_name,
@@ -28,12 +22,7 @@ class Protection(APIClientBase):
28
22
  return self.post_request("/policies", body=body)
29
23
 
30
24
  def patch_policy(
31
- self,
32
- id: str,
33
- policy_name: str = None,
34
- pool_name: str = None,
35
- alert_enabled: bool = None,
36
- rules: list = None,
25
+ self, id: str, policy_name: str = None, pool_name: str = None, alert_enabled: bool = None, rules: list = None
37
26
  ):
38
27
  body = {}
39
28
 
@@ -105,13 +94,13 @@ class Protection(APIClientBase):
105
94
  bandwidth_limit_value is in bytes
106
95
  bandwidth_limit_start_time format is HHMMSS'Z', the time should always use UTC timezone.
107
96
  bandwidth_limit_stop_time format is HHMMSS'Z', the time should always use UTC timezone.
108
- bandwidth_limit_start_time and bandwidth_limit_stop_time time interval is when the
97
+ bandwidth_limit_start_time and bandwidth_limit_stop_time time interval is when the
109
98
  replication bandwidth will be limited to the bandwidth_limit_value.
110
99
  Example:
111
100
  bandwidth_limit_value: 50000,
112
101
  bandwidth_limit_start_time: 090000Z,
113
102
  bandwidth_limit_stop_time: 163000Z,
114
- The effect of these values is that between 9 AM UTC and 4:30 PM UTC
103
+ The effect of these values is that between 9 AM UTC and 4:30 PM UTC
115
104
  the replication bandwidth will be limited to 50KB.
116
105
  """
117
106
 
@@ -155,13 +144,13 @@ class Protection(APIClientBase):
155
144
  bandwidth_limit_value is in bytes
156
145
  bandwidth_limit_start_time format is HHMMSS'Z', the time should always use UTC timezone.
157
146
  bandwidth_limit_stop_time format is HHMMSS'Z', the time should always use UTC timezone.
158
- bandwidth_limit_start_time and bandwidth_limit_stop_time time interval is when the
147
+ bandwidth_limit_start_time and bandwidth_limit_stop_time time interval is when the
159
148
  replication bandwidth will be limited to the bandwidth_limit_value.
160
149
  Example:
161
150
  bandwidth_limit_value: 50000,
162
151
  bandwidth_limit_start_time: 090000Z,
163
152
  bandwidth_limit_stop_time: 163000Z,
164
- The effect of these values is that between 9 AM UTC and 4:30 PM UTC
153
+ The effect of these values is that between 9 AM UTC and 4:30 PM UTC
165
154
  the replication bandwidth will be limited to 50KB.
166
155
  In order to update a replication host's bandwidth limit values you must include a value for
167
156
  bandwidth_limit_value, bandwidth_limit_start_time and bandwidth_limit_stop_time, if one of them
@@ -200,15 +189,18 @@ class Protection(APIClientBase):
200
189
  return self.get_request("/vms/{vm_syn_id}", vm_syn_id=vm_syn_id)
201
190
 
202
191
  def post_vm_recovery_point(self, vm_syn_id: str, job_id: str):
203
- return self.post_request(
204
- "/vms/recovery-points", body={"synId": vm_syn_id, "jobId": job_id}
205
- )
192
+ return self.post_request("/vms/recovery-points", body={"synId": vm_syn_id, "jobId": job_id})
206
193
 
207
194
  def get_recovery_points(self, vm_syn_id: str, limit: int = 100, offset: int = 0):
208
195
  return self.get_request(
209
- "/recovery-points",
210
- query_args={"vm_syn_id": vm_syn_id, "limit": limit, "offset": offset},
196
+ "/recovery-points", query_args={"vm_syn_id": vm_syn_id, "limit": limit, "offset": offset}
211
197
  )
212
198
 
213
199
  def delete_recovery_point(self, recovery_point_id: str):
214
200
  return self.delete_request(f"/recovery-points/{recovery_point_id}")
201
+
202
+ def post_recovery_point_replicas(self, replicas):
203
+ return self.post_request("/recovery-points/replicas", body=replicas)
204
+
205
+ def post_vm_replicas(self, replicas):
206
+ return self.post_request("/vms/replicas", body=replicas)
@@ -29,7 +29,7 @@ class Storage(APIClientBase):
29
29
 
30
30
  def set_pool_periodic_scrub(self, pool_name: str, interval: str):
31
31
  return self.post_request(
32
- "/pools/{pool_name}/scrub/interval", pool_name=pool_name, query_args={"interval": interval},
32
+ "/pools/{pool_name}/scrub/interval", pool_name=pool_name, query_args={"interval": interval}
33
33
  )
34
34
 
35
35
  def delete_pool_periodic_scrub(self, pool_name: str):
@@ -41,16 +41,12 @@ class Storage(APIClientBase):
41
41
  def stop_pool_scrub(self, pool_name: str):
42
42
  return self.put_request("/pools/{pool_name}/scrub/stop", pool_name=pool_name)
43
43
 
44
- def get_volumes(
45
- self, volume_type: str = "NFS", only_clones: bool = False, label_key: str = "",
46
- ):
44
+ def get_volumes(self, volume_type: str = "NFS", only_clones: bool = False, label_key: str = ""):
47
45
  return self.get_request(
48
- "/volumes", query_args={"volume_type": volume_type, "only_clones": only_clones, "label_key": label_key,},
46
+ "/volumes", query_args={"volume_type": volume_type, "only_clones": only_clones, "label_key": label_key}
49
47
  )
50
48
 
51
- def create_volume(
52
- self, pool_name: str, ips: list, volume_type: str = "NFS", quota: str = "none",
53
- ):
49
+ def create_volume(self, pool_name: str, ips: list, volume_type: str = "NFS", quota: str = "none"):
54
50
  hosts = [{"ipNetwork": ip, "accessType": "rw"} for ip in ips]
55
51
  body = {
56
52
  "type": volume_type,
@@ -58,35 +54,35 @@ class Storage(APIClientBase):
58
54
  "nfsShareConfig": {"rootAccess": "true", "hosts": hosts},
59
55
  "quota": quota,
60
56
  }
61
- return self.post_request("/volumes", body=body,)
57
+ return self.post_request("/volumes", body=body)
62
58
 
63
59
  def get_volume(self, volume_id: str):
64
- return self.get_request("/volumes/{volume_id}", volume_id=volume_id,)
60
+ return self.get_request("/volumes/{volume_id}", volume_id=volume_id)
65
61
 
66
62
  def delete_volume(self, volume_id: str):
67
- return self.delete_request("/volumes", query_args={"volume_id": volume_id},)
63
+ return self.delete_request("/volumes", query_args={"volume_id": volume_id})
68
64
 
69
65
  def promote_volume(self, volume_id: str):
70
66
  return self.put_request("/volumes/{volume_id}/promote", volume_id=volume_id)
71
67
 
72
68
  def get_volume_meta(self, volume_id: str, key: str):
73
- return self.get_request("/volumes/{volume_id}/meta", volume_id=volume_id, query_args={"key": key},)
69
+ return self.get_request("/volumes/{volume_id}/meta", volume_id=volume_id, query_args={"key": key})
74
70
 
75
71
  def set_volume_meta(self, volume_id: str, key: str, value: dict):
76
72
  body = {"key": key, "value": value}
77
- return self.put_request("/volumes/{volume_id}/meta", volume_id=volume_id, body=body,)
73
+ return self.put_request("/volumes/{volume_id}/meta", volume_id=volume_id, body=body)
78
74
 
79
75
  def get_volume_snapshots(self, volume_id: str):
80
- return self.get_request("/volumes/{volume_id}/snapshots", volume_id=volume_id,)
76
+ return self.get_request("/volumes/{volume_id}/snapshots", volume_id=volume_id)
81
77
 
82
78
  def create_volume_snapshot(self, volume_id: str, snapshot_name: str):
83
79
  return self.post_request(
84
- "/volumes/{volume_id}/snapshots", volume_id=volume_id, query_args={"snapshot_name": snapshot_name},
80
+ "/volumes/{volume_id}/snapshots", volume_id=volume_id, query_args={"snapshot_name": snapshot_name}
85
81
  )
86
82
 
87
83
  def get_volume_snapshot(self, volume_id: str, snapshot_name: str):
88
84
  return self.get_request(
89
- "/volumes/{volume_id}/snapshots/{snapshot_name}", volume_id=volume_id, snapshot_name=snapshot_name,
85
+ "/volumes/{volume_id}/snapshots/{snapshot_name}", volume_id=volume_id, snapshot_name=snapshot_name
90
86
  )
91
87
 
92
88
  def remove_volume_snapshot(self, volume_id: str, snapshot_name: str):
@@ -95,43 +91,41 @@ class Storage(APIClientBase):
95
91
 
96
92
  def delete_volume_snapshot(self, volume_id: str, snapshot_name: str):
97
93
  return self.delete_request(
98
- "/volumes/{volume_id}/snapshots", volume_id=volume_id, query_args={"snapshot_name": snapshot_name},
94
+ "/volumes/{volume_id}/snapshots", volume_id=volume_id, query_args={"snapshot_name": snapshot_name}
99
95
  )
100
96
 
101
97
  def rollback_volume_snapshot(self, volume_id: str, snapshot_name: str):
102
98
  return self.put_request(
103
- "/volumes/{volume_id}/snapshots/{snapshot_name}/rollback", volume_id=volume_id, snapshot_name=snapshot_name,
99
+ "/volumes/{volume_id}/snapshots/{snapshot_name}/rollback", volume_id=volume_id, snapshot_name=snapshot_name
104
100
  )
105
101
 
106
102
  def get_volume_snapshot_clones(self, volume_id: str, snapshot_name: str):
107
103
  return self.get_request(
108
- "/volumes/{volume_id}/snapshots/{snapshot_name}/clones", volume_id=volume_id, snapshot_name=snapshot_name,
104
+ "/volumes/{volume_id}/snapshots/{snapshot_name}/clones", volume_id=volume_id, snapshot_name=snapshot_name
109
105
  )
110
106
 
111
107
  def create_volume_snapshot_clone(self, volume_id: str, snapshot_name: str):
112
108
  return self.post_request(
113
- "/volumes/{volume_id}/snapshots/{snapshot_name}/clones", volume_id=volume_id, snapshot_name=snapshot_name,
109
+ "/volumes/{volume_id}/snapshots/{snapshot_name}/clones", volume_id=volume_id, snapshot_name=snapshot_name
114
110
  )
115
111
 
116
- def get_volume_files(
117
- self, volume_id: str, path: str = "", recursive: bool = False, match_pattern: str = "",
118
- ):
112
+ def get_volume_files(self, volume_id: str, path: str = "", recursive: bool = False, match_pattern: str = ""):
119
113
  return self.get_request(
120
114
  "/volumes/{volume_id}/files",
121
115
  volume_id=volume_id,
122
- query_args={"path": path, "recursive": recursive, "match_pattern": match_pattern,},
116
+ query_args={"path": path, "recursive": recursive, "match_pattern": match_pattern},
123
117
  )
124
118
 
125
119
  def delete_volume_files(self, volume_id: str, file_path: str = None, pattern: str = None):
126
120
  return self.delete_request(
127
121
  "/volumes/{volume_id}/files",
128
122
  volume_id=volume_id,
129
- body={"volume_id": volume_id, "file_path": file_path, "pattern": pattern,},
123
+ body={"volume_id": volume_id, "file_path": file_path, "pattern": pattern},
130
124
  )
131
125
 
132
126
  def get_volume_file_content(self, volume_id: str, file_path: str):
133
127
  return self.get_request(
134
- "/volumes/{volume_id}/files/content", volume_id=volume_id, query_args={"file_path": file_path},
128
+ "/volumes/{volume_id}/files/content", volume_id=volume_id, query_args={"file_path": file_path}
135
129
  )
136
130
 
137
131
  def set_volume_file_content(self, volume_id: str, file_path: str, content: str):
@@ -143,18 +137,18 @@ class Storage(APIClientBase):
143
137
  )
144
138
 
145
139
  def create_directory_in_volume(self, volume_id: str, dir_path: str):
146
- return self.post_request("/volumes/{volume_id}/folder", volume_id=volume_id, query_args={"dir_path": dir_path},)
140
+ return self.post_request("/volumes/{volume_id}/folder", volume_id=volume_id, query_args={"dir_path": dir_path})
147
141
 
148
142
  def delete_directory_in_volume(self, volume_id: str, dir_path: str):
149
143
  return self.delete_request(
150
- "/volumes/{volume_id}/folder", volume_id=volume_id, query_args={"dir_path": dir_path},
144
+ "/volumes/{volume_id}/folder", volume_id=volume_id, query_args={"dir_path": dir_path}
151
145
  )
152
146
 
153
147
  def get_file_recovery_volume(self):
154
148
  return self.get_request("/file-recovery")
155
149
 
156
150
  def create_file_recovery_volume(self, pool_name: str):
157
- return self.post_request("/file-recovery", body={"pool": pool_name},)
151
+ return self.post_request("/file-recovery", body={"pool": pool_name})
158
152
 
159
153
  def get_system_information(self):
160
154
  return self.get_request("/system")
File without changes