Allow getting service via any endpoint
[functest.git] / functest / utils / functest_utils.py
1 #!/usr/bin/env python
2 #
3 # jose.lausuch@ericsson.com
4 # valentin.boucher@orange.com
5 # All rights reserved. This program and the accompanying materials
6 # are made available under the terms of the Apache License, Version 2.0
7 # which accompanies this distribution, and is available at
8 # http://www.apache.org/licenses/LICENSE-2.0
9
10 # pylint: disable=missing-docstring
11
12 from __future__ import print_function
13 import logging
14 import os
15 import subprocess
16 import sys
17 import yaml
18
19 from shade import _utils
20 import six
21
22 LOGGER = logging.getLogger(__name__)
23
24
25 def execute_command_raise(cmd, info=False, error_msg="",
26                           verbose=True, output_file=None):
27     ret = execute_command(cmd, info, error_msg, verbose, output_file)
28     if ret != 0:
29         raise Exception(error_msg)
30
31
32 def execute_command(cmd, info=False, error_msg="",
33                     verbose=True, output_file=None):
34     if not error_msg:
35         error_msg = ("The command '%s' failed." % cmd)
36     msg_exec = ("Executing command: '%s'" % cmd)
37     if verbose:
38         if info:
39             LOGGER.info(msg_exec)
40         else:
41             LOGGER.debug(msg_exec)
42     popen = subprocess.Popen(
43         cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
44     if output_file:
45         ofd = open(output_file, "w")
46     for line in iter(popen.stdout.readline, b''):
47         if output_file:
48             ofd.write(line.decode("utf-8"))
49         else:
50             line = line.decode("utf-8").replace('\n', '')
51             print(line)
52             sys.stdout.flush()
53     if output_file:
54         ofd.close()
55     popen.stdout.close()
56     returncode = popen.wait()
57     if returncode != 0:
58         if verbose:
59             LOGGER.error(error_msg)
60
61     return returncode
62
63
64 def get_parameter_from_yaml(parameter, yfile):
65     """
66     Returns the value of a given parameter in file.yaml
67     parameter must be given in string format with dots
68     Example: general.openstack.image_name
69     """
70     with open(yfile) as yfd:
71         file_yaml = yaml.safe_load(yfd)
72     value = file_yaml
73     for element in parameter.split("."):
74         value = value.get(element)
75         if value is None:
76             raise ValueError("The parameter %s is not defined in"
77                              " %s" % (parameter, yfile))
78     return value
79
80
81 def get_nova_version(cloud):
82     """ Get Nova API microversion
83
84     Returns:
85
86     - Nova API microversion
87     - None on operation error
88     """
89     # pylint: disable=protected-access
90     try:
91         request = cloud._compute_client.request("/", "GET")
92         LOGGER.debug('cloud._compute_client.request: %s', request)
93         version = request["version"]["version"]
94         major, minor = version.split('.')
95         LOGGER.debug('nova version: %s', (int(major), int(minor)))
96         return (int(major), int(minor))
97     except Exception:  # pylint: disable=broad-except
98         LOGGER.exception("Cannot detect Nova version")
99         return None
100
101
102 def get_openstack_version(cloud):
103     """ Detect OpenStack version via Nova API microversion
104
105     It follows `MicroversionHistory
106     <https://docs.openstack.org/nova/latest/reference/api-microversion-history.html>`_.
107
108     Returns:
109
110     - OpenStack release
111     - Unknown on operation error
112     """
113     version = get_nova_version(cloud)
114     try:
115         assert version
116         if version > (2, 72):
117             osversion = "Master"
118         elif version > (2, 65):
119             osversion = "Stein"
120         elif version > (2, 60):
121             osversion = "Rocky"
122         elif version > (2, 53):
123             osversion = "Queens"
124         elif version > (2, 42):
125             osversion = "Pike"
126         elif version > (2, 38):
127             osversion = "Ocata"
128         elif version > (2, 25):
129             osversion = "Newton"
130         elif version > (2, 12):
131             osversion = "Mitaka"
132         elif version > (2, 3):
133             osversion = "Liberty"
134         elif version >= (2, 1):
135             osversion = "Kilo"
136         else:
137             osversion = "Unknown"
138         LOGGER.info('Detect OpenStack version: %s', osversion)
139         return osversion
140     except AssertionError:
141         LOGGER.exception("Cannot detect OpenStack version")
142         return "Unknown"
143
144
145 def list_services(cloud):
146     # pylint: disable=protected-access
147     """Search Keystone services via $OS_INTERFACE.
148
149     It mainly conforms with `Shade
150     <latest/reference/api-microversion-history.html>`_ but allows testing vs
151     public endpoints. It's worth mentioning that it doesn't support keystone
152     v2.
153
154     :returns: a list of ``munch.Munch`` containing the services description
155
156     :raises: ``OpenStackCloudException`` if something goes wrong during the
157         openstack API call.
158     """
159     url, key = '/services', 'services'
160     data = cloud._identity_client.get(
161         url, endpoint_filter={
162             'interface': os.environ.get('OS_INTERFACE', 'public')},
163         error_message="Failed to list services")
164     services = cloud._get_and_munchify(key, data)
165     return _utils.normalize_keystone_services(services)
166
167
168 def search_services(cloud, name_or_id=None, filters=None):
169     # pylint: disable=protected-access
170     """Search Keystone services ia $OS_INTERFACE.
171
172     It mainly conforms with `Shade
173     <latest/reference/api-microversion-history.html>`_ but allows testing vs
174     public endpoints. It's worth mentioning that it doesn't support keystone
175     v2.
176
177     :param name_or_id: Name or id of the desired service.
178     :param filters: a dict containing additional filters to use. e.g.
179                     {'type': 'network'}.
180
181     :returns: a list of ``munch.Munch`` containing the services description
182
183     :raises: ``OpenStackCloudException`` if something goes wrong during the
184         openstack API call.
185     """
186     services = list_services(cloud)
187     return _utils._filter_list(services, name_or_id, filters)
188
189
190 def convert_dict_to_ini(value):
191     "Convert dict to oslo.conf input"
192     assert isinstance(value, dict)
193     return ",".join("{}:{}".format(
194         key, val) for (key, val) in six.iteritems(value))
195
196
197 def convert_list_to_ini(value):
198     "Convert list to oslo.conf input"
199     assert isinstance(value, list)
200     return ",".join("{}".format(val) for val in value)
201
202
203 def convert_ini_to_dict(value):
204     "Convert oslo.conf input to dict"
205     assert isinstance(value, str)
206     try:
207         return {k: v for k, v in (x.rsplit(':', 1) for x in value.split(','))}
208     except ValueError:
209         return {}
210
211
212 def convert_ini_to_list(value):
213     "Convert list to oslo.conf input"
214     assert isinstance(value, str)
215     if not value:
216         return []
217     return [x for x in value.split(',')]