]>
git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/mgr_util.py
8 from typing
import Tuple
10 TYPE_CHECKING
= False # just for type checking
24 COLOR_SEQ
= "\033[1;%dm"
25 COLOR_DARK_SEQ
= "\033[0;%dm"
27 UNDERLINE_SEQ
= "\033[4m"
29 logger
= logging
.getLogger(__name__
)
32 def colorize(msg
, color
, dark
=False):
34 Decorate `msg` with escape sequences to give the requested color
36 return (COLOR_DARK_SEQ
if dark
else COLOR_SEQ
) % (30 + color
) \
42 Decorate `msg` with escape sequences to make it appear bold
44 return BOLD_SEQ
+ msg
+ RESET_SEQ
47 def format_units(n
, width
, colored
, decimal
):
49 Format a number without units, so as to fit into `width` characters, substituting
50 an appropriate unit suffix.
52 Use decimal for dimensionless things, use base 2 (decimal=False) for byte sizes/rates.
55 factor
= 1000 if decimal
else 1024
56 units
= [' ', 'k', 'M', 'G', 'T', 'P', 'E']
58 while len("%s" % (int(n
) // (factor
**unit
))) > width
- 1:
62 truncated_float
= ("%f" % (n
/ (float(factor
) ** unit
)))[0:width
- 1]
63 if truncated_float
[-1] == '.':
64 truncated_float
= " " + truncated_float
[0:-1]
66 truncated_float
= "%{wid}d".format(wid
=width
- 1) % n
67 formatted
= "%s%s" % (truncated_float
, units
[unit
])
74 return bold(colorize(formatted
[0:-1], color
[0], color
[1])) \
75 + bold(colorize(formatted
[-1], BLACK
, False))
80 def format_dimless(n
, width
, colored
=False):
81 return format_units(n
, width
, colored
, decimal
=True)
84 def format_bytes(n
, width
, colored
=False):
85 return format_units(n
, width
, colored
, decimal
=False)
88 def merge_dicts(*args
):
89 # type: (dict) -> dict
91 >>> merge_dicts({1:2}, {3:4})
94 You can also overwrite keys:
95 >>> merge_dicts({1:2}, {1:4})
98 :rtype: dict[str, Any]
106 def get_default_addr():
108 def is_ipv6_enabled():
110 sock
= socket
.socket(socket
.AF_INET6
)
111 with contextlib
.closing(sock
):
112 sock
.bind(("::1", 0))
114 except (AttributeError, socket
.error
) as e
:
118 return get_default_addr
.result
# type: ignore
119 except AttributeError:
120 result
= '::' if is_ipv6_enabled() else '0.0.0.0'
121 get_default_addr
.result
= result
# type: ignore
125 class ServerConfigException(Exception):
129 def create_self_signed_cert(organisation
='Ceph', common_name
='mgr') -> Tuple
[str, str]:
130 """Returns self-signed PEM certificates valid for 10 years.
134 from OpenSSL
import crypto
135 from uuid
import uuid4
139 pkey
.generate_key(crypto
.TYPE_RSA
, 2048)
141 # create a self-signed cert
143 cert
.get_subject().O
= organisation
144 cert
.get_subject().CN
= common_name
145 cert
.set_serial_number(int(uuid4()))
146 cert
.gmtime_adj_notBefore(0)
147 cert
.gmtime_adj_notAfter(10 * 365 * 24 * 60 * 60) # 10 years
148 cert
.set_issuer(cert
.get_subject())
149 cert
.set_pubkey(pkey
)
150 cert
.sign(pkey
, 'sha512')
152 cert
= crypto
.dump_certificate(crypto
.FILETYPE_PEM
, cert
)
153 pkey
= crypto
.dump_privatekey(crypto
.FILETYPE_PEM
, pkey
)
155 return cert
.decode('utf-8'), pkey
.decode('utf-8')
158 def verify_cacrt_content(crt
):
159 # type: (str) -> None
160 from OpenSSL
import crypto
162 x509
= crypto
.load_certificate(crypto
.FILETYPE_PEM
, crt
)
163 if x509
.has_expired():
164 logger
.warning('Certificate has expired: {}'.format(crt
))
165 except (ValueError, crypto
.Error
) as e
:
166 raise ServerConfigException(
167 'Invalid certificate: {}'.format(str(e
)))
170 def verify_cacrt(cert_fname
):
171 # type: (str) -> None
172 """Basic validation of a ca cert"""
175 raise ServerConfigException("CA cert not configured")
176 if not os
.path
.isfile(cert_fname
):
177 raise ServerConfigException("Certificate {} does not exist".format(cert_fname
))
180 with
open(cert_fname
) as f
:
181 verify_cacrt_content(f
.read())
182 except ValueError as e
:
183 raise ServerConfigException(
184 'Invalid certificate {}: {}'.format(cert_fname
, str(e
)))
187 def verify_tls(crt
, key
):
188 # type: (str, str) -> None
189 verify_cacrt_content(crt
)
191 from OpenSSL
import crypto
, SSL
193 _key
= crypto
.load_privatekey(crypto
.FILETYPE_PEM
, key
)
195 except (ValueError, crypto
.Error
) as e
:
196 raise ServerConfigException(
197 'Invalid private key: {}'.format(str(e
)))
199 _crt
= crypto
.load_certificate(crypto
.FILETYPE_PEM
, crt
)
200 except ValueError as e
:
201 raise ServerConfigException(
202 'Invalid certificate key: {}'.format(str(e
))
206 context
= SSL
.Context(SSL
.TLSv1_METHOD
)
207 context
.use_certificate(_crt
)
208 context
.use_privatekey(_key
)
209 context
.check_privatekey()
210 except crypto
.Error
as e
:
212 'Private key and certificate do not match up: {}'.format(str(e
)))
215 def verify_tls_files(cert_fname
, pkey_fname
):
216 # type: (str, str) -> None
217 """Basic checks for TLS certificate and key files
219 Do some validations to the private key and certificate:
220 - Check the type and format
221 - Check the certificate expiration date
222 - Check the consistency of the private key
223 - Check that the private key and certificate match up
225 :param cert_fname: Name of the certificate file
226 :param pkey_fname: name of the certificate public key file
228 :raises ServerConfigException: An error with a message
232 if not cert_fname
or not pkey_fname
:
233 raise ServerConfigException('no certificate configured')
235 verify_cacrt(cert_fname
)
237 if not os
.path
.isfile(pkey_fname
):
238 raise ServerConfigException('private key %s does not exist' % pkey_fname
)
240 from OpenSSL
import crypto
, SSL
243 with
open(pkey_fname
) as f
:
244 pkey
= crypto
.load_privatekey(crypto
.FILETYPE_PEM
, f
.read())
246 except (ValueError, crypto
.Error
) as e
:
247 raise ServerConfigException(
248 'Invalid private key {}: {}'.format(pkey_fname
, str(e
)))
250 context
= SSL
.Context(SSL
.TLSv1_METHOD
)
251 context
.use_certificate_file(cert_fname
, crypto
.FILETYPE_PEM
)
252 context
.use_privatekey_file(pkey_fname
, crypto
.FILETYPE_PEM
)
253 context
.check_privatekey()
254 except crypto
.Error
as e
:
256 'Private key {} and certificate {} do not match up: {}'.format(
257 pkey_fname
, cert_fname
, str(e
)))
259 def get_most_recent_rate(rates
):
260 """ Get most recent rate from rates
262 :param rates: The derivative between all time series data points [time in seconds, value]
263 :type rates: list[tuple[int, float]]
265 :return: The last derivative or 0.0 if none exists
268 >>> get_most_recent_rate(None)
270 >>> get_most_recent_rate([])
272 >>> get_most_recent_rate([(1, -2.0)])
274 >>> get_most_recent_rate([(1, 2.0), (2, 1.5), (3, 5.0)])
281 def get_time_series_rates(data
):
282 """ Rates from time series data
284 :param data: Time series data [time in seconds, value]
285 :type data: list[tuple[int, float]]
287 :return: The derivative between all time series data points [time in seconds, value]
288 :rtype: list[tuple[int, float]]
290 >>> logger.debug = lambda s,x,y: print(s % (x,y))
291 >>> get_time_series_rates([])
293 >>> get_time_series_rates([[0, 1], [1, 3]])
295 >>> get_time_series_rates([[0, 2], [0, 3], [0, 1], [1, 2], [1, 3]])
296 Duplicate timestamp in time series data: [0, 2], [0, 3]
297 Duplicate timestamp in time series data: [0, 3], [0, 1]
298 Duplicate timestamp in time series data: [1, 2], [1, 3]
300 >>> get_time_series_rates([[1, 1], [2, 3], [4, 11], [5, 16], [6, 22]])
301 [(2, 2.0), (4, 4.0), (5, 5.0), (6, 6.0)]
303 data
= _filter_time_series(data
)
306 return [(data2
[0], _derivative(data1
, data2
)) for data1
, data2
in
309 def _filter_time_series(data
):
310 """ Filters time series data
312 Filters out samples with the same timestamp in given time series data.
313 It also enforces the list to contain at least two samples.
315 All filtered values will be shown in the debug log. If values were filtered it's a bug in the
316 time series data collector, please report it.
318 :param data: Time series data [time in seconds, value]
319 :type data: list[tuple[int, float]]
321 :return: Filtered time series data [time in seconds, value]
322 :rtype: list[tuple[int, float]]
324 >>> logger.debug = lambda s,x,y: print(s % (x,y))
325 >>> _filter_time_series([])
327 >>> _filter_time_series([[1, 42]])
329 >>> _filter_time_series([[10, 2], [10, 3]])
330 Duplicate timestamp in time series data: [10, 2], [10, 3]
332 >>> _filter_time_series([[0, 1], [1, 2]])
334 >>> _filter_time_series([[0, 2], [0, 3], [0, 1], [1, 2], [1, 3]])
335 Duplicate timestamp in time series data: [0, 2], [0, 3]
336 Duplicate timestamp in time series data: [0, 3], [0, 1]
337 Duplicate timestamp in time series data: [1, 2], [1, 3]
339 >>> _filter_time_series([[1, 1], [2, 3], [4, 11], [5, 16], [6, 22]])
340 [[1, 1], [2, 3], [4, 11], [5, 16], [6, 22]]
343 for i
in range(len(data
) - 1):
344 if data
[i
][0] == data
[i
+ 1][0]: # Same timestamp
345 logger
.debug("Duplicate timestamp in time series data: %s, %s", data
[i
], data
[i
+ 1])
347 filtered
.append(data
[i
])
350 filtered
.append(data
[-1])
353 def _derivative(p1
, p2
):
354 """ Derivative between two time series data points
356 :param p1: Time series data [time in seconds, value]
357 :type p1: tuple[int, float]
358 :param p2: Time series data [time in seconds, value]
359 :type p2: tuple[int, float]
361 :return: Derivative between both points
364 >>> _derivative([0, 0], [2, 1])
366 >>> _derivative([0, 1], [2, 0])
368 >>> _derivative([0, 0], [3, 1])
371 return (p2
[1] - p1
[1]) / float(p2
[0] - p1
[0])
373 def _pairwise(iterable
):
381 def to_pretty_timedelta(n
):
382 if n
< datetime
.timedelta(seconds
=120):
383 return str(n
.seconds
) + 's'
384 if n
< datetime
.timedelta(minutes
=120):
385 return str(n
.seconds
// 60) + 'm'
386 if n
< datetime
.timedelta(hours
=48):
387 return str(n
.seconds
// 3600) + 'h'
388 if n
< datetime
.timedelta(days
=14):
389 return str(n
.days
) + 'd'
390 if n
< datetime
.timedelta(days
=7*12):
391 return str(n
.days
// 7) + 'w'
392 if n
< datetime
.timedelta(days
=365*2):
393 return str(n
.days
// 30) + 'M'
394 return str(n
.days
// 365) + 'y'