root / lib / rapi / client.py @ 95ab4de9
History | View | Annotate | Download (23.1 kB)
1 | 95ab4de9 | David Knowles | #
|
---|---|---|---|
2 | 95ab4de9 | David Knowles | #
|
3 | 95ab4de9 | David Knowles | |
4 | 95ab4de9 | David Knowles | # Copyright (C) 2010 Google Inc.
|
5 | 95ab4de9 | David Knowles | #
|
6 | 95ab4de9 | David Knowles | # This program is free software; you can redistribute it and/or modify
|
7 | 95ab4de9 | David Knowles | # it under the terms of the GNU General Public License as published by
|
8 | 95ab4de9 | David Knowles | # the Free Software Foundation; either version 2 of the License, or
|
9 | 95ab4de9 | David Knowles | # (at your option) any later version.
|
10 | 95ab4de9 | David Knowles | #
|
11 | 95ab4de9 | David Knowles | # This program is distributed in the hope that it will be useful, but
|
12 | 95ab4de9 | David Knowles | # WITHOUT ANY WARRANTY; without even the implied warranty of
|
13 | 95ab4de9 | David Knowles | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
14 | 95ab4de9 | David Knowles | # General Public License for more details.
|
15 | 95ab4de9 | David Knowles | #
|
16 | 95ab4de9 | David Knowles | # You should have received a copy of the GNU General Public License
|
17 | 95ab4de9 | David Knowles | # along with this program; if not, write to the Free Software
|
18 | 95ab4de9 | David Knowles | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
19 | 95ab4de9 | David Knowles | # 02110-1301, USA.
|
20 | 95ab4de9 | David Knowles | |
21 | 95ab4de9 | David Knowles | |
22 | 95ab4de9 | David Knowles | """Ganeti RAPI client."""
|
23 | 95ab4de9 | David Knowles | |
24 | 95ab4de9 | David Knowles | import httplib |
25 | 95ab4de9 | David Knowles | import httplib2 |
26 | 95ab4de9 | David Knowles | import simplejson |
27 | 95ab4de9 | David Knowles | import socket |
28 | 95ab4de9 | David Knowles | import urllib |
29 | 95ab4de9 | David Knowles | from OpenSSL import SSL |
30 | 95ab4de9 | David Knowles | from OpenSSL import crypto |
31 | 95ab4de9 | David Knowles | |
32 | 95ab4de9 | David Knowles | |
33 | 95ab4de9 | David Knowles | HTTP_DELETE = "DELETE"
|
34 | 95ab4de9 | David Knowles | HTTP_GET = "GET"
|
35 | 95ab4de9 | David Knowles | HTTP_PUT = "PUT"
|
36 | 95ab4de9 | David Knowles | HTTP_POST = "POST"
|
37 | 95ab4de9 | David Knowles | REPLACE_DISK_PRI = "replace_on_primary"
|
38 | 95ab4de9 | David Knowles | REPLACE_DISK_SECONDARY = "replace_on_secondary"
|
39 | 95ab4de9 | David Knowles | REPLACE_DISK_CHG = "replace_new_secondary"
|
40 | 95ab4de9 | David Knowles | REPLACE_DISK_AUTO = "replace_auto"
|
41 | 95ab4de9 | David Knowles | VALID_REPLACEMENT_MODES = frozenset([
|
42 | 95ab4de9 | David Knowles | REPLACE_DISK_PRI, REPLACE_DISK_SECONDARY, REPLACE_DISK_CHG, |
43 | 95ab4de9 | David Knowles | REPLACE_DISK_AUTO |
44 | 95ab4de9 | David Knowles | ]) |
45 | 95ab4de9 | David Knowles | VALID_NODE_ROLES = frozenset([
|
46 | 95ab4de9 | David Knowles | "drained", "master", "master-candidate", "offline", "regular" |
47 | 95ab4de9 | David Knowles | ]) |
48 | 95ab4de9 | David Knowles | VALID_STORAGE_TYPES = frozenset(["file", "lvm-pv", "lvm-vg"]) |
49 | 95ab4de9 | David Knowles | |
50 | 95ab4de9 | David Knowles | |
51 | 95ab4de9 | David Knowles | class Error(Exception): |
52 | 95ab4de9 | David Knowles | """Base error class for this module.
|
53 | 95ab4de9 | David Knowles |
|
54 | 95ab4de9 | David Knowles | """
|
55 | 95ab4de9 | David Knowles | pass
|
56 | 95ab4de9 | David Knowles | |
57 | 95ab4de9 | David Knowles | |
58 | 95ab4de9 | David Knowles | class CertificateError(Error): |
59 | 95ab4de9 | David Knowles | """Raised when a problem is found with the SSL certificate.
|
60 | 95ab4de9 | David Knowles |
|
61 | 95ab4de9 | David Knowles | """
|
62 | 95ab4de9 | David Knowles | pass
|
63 | 95ab4de9 | David Knowles | |
64 | 95ab4de9 | David Knowles | |
65 | 95ab4de9 | David Knowles | class GanetiApiError(Error): |
66 | 95ab4de9 | David Knowles | """Generic error raised from Ganeti API.
|
67 | 95ab4de9 | David Knowles |
|
68 | 95ab4de9 | David Knowles | """
|
69 | 95ab4de9 | David Knowles | pass
|
70 | 95ab4de9 | David Knowles | |
71 | 95ab4de9 | David Knowles | |
72 | 95ab4de9 | David Knowles | class InvalidReplacementMode(Error): |
73 | 95ab4de9 | David Knowles | """Raised when an invalid disk replacement mode is attempted.
|
74 | 95ab4de9 | David Knowles |
|
75 | 95ab4de9 | David Knowles | """
|
76 | 95ab4de9 | David Knowles | pass
|
77 | 95ab4de9 | David Knowles | |
78 | 95ab4de9 | David Knowles | |
79 | 95ab4de9 | David Knowles | class InvalidStorageType(Error): |
80 | 95ab4de9 | David Knowles | """Raised when an invalid storage type is used.
|
81 | 95ab4de9 | David Knowles |
|
82 | 95ab4de9 | David Knowles | """
|
83 | 95ab4de9 | David Knowles | pass
|
84 | 95ab4de9 | David Knowles | |
85 | 95ab4de9 | David Knowles | |
86 | 95ab4de9 | David Knowles | class InvalidNodeRole(Error): |
87 | 95ab4de9 | David Knowles | """Raised when an invalid node role is used.
|
88 | 95ab4de9 | David Knowles |
|
89 | 95ab4de9 | David Knowles | """
|
90 | 95ab4de9 | David Knowles | pass
|
91 | 95ab4de9 | David Knowles | |
92 | 95ab4de9 | David Knowles | |
93 | 95ab4de9 | David Knowles | class GanetiRapiClient(object): |
94 | 95ab4de9 | David Knowles | """Ganeti RAPI client.
|
95 | 95ab4de9 | David Knowles |
|
96 | 95ab4de9 | David Knowles | """
|
97 | 95ab4de9 | David Knowles | |
98 | 95ab4de9 | David Knowles | USER_AGENT = "Ganeti RAPI Client"
|
99 | 95ab4de9 | David Knowles | |
100 | 95ab4de9 | David Knowles | def __init__(self, master_hostname, port=5080, username=None, password=None, |
101 | 95ab4de9 | David Knowles | ssl_cert=None):
|
102 | 95ab4de9 | David Knowles | """Constructor.
|
103 | 95ab4de9 | David Knowles |
|
104 | 95ab4de9 | David Knowles | @type master_hostname: str
|
105 | 95ab4de9 | David Knowles | @param master_hostname: the ganeti cluster master to interact with
|
106 | 95ab4de9 | David Knowles | @type port: int
|
107 | 95ab4de9 | David Knowles | @param port: the port on which the RAPI is running. (default is 5080)
|
108 | 95ab4de9 | David Knowles | @type username: str
|
109 | 95ab4de9 | David Knowles | @param username: the username to connect with
|
110 | 95ab4de9 | David Knowles | @type password: str
|
111 | 95ab4de9 | David Knowles | @param password: the password to connect with
|
112 | 95ab4de9 | David Knowles | @type ssl_cert: str or None
|
113 | 95ab4de9 | David Knowles | @param ssl_cert: the expected SSL certificate. if None, SSL certificate
|
114 | 95ab4de9 | David Knowles | will not be verified
|
115 | 95ab4de9 | David Knowles |
|
116 | 95ab4de9 | David Knowles | """
|
117 | 95ab4de9 | David Knowles | self._master_hostname = master_hostname
|
118 | 95ab4de9 | David Knowles | self._port = port
|
119 | 95ab4de9 | David Knowles | if ssl_cert:
|
120 | 95ab4de9 | David Knowles | _VerifyCertificate(self._master_hostname, self._port, ssl_cert) |
121 | 95ab4de9 | David Knowles | |
122 | 95ab4de9 | David Knowles | self._http = httplib2.Http()
|
123 | 95ab4de9 | David Knowles | self._headers = {
|
124 | 95ab4de9 | David Knowles | "Accept": "text/plain", |
125 | 95ab4de9 | David Knowles | "Content-type": "application/x-www-form-urlencoded", |
126 | 95ab4de9 | David Knowles | "User-Agent": self.USER_AGENT} |
127 | 95ab4de9 | David Knowles | self._version = None |
128 | 95ab4de9 | David Knowles | if username and password: |
129 | 95ab4de9 | David Knowles | self._http.add_credentials(username, password)
|
130 | 95ab4de9 | David Knowles | |
131 | 95ab4de9 | David Knowles | def _MakeUrl(self, path, query=None, prepend_version=True): |
132 | 95ab4de9 | David Knowles | """Constructs the URL to pass to the HTTP client.
|
133 | 95ab4de9 | David Knowles |
|
134 | 95ab4de9 | David Knowles | @type path: str
|
135 | 95ab4de9 | David Knowles | @param path: HTTP URL path
|
136 | 95ab4de9 | David Knowles | @type query: list of two-tuples
|
137 | 95ab4de9 | David Knowles | @param query: query arguments to pass to urllib.urlencode
|
138 | 95ab4de9 | David Knowles | @type prepend_version: bool
|
139 | 95ab4de9 | David Knowles | @param prepend_version: whether to automatically fetch and prepend the
|
140 | 95ab4de9 | David Knowles | Ganeti version to the URL path
|
141 | 95ab4de9 | David Knowles |
|
142 | 95ab4de9 | David Knowles | @rtype: str
|
143 | 95ab4de9 | David Knowles | @return: URL path
|
144 | 95ab4de9 | David Knowles |
|
145 | 95ab4de9 | David Knowles | """
|
146 | 95ab4de9 | David Knowles | if prepend_version:
|
147 | 95ab4de9 | David Knowles | if not self._version: |
148 | 95ab4de9 | David Knowles | self._GetVersionInternal()
|
149 | 95ab4de9 | David Knowles | path = "/%d%s" % (self._version, path) |
150 | 95ab4de9 | David Knowles | |
151 | 95ab4de9 | David Knowles | return "https://%(host)s:%(port)d%(path)s?%(query)s" % { |
152 | 95ab4de9 | David Knowles | "host": self._master_hostname, |
153 | 95ab4de9 | David Knowles | "port": self._port, |
154 | 95ab4de9 | David Knowles | "path": path,
|
155 | 95ab4de9 | David Knowles | "query": urllib.urlencode(query or [])} |
156 | 95ab4de9 | David Knowles | |
157 | 95ab4de9 | David Knowles | def _SendRequest(self, method, path, query=None, content=None, |
158 | 95ab4de9 | David Knowles | prepend_version=True):
|
159 | 95ab4de9 | David Knowles | """Sends an HTTP request.
|
160 | 95ab4de9 | David Knowles |
|
161 | 95ab4de9 | David Knowles | This constructs a full URL, encodes and decodes HTTP bodies, and
|
162 | 95ab4de9 | David Knowles | handles invalid responses in a pythonic way.
|
163 | 95ab4de9 | David Knowles |
|
164 | 95ab4de9 | David Knowles | @type method: str
|
165 | 95ab4de9 | David Knowles | @param method: HTTP method to use
|
166 | 95ab4de9 | David Knowles | @type path: str
|
167 | 95ab4de9 | David Knowles | @param path: HTTP URL path
|
168 | 95ab4de9 | David Knowles | @type query: list of two-tuples
|
169 | 95ab4de9 | David Knowles | @param query: query arguments to pass to urllib.urlencode
|
170 | 95ab4de9 | David Knowles | @type content: str or None
|
171 | 95ab4de9 | David Knowles | @param content: HTTP body content
|
172 | 95ab4de9 | David Knowles | @type prepend_version: bool
|
173 | 95ab4de9 | David Knowles | @param prepend_version: whether to automatically fetch and prepend the
|
174 | 95ab4de9 | David Knowles | Ganeti version to the URL path
|
175 | 95ab4de9 | David Knowles |
|
176 | 95ab4de9 | David Knowles | @rtype: str
|
177 | 95ab4de9 | David Knowles | @return: JSON-Decoded response
|
178 | 95ab4de9 | David Knowles |
|
179 | 95ab4de9 | David Knowles | @raises GanetiApiError: If an invalid response is returned
|
180 | 95ab4de9 | David Knowles |
|
181 | 95ab4de9 | David Knowles | """
|
182 | 95ab4de9 | David Knowles | if content:
|
183 | 95ab4de9 | David Knowles | simplejson.JSONEncoder(sort_keys=True).encode(content)
|
184 | 95ab4de9 | David Knowles | |
185 | 95ab4de9 | David Knowles | url = self._MakeUrl(path, query, prepend_version)
|
186 | 95ab4de9 | David Knowles | resp_headers, resp_content = self._http.request(
|
187 | 95ab4de9 | David Knowles | url, method, body=content, headers=self._headers)
|
188 | 95ab4de9 | David Knowles | |
189 | 95ab4de9 | David Knowles | if resp_content:
|
190 | 95ab4de9 | David Knowles | resp_content = simplejson.loads(resp_content) |
191 | 95ab4de9 | David Knowles | |
192 | 95ab4de9 | David Knowles | # TODO: Are there other status codes that are valid? (redirect?)
|
193 | 95ab4de9 | David Knowles | if resp_headers.status != 200: |
194 | 95ab4de9 | David Knowles | if isinstance(resp_content, dict): |
195 | 95ab4de9 | David Knowles | msg = ("%s %s: %s" %
|
196 | 95ab4de9 | David Knowles | (resp_content["code"], resp_content["message"], |
197 | 95ab4de9 | David Knowles | resp_content["explain"]))
|
198 | 95ab4de9 | David Knowles | else:
|
199 | 95ab4de9 | David Knowles | msg = resp_content |
200 | 95ab4de9 | David Knowles | raise GanetiApiError(msg)
|
201 | 95ab4de9 | David Knowles | |
202 | 95ab4de9 | David Knowles | return resp_content
|
203 | 95ab4de9 | David Knowles | |
204 | 95ab4de9 | David Knowles | def _GetVersionInternal(self): |
205 | 95ab4de9 | David Knowles | """Gets the Remote API version running on the cluster.
|
206 | 95ab4de9 | David Knowles |
|
207 | 95ab4de9 | David Knowles | @rtype: int
|
208 | 95ab4de9 | David Knowles | @return: Ganeti version
|
209 | 95ab4de9 | David Knowles |
|
210 | 95ab4de9 | David Knowles | """
|
211 | 95ab4de9 | David Knowles | self._version = self._SendRequest(HTTP_GET, "/version", |
212 | 95ab4de9 | David Knowles | prepend_version=False)
|
213 | 95ab4de9 | David Knowles | return self._version |
214 | 95ab4de9 | David Knowles | |
215 | 95ab4de9 | David Knowles | def GetVersion(self): |
216 | 95ab4de9 | David Knowles | """Gets the ganeti version running on the cluster.
|
217 | 95ab4de9 | David Knowles |
|
218 | 95ab4de9 | David Knowles | @rtype: int
|
219 | 95ab4de9 | David Knowles | @return: Ganeti version
|
220 | 95ab4de9 | David Knowles |
|
221 | 95ab4de9 | David Knowles | """
|
222 | 95ab4de9 | David Knowles | if not self._version: |
223 | 95ab4de9 | David Knowles | self._GetVersionInternal()
|
224 | 95ab4de9 | David Knowles | return self._version |
225 | 95ab4de9 | David Knowles | |
226 | 95ab4de9 | David Knowles | def GetOperatingSystems(self): |
227 | 95ab4de9 | David Knowles | """Gets the Operating Systems running in the Ganeti cluster.
|
228 | 95ab4de9 | David Knowles |
|
229 | 95ab4de9 | David Knowles | @rtype: list of str
|
230 | 95ab4de9 | David Knowles | @return: operating systems
|
231 | 95ab4de9 | David Knowles |
|
232 | 95ab4de9 | David Knowles | """
|
233 | 95ab4de9 | David Knowles | return self._SendRequest(HTTP_GET, "/os") |
234 | 95ab4de9 | David Knowles | |
235 | 95ab4de9 | David Knowles | def GetInfo(self): |
236 | 95ab4de9 | David Knowles | """Gets info about the cluster.
|
237 | 95ab4de9 | David Knowles |
|
238 | 95ab4de9 | David Knowles | @rtype: dict
|
239 | 95ab4de9 | David Knowles | @return: information about the cluster
|
240 | 95ab4de9 | David Knowles |
|
241 | 95ab4de9 | David Knowles | """
|
242 | 95ab4de9 | David Knowles | return self._SendRequest(HTTP_GET, "/info") |
243 | 95ab4de9 | David Knowles | |
244 | 95ab4de9 | David Knowles | def GetClusterTags(self): |
245 | 95ab4de9 | David Knowles | """Gets the cluster tags.
|
246 | 95ab4de9 | David Knowles |
|
247 | 95ab4de9 | David Knowles | @rtype: list of str
|
248 | 95ab4de9 | David Knowles | @return: cluster tags
|
249 | 95ab4de9 | David Knowles |
|
250 | 95ab4de9 | David Knowles | """
|
251 | 95ab4de9 | David Knowles | return self._SendRequest(HTTP_GET, "/tags") |
252 | 95ab4de9 | David Knowles | |
253 | 95ab4de9 | David Knowles | def AddClusterTags(self, tags, dry_run=False): |
254 | 95ab4de9 | David Knowles | """Adds tags to the cluster.
|
255 | 95ab4de9 | David Knowles |
|
256 | 95ab4de9 | David Knowles | @type tags: list of str
|
257 | 95ab4de9 | David Knowles | @param tags: tags to add to the cluster
|
258 | 95ab4de9 | David Knowles | @type dry_run: bool
|
259 | 95ab4de9 | David Knowles | @param dry_run: whether to perform a dry run
|
260 | 95ab4de9 | David Knowles |
|
261 | 95ab4de9 | David Knowles | @rtype: int
|
262 | 95ab4de9 | David Knowles | @return: job id
|
263 | 95ab4de9 | David Knowles |
|
264 | 95ab4de9 | David Knowles | """
|
265 | 95ab4de9 | David Knowles | query = [("tag", t) for t in tags] |
266 | 95ab4de9 | David Knowles | if dry_run:
|
267 | 95ab4de9 | David Knowles | query.append(("dry-run", 1)) |
268 | 95ab4de9 | David Knowles | |
269 | 95ab4de9 | David Knowles | self._SendRequest(HTTP_PUT, "/tags", query) |
270 | 95ab4de9 | David Knowles | |
271 | 95ab4de9 | David Knowles | def DeleteClusterTags(self, tags, dry_run=False): |
272 | 95ab4de9 | David Knowles | """Deletes tags from the cluster.
|
273 | 95ab4de9 | David Knowles |
|
274 | 95ab4de9 | David Knowles | @type tags: list of str
|
275 | 95ab4de9 | David Knowles | @param tags: tags to delete
|
276 | 95ab4de9 | David Knowles | @type dry_run: bool
|
277 | 95ab4de9 | David Knowles | @param dry_run: whether to perform a dry run
|
278 | 95ab4de9 | David Knowles |
|
279 | 95ab4de9 | David Knowles | """
|
280 | 95ab4de9 | David Knowles | query = [("tag", t) for t in tags] |
281 | 95ab4de9 | David Knowles | if dry_run:
|
282 | 95ab4de9 | David Knowles | query.append(("dry-run", 1)) |
283 | 95ab4de9 | David Knowles | |
284 | 95ab4de9 | David Knowles | self._SendRequest(HTTP_DELETE, "/tags", query) |
285 | 95ab4de9 | David Knowles | |
286 | 95ab4de9 | David Knowles | def GetInstances(self, bulk=False): |
287 | 95ab4de9 | David Knowles | """Gets information about instances on the cluster.
|
288 | 95ab4de9 | David Knowles |
|
289 | 95ab4de9 | David Knowles | @type bulk: bool
|
290 | 95ab4de9 | David Knowles | @param bulk: whether to return all information about all instances
|
291 | 95ab4de9 | David Knowles |
|
292 | 95ab4de9 | David Knowles | @rtype: list of dict or list of str
|
293 | 95ab4de9 | David Knowles | @return: if bulk is True, info about the instances, else a list of instances
|
294 | 95ab4de9 | David Knowles |
|
295 | 95ab4de9 | David Knowles | """
|
296 | 95ab4de9 | David Knowles | query = [] |
297 | 95ab4de9 | David Knowles | if bulk:
|
298 | 95ab4de9 | David Knowles | query.append(("bulk", 1)) |
299 | 95ab4de9 | David Knowles | |
300 | 95ab4de9 | David Knowles | instances = self._SendRequest(HTTP_GET, "/instances", query) |
301 | 95ab4de9 | David Knowles | if bulk:
|
302 | 95ab4de9 | David Knowles | return instances
|
303 | 95ab4de9 | David Knowles | else:
|
304 | 95ab4de9 | David Knowles | return [i["id"] for i in instances] |
305 | 95ab4de9 | David Knowles | |
306 | 95ab4de9 | David Knowles | |
307 | 95ab4de9 | David Knowles | def GetInstanceInfo(self, instance): |
308 | 95ab4de9 | David Knowles | """Gets information about an instance.
|
309 | 95ab4de9 | David Knowles |
|
310 | 95ab4de9 | David Knowles | @type instance: str
|
311 | 95ab4de9 | David Knowles | @param instance: instance whose info to return
|
312 | 95ab4de9 | David Knowles |
|
313 | 95ab4de9 | David Knowles | @rtype: dict
|
314 | 95ab4de9 | David Knowles | @return: info about the instance
|
315 | 95ab4de9 | David Knowles |
|
316 | 95ab4de9 | David Knowles | """
|
317 | 95ab4de9 | David Knowles | return self._SendRequest(HTTP_GET, "/instances/%s" % instance) |
318 | 95ab4de9 | David Knowles | |
319 | 95ab4de9 | David Knowles | def CreateInstance(self, dry_run=False): |
320 | 95ab4de9 | David Knowles | """Creates a new instance.
|
321 | 95ab4de9 | David Knowles |
|
322 | 95ab4de9 | David Knowles | @type dry_run: bool
|
323 | 95ab4de9 | David Knowles | @param dry_run: whether to perform a dry run
|
324 | 95ab4de9 | David Knowles |
|
325 | 95ab4de9 | David Knowles | @rtype: int
|
326 | 95ab4de9 | David Knowles | @return: job id
|
327 | 95ab4de9 | David Knowles |
|
328 | 95ab4de9 | David Knowles | """
|
329 | 95ab4de9 | David Knowles | # TODO: Pass arguments needed to actually create an instance.
|
330 | 95ab4de9 | David Knowles | query = [] |
331 | 95ab4de9 | David Knowles | if dry_run:
|
332 | 95ab4de9 | David Knowles | query.append(("dry-run", 1)) |
333 | 95ab4de9 | David Knowles | |
334 | 95ab4de9 | David Knowles | return self._SendRequest(HTTP_POST, "/instances", query) |
335 | 95ab4de9 | David Knowles | |
336 | 95ab4de9 | David Knowles | def DeleteInstance(self, instance, dry_run=False): |
337 | 95ab4de9 | David Knowles | """Deletes an instance.
|
338 | 95ab4de9 | David Knowles |
|
339 | 95ab4de9 | David Knowles | @type instance: str
|
340 | 95ab4de9 | David Knowles | @param instance: the instance to delete
|
341 | 95ab4de9 | David Knowles |
|
342 | 95ab4de9 | David Knowles | """
|
343 | 95ab4de9 | David Knowles | query = [] |
344 | 95ab4de9 | David Knowles | if dry_run:
|
345 | 95ab4de9 | David Knowles | query.append(("dry-run", 1)) |
346 | 95ab4de9 | David Knowles | |
347 | 95ab4de9 | David Knowles | self._SendRequest(HTTP_DELETE, "/instances/%s" % instance, query) |
348 | 95ab4de9 | David Knowles | |
349 | 95ab4de9 | David Knowles | def GetInstanceTags(self, instance): |
350 | 95ab4de9 | David Knowles | """Gets tags for an instance.
|
351 | 95ab4de9 | David Knowles |
|
352 | 95ab4de9 | David Knowles | @type instance: str
|
353 | 95ab4de9 | David Knowles | @param instance: instance whose tags to return
|
354 | 95ab4de9 | David Knowles |
|
355 | 95ab4de9 | David Knowles | @rtype: list of str
|
356 | 95ab4de9 | David Knowles | @return: tags for the instance
|
357 | 95ab4de9 | David Knowles |
|
358 | 95ab4de9 | David Knowles | """
|
359 | 95ab4de9 | David Knowles | return self._SendRequest(HTTP_GET, "/instances/%s/tags" % instance) |
360 | 95ab4de9 | David Knowles | |
361 | 95ab4de9 | David Knowles | def AddInstanceTags(self, instance, tags, dry_run=False): |
362 | 95ab4de9 | David Knowles | """Adds tags to an instance.
|
363 | 95ab4de9 | David Knowles |
|
364 | 95ab4de9 | David Knowles | @type instance: str
|
365 | 95ab4de9 | David Knowles | @param instance: instance to add tags to
|
366 | 95ab4de9 | David Knowles | @type tags: list of str
|
367 | 95ab4de9 | David Knowles | @param tags: tags to add to the instance
|
368 | 95ab4de9 | David Knowles | @type dry_run: bool
|
369 | 95ab4de9 | David Knowles | @param dry_run: whether to perform a dry run
|
370 | 95ab4de9 | David Knowles |
|
371 | 95ab4de9 | David Knowles | @rtype: int
|
372 | 95ab4de9 | David Knowles | @return: job id
|
373 | 95ab4de9 | David Knowles |
|
374 | 95ab4de9 | David Knowles | """
|
375 | 95ab4de9 | David Knowles | query = [("tag", t) for t in tags] |
376 | 95ab4de9 | David Knowles | if dry_run:
|
377 | 95ab4de9 | David Knowles | query.append(("dry-run", 1)) |
378 | 95ab4de9 | David Knowles | |
379 | 95ab4de9 | David Knowles | self._SendRequest(HTTP_PUT, "/instances/%s/tags" % instance, query) |
380 | 95ab4de9 | David Knowles | |
381 | 95ab4de9 | David Knowles | def DeleteInstanceTags(self, instance, tags, dry_run=False): |
382 | 95ab4de9 | David Knowles | """Deletes tags from an instance.
|
383 | 95ab4de9 | David Knowles |
|
384 | 95ab4de9 | David Knowles | @type instance: str
|
385 | 95ab4de9 | David Knowles | @param instance: instance to delete tags from
|
386 | 95ab4de9 | David Knowles | @type tags: list of str
|
387 | 95ab4de9 | David Knowles | @param tags: tags to delete
|
388 | 95ab4de9 | David Knowles | @type dry_run: bool
|
389 | 95ab4de9 | David Knowles | @param dry_run: whether to perform a dry run
|
390 | 95ab4de9 | David Knowles |
|
391 | 95ab4de9 | David Knowles | """
|
392 | 95ab4de9 | David Knowles | query = [("tag", t) for t in tags] |
393 | 95ab4de9 | David Knowles | if dry_run:
|
394 | 95ab4de9 | David Knowles | query.append(("dry-run", 1)) |
395 | 95ab4de9 | David Knowles | |
396 | 95ab4de9 | David Knowles | self._SendRequest(HTTP_DELETE, "/instances/%s/tags" % instance, query) |
397 | 95ab4de9 | David Knowles | |
398 | 95ab4de9 | David Knowles | def RebootInstance(self, instance, reboot_type=None, ignore_secondaries=None, |
399 | 95ab4de9 | David Knowles | dry_run=False):
|
400 | 95ab4de9 | David Knowles | """Reboots an instance.
|
401 | 95ab4de9 | David Knowles |
|
402 | 95ab4de9 | David Knowles | @type instance: str
|
403 | 95ab4de9 | David Knowles | @param instance: instance to rebot
|
404 | 95ab4de9 | David Knowles | @type reboot_type: str
|
405 | 95ab4de9 | David Knowles | @param reboot_type: one of: hard, soft, full
|
406 | 95ab4de9 | David Knowles | @type ignore_secondaries: bool
|
407 | 95ab4de9 | David Knowles | @param ignore_secondaries: if True, ignores errors for the secondary node
|
408 | 95ab4de9 | David Knowles | while re-assembling disks (in hard-reboot mode only)
|
409 | 95ab4de9 | David Knowles | @type dry_run: bool
|
410 | 95ab4de9 | David Knowles | @param dry_run: whether to perform a dry run
|
411 | 95ab4de9 | David Knowles |
|
412 | 95ab4de9 | David Knowles | """
|
413 | 95ab4de9 | David Knowles | query = [] |
414 | 95ab4de9 | David Knowles | if reboot_type:
|
415 | 95ab4de9 | David Knowles | query.append(("type", reboot_type))
|
416 | 95ab4de9 | David Knowles | if ignore_secondaries is not None: |
417 | 95ab4de9 | David Knowles | query.append(("ignore_secondaries", ignore_secondaries))
|
418 | 95ab4de9 | David Knowles | if dry_run:
|
419 | 95ab4de9 | David Knowles | query.append(("dry-run", 1)) |
420 | 95ab4de9 | David Knowles | |
421 | 95ab4de9 | David Knowles | self._SendRequest(HTTP_POST, "/instances/%s/reboot" % instance, query) |
422 | 95ab4de9 | David Knowles | |
423 | 95ab4de9 | David Knowles | def ShutdownInstance(self, instance, dry_run=False): |
424 | 95ab4de9 | David Knowles | """Shuts down an instance.
|
425 | 95ab4de9 | David Knowles |
|
426 | 95ab4de9 | David Knowles | @type instance: str
|
427 | 95ab4de9 | David Knowles | @param instance: the instance to shut down
|
428 | 95ab4de9 | David Knowles | @type dry_run: bool
|
429 | 95ab4de9 | David Knowles | @param dry_run: whether to perform a dry run
|
430 | 95ab4de9 | David Knowles |
|
431 | 95ab4de9 | David Knowles | """
|
432 | 95ab4de9 | David Knowles | query = [] |
433 | 95ab4de9 | David Knowles | if dry_run:
|
434 | 95ab4de9 | David Knowles | query.append(("dry-run", 1)) |
435 | 95ab4de9 | David Knowles | |
436 | 95ab4de9 | David Knowles | self._SendRequest(HTTP_PUT, "/instances/%s/shutdown" % instance, query) |
437 | 95ab4de9 | David Knowles | |
438 | 95ab4de9 | David Knowles | def StartupInstance(self, instance, dry_run=False): |
439 | 95ab4de9 | David Knowles | """Starts up an instance.
|
440 | 95ab4de9 | David Knowles |
|
441 | 95ab4de9 | David Knowles | @type instance: str
|
442 | 95ab4de9 | David Knowles | @param instance: the instance to start up
|
443 | 95ab4de9 | David Knowles | @type dry_run: bool
|
444 | 95ab4de9 | David Knowles | @param dry_run: whether to perform a dry run
|
445 | 95ab4de9 | David Knowles |
|
446 | 95ab4de9 | David Knowles | """
|
447 | 95ab4de9 | David Knowles | query = [] |
448 | 95ab4de9 | David Knowles | if dry_run:
|
449 | 95ab4de9 | David Knowles | query.append(("dry-run", 1)) |
450 | 95ab4de9 | David Knowles | |
451 | 95ab4de9 | David Knowles | self._SendRequest(HTTP_PUT, "/instances/%s/startup" % instance, query) |
452 | 95ab4de9 | David Knowles | |
453 | 95ab4de9 | David Knowles | def ReinstallInstance(self, instance, os, no_startup=False): |
454 | 95ab4de9 | David Knowles | """Reinstalls an instance.
|
455 | 95ab4de9 | David Knowles |
|
456 | 95ab4de9 | David Knowles | @type instance: str
|
457 | 95ab4de9 | David Knowles | @param instance: the instance to reinstall
|
458 | 95ab4de9 | David Knowles | @type os: str
|
459 | 95ab4de9 | David Knowles | @param os: the os to reinstall
|
460 | 95ab4de9 | David Knowles | @type no_startup: bool
|
461 | 95ab4de9 | David Knowles | @param no_startup: whether to start the instance automatically
|
462 | 95ab4de9 | David Knowles |
|
463 | 95ab4de9 | David Knowles | """
|
464 | 95ab4de9 | David Knowles | query = [("os", os)]
|
465 | 95ab4de9 | David Knowles | if no_startup:
|
466 | 95ab4de9 | David Knowles | query.append(("nostartup", 1)) |
467 | 95ab4de9 | David Knowles | self._SendRequest(HTTP_POST, "/instances/%s/reinstall" % instance, query) |
468 | 95ab4de9 | David Knowles | |
469 | 95ab4de9 | David Knowles | def ReplaceInstanceDisks(self, instance, disks, mode="replace_auto", |
470 | 95ab4de9 | David Knowles | remote_node=None, iallocator="hail", dry_run=False): |
471 | 95ab4de9 | David Knowles | """Replaces disks on an instance.
|
472 | 95ab4de9 | David Knowles |
|
473 | 95ab4de9 | David Knowles | @type instance: str
|
474 | 95ab4de9 | David Knowles | @param instance: instance whose disks to replace
|
475 | 95ab4de9 | David Knowles | @type disks: list of str
|
476 | 95ab4de9 | David Knowles | @param disks: disks to replace
|
477 | 95ab4de9 | David Knowles | @type mode: str
|
478 | 95ab4de9 | David Knowles | @param mode: replacement mode to use. defaults to replace_auto
|
479 | 95ab4de9 | David Knowles | @type remote_node: str or None
|
480 | 95ab4de9 | David Knowles | @param remote_node: new secondary node to use (for use with
|
481 | 95ab4de9 | David Knowles | replace_new_secondary mdoe)
|
482 | 95ab4de9 | David Knowles | @type iallocator: str or None
|
483 | 95ab4de9 | David Knowles | @param iallocator: instance allocator plugin to use (for use with
|
484 | 95ab4de9 | David Knowles | replace_auto mdoe). default is hail
|
485 | 95ab4de9 | David Knowles | @type dry_run: bool
|
486 | 95ab4de9 | David Knowles | @param dry_run: whether to perform a dry run
|
487 | 95ab4de9 | David Knowles |
|
488 | 95ab4de9 | David Knowles | @rtype: int
|
489 | 95ab4de9 | David Knowles | @return: job id
|
490 | 95ab4de9 | David Knowles |
|
491 | 95ab4de9 | David Knowles | @raises InvalidReplacementMode: If an invalid disk replacement mode is given
|
492 | 95ab4de9 | David Knowles | @raises GanetiApiError: If no secondary node is given with a non-auto
|
493 | 95ab4de9 | David Knowles | replacement mode is requested.
|
494 | 95ab4de9 | David Knowles |
|
495 | 95ab4de9 | David Knowles | """
|
496 | 95ab4de9 | David Knowles | if mode not in VALID_REPLACEMENT_MODES: |
497 | 95ab4de9 | David Knowles | raise InvalidReplacementMode("%s is not a valid disk replacement mode.", |
498 | 95ab4de9 | David Knowles | mode) |
499 | 95ab4de9 | David Knowles | |
500 | 95ab4de9 | David Knowles | query = [("mode", mode), ("disks", ",".join(disks))] |
501 | 95ab4de9 | David Knowles | |
502 | 95ab4de9 | David Knowles | if mode is REPLACE_DISK_AUTO: |
503 | 95ab4de9 | David Knowles | query.append(("iallocator", iallocator))
|
504 | 95ab4de9 | David Knowles | elif mode is REPLACE_DISK_SECONDARY: |
505 | 95ab4de9 | David Knowles | if remote_node is None: |
506 | 95ab4de9 | David Knowles | raise GanetiApiError("You must supply a new secondary node.") |
507 | 95ab4de9 | David Knowles | query.append(("remote_node", remote_node))
|
508 | 95ab4de9 | David Knowles | |
509 | 95ab4de9 | David Knowles | if dry_run:
|
510 | 95ab4de9 | David Knowles | query.append(("dry-run", 1)) |
511 | 95ab4de9 | David Knowles | |
512 | 95ab4de9 | David Knowles | return self._SendRequest(HTTP_POST, |
513 | 95ab4de9 | David Knowles | "/instances/%s/replace-disks" % instance, query)
|
514 | 95ab4de9 | David Knowles | |
515 | 95ab4de9 | David Knowles | def GetJobs(self): |
516 | 95ab4de9 | David Knowles | """Gets all jobs for the cluster.
|
517 | 95ab4de9 | David Knowles |
|
518 | 95ab4de9 | David Knowles | @rtype: list of int
|
519 | 95ab4de9 | David Knowles | @return: job ids for the cluster
|
520 | 95ab4de9 | David Knowles |
|
521 | 95ab4de9 | David Knowles | """
|
522 | 95ab4de9 | David Knowles | return [int(j["id"]) for j in self._SendRequest(HTTP_GET, "/jobs")] |
523 | 95ab4de9 | David Knowles | |
524 | 95ab4de9 | David Knowles | def GetJobStatus(self, job_id): |
525 | 95ab4de9 | David Knowles | """Gets the status of a job.
|
526 | 95ab4de9 | David Knowles |
|
527 | 95ab4de9 | David Knowles | @type job_id: int
|
528 | 95ab4de9 | David Knowles | @param job_id: job id whose status to query
|
529 | 95ab4de9 | David Knowles |
|
530 | 95ab4de9 | David Knowles | @rtype: dict
|
531 | 95ab4de9 | David Knowles | @return: job status
|
532 | 95ab4de9 | David Knowles |
|
533 | 95ab4de9 | David Knowles | """
|
534 | 95ab4de9 | David Knowles | return self._SendRequest(HTTP_GET, "/jobs/%d" % job_id) |
535 | 95ab4de9 | David Knowles | |
536 | 95ab4de9 | David Knowles | def DeleteJob(self, job_id, dry_run=False): |
537 | 95ab4de9 | David Knowles | """Deletes a job.
|
538 | 95ab4de9 | David Knowles |
|
539 | 95ab4de9 | David Knowles | @type job_id: int
|
540 | 95ab4de9 | David Knowles | @param job_id: id of the job to delete
|
541 | 95ab4de9 | David Knowles | @type dry_run: bool
|
542 | 95ab4de9 | David Knowles | @param dry_run: whether to perform a dry run
|
543 | 95ab4de9 | David Knowles |
|
544 | 95ab4de9 | David Knowles | """
|
545 | 95ab4de9 | David Knowles | query = [] |
546 | 95ab4de9 | David Knowles | if dry_run:
|
547 | 95ab4de9 | David Knowles | query.append(("dry-run", 1)) |
548 | 95ab4de9 | David Knowles | |
549 | 95ab4de9 | David Knowles | self._SendRequest(HTTP_DELETE, "/jobs/%d" % job_id, query) |
550 | 95ab4de9 | David Knowles | |
551 | 95ab4de9 | David Knowles | def GetNodes(self, bulk=False): |
552 | 95ab4de9 | David Knowles | """Gets all nodes in the cluster.
|
553 | 95ab4de9 | David Knowles |
|
554 | 95ab4de9 | David Knowles | @type bulk: bool
|
555 | 95ab4de9 | David Knowles | @param bulk: whether to return all information about all instances
|
556 | 95ab4de9 | David Knowles |
|
557 | 95ab4de9 | David Knowles | @rtype: list of dict or str
|
558 | 95ab4de9 | David Knowles | @return: if bulk is true, info about nodes in the cluster,
|
559 | 95ab4de9 | David Knowles | else list of nodes in the cluster
|
560 | 95ab4de9 | David Knowles |
|
561 | 95ab4de9 | David Knowles | """
|
562 | 95ab4de9 | David Knowles | query = [] |
563 | 95ab4de9 | David Knowles | if bulk:
|
564 | 95ab4de9 | David Knowles | query.append(("bulk", 1)) |
565 | 95ab4de9 | David Knowles | |
566 | 95ab4de9 | David Knowles | nodes = self._SendRequest(HTTP_GET, "/nodes", query) |
567 | 95ab4de9 | David Knowles | if bulk:
|
568 | 95ab4de9 | David Knowles | return nodes
|
569 | 95ab4de9 | David Knowles | else:
|
570 | 95ab4de9 | David Knowles | return [n["id"] for n in nodes] |
571 | 95ab4de9 | David Knowles | |
572 | 95ab4de9 | David Knowles | def GetNodeInfo(self, node): |
573 | 95ab4de9 | David Knowles | """Gets information about a node.
|
574 | 95ab4de9 | David Knowles |
|
575 | 95ab4de9 | David Knowles | @type node: str
|
576 | 95ab4de9 | David Knowles | @param node: node whose info to return
|
577 | 95ab4de9 | David Knowles |
|
578 | 95ab4de9 | David Knowles | @rtype: dict
|
579 | 95ab4de9 | David Knowles | @return: info about the node
|
580 | 95ab4de9 | David Knowles |
|
581 | 95ab4de9 | David Knowles | """
|
582 | 95ab4de9 | David Knowles | return self._SendRequest(HTTP_GET, "/nodes/%s" % node) |
583 | 95ab4de9 | David Knowles | |
584 | 95ab4de9 | David Knowles | def EvacuateNode(self, node, iallocator=None, remote_node=None, |
585 | 95ab4de9 | David Knowles | dry_run=False):
|
586 | 95ab4de9 | David Knowles | """Evacuates instances from a Ganeti node.
|
587 | 95ab4de9 | David Knowles |
|
588 | 95ab4de9 | David Knowles | @type node: str
|
589 | 95ab4de9 | David Knowles | @param node: node to evacuate
|
590 | 95ab4de9 | David Knowles | @type iallocator: str or None
|
591 | 95ab4de9 | David Knowles | @param iallocator: instance allocator to use
|
592 | 95ab4de9 | David Knowles | @type remote_node: str
|
593 | 95ab4de9 | David Knowles | @param remote_node: node to evaucate to
|
594 | 95ab4de9 | David Knowles | @type dry_run: bool
|
595 | 95ab4de9 | David Knowles | @param dry_run: whether to perform a dry run
|
596 | 95ab4de9 | David Knowles |
|
597 | 95ab4de9 | David Knowles | @rtype: int
|
598 | 95ab4de9 | David Knowles | @return: job id
|
599 | 95ab4de9 | David Knowles |
|
600 | 95ab4de9 | David Knowles | @raises GanetiApiError: if an iallocator and remote_node are both specified
|
601 | 95ab4de9 | David Knowles |
|
602 | 95ab4de9 | David Knowles | """
|
603 | 95ab4de9 | David Knowles | query = [] |
604 | 95ab4de9 | David Knowles | if iallocator and remote_node: |
605 | 95ab4de9 | David Knowles | raise GanetiApiError("Only one of iallocator or remote_node can be used.") |
606 | 95ab4de9 | David Knowles | |
607 | 95ab4de9 | David Knowles | if iallocator:
|
608 | 95ab4de9 | David Knowles | query.append(("iallocator", iallocator))
|
609 | 95ab4de9 | David Knowles | if remote_node:
|
610 | 95ab4de9 | David Knowles | query.append(("remote_node", remote_node))
|
611 | 95ab4de9 | David Knowles | if dry_run:
|
612 | 95ab4de9 | David Knowles | query.append(("dry-run", 1)) |
613 | 95ab4de9 | David Knowles | |
614 | 95ab4de9 | David Knowles | return self._SendRequest(HTTP_POST, "/nodes/%s/evacuate" % node, query) |
615 | 95ab4de9 | David Knowles | |
616 | 95ab4de9 | David Knowles | def MigrateNode(self, node, live=True, dry_run=False): |
617 | 95ab4de9 | David Knowles | """Migrates all primary instances from a node.
|
618 | 95ab4de9 | David Knowles |
|
619 | 95ab4de9 | David Knowles | @type node: str
|
620 | 95ab4de9 | David Knowles | @param node: node to migrate
|
621 | 95ab4de9 | David Knowles | @type live: bool
|
622 | 95ab4de9 | David Knowles | @param live: whether to use live migration
|
623 | 95ab4de9 | David Knowles | @type dry_run: bool
|
624 | 95ab4de9 | David Knowles | @param dry_run: whether to perform a dry run
|
625 | 95ab4de9 | David Knowles |
|
626 | 95ab4de9 | David Knowles | @rtype: int
|
627 | 95ab4de9 | David Knowles | @return: job id
|
628 | 95ab4de9 | David Knowles |
|
629 | 95ab4de9 | David Knowles | """
|
630 | 95ab4de9 | David Knowles | query = [] |
631 | 95ab4de9 | David Knowles | if live:
|
632 | 95ab4de9 | David Knowles | query.append(("live", 1)) |
633 | 95ab4de9 | David Knowles | if dry_run:
|
634 | 95ab4de9 | David Knowles | query.append(("dry-run", 1)) |
635 | 95ab4de9 | David Knowles | |
636 | 95ab4de9 | David Knowles | return self._SendRequest(HTTP_POST, "/nodes/%s/migrate" % node, query) |
637 | 95ab4de9 | David Knowles | |
638 | 95ab4de9 | David Knowles | def GetNodeRole(self, node): |
639 | 95ab4de9 | David Knowles | """Gets the current role for a node.
|
640 | 95ab4de9 | David Knowles |
|
641 | 95ab4de9 | David Knowles | @type node: str
|
642 | 95ab4de9 | David Knowles | @param node: node whose role to return
|
643 | 95ab4de9 | David Knowles |
|
644 | 95ab4de9 | David Knowles | @rtype: str
|
645 | 95ab4de9 | David Knowles | @return: the current role for a node
|
646 | 95ab4de9 | David Knowles |
|
647 | 95ab4de9 | David Knowles | """
|
648 | 95ab4de9 | David Knowles | return self._SendRequest(HTTP_GET, "/nodes/%s/role" % node) |
649 | 95ab4de9 | David Knowles | |
650 | 95ab4de9 | David Knowles | def SetNodeRole(self, node, role, force=False): |
651 | 95ab4de9 | David Knowles | """Sets the role for a node.
|
652 | 95ab4de9 | David Knowles |
|
653 | 95ab4de9 | David Knowles | @type node: str
|
654 | 95ab4de9 | David Knowles | @param node: the node whose role to set
|
655 | 95ab4de9 | David Knowles | @type role: str
|
656 | 95ab4de9 | David Knowles | @param role: the role to set for the node
|
657 | 95ab4de9 | David Knowles | @type force: bool
|
658 | 95ab4de9 | David Knowles | @param force: whether to force the role change
|
659 | 95ab4de9 | David Knowles |
|
660 | 95ab4de9 | David Knowles | @rtype: int
|
661 | 95ab4de9 | David Knowles | @return: job id
|
662 | 95ab4de9 | David Knowles |
|
663 | 95ab4de9 | David Knowles | @raise InvalidNodeRole: If an invalid node role is specified
|
664 | 95ab4de9 | David Knowles |
|
665 | 95ab4de9 | David Knowles | """
|
666 | 95ab4de9 | David Knowles | if role not in VALID_NODE_ROLES: |
667 | 95ab4de9 | David Knowles | raise InvalidNodeRole("%s is not a valid node role.", role) |
668 | 95ab4de9 | David Knowles | |
669 | 95ab4de9 | David Knowles | query = [("force", force)]
|
670 | 95ab4de9 | David Knowles | return self._SendRequest(HTTP_PUT, "/nodes/%s/role" % node, query, |
671 | 95ab4de9 | David Knowles | content=role) |
672 | 95ab4de9 | David Knowles | |
673 | 95ab4de9 | David Knowles | def GetNodeStorageUnits(self, node, storage_type, output_fields): |
674 | 95ab4de9 | David Knowles | """Gets the storage units for a node.
|
675 | 95ab4de9 | David Knowles |
|
676 | 95ab4de9 | David Knowles | @type node: str
|
677 | 95ab4de9 | David Knowles | @param node: the node whose storage units to return
|
678 | 95ab4de9 | David Knowles | @type storage_type: str
|
679 | 95ab4de9 | David Knowles | @param storage_type: storage type whose units to return
|
680 | 95ab4de9 | David Knowles | @type output_fields: str
|
681 | 95ab4de9 | David Knowles | @param output_fields: storage type fields to return
|
682 | 95ab4de9 | David Knowles |
|
683 | 95ab4de9 | David Knowles | @rtype: int
|
684 | 95ab4de9 | David Knowles | @return: job id where results can be retrieved
|
685 | 95ab4de9 | David Knowles |
|
686 | 95ab4de9 | David Knowles | @raise InvalidStorageType: If an invalid storage type is specified
|
687 | 95ab4de9 | David Knowles |
|
688 | 95ab4de9 | David Knowles | """
|
689 | 95ab4de9 | David Knowles | # TODO: Add default for storage_type & output_fields
|
690 | 95ab4de9 | David Knowles | if storage_type not in VALID_STORAGE_TYPES: |
691 | 95ab4de9 | David Knowles | raise InvalidStorageType("%s is an invalid storage type.", storage_type) |
692 | 95ab4de9 | David Knowles | |
693 | 95ab4de9 | David Knowles | query = [("storage_type", storage_type), ("output_fields", output_fields)] |
694 | 95ab4de9 | David Knowles | return self._SendRequest(HTTP_GET, "/nodes/%s/storage" % node, query) |
695 | 95ab4de9 | David Knowles | |
696 | 95ab4de9 | David Knowles | def ModifyNodeStorageUnits(self, node, storage_type, name, allocatable=True): |
697 | 95ab4de9 | David Knowles | """Modifies parameters of storage units on the node.
|
698 | 95ab4de9 | David Knowles |
|
699 | 95ab4de9 | David Knowles | @type node: str
|
700 | 95ab4de9 | David Knowles | @param node: node whose storage units to modify
|
701 | 95ab4de9 | David Knowles | @type storage_type: str
|
702 | 95ab4de9 | David Knowles | @param storage_type: storage type whose units to modify
|
703 | 95ab4de9 | David Knowles | @type name: str
|
704 | 95ab4de9 | David Knowles | @param name: name of the storage unit
|
705 | 95ab4de9 | David Knowles | @type allocatable: bool
|
706 | 95ab4de9 | David Knowles | @param allocatable: TODO: Document me
|
707 | 95ab4de9 | David Knowles |
|
708 | 95ab4de9 | David Knowles | @rtype: int
|
709 | 95ab4de9 | David Knowles | @return: job id
|
710 | 95ab4de9 | David Knowles |
|
711 | 95ab4de9 | David Knowles | @raise InvalidStorageType: If an invalid storage type is specified
|
712 | 95ab4de9 | David Knowles |
|
713 | 95ab4de9 | David Knowles | """
|
714 | 95ab4de9 | David Knowles | if storage_type not in VALID_STORAGE_TYPES: |
715 | 95ab4de9 | David Knowles | raise InvalidStorageType("%s is an invalid storage type.", storage_type) |
716 | 95ab4de9 | David Knowles | |
717 | 95ab4de9 | David Knowles | query = [ |
718 | 95ab4de9 | David Knowles | ("storage_type", storage_type), ("name", name), |
719 | 95ab4de9 | David Knowles | ("allocatable", allocatable)
|
720 | 95ab4de9 | David Knowles | ] |
721 | 95ab4de9 | David Knowles | return self._SendRequest(HTTP_PUT, "/nodes/%s/storage/modify" % node, query) |
722 | 95ab4de9 | David Knowles | |
723 | 95ab4de9 | David Knowles | def RepairNodeStorageUnits(self, node, storage_type, name): |
724 | 95ab4de9 | David Knowles | """Repairs a storage unit on the node.
|
725 | 95ab4de9 | David Knowles |
|
726 | 95ab4de9 | David Knowles | @type node: str
|
727 | 95ab4de9 | David Knowles | @param node: node whose storage units to repair
|
728 | 95ab4de9 | David Knowles | @type storage_type: str
|
729 | 95ab4de9 | David Knowles | @param storage_type: storage type to repair
|
730 | 95ab4de9 | David Knowles | @type name: str
|
731 | 95ab4de9 | David Knowles | @param name: name of the storage unit to repair
|
732 | 95ab4de9 | David Knowles |
|
733 | 95ab4de9 | David Knowles | @rtype: int
|
734 | 95ab4de9 | David Knowles | @return: job id
|
735 | 95ab4de9 | David Knowles |
|
736 | 95ab4de9 | David Knowles | @raise InvalidStorageType: If an invalid storage type is specified
|
737 | 95ab4de9 | David Knowles |
|
738 | 95ab4de9 | David Knowles | """
|
739 | 95ab4de9 | David Knowles | if storage_type not in VALID_STORAGE_TYPES: |
740 | 95ab4de9 | David Knowles | raise InvalidStorageType("%s is an invalid storage type.", storage_type) |
741 | 95ab4de9 | David Knowles | |
742 | 95ab4de9 | David Knowles | query = [("storage_type", storage_type), ("name", name)] |
743 | 95ab4de9 | David Knowles | return self._SendRequest(HTTP_PUT, "/nodes/%s/storage/repair" % node, query) |
744 | 95ab4de9 | David Knowles | |
745 | 95ab4de9 | David Knowles | def GetNodeTags(self, node): |
746 | 95ab4de9 | David Knowles | """Gets the tags for a node.
|
747 | 95ab4de9 | David Knowles |
|
748 | 95ab4de9 | David Knowles | @type node: str
|
749 | 95ab4de9 | David Knowles | @param node: node whose tags to return
|
750 | 95ab4de9 | David Knowles |
|
751 | 95ab4de9 | David Knowles | @rtype: list of str
|
752 | 95ab4de9 | David Knowles | @return: tags for the node
|
753 | 95ab4de9 | David Knowles |
|
754 | 95ab4de9 | David Knowles | """
|
755 | 95ab4de9 | David Knowles | return self._SendRequest(HTTP_GET, "/nodes/%s/tags" % node) |
756 | 95ab4de9 | David Knowles | |
757 | 95ab4de9 | David Knowles | def AddNodeTags(self, node, tags, dry_run=False): |
758 | 95ab4de9 | David Knowles | """Adds tags to a node.
|
759 | 95ab4de9 | David Knowles |
|
760 | 95ab4de9 | David Knowles | @type node: str
|
761 | 95ab4de9 | David Knowles | @param node: node to add tags to
|
762 | 95ab4de9 | David Knowles | @type tags: list of str
|
763 | 95ab4de9 | David Knowles | @param tags: tags to add to the node
|
764 | 95ab4de9 | David Knowles | @type dry_run: bool
|
765 | 95ab4de9 | David Knowles | @param dry_run: whether to perform a dry run
|
766 | 95ab4de9 | David Knowles |
|
767 | 95ab4de9 | David Knowles | @rtype: int
|
768 | 95ab4de9 | David Knowles | @return: job id
|
769 | 95ab4de9 | David Knowles |
|
770 | 95ab4de9 | David Knowles | """
|
771 | 95ab4de9 | David Knowles | query = [("tag", t) for t in tags] |
772 | 95ab4de9 | David Knowles | if dry_run:
|
773 | 95ab4de9 | David Knowles | query.append(("dry-run", 1)) |
774 | 95ab4de9 | David Knowles | |
775 | 95ab4de9 | David Knowles | return self._SendRequest(HTTP_PUT, "/nodes/%s/tags" % node, query, |
776 | 95ab4de9 | David Knowles | content=tags) |
777 | 95ab4de9 | David Knowles | |
778 | 95ab4de9 | David Knowles | def DeleteNodeTags(self, node, tags, dry_run=False): |
779 | 95ab4de9 | David Knowles | """Delete tags from a node.
|
780 | 95ab4de9 | David Knowles |
|
781 | 95ab4de9 | David Knowles | @type node: str
|
782 | 95ab4de9 | David Knowles | @param node: node to remove tags from
|
783 | 95ab4de9 | David Knowles | @type tags: list of str
|
784 | 95ab4de9 | David Knowles | @param tags: tags to remove from the node
|
785 | 95ab4de9 | David Knowles | @type dry_run: bool
|
786 | 95ab4de9 | David Knowles | @param dry_run: whether to perform a dry run
|
787 | 95ab4de9 | David Knowles |
|
788 | 95ab4de9 | David Knowles | @rtype: int
|
789 | 95ab4de9 | David Knowles | @return: job id
|
790 | 95ab4de9 | David Knowles |
|
791 | 95ab4de9 | David Knowles | """
|
792 | 95ab4de9 | David Knowles | query = [("tag", t) for t in tags] |
793 | 95ab4de9 | David Knowles | if dry_run:
|
794 | 95ab4de9 | David Knowles | query.append(("dry-run", 1)) |
795 | 95ab4de9 | David Knowles | |
796 | 95ab4de9 | David Knowles | return self._SendRequest(HTTP_DELETE, "/nodes/%s/tags" % node, query) |
797 | 95ab4de9 | David Knowles | |
798 | 95ab4de9 | David Knowles | |
799 | 95ab4de9 | David Knowles | class HTTPSConnectionOpenSSL(httplib.HTTPSConnection): |
800 | 95ab4de9 | David Knowles | """HTTPS Connection handler that verifies the SSL certificate.
|
801 | 95ab4de9 | David Knowles |
|
802 | 95ab4de9 | David Knowles | """
|
803 | 95ab4de9 | David Knowles | |
804 | 95ab4de9 | David Knowles | # pylint: disable-msg=W0142
|
805 | 95ab4de9 | David Knowles | def __init__(self, *args, **kwargs): |
806 | 95ab4de9 | David Knowles | """Constructor.
|
807 | 95ab4de9 | David Knowles |
|
808 | 95ab4de9 | David Knowles | """
|
809 | 95ab4de9 | David Knowles | httplib.HTTPSConnection.__init__(self, *args, **kwargs)
|
810 | 95ab4de9 | David Knowles | |
811 | 95ab4de9 | David Knowles | self._ssl_cert = None |
812 | 95ab4de9 | David Knowles | if self.cert_file: |
813 | 95ab4de9 | David Knowles | f = open(self.cert_file, "r") |
814 | 95ab4de9 | David Knowles | self._ssl_cert = crypto.load_certificate(crypto.FILETYPE_PEM, f.read())
|
815 | 95ab4de9 | David Knowles | f.close() |
816 | 95ab4de9 | David Knowles | |
817 | 95ab4de9 | David Knowles | # pylint: disable-msg=W0613
|
818 | 95ab4de9 | David Knowles | def _VerifySSLCertCallback(self, conn, cert, errnum, errdepth, ok): |
819 | 95ab4de9 | David Knowles | """Verifies the SSL certificate provided by the peer.
|
820 | 95ab4de9 | David Knowles |
|
821 | 95ab4de9 | David Knowles | """
|
822 | 95ab4de9 | David Knowles | return (self._ssl_cert.digest("sha1") == cert.digest("sha1") and |
823 | 95ab4de9 | David Knowles | self._ssl_cert.digest("md5") == cert.digest("md5")) |
824 | 95ab4de9 | David Knowles | |
825 | 95ab4de9 | David Knowles | def connect(self): |
826 | 95ab4de9 | David Knowles | """Connect to the server specified when the object was created.
|
827 | 95ab4de9 | David Knowles |
|
828 | 95ab4de9 | David Knowles | This ensures that SSL certificates are verified.
|
829 | 95ab4de9 | David Knowles |
|
830 | 95ab4de9 | David Knowles | """
|
831 | 95ab4de9 | David Knowles | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
832 | 95ab4de9 | David Knowles | ctx = SSL.Context(SSL.SSLv23_METHOD) |
833 | 95ab4de9 | David Knowles | ctx.set_options(SSL.OP_NO_SSLv2) |
834 | 95ab4de9 | David Knowles | ctx.use_certificate(self._ssl_cert)
|
835 | 95ab4de9 | David Knowles | ctx.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT, |
836 | 95ab4de9 | David Knowles | self._VerifySSLCertCallback)
|
837 | 95ab4de9 | David Knowles | |
838 | 95ab4de9 | David Knowles | ssl = SSL.Connection(ctx, sock) |
839 | 95ab4de9 | David Knowles | ssl.connect((self.host, self.port)) |
840 | 95ab4de9 | David Knowles | self.sock = httplib.FakeSocket(sock, ssl)
|
841 | 95ab4de9 | David Knowles | |
842 | 95ab4de9 | David Knowles | |
843 | 95ab4de9 | David Knowles | def _VerifyCertificate(hostname, port, cert_file): |
844 | 95ab4de9 | David Knowles | """Verifies the SSL certificate for the given host/port.
|
845 | 95ab4de9 | David Knowles |
|
846 | 95ab4de9 | David Knowles | @type hostname: str
|
847 | 95ab4de9 | David Knowles | @param hostname: the ganeti cluster master whose certificate to verify
|
848 | 95ab4de9 | David Knowles | @type port: int
|
849 | 95ab4de9 | David Knowles | @param port: the port on which the RAPI is running
|
850 | 95ab4de9 | David Knowles | @type cert_file: str
|
851 | 95ab4de9 | David Knowles | @param cert_file: filename of the expected SSL certificate
|
852 | 95ab4de9 | David Knowles |
|
853 | 95ab4de9 | David Knowles | @raises CertificateError: If an invalid SSL certificate is found
|
854 | 95ab4de9 | David Knowles |
|
855 | 95ab4de9 | David Knowles | """
|
856 | 95ab4de9 | David Knowles | https = HTTPSConnectionOpenSSL(hostname, port, cert_file=cert_file) |
857 | 95ab4de9 | David Knowles | try:
|
858 | 95ab4de9 | David Knowles | try:
|
859 | 95ab4de9 | David Knowles | https.request(HTTP_GET, "/version")
|
860 | 95ab4de9 | David Knowles | except (crypto.Error, SSL.Error):
|
861 | 95ab4de9 | David Knowles | raise CertificateError("Invalid SSL certificate.") |
862 | 95ab4de9 | David Knowles | finally:
|
863 | 95ab4de9 | David Knowles | https.close() |