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