]>
git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/mgr_util.py
df6486455d86289081cbef4d0d4d7c916b6be9a7
7 from functools
import wraps
10 from typing
import Tuple
, Any
, Callable
12 TYPE_CHECKING
= False # just for type checking
26 COLOR_SEQ
= "\033[1;%dm"
27 COLOR_DARK_SEQ
= "\033[0;%dm"
29 UNDERLINE_SEQ
= "\033[4m"
31 logger
= logging
.getLogger(__name__
)
34 def colorize(msg
, color
, dark
=False):
36 Decorate `msg` with escape sequences to give the requested color
38 return (COLOR_DARK_SEQ
if dark
else COLOR_SEQ
) % (30 + color
) \
44 Decorate `msg` with escape sequences to make it appear bold
46 return BOLD_SEQ
+ msg
+ RESET_SEQ
49 def format_units(n
, width
, colored
, decimal
):
51 Format a number without units, so as to fit into `width` characters, substituting
52 an appropriate unit suffix.
54 Use decimal for dimensionless things, use base 2 (decimal=False) for byte sizes/rates.
57 factor
= 1000 if decimal
else 1024
58 units
= [' ', 'k', 'M', 'G', 'T', 'P', 'E']
60 while len("%s" % (int(n
) // (factor
**unit
))) > width
- 1:
64 truncated_float
= ("%f" % (n
/ (float(factor
) ** unit
)))[0:width
- 1]
65 if truncated_float
[-1] == '.':
66 truncated_float
= " " + truncated_float
[0:-1]
68 truncated_float
= "%{wid}d".format(wid
=width
- 1) % n
69 formatted
= "%s%s" % (truncated_float
, units
[unit
])
76 return bold(colorize(formatted
[0:-1], color
[0], color
[1])) \
77 + bold(colorize(formatted
[-1], BLACK
, False))
82 def format_dimless(n
, width
, colored
=False):
83 return format_units(n
, width
, colored
, decimal
=True)
86 def format_bytes(n
, width
, colored
=False):
87 return format_units(n
, width
, colored
, decimal
=False)
90 def merge_dicts(*args
):
91 # type: (dict) -> dict
93 >>> merge_dicts({1:2}, {3:4})
96 You can also overwrite keys:
97 >>> merge_dicts({1:2}, {1:4})
100 :rtype: dict[str, Any]
108 def get_default_addr():
110 def is_ipv6_enabled():
112 sock
= socket
.socket(socket
.AF_INET6
)
113 with contextlib
.closing(sock
):
114 sock
.bind(("::1", 0))
116 except (AttributeError, socket
.error
) as e
:
120 return get_default_addr
.result
# type: ignore
121 except AttributeError:
122 result
= '::' if is_ipv6_enabled() else '0.0.0.0'
123 get_default_addr
.result
= result
# type: ignore
127 class ServerConfigException(Exception):
131 def create_self_signed_cert(organisation
='Ceph', common_name
='mgr') -> Tuple
[str, str]:
132 """Returns self-signed PEM certificates valid for 10 years.
136 from OpenSSL
import crypto
137 from uuid
import uuid4
141 pkey
.generate_key(crypto
.TYPE_RSA
, 2048)
143 # create a self-signed cert
145 cert
.get_subject().O
= organisation
146 cert
.get_subject().CN
= common_name
147 cert
.set_serial_number(int(uuid4()))
148 cert
.gmtime_adj_notBefore(0)
149 cert
.gmtime_adj_notAfter(10 * 365 * 24 * 60 * 60) # 10 years
150 cert
.set_issuer(cert
.get_subject())
151 cert
.set_pubkey(pkey
)
152 cert
.sign(pkey
, 'sha512')
154 cert
= crypto
.dump_certificate(crypto
.FILETYPE_PEM
, cert
)
155 pkey
= crypto
.dump_privatekey(crypto
.FILETYPE_PEM
, pkey
)
157 return cert
.decode('utf-8'), pkey
.decode('utf-8')
160 def verify_cacrt_content(crt
):
161 # type: (str) -> None
162 from OpenSSL
import crypto
164 x509
= crypto
.load_certificate(crypto
.FILETYPE_PEM
, crt
)
165 if x509
.has_expired():
166 logger
.warning('Certificate has expired: {}'.format(crt
))
167 except (ValueError, crypto
.Error
) as e
:
168 raise ServerConfigException(
169 'Invalid certificate: {}'.format(str(e
)))
172 def verify_cacrt(cert_fname
):
173 # type: (str) -> None
174 """Basic validation of a ca cert"""
177 raise ServerConfigException("CA cert not configured")
178 if not os
.path
.isfile(cert_fname
):
179 raise ServerConfigException("Certificate {} does not exist".format(cert_fname
))
182 with
open(cert_fname
) as f
:
183 verify_cacrt_content(f
.read())
184 except ValueError as e
:
185 raise ServerConfigException(
186 'Invalid certificate {}: {}'.format(cert_fname
, str(e
)))
189 def verify_tls(crt
, key
):
190 # type: (str, str) -> None
191 verify_cacrt_content(crt
)
193 from OpenSSL
import crypto
, SSL
195 _key
= crypto
.load_privatekey(crypto
.FILETYPE_PEM
, key
)
197 except (ValueError, crypto
.Error
) as e
:
198 raise ServerConfigException(
199 'Invalid private key: {}'.format(str(e
)))
201 _crt
= crypto
.load_certificate(crypto
.FILETYPE_PEM
, crt
)
202 except ValueError as e
:
203 raise ServerConfigException(
204 'Invalid certificate key: {}'.format(str(e
))
208 context
= SSL
.Context(SSL
.TLSv1_METHOD
)
209 context
.use_certificate(_crt
)
210 context
.use_privatekey(_key
)
211 context
.check_privatekey()
212 except crypto
.Error
as e
:
214 'Private key and certificate do not match up: {}'.format(str(e
)))
217 def verify_tls_files(cert_fname
, pkey_fname
):
218 # type: (str, str) -> None
219 """Basic checks for TLS certificate and key files
221 Do some validations to the private key and certificate:
222 - Check the type and format
223 - Check the certificate expiration date
224 - Check the consistency of the private key
225 - Check that the private key and certificate match up
227 :param cert_fname: Name of the certificate file
228 :param pkey_fname: name of the certificate public key file
230 :raises ServerConfigException: An error with a message
234 if not cert_fname
or not pkey_fname
:
235 raise ServerConfigException('no certificate configured')
237 verify_cacrt(cert_fname
)
239 if not os
.path
.isfile(pkey_fname
):
240 raise ServerConfigException('private key %s does not exist' % pkey_fname
)
242 from OpenSSL
import crypto
, SSL
245 with
open(pkey_fname
) as f
:
246 pkey
= crypto
.load_privatekey(crypto
.FILETYPE_PEM
, f
.read())
248 except (ValueError, crypto
.Error
) as e
:
249 raise ServerConfigException(
250 'Invalid private key {}: {}'.format(pkey_fname
, str(e
)))
252 context
= SSL
.Context(SSL
.TLSv1_METHOD
)
253 context
.use_certificate_file(cert_fname
, crypto
.FILETYPE_PEM
)
254 context
.use_privatekey_file(pkey_fname
, crypto
.FILETYPE_PEM
)
255 context
.check_privatekey()
256 except crypto
.Error
as e
:
258 'Private key {} and certificate {} do not match up: {}'.format(
259 pkey_fname
, cert_fname
, str(e
)))
261 def get_most_recent_rate(rates
):
262 """ Get most recent rate from rates
264 :param rates: The derivative between all time series data points [time in seconds, value]
265 :type rates: list[tuple[int, float]]
267 :return: The last derivative or 0.0 if none exists
270 >>> get_most_recent_rate(None)
272 >>> get_most_recent_rate([])
274 >>> get_most_recent_rate([(1, -2.0)])
276 >>> get_most_recent_rate([(1, 2.0), (2, 1.5), (3, 5.0)])
283 def get_time_series_rates(data
):
284 """ Rates from time series data
286 :param data: Time series data [time in seconds, value]
287 :type data: list[tuple[int, float]]
289 :return: The derivative between all time series data points [time in seconds, value]
290 :rtype: list[tuple[int, float]]
292 >>> logger.debug = lambda s,x,y: print(s % (x,y))
293 >>> get_time_series_rates([])
295 >>> get_time_series_rates([[0, 1], [1, 3]])
297 >>> get_time_series_rates([[0, 2], [0, 3], [0, 1], [1, 2], [1, 3]])
298 Duplicate timestamp in time series data: [0, 2], [0, 3]
299 Duplicate timestamp in time series data: [0, 3], [0, 1]
300 Duplicate timestamp in time series data: [1, 2], [1, 3]
302 >>> get_time_series_rates([[1, 1], [2, 3], [4, 11], [5, 16], [6, 22]])
303 [(2, 2.0), (4, 4.0), (5, 5.0), (6, 6.0)]
305 data
= _filter_time_series(data
)
308 return [(data2
[0], _derivative(data1
, data2
)) for data1
, data2
in
311 def _filter_time_series(data
):
312 """ Filters time series data
314 Filters out samples with the same timestamp in given time series data.
315 It also enforces the list to contain at least two samples.
317 All filtered values will be shown in the debug log. If values were filtered it's a bug in the
318 time series data collector, please report it.
320 :param data: Time series data [time in seconds, value]
321 :type data: list[tuple[int, float]]
323 :return: Filtered time series data [time in seconds, value]
324 :rtype: list[tuple[int, float]]
326 >>> logger.debug = lambda s,x,y: print(s % (x,y))
327 >>> _filter_time_series([])
329 >>> _filter_time_series([[1, 42]])
331 >>> _filter_time_series([[10, 2], [10, 3]])
332 Duplicate timestamp in time series data: [10, 2], [10, 3]
334 >>> _filter_time_series([[0, 1], [1, 2]])
336 >>> _filter_time_series([[0, 2], [0, 3], [0, 1], [1, 2], [1, 3]])
337 Duplicate timestamp in time series data: [0, 2], [0, 3]
338 Duplicate timestamp in time series data: [0, 3], [0, 1]
339 Duplicate timestamp in time series data: [1, 2], [1, 3]
341 >>> _filter_time_series([[1, 1], [2, 3], [4, 11], [5, 16], [6, 22]])
342 [[1, 1], [2, 3], [4, 11], [5, 16], [6, 22]]
345 for i
in range(len(data
) - 1):
346 if data
[i
][0] == data
[i
+ 1][0]: # Same timestamp
347 logger
.debug("Duplicate timestamp in time series data: %s, %s", data
[i
], data
[i
+ 1])
349 filtered
.append(data
[i
])
352 filtered
.append(data
[-1])
355 def _derivative(p1
, p2
):
356 """ Derivative between two time series data points
358 :param p1: Time series data [time in seconds, value]
359 :type p1: tuple[int, float]
360 :param p2: Time series data [time in seconds, value]
361 :type p2: tuple[int, float]
363 :return: Derivative between both points
366 >>> _derivative([0, 0], [2, 1])
368 >>> _derivative([0, 1], [2, 0])
370 >>> _derivative([0, 0], [3, 1])
373 return (p2
[1] - p1
[1]) / float(p2
[0] - p1
[0])
375 def _pairwise(iterable
):
383 def to_pretty_timedelta(n
):
384 if n
< datetime
.timedelta(seconds
=120):
385 return str(n
.seconds
) + 's'
386 if n
< datetime
.timedelta(minutes
=120):
387 return str(n
.seconds
// 60) + 'm'
388 if n
< datetime
.timedelta(hours
=48):
389 return str(n
.seconds
// 3600) + 'h'
390 if n
< datetime
.timedelta(days
=14):
391 return str(n
.days
) + 'd'
392 if n
< datetime
.timedelta(days
=7*12):
393 return str(n
.days
// 7) + 'w'
394 if n
< datetime
.timedelta(days
=365*2):
395 return str(n
.days
// 30) + 'M'
396 return str(n
.days
// 365) + 'y'
399 def profile_method(skip_attribute
=False):
401 Decorator for methods of the Module class. Logs the name of the given
402 function f with the time it takes to execute it.
406 def wrapper(*args
, **kwargs
):
409 self
.log
.debug('Starting method {}.'.format(f
.__name
__))
410 result
= f(*args
, **kwargs
)
411 duration
= time
.time() - t
412 if not skip_attribute
:
413 wrapper
._execution
_duration
= duration
# type: ignore
414 self
.log
.debug('Method {} ran {:.3f} seconds.'.format(f
.__name
__, duration
))