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 path: 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 path: 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 path: 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   	
CID (unavailable; MK=326f7519547b80f5a260a97c1d2036fa) (#1 of 1): Bad use of null-like value (FORWARD_NULL):
(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.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:
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  	
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