1 #!@PYTHON@ -tt
2
3 import atexit
4 import logging
5 import sys
6 import os
7
8 import urllib3
9
10 sys.path.append("@FENCEAGENTSLIBDIR@")
11 from fencing import *
12 from fencing import fail_usage, run_delay, source_env
13
14 try:
15 from novaclient import client
16 from novaclient.exceptions import Conflict, NotFound
17 except ImportError:
18 pass
19
20 urllib3.disable_warnings(urllib3.exceptions.SecurityWarning)
21
22
23 def translate_status(instance_status):
24 if instance_status == "ACTIVE":
25 return "on"
26 elif instance_status == "SHUTOFF":
27 return "off"
28 return "unknown"
29
30 def get_cloud(options):
31 import yaml
32
33 clouds_yaml = "~/.config/openstack/clouds.yaml"
34 if not os.path.exists(os.path.expanduser(clouds_yaml)):
35 clouds_yaml = "/etc/openstack/clouds.yaml"
36 if not os.path.exists(os.path.expanduser(clouds_yaml)):
37 fail_usage("Failed: ~/.config/openstack/clouds.yaml and /etc/openstack/clouds.yaml does not exist")
38
39 clouds_yaml = os.path.expanduser(clouds_yaml)
40 if os.path.exists(clouds_yaml):
41 with open(clouds_yaml, "r") as yaml_stream:
42 try:
43 clouds = yaml.safe_load(yaml_stream)
44 except yaml.YAMLError as exc:
45 fail_usage("Failed: Unable to read: " + clouds_yaml)
46
47 cloud = clouds.get("clouds").get(options["--cloud"])
48 if not cloud:
49 fail_usage("Cloud: {} not found.".format(options["--cloud"]))
50
51 return cloud
52
53
54 def get_nodes_list(conn, options):
55 logging.info("Running %s action", options.get("--original-action", options.get("--action")))
56 result = {}
57 search_opts = {}
58 max_results = 1 if options.get("--original-action") == "monitor" else None
59
60 if "--plug" in options:
61 search_opts["uuid"] = options["--plug"]
62
63 try:
64 response = conn.servers.list(detailed=True, search_opts=search_opts, limit=max_results)
65 if response is not None:
66 for item in response:
67 instance_id = item.id
68 instance_name = item.name
69 instance_status = item.status
70 result[instance_id] = (instance_name, translate_status(instance_status))
71 except Exception as e:
72 logging.error("Failed to retrieve node list: %s", e)
73 return result
74
75
76 def get_power_status(conn, options):
77 logging.info("Running %s action on %s", options["--action"], options["--plug"])
78 server = None
79 try:
|
(1) Event path: |
Falling through to end of try statement. |
80 server = conn.servers.get(options["--plug"])
81 except NotFound as e:
82 fail_usage("Failed: Not Found: " + str(e))
|
(2) Event path: |
Condition "server === None", taking true branch. |
|
(3) Event null_check: |
Comparing "server" to a null-like value implies that "server" might be null-like. |
| Also see events: |
[property_access] |
83 if server is None:
84 fail_usage("Server %s not found", options["--plug"])
|
CID (unavailable; MK=b0077c5747be6ffbad90067b77ab6eac) (#1 of 1): Bad use of null-like value (FORWARD_NULL): |
|
(4) Event property_access: |
Accessing a property of null-like value "server". |
| Also see events: |
[null_check] |
85 state = server.status
86 status = translate_status(state)
87 logging.info("get_power_status: %s (state: %s)" % (status, state))
88 return status
89
90
91 def set_power_status(conn, options):
92 logging.info("Running %s action on %s", options["--action"], options["--plug"])
93 action = options["--action"]
94 server = None
95 try:
96 server = conn.servers.get(options["--plug"])
97 except NotFound as e:
98 fail_usage("Failed: Not Found: " + str(e))
99 if server is None:
100 fail_usage("Server %s not found", options["--plug"])
101 if action == "on":
102 logging.info("Starting instance " + server.name)
103 try:
104 server.start()
105 except Conflict as e:
106 fail_usage(e)
107 logging.info("Called start API call for " + server.id)
108 if action == "off":
109 logging.info("Stopping instance " + server.name)
110 try:
111 server.stop()
112 except Conflict as e:
113 fail_usage(e)
114 logging.info("Called stop API call for " + server.id)
115 if action == "reboot":
116 logging.info("Rebooting instance " + server.name)
117 try:
118 server.reboot("HARD")
119 except Conflict as e:
120 fail_usage(e)
121 logging.info("Called reboot hard API call for " + server.id)
122
123
124 def nova_login(username, password, projectname, auth_url, user_domain_name,
125 project_domain_name, ssl_insecure, cacert, apitimeout,
126 auth_plugin="password", auth_options=None):
127 legacy_import = False
128
129 try:
130 from keystoneauth1 import loading
131 from keystoneauth1 import session as ksc_session
132 from keystoneauth1.exceptions.discovery import DiscoveryFailure
133 from keystoneauth1.exceptions.http import Unauthorized
134 except ImportError:
135 try:
136 from keystoneclient import session as ksc_session
137 from keystoneclient.auth.identity import v3
138
139 legacy_import = True
140 except ImportError:
141 fail_usage("Failed: Keystone client not found or not accessible")
142
143 if not legacy_import:
144 loader = loading.get_plugin_loader(auth_plugin)
145 if auth_options is None:
146 auth_options = dict(
147 auth_url=auth_url,
148 username=username,
149 password=password,
150 project_name=projectname,
151 user_domain_name=user_domain_name,
152 project_domain_name=project_domain_name,
153 )
154 auth = loader.load_from_options(**auth_options)
155 else:
156 if auth_plugin != "password":
157 fail_usage("Failed: Keystone auth plugins require keystoneauth1")
158 auth = v3.Password(
159 auth_url=auth_url,
160 username=username,
161 password=password,
162 project_name=projectname,
163 user_domain_name=user_domain_name,
164 project_domain_name=project_domain_name,
165 cacert=cacert,
166 )
167
168 caverify=True
169 if ssl_insecure:
170 caverify=False
171 elif cacert:
172 caverify=cacert
173
174 session = ksc_session.Session(auth=auth, verify=caverify, timeout=apitimeout)
175 nova = client.Client("2", session=session, timeout=apitimeout)
176 apiversion = None
177 try:
178 apiversion = nova.versions.get_current()
179 except DiscoveryFailure as e:
180 fail_usage("Failed: Discovery Failure: " + str(e))
181 except Unauthorized as e:
182 fail_usage("Failed: Unauthorized: " + str(e))
183 except Exception as e:
184 logging.error(e)
185 logging.debug("Nova version: %s", apiversion)
186 return nova
187
188
189 def define_new_opts():
190 all_opt["auth-url"] = {
191 "getopt": ":",
192 "longopt": "auth-url",
193 "help": "--auth-url=[authurl] Keystone Auth URL",
194 "required": "0",
195 "shortdesc": "Keystone Auth URL",
196 "order": 2,
197 }
198 all_opt["project-name"] = {
199 "getopt": ":",
200 "longopt": "project-name",
201 "help": "--project-name=[project] Tenant Or Project Name",
202 "required": "0",
203 "shortdesc": "Keystone Project",
204 "default": "admin",
205 "order": 3,
206 }
207 all_opt["user-domain-name"] = {
208 "getopt": ":",
209 "longopt": "user-domain-name",
210 "help": "--user-domain-name=[domain] Keystone User Domain Name",
211 "required": "0",
212 "shortdesc": "Keystone User Domain Name",
213 "default": "Default",
214 "order": 4,
215 }
216 all_opt["project-domain-name"] = {
217 "getopt": ":",
218 "longopt": "project-domain-name",
219 "help": "--project-domain-name=[domain] Keystone Project Domain Name",
220 "required": "0",
221 "shortdesc": "Keystone Project Domain Name",
222 "default": "Default",
223 "order": 5,
224 }
225 all_opt["cloud"] = {
226 "getopt": ":",
227 "longopt": "cloud",
228 "help": "--cloud=[cloud] Openstack cloud (from ~/.config/openstack/clouds.yaml or /etc/openstack/clouds.yaml).",
229 "required": "0",
230 "shortdesc": "Cloud from clouds.yaml",
231 "order": 6,
232 }
233 all_opt["openrc"] = {
234 "getopt": ":",
235 "longopt": "openrc",
236 "help": "--openrc=[openrc] Path to the openrc config file",
237 "required": "0",
238 "shortdesc": "openrc config file",
239 "order": 7,
240 }
241 all_opt["uuid"] = {
242 "getopt": ":",
243 "longopt": "uuid",
244 "help": "--uuid=[uuid] Replaced by -n, --plug",
245 "required": "0",
246 "shortdesc": "Replaced by port/-n/--plug",
247 "order": 8,
248 }
249 all_opt["cacert"] = {
250 "getopt": ":",
251 "longopt": "cacert",
252 "help": "--cacert=[cacert] Path to the PEM file with trusted authority certificates (override global CA trust)",
253 "required": "0",
254 "shortdesc": "SSL X.509 certificates file",
255 "default": "",
256 "order": 9,
257 }
258 all_opt["apitimeout"] = {
259 "getopt": ":",
260 "type": "second",
261 "longopt": "apitimeout",
262 "help": "--apitimeout=[seconds] Timeout to use for API calls",
263 "shortdesc": "Timeout in seconds to use for API calls, default is 60.",
264 "required": "0",
265 "default": 60,
266 "order": 10,
267 }
268 all_opt["auth-plugin"] = {
269 "getopt": ":",
270 "longopt": "auth-plugin",
271 "help": "--auth-plugin=[plugin] Keystone auth plugin",
272 "required": "0",
273 "shortdesc": "Keystone auth plugin",
274 "default": "password",
275 "order": 11,
276 }
277
278
279 def main():
280 conn = None
281
282 device_opt = [
283 "login",
284 "no_login",
285 "passwd",
286 "no_password",
287 "auth-url",
288 "project-name",
289 "user-domain-name",
290 "project-domain-name",
291 "auth-plugin",
292 "cloud",
293 "openrc",
294 "port",
295 "no_port",
296 "uuid",
297 "ssl_insecure",
298 "cacert",
299 "apitimeout",
300 ]
301
302 atexit.register(atexit_handler)
303
304 define_new_opts()
305
306 all_opt["port"]["required"] = "0"
307 all_opt["port"]["help"] = "-n, --plug=[UUID] UUID of the node to be fenced"
308 all_opt["port"]["shortdesc"] = "UUID of the node to be fenced."
309 all_opt["power_timeout"]["default"] = "60"
310
311 options = check_input(device_opt, process_input(device_opt))
312
313 # workaround to avoid regressions
314 if "--uuid" in options:
315 options["--plug"] = options["--uuid"]
316 del options["--uuid"]
317 elif ("--help" not in options
318 and options["--action"] in ["off", "on", "reboot", "status", "validate-all"]
319 and "--plug" not in options):
320 stop_after_error = False if options["--action"] == "validate-all" else True
321 fail_usage(
322 "Failed: You have to enter plug number or machine identification",
323 stop_after_error,
324 )
325
326 docs = {}
327 docs["shortdesc"] = "Fence agent for OpenStack's Nova service"
328 docs["longdesc"] = "fence_openstack is a Power Fencing agent \
329 which can be used with machines controlled by the Openstack's Nova service. \
330 This agent calls the python-novaclient and it is mandatory to be installed "
331 docs["vendorurl"] = "https://wiki.openstack.org/wiki/Nova"
332 show_docs(options, docs)
333
334 run_delay(options)
335
336 auth_options = None
337 if options.get("--cloud"):
338 cloud = get_cloud(options)
339 if options["--auth-plugin"] == "password":
340 options["--auth-plugin"] = cloud.get("auth_type") or options["--auth-plugin"]
341 if options["--auth-plugin"] != "password":
342 auth_options = cloud.get("auth")
343 username = cloud.get("auth").get("username")
344 password = cloud.get("auth").get("password")
345 projectname = cloud.get("auth").get("project_name")
346 auth_url = None
347 try:
348 auth_url = cloud.get("auth").get("auth_url")
349 except KeyError:
350 fail_usage("Failed: You have to set the Keystone service endpoint for authorization")
351 user_domain_name = cloud.get("auth").get("user_domain_name")
352 project_domain_name = cloud.get("auth").get("project_domain_name")
353 caverify = cloud.get("verify")
354 if caverify in [True, False]:
355 options["--ssl-insecure"] = caverify
356 else:
357 options["--cacert"] = caverify
358 elif options.get("--openrc"):
359 if not os.path.exists(os.path.expanduser(options["--openrc"])):
360 fail_usage("Failed: {} does not exist".format(options.get("--openrc")))
361 source_env(options["--openrc"])
362 env = os.environ
363 username = env.get("OS_USERNAME")
364 password = env.get("OS_PASSWORD")
365 projectname = env.get("OS_PROJECT_NAME")
366 auth_url = None
367 try:
368 auth_url = env["OS_AUTH_URL"]
369 except KeyError:
370 fail_usage("Failed: You have to set the Keystone service endpoint for authorization")
371 user_domain_name = env.get("OS_USER_DOMAIN_NAME")
372 project_domain_name = env.get("OS_PROJECT_DOMAIN_NAME")
373 else:
374 username = options["--username"]
375 password = options["--password"]
376 projectname = options["--project-name"]
377 auth_url = None
378 try:
379 auth_url = options["--auth-url"]
380 except KeyError:
381 fail_usage("Failed: You have to set the Keystone service endpoint for authorization")
382 user_domain_name = options["--user-domain-name"]
383 project_domain_name = options["--project-domain-name"]
384
385 ssl_insecure = "--ssl-insecure" in options
386 cacert = options["--cacert"]
387 apitimeout = options["--apitimeout"]
388
389 try:
390 conn = nova_login(
391 username,
392 password,
393 projectname,
394 auth_url,
395 user_domain_name,
396 project_domain_name,
397 ssl_insecure,
398 cacert,
399 apitimeout,
400 options["--auth-plugin"],
401 auth_options,
402 )
403 except Exception as e:
404 fail_usage("Failed: Unable to connect to Nova: " + str(e))
405
406 # Operate the fencing device
407 result = fence_action(conn, options, set_power_status, get_power_status, get_nodes_list)
408 sys.exit(result)
409
410
411 if __name__ == "__main__":
412 main()
413