1# Copyright 2011, VMware, Inc. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); you may 4# not use this file except in compliance with the License. You may obtain 5# a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# License for the specific language governing permissions and limitations 13# under the License. 14# 15# Borrowed from nova code base, more utilities will be added/borrowed as and 16# when needed. 17 18"""Utilities and helper functions.""" 19 20import argparse 21import functools 22import hashlib 23import logging 24import os 25 26from oslo_utils import encodeutils 27from oslo_utils import importutils 28 29from neutronclient._i18n import _ 30from neutronclient.common import exceptions 31 32SENSITIVE_HEADERS = ('X-Auth-Token',) 33 34 35def env(*vars, **kwargs): 36 """Returns the first environment variable set. 37 38 If none are non-empty, defaults to '' or keyword arg default. 39 """ 40 for v in vars: 41 value = os.environ.get(v) 42 if value: 43 return value 44 return kwargs.get('default', '') 45 46 47def convert_to_uppercase(string): 48 return string.upper() 49 50 51def convert_to_lowercase(string): 52 return string.lower() 53 54 55def get_client_class(api_name, version, version_map): 56 """Returns the client class for the requested API version. 57 58 :param api_name: the name of the API, e.g. 'compute', 'image', etc 59 :param version: the requested API version 60 :param version_map: a dict of client classes keyed by version 61 :rtype: a client class for the requested API version 62 """ 63 try: 64 client_path = version_map[str(version)] 65 except (KeyError, ValueError): 66 msg = _("Invalid %(api_name)s client version '%(version)s'. must be " 67 "one of: %(map_keys)s") 68 msg = msg % {'api_name': api_name, 'version': version, 69 'map_keys': ', '.join(version_map.keys())} 70 raise exceptions.UnsupportedVersion(msg) 71 72 return importutils.import_class(client_path) 73 74 75def get_item_properties(item, fields, mixed_case_fields=(), formatters=None): 76 """Return a tuple containing the item properties. 77 78 :param item: a single item resource (e.g. Server, Tenant, etc) 79 :param fields: tuple of strings with the desired field names 80 :param mixed_case_fields: tuple of field names to preserve case 81 :param formatters: dictionary mapping field names to callables 82 to format the values 83 """ 84 if formatters is None: 85 formatters = {} 86 87 row = [] 88 89 for field in fields: 90 if field in formatters: 91 row.append(formatters[field](item)) 92 else: 93 if field in mixed_case_fields: 94 field_name = field.replace(' ', '_') 95 else: 96 field_name = field.lower().replace(' ', '_') 97 if not hasattr(item, field_name) and isinstance(item, dict): 98 data = item[field_name] 99 else: 100 data = getattr(item, field_name, '') 101 if data is None: 102 data = '' 103 row.append(data) 104 return tuple(row) 105 106 107def str2bool(strbool): 108 if strbool is None: 109 return None 110 return strbool.lower() == 'true' 111 112 113def str2dict(strdict, required_keys=None, optional_keys=None): 114 """Convert key1=value1,key2=value2,... string into dictionary. 115 116 :param strdict: string in the form of key1=value1,key2=value2 117 :param required_keys: list of required keys. All keys in this list must be 118 specified. Otherwise ArgumentTypeError will be raised. 119 If this parameter is unspecified, no required key check 120 will be done. 121 :param optional_keys: list of optional keys. 122 This parameter is used for valid key check. 123 When at least one of required_keys and optional_keys, 124 a key must be a member of either of required_keys or 125 optional_keys. Otherwise, ArgumentTypeError will be 126 raised. When both required_keys and optional_keys are 127 unspecified, no valid key check will be done. 128 """ 129 result = {} 130 if strdict: 131 i = 0 132 kvlist = [] 133 for kv in strdict.split(','): 134 if '=' in kv: 135 kvlist.append(kv) 136 i += 1 137 elif i == 0: 138 msg = _("missing value for key '%s'") 139 raise argparse.ArgumentTypeError(msg % kv) 140 else: 141 kvlist[i-1] = "%s,%s" % (kvlist[i-1], kv) 142 for kv in kvlist: 143 key, sep, value = kv.partition('=') 144 if not sep: 145 msg = _("invalid key-value '%s', expected format: key=value") 146 raise argparse.ArgumentTypeError(msg % kv) 147 result[key] = value 148 valid_keys = set(required_keys or []) | set(optional_keys or []) 149 if valid_keys: 150 invalid_keys = [k for k in result if k not in valid_keys] 151 if invalid_keys: 152 msg = _("Invalid key(s) '%(invalid_keys)s' specified. " 153 "Valid key(s): '%(valid_keys)s'.") 154 raise argparse.ArgumentTypeError( 155 msg % {'invalid_keys': ', '.join(sorted(invalid_keys)), 156 'valid_keys': ', '.join(sorted(valid_keys))}) 157 if required_keys: 158 not_found_keys = [k for k in required_keys if k not in result] 159 if not_found_keys: 160 msg = _("Required key(s) '%s' not specified.") 161 raise argparse.ArgumentTypeError(msg % ', '.join(not_found_keys)) 162 return result 163 164 165def str2dict_type(optional_keys=None, required_keys=None): 166 return functools.partial(str2dict, 167 optional_keys=optional_keys, 168 required_keys=required_keys) 169 170 171def http_log_req(_logger, args, kwargs): 172 if not _logger.isEnabledFor(logging.DEBUG): 173 return 174 175 string_parts = ['curl -i'] 176 for element in args: 177 if element in ('GET', 'POST', 'DELETE', 'PUT'): 178 string_parts.append(' -X %s' % element) 179 else: 180 string_parts.append(' %s' % element) 181 182 for (key, value) in kwargs['headers'].items(): 183 if key in SENSITIVE_HEADERS: 184 v = value.encode('utf-8') 185 h = hashlib.sha256(v) 186 d = h.hexdigest() 187 value = "{SHA256}%s" % d 188 header = ' -H "%s: %s"' % (key, value) 189 string_parts.append(header) 190 191 if 'body' in kwargs and kwargs['body']: 192 string_parts.append(" -d '%s'" % (kwargs['body'])) 193 req = encodeutils.safe_encode("".join(string_parts)) 194 _logger.debug("REQ: %s", req) 195 196 197def http_log_resp(_logger, resp, body): 198 if not _logger.isEnabledFor(logging.DEBUG): 199 return 200 _logger.debug("RESP: %(code)s %(headers)s %(body)s", 201 {'code': resp.status_code, 202 'headers': resp.headers, 203 'body': body}) 204 205 206def _safe_encode_without_obj(data): 207 if isinstance(data, str): 208 return encodeutils.safe_encode(data) 209 return data 210 211 212def safe_encode_list(data): 213 return list(map(_safe_encode_without_obj, data)) 214 215 216def safe_encode_dict(data): 217 def _encode_item(item): 218 k, v = item 219 if isinstance(v, list): 220 return (k, safe_encode_list(v)) 221 elif isinstance(v, dict): 222 return (k, safe_encode_dict(v)) 223 return (k, _safe_encode_without_obj(v)) 224 225 return dict(list(map(_encode_item, data.items()))) 226 227 228def add_boolean_argument(parser, name, **kwargs): 229 for keyword in ('metavar', 'choices'): 230 kwargs.pop(keyword, None) 231 default = kwargs.pop('default', argparse.SUPPRESS) 232 parser.add_argument( 233 name, 234 metavar='{True,False}', 235 choices=['True', 'true', 'False', 'false'], 236 default=default, 237 **kwargs) 238