6cb178440fd767a20945a064663aa9d07ecc397f
[escalator.git] / api / escalator / common / utils.py
1 # Copyright 2010 United States Government as represented by the
2 # Administrator of the National Aeronautics and Space Administration.
3 # Copyright 2014 SoftLayer Technologies, Inc.
4 # Copyright 2015 Mirantis, Inc
5 # All Rights Reserved.
6 #
7 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
8 #    not use this file except in compliance with the License. You may obtain
9 #    a copy of the License at
10 #
11 #         http://www.apache.org/licenses/LICENSE-2.0
12 #
13 #    Unless required by applicable law or agreed to in writing, software
14 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16 #    License for the specific language governing permissions and limitations
17 #    under the License.
18
19 """
20 System-level utilities and helper functions.
21 """
22
23 import errno
24 from functools import reduce
25
26 try:
27     from eventlet import sleep
28 except ImportError:
29     from time import sleep
30 from eventlet.green import socket
31
32 import functools
33 import os
34 import platform
35 import re
36 import subprocess
37 import sys
38 import uuid
39 import copy
40
41 from OpenSSL import crypto
42 from oslo_config import cfg
43 from oslo_log import log as logging
44 from oslo_utils import encodeutils
45 from oslo_utils import excutils
46 import six
47 from webob import exc
48 from escalator.common import exception
49 from escalator import i18n
50
51 CONF = cfg.CONF
52
53 LOG = logging.getLogger(__name__)
54 _ = i18n._
55 _LE = i18n._LE
56
57
58 ESCALATOR_TEST_SOCKET_FD_STR = 'ESCALATOR_TEST_SOCKET_FD'
59
60
61 def chunkreadable(iter, chunk_size=65536):
62     """
63     Wrap a readable iterator with a reader yielding chunks of
64     a preferred size, otherwise leave iterator unchanged.
65
66     :param iter: an iter which may also be readable
67     :param chunk_size: maximum size of chunk
68     """
69     return chunkiter(iter, chunk_size) if hasattr(iter, 'read') else iter
70
71
72 def chunkiter(fp, chunk_size=65536):
73     """
74     Return an iterator to a file-like obj which yields fixed size chunks
75
76     :param fp: a file-like object
77     :param chunk_size: maximum size of chunk
78     """
79     while True:
80         chunk = fp.read(chunk_size)
81         if chunk:
82             yield chunk
83         else:
84             break
85
86
87 def cooperative_iter(iter):
88     """
89     Return an iterator which schedules after each
90     iteration. This can prevent eventlet thread starvation.
91
92     :param iter: an iterator to wrap
93     """
94     try:
95         for chunk in iter:
96             sleep(0)
97             yield chunk
98     except Exception as err:
99         with excutils.save_and_reraise_exception():
100             msg = _LE("Error: cooperative_iter exception %s") % err
101             LOG.error(msg)
102
103
104 def cooperative_read(fd):
105     """
106     Wrap a file descriptor's read with a partial function which schedules
107     after each read. This can prevent eventlet thread starvation.
108
109     :param fd: a file descriptor to wrap
110     """
111     def readfn(*args):
112         result = fd.read(*args)
113         sleep(0)
114         return result
115     return readfn
116
117
118 MAX_COOP_READER_BUFFER_SIZE = 134217728  # 128M seems like a sane buffer limit
119
120
121 class CooperativeReader(object):
122
123     """
124     An eventlet thread friendly class for reading in image data.
125
126     When accessing data either through the iterator or the read method
127     we perform a sleep to allow a co-operative yield. When there is more than
128     one image being uploaded/downloaded this prevents eventlet thread
129     starvation, ie allows all threads to be scheduled periodically rather than
130     having the same thread be continuously active.
131     """
132
133     def __init__(self, fd):
134         """
135         :param fd: Underlying image file object
136         """
137         self.fd = fd
138         self.iterator = None
139         # NOTE(markwash): if the underlying supports read(), overwrite the
140         # default iterator-based implementation with cooperative_read which
141         # is more straightforward
142         if hasattr(fd, 'read'):
143             self.read = cooperative_read(fd)
144         else:
145             self.iterator = None
146             self.buffer = ''
147             self.position = 0
148
149     def read(self, length=None):
150         """Return the requested amount of bytes, fetching the next chunk of
151         the underlying iterator when needed.
152
153         This is replaced with cooperative_read in __init__ if the underlying
154         fd already supports read().
155         """
156         if length is None:
157             if len(self.buffer) - self.position > 0:
158                 # if no length specified but some data exists in buffer,
159                 # return that data and clear the buffer
160                 result = self.buffer[self.position:]
161                 self.buffer = ''
162                 self.position = 0
163                 return str(result)
164             else:
165                 # otherwise read the next chunk from the underlying iterator
166                 # and return it as a whole. Reset the buffer, as subsequent
167                 # calls may specify the length
168                 try:
169                     if self.iterator is None:
170                         self.iterator = self.__iter__()
171                     return self.iterator.next()
172                 except StopIteration:
173                     return ''
174                 finally:
175                     self.buffer = ''
176                     self.position = 0
177         else:
178             result = bytearray()
179             while len(result) < length:
180                 if self.position < len(self.buffer):
181                     to_read = length - len(result)
182                     chunk = self.buffer[self.position:self.position + to_read]
183                     result.extend(chunk)
184
185                     # This check is here to prevent potential OOM issues if
186                     # this code is called with unreasonably high values of read
187                     # size. Currently it is only called from the HTTP clients
188                     # of Glance backend stores, which use httplib for data
189                     # streaming, which has readsize hardcoded to 8K, so this
190                     # check should never fire. Regardless it still worths to
191                     # make the check, as the code may be reused somewhere else.
192                     if len(result) >= MAX_COOP_READER_BUFFER_SIZE:
193                         raise exception.LimitExceeded()
194                     self.position += len(chunk)
195                 else:
196                     try:
197                         if self.iterator is None:
198                             self.iterator = self.__iter__()
199                         self.buffer = self.iterator.next()
200                         self.position = 0
201                     except StopIteration:
202                         self.buffer = ''
203                         self.position = 0
204                         return str(result)
205             return str(result)
206
207     def __iter__(self):
208         return cooperative_iter(self.fd.__iter__())
209
210
211 class LimitingReader(object):
212
213     """
214     Reader designed to fail when reading image data past the configured
215     allowable amount.
216     """
217
218     def __init__(self, data, limit):
219         """
220         :param data: Underlying image data object
221         :param limit: maximum number of bytes the reader should allow
222         """
223         self.data = data
224         self.limit = limit
225         self.bytes_read = 0
226
227     def __iter__(self):
228         for chunk in self.data:
229             self.bytes_read += len(chunk)
230             if self.bytes_read > self.limit:
231                 raise exception.ImageSizeLimitExceeded()
232             else:
233                 yield chunk
234
235     def read(self, i):
236         result = self.data.read(i)
237         self.bytes_read += len(result)
238         if self.bytes_read > self.limit:
239             raise exception.ImageSizeLimitExceeded()
240         return result
241
242
243 def get_dict_meta(response):
244     result = {}
245     for key, value in response.json.items():
246         result[key] = value
247     return result
248
249
250 def create_mashup_dict(image_meta):
251     """
252     Returns a dictionary-like mashup of the image core properties
253     and the image custom properties from given image metadata.
254
255     :param image_meta: metadata of image with core and custom properties
256     """
257
258     def get_items():
259         for key, value in six.iteritems(image_meta):
260             if isinstance(value, dict):
261                 for subkey, subvalue in six.iteritems(
262                         create_mashup_dict(value)):
263                     if subkey not in image_meta:
264                         yield subkey, subvalue
265             else:
266                 yield key, value
267
268     return dict(get_items())
269
270
271 def safe_mkdirs(path):
272     try:
273         os.makedirs(path)
274     except OSError as e:
275         if e.errno != errno.EEXIST:
276             raise
277
278
279 def safe_remove(path):
280     try:
281         os.remove(path)
282     except OSError as e:
283         if e.errno != errno.ENOENT:
284             raise
285
286
287 class PrettyTable(object):
288
289     """Creates an ASCII art table for use in bin/escalator
290
291     """
292
293     def __init__(self):
294         self.columns = []
295
296     def add_column(self, width, label="", just='l'):
297         """Add a column to the table
298
299         :param width: number of characters wide the column should be
300         :param label: column heading
301         :param just: justification for the column, 'l' for left,
302                      'r' for right
303         """
304         self.columns.append((width, label, just))
305
306     def make_header(self):
307         label_parts = []
308         break_parts = []
309         for width, label, _ in self.columns:
310             # NOTE(sirp): headers are always left justified
311             label_part = self._clip_and_justify(label, width, 'l')
312             label_parts.append(label_part)
313
314             break_part = '-' * width
315             break_parts.append(break_part)
316
317         label_line = ' '.join(label_parts)
318         break_line = ' '.join(break_parts)
319         return '\n'.join([label_line, break_line])
320
321     def make_row(self, *args):
322         row = args
323         row_parts = []
324         for data, (width, _, just) in zip(row, self.columns):
325             row_part = self._clip_and_justify(data, width, just)
326             row_parts.append(row_part)
327
328         row_line = ' '.join(row_parts)
329         return row_line
330
331     @staticmethod
332     def _clip_and_justify(data, width, just):
333         # clip field to column width
334         clipped_data = str(data)[:width]
335
336         if just == 'r':
337             # right justify
338             justified = clipped_data.rjust(width)
339         else:
340             # left justify
341             justified = clipped_data.ljust(width)
342
343         return justified
344
345
346 def get_terminal_size():
347
348     def _get_terminal_size_posix():
349         import fcntl
350         import struct
351         import termios
352
353         height_width = None
354
355         try:
356             height_width = struct.unpack('hh', fcntl.ioctl(sys.stderr.fileno(),
357                                                            termios.TIOCGWINSZ,
358                                                            struct.pack(
359                                                                'HH', 0, 0)))
360         except Exception:
361             pass
362
363         if not height_width:
364             try:
365                 p = subprocess.Popen(['stty', 'size'],
366                                      shell=False,
367                                      stdout=subprocess.PIPE,
368                                      stderr=open(os.devnull, 'w'))
369                 result = p.communicate()
370                 if p.returncode == 0:
371                     return tuple(int(x) for x in result[0].split())
372             except Exception:
373                 pass
374
375         return height_width
376
377     def _get_terminal_size_win32():
378         try:
379             from ctypes import create_string_buffer
380             from ctypes import windll
381             handle = windll.kernel32.GetStdHandle(-12)
382             csbi = create_string_buffer(22)
383             res = windll.kernel32.GetConsoleScreenBufferInfo(handle, csbi)
384         except Exception:
385             return None
386         if res:
387             import struct
388             unpack_tmp = struct.unpack("hhhhHhhhhhh", csbi.raw)
389             (bufx, bufy, curx, cury, wattr,
390              left, top, right, bottom, maxx, maxy) = unpack_tmp
391             height = bottom - top + 1
392             width = right - left + 1
393             return (height, width)
394         else:
395             return None
396
397     def _get_terminal_size_unknownOS():
398         raise NotImplementedError
399
400     func = {'posix': _get_terminal_size_posix,
401             'win32': _get_terminal_size_win32}
402
403     height_width = func.get(platform.os.name, _get_terminal_size_unknownOS)()
404
405     if height_width is None:
406         raise exception.Invalid()
407
408     for i in height_width:
409         if not isinstance(i, int) or i <= 0:
410             raise exception.Invalid()
411
412     return height_width[0], height_width[1]
413
414
415 def mutating(func):
416     """Decorator to enforce read-only logic"""
417     @functools.wraps(func)
418     def wrapped(self, req, *args, **kwargs):
419         if req.context.read_only:
420             msg = "Read-only access"
421             LOG.debug(msg)
422             raise exc.HTTPForbidden(msg, request=req,
423                                     content_type="text/plain")
424         return func(self, req, *args, **kwargs)
425     return wrapped
426
427
428 def setup_remote_pydev_debug(host, port):
429     error_msg = _LE('Error setting up the debug environment. Verify that the'
430                     ' option pydev_worker_debug_host is pointing to a valid '
431                     'hostname or IP on which a pydev server is listening on'
432                     ' the port indicated by pydev_worker_debug_port.')
433
434     try:
435         try:
436             from pydev import pydevd
437         except ImportError:
438             import pydevd
439
440         pydevd.settrace(host,
441                         port=port,
442                         stdoutToServer=True,
443                         stderrToServer=True)
444         return True
445     except Exception:
446         with excutils.save_and_reraise_exception():
447             LOG.exception(error_msg)
448
449
450 def validate_key_cert(key_file, cert_file):
451     try:
452         error_key_name = "private key"
453         error_filename = key_file
454         with open(key_file, 'r') as keyfile:
455             key_str = keyfile.read()
456         key = crypto.load_privatekey(crypto.FILETYPE_PEM, key_str)
457
458         error_key_name = "certificate"
459         error_filename = cert_file
460         with open(cert_file, 'r') as certfile:
461             cert_str = certfile.read()
462         cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert_str)
463     except IOError as ioe:
464         raise RuntimeError(_("There is a problem with your %(error_key_name)s "
465                              "%(error_filename)s.  Please verify it."
466                              "  Error: %(ioe)s") %
467                            {'error_key_name': error_key_name,
468                             'error_filename': error_filename,
469                             'ioe': ioe})
470     except crypto.Error as ce:
471         raise RuntimeError(_("There is a problem with your %(error_key_name)s "
472                              "%(error_filename)s.  Please verify it. OpenSSL"
473                              " error: %(ce)s") %
474                            {'error_key_name': error_key_name,
475                             'error_filename': error_filename,
476                             'ce': ce})
477
478     try:
479         data = str(uuid.uuid4())
480         digest = CONF.digest_algorithm
481         if digest == 'sha1':
482             LOG.warn('The FIPS (FEDERAL INFORMATION PROCESSING STANDARDS)'
483                      ' state that the SHA-1 is not suitable for'
484                      ' general-purpose digital signature applications (as'
485                      ' specified in FIPS 186-3) that require 112 bits of'
486                      ' security. The default value is sha1 in Kilo for a'
487                      ' smooth upgrade process, and it will be updated'
488                      ' with sha256 in next release(L).')
489         out = crypto.sign(key, data, digest)
490         crypto.verify(cert, out, data, digest)
491     except crypto.Error as ce:
492         raise RuntimeError(_("There is a problem with your key pair.  "
493                              "Please verify that cert %(cert_file)s and "
494                              "key %(key_file)s belong together.  OpenSSL "
495                              "error %(ce)s") % {'cert_file': cert_file,
496                                                 'key_file': key_file,
497                                                 'ce': ce})
498
499
500 def get_test_suite_socket():
501     global ESCALATOR_TEST_SOCKET_FD_STR
502     if ESCALATOR_TEST_SOCKET_FD_STR in os.environ:
503         fd = int(os.environ[ESCALATOR_TEST_SOCKET_FD_STR])
504         sock = socket.fromfd(fd, socket.AF_INET, socket.SOCK_STREAM)
505         sock = socket.SocketType(_sock=sock)
506         sock.listen(CONF.backlog)
507         del os.environ[ESCALATOR_TEST_SOCKET_FD_STR]
508         os.close(fd)
509         return sock
510     return None
511
512
513 def is_uuid_like(val):
514     """Returns validation of a value as a UUID.
515
516     For our purposes, a UUID is a canonical form string:
517     aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
518     """
519     try:
520         return str(uuid.UUID(val)) == val
521     except (TypeError, ValueError, AttributeError):
522         return False
523
524
525 def exception_to_str(exc):
526     try:
527         error = six.text_type(exc)
528     except UnicodeError:
529         try:
530             error = str(exc)
531         except UnicodeError:
532             error = ("Caught '%(exception)s' exception." %
533                      {"exception": exc.__class__.__name__})
534     return encodeutils.safe_encode(error, errors='ignore')
535
536
537 try:
538     REGEX_4BYTE_UNICODE = re.compile(u'[\U00010000-\U0010ffff]')
539 except re.error:
540     # UCS-2 build case
541     REGEX_4BYTE_UNICODE = re.compile(u'[\uD800-\uDBFF][\uDC00-\uDFFF]')
542
543
544 def no_4byte_params(f):
545     """
546     Checks that no 4 byte unicode characters are allowed
547     in dicts' keys/values and string's parameters
548     """
549     def wrapper(*args, **kwargs):
550
551         def _is_match(some_str):
552             return (isinstance(some_str, unicode) and
553                     REGEX_4BYTE_UNICODE.findall(some_str) != [])
554
555         def _check_dict(data_dict):
556             # a dict of dicts has to be checked recursively
557             for key, value in data_dict.iteritems():
558                 if isinstance(value, dict):
559                     _check_dict(value)
560                 else:
561                     if _is_match(key):
562                         msg = _("Property names can't contain 4 byte unicode.")
563                         raise exception.Invalid(msg)
564                     if _is_match(value):
565                         msg = (_("%s can't contain 4 byte unicode characters.")
566                                % key.title())
567                         raise exception.Invalid(msg)
568
569         for data_dict in [arg for arg in args if isinstance(arg, dict)]:
570             _check_dict(data_dict)
571         # now check args for str values
572         for arg in args:
573             if _is_match(arg):
574                 msg = _("Param values can't contain 4 byte unicode.")
575                 raise exception.Invalid(msg)
576         # check kwargs as well, as params are passed as kwargs via
577         # registry calls
578         _check_dict(kwargs)
579         return f(*args, **kwargs)
580     return wrapper
581
582
583 def stash_conf_values():
584     """
585     Make a copy of some of the current global CONF's settings.
586     Allows determining if any of these values have changed
587     when the config is reloaded.
588     """
589     conf = {}
590     conf['bind_host'] = CONF.bind_host
591     conf['bind_port'] = CONF.bind_port
592     conf['tcp_keepidle'] = CONF.cert_file
593     conf['backlog'] = CONF.backlog
594     conf['key_file'] = CONF.key_file
595     conf['cert_file'] = CONF.cert_file
596
597     return conf
598
599
600 def validate_ip_format(ip_str):
601     '''
602     valid ip_str format = '10.43.178.9'
603     invalid ip_str format : '123. 233.42.12', spaces existed in field
604                             '3234.23.453.353', out of range
605                             '-2.23.24.234', negative number in field
606                             '1.2.3.4d', letter in field
607                             '10.43.1789', invalid format
608     '''
609     if not ip_str:
610         msg = (_("No ip given when check ip"))
611         LOG.error(msg)
612         raise exc.HTTPBadRequest(msg, content_type="text/plain")
613
614     valid_fromat = False
615     if ip_str.count('.') == 3 and all(num.isdigit() and 0 <= int(
616             num) < 256 for num in ip_str.rstrip().split('.')):
617         valid_fromat = True
618     if not valid_fromat:
619         msg = (_("%s invalid ip format!") % ip_str)
620         LOG.error(msg)
621         raise exc.HTTPBadRequest(msg, content_type="text/plain")
622
623
624 def valid_cidr(cidr):
625     if not cidr:
626         msg = (_("No CIDR given."))
627         LOG.error(msg)
628         raise exc.HTTPBadRequest(explanation=msg)
629
630     cidr_division = cidr.split('/')
631     if (len(cidr_division) != 2 or
632             not cidr_division[0] or
633             not cidr_division[1]):
634         msg = (_("CIDR format error."))
635         LOG.error(msg)
636         raise exc.HTTPBadRequest(explanation=msg)
637
638     netmask_err_msg = (_("CIDR netmask error, "
639                          "it should be a integer between 0-32."))
640     try:
641         netmask_cidr = int(cidr_division[1])
642     except ValueError:
643         LOG.warn(netmask_err_msg)
644         raise exc.HTTPBadRequest(explanation=netmask_err_msg)
645
646     if (netmask_cidr < 0 and
647             netmask_cidr > 32):
648         LOG.warn(netmask_err_msg)
649         raise exc.HTTPBadRequest(explanation=netmask_err_msg)
650
651     validate_ip_format(cidr_division[0])
652
653
654 def ip_into_int(ip):
655     """
656     Switch ip string to decimalism integer..
657     :param ip: ip string
658     :return: decimalism integer
659     """
660     return reduce(lambda x, y: (x << 8) + y, map(int, ip.split('.')))
661
662
663 def int_into_ip(num):
664     s = []
665     for i in range(4):
666         s.append(str(num % 256))
667         num /= 256
668     return '.'.join(s[::-1])
669
670
671 def is_ip_in_cidr(ip, cidr):
672     """
673     Check ip is in cidr
674     :param ip: Ip will be checked, like:192.168.1.2.
675     :param cidr: Ip range,like:192.168.0.0/24.
676     :return: If ip in cidr, return True, else return False.
677     """
678     if not ip:
679         msg = "Error, ip is empty"
680         raise exc.HTTPBadRequest(explanation=msg)
681     if not cidr:
682         msg = "Error, CIDR is empty"
683         raise exc.HTTPBadRequest(explanation=msg)
684     network = cidr.split('/')
685     mask = ~(2**(32 - int(network[1])) - 1)
686     return (ip_into_int(ip) & mask) == (ip_into_int(network[0]) & mask)
687
688
689 def is_ip_in_ranges(ip, ip_ranges):
690     """
691     Check ip is in range
692     : ip: Ip will be checked, like:192.168.1.2.
693     : ip_ranges : Ip ranges, like:
694                     [{'start':'192.168.0.10', 'end':'192.168.0.20'}
695                     {'start':'192.168.0.50', 'end':'192.168.0.60'}]
696     :return: If ip in ip_ranges, return True, else return False.
697     """
698     if not ip:
699         msg = "Error, ip is empty"
700         raise exc.HTTPBadRequest(explanation=msg)
701
702     if not ip_ranges:
703         return True
704
705     for ip_range in ip_ranges:
706         start_ip_int = ip_into_int(ip_range['start'])
707         end_ip_int = ip_into_int(ip_range['end'])
708         ip_int = ip_into_int(ip)
709         if ip_int >= start_ip_int and ip_int <= end_ip_int:
710             return True
711
712     return False
713
714
715 def merge_ip_ranges(ip_ranges):
716     if not ip_ranges:
717         return ip_ranges
718     sort_ranges_by_start_ip = {}
719     for ip_range in ip_ranges:
720         start_ip_int = ip_into_int(ip_range['start'])
721         sort_ranges_by_start_ip.update({str(start_ip_int): ip_range})
722     sort_ranges = [sort_ranges_by_start_ip[key] for key in
723                    sorted(sort_ranges_by_start_ip.keys())]
724     last_range_end_ip = None
725
726     merged_ip_ranges = []
727     for ip_range in sort_ranges:
728         if last_range_end_ip is None:
729             last_range_end_ip = ip_range['end']
730             merged_ip_ranges.append(ip_range)
731             continue
732         else:
733             last_range_end_ip_int = ip_into_int(last_range_end_ip)
734             ip_range_start_ip_int = ip_into_int(ip_range['start'])
735             if (last_range_end_ip_int + 1) == ip_range_start_ip_int:
736                 merged_ip_ranges[-1]['end'] = ip_range['end']
737             else:
738                 merged_ip_ranges.append(ip_range)
739     return merged_ip_ranges
740
741
742 def _split_ip_ranges(ip_ranges):
743     ip_ranges_start = set()
744     ip_ranges_end = set()
745     if not ip_ranges:
746         return (ip_ranges_start, ip_ranges_end)
747
748     for ip_range in ip_ranges:
749         ip_ranges_start.add(ip_range['start'])
750         ip_ranges_end.add(ip_range['end'])
751
752     return (ip_ranges_start, ip_ranges_end)
753
754
755 # [{'start':'192.168.0.10', 'end':'192.168.0.20'},
756 #  {'start':'192.168.0.21', 'end':'192.168.0.22'}] and
757 # [{'start':'192.168.0.10', 'end':'192.168.0.22'}] is equal here
758 def is_ip_ranges_equal(ip_ranges1, ip_ranges2):
759     if not ip_ranges1 and not ip_ranges2:
760         return True
761     if ((ip_ranges1 and not ip_ranges2) or
762             (ip_ranges2 and not ip_ranges1)):
763         return False
764     ip_ranges_1 = copy.deepcopy(ip_ranges1)
765     ip_ranges_2 = copy.deepcopy(ip_ranges2)
766     merged_ip_ranges1 = merge_ip_ranges(ip_ranges_1)
767     merged_ip_ranges2 = merge_ip_ranges(ip_ranges_2)
768     ip_ranges1_start, ip_ranges1_end = _split_ip_ranges(merged_ip_ranges1)
769     ip_ranges2_start, ip_ranges2_end = _split_ip_ranges(merged_ip_ranges2)
770     if (ip_ranges1_start == ip_ranges2_start and
771             ip_ranges1_end == ip_ranges2_end):
772         return True
773     else:
774         return False
775
776
777 def get_dvs_interfaces(host_interfaces):
778     dvs_interfaces = []
779     if not isinstance(host_interfaces, list):
780         host_interfaces = eval(host_interfaces)
781     for interface in host_interfaces:
782         if not isinstance(interface, dict):
783             interface = eval(interface)
784         if ('vswitch_type' in interface and
785                 interface['vswitch_type'] == 'dvs'):
786             dvs_interfaces.append(interface)
787
788     return dvs_interfaces
789
790
791 def get_clc_pci_info(pci_info):
792     clc_pci = []
793     flag1 = 'Intel Corporation Coleto Creek PCIe Endpoint'
794     flag2 = '8086:0435'
795     for pci in pci_info:
796         if flag1 in pci or flag2 in pci:
797             clc_pci.append(pci.split()[0])
798     return clc_pci
799
800
801 def cpu_str_to_list(spec):
802     """Parse a CPU set specification.
803
804     :param spec: cpu set string eg "1-4,^3,6"
805
806     Each element in the list is either a single
807     CPU number, a range of CPU numbers, or a
808     caret followed by a CPU number to be excluded
809     from a previous range.
810
811     :returns: a set of CPU indexes
812     """
813
814     cpusets = []
815     if not spec:
816         return cpusets
817
818     cpuset_ids = set()
819     cpuset_reject_ids = set()
820     for rule in spec.split(','):
821         rule = rule.strip()
822         # Handle multi ','
823         if len(rule) < 1:
824             continue
825         # Note the count limit in the .split() call
826         range_parts = rule.split('-', 1)
827         if len(range_parts) > 1:
828             # So, this was a range; start by converting the parts to ints
829             try:
830                 start, end = [int(p.strip()) for p in range_parts]
831             except ValueError:
832                 raise exception.Invalid(_("Invalid range expression %r")
833                                         % rule)
834             # Make sure it's a valid range
835             if start > end:
836                 raise exception.Invalid(_("Invalid range expression %r")
837                                         % rule)
838             # Add available CPU ids to set
839             cpuset_ids |= set(range(start, end + 1))
840         elif rule[0] == '^':
841             # Not a range, the rule is an exclusion rule; convert to int
842             try:
843                 cpuset_reject_ids.add(int(rule[1:].strip()))
844             except ValueError:
845                 raise exception.Invalid(_("Invalid exclusion "
846                                           "expression %r") % rule)
847         else:
848             # OK, a single CPU to include; convert to int
849             try:
850                 cpuset_ids.add(int(rule))
851             except ValueError:
852                 raise exception.Invalid(_("Invalid inclusion "
853                                           "expression %r") % rule)
854
855     # Use sets to handle the exclusion rules for us
856     cpuset_ids -= cpuset_reject_ids
857     cpusets = list(cpuset_ids)
858     cpusets.sort()
859     return cpusets
860
861
862 def cpu_list_to_str(cpu_list):
863     """Parse a CPU list to string.
864
865     :param cpu_list: eg "[1,2,3,4,6,7]"
866
867     :returns: a string of CPU ranges, eg 1-4,6,7
868     """
869     spec = ''
870     if not cpu_list:
871         return spec
872
873     cpu_list.sort()
874     count = 0
875     group_cpus = []
876     tmp_cpus = []
877     for cpu in cpu_list:
878         if count == 0:
879             init = cpu
880             tmp_cpus.append(cpu)
881         else:
882             if cpu == (init + count):
883                 tmp_cpus.append(cpu)
884             else:
885                 group_cpus.append(tmp_cpus)
886                 tmp_cpus = []
887                 count = 0
888                 init = cpu
889                 tmp_cpus.append(cpu)
890         count += 1
891
892     group_cpus.append(tmp_cpus)
893
894     for group in group_cpus:
895         if len(group) > 2:
896             group_spec = ("%s-%s" % (group[0], group[0]+len(group)-1))
897         else:
898             group_str = [str(num) for num in group]
899             group_spec = ','.join(group_str)
900         if spec:
901             spec += ',' + group_spec
902         else:
903             spec = group_spec
904
905     return spec
906
907
908 def simple_subprocess_call(cmd):
909     return_code = subprocess.call(cmd,
910                                   shell=True,
911                                   stdout=subprocess.PIPE,
912                                   stderr=subprocess.PIPE)
913     return return_code
914
915
916 def translate_quotation_marks_for_shell(orig_str):
917     translated_str = ''
918     quotation_marks = '"'
919     quotation_marks_count = orig_str.count(quotation_marks)
920     if quotation_marks_count > 0:
921         replace_marks = '\\"'
922         translated_str = orig_str.replace(quotation_marks, replace_marks)
923     else:
924         translated_str = orig_str
925     return translated_str
926
927
928 def translate_marks_4_sed_command(ori_str):
929     translated_str = ori_str
930     translated_marks = {
931         '/': '\/',
932         '.': '\.',
933         '"': '\\"'}
934     for translated_mark in translated_marks:
935         if translated_str.count(translated_mark):
936             translated_str = translated_str.\
937                 replace(translated_mark, translated_marks[translated_mark])
938     return translated_str