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