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:
(1) Event try_fallthrough: Falling through to end of try statement.
71   	        server = conn.servers.get(options["--plug"])
72   	    except NotFound as e:
73   	        fail_usage("Failed: Not Found: " + str(e))
(2) Event cond_true: 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]
74   	    if server is None:
75   	        fail_usage("Server %s not found", options["--plug"])
(4) Event property_access: Accessing a property of null-like value "server".
Also see events: [null_check]
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