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 true branch.
99   	    if action == "off":
CID (unavailable; MK=4d8ecdbf693f8f673f4aa5288da936e7) (#2 of 3): Bad use of null-like value (FORWARD_NULL):
(6) Event property_access: Accessing a property of null-like value "server".
Also see events: [null_check]
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  	               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