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