 01c6bcd9e4
			
		
	
	01c6bcd9e4
	
	
	
		
			
			That patch allows a developer to remotely run a pydev debugger and a
glance worker process connect back to it.  Two configuration options
have been added:
    pydev_worker_debug_host <host>. Default is None
    pydev_worker_debug_port <port>. Default is 5678
The behavior is enabled if pydev_worker_debug_host is not None.  If
a pydev connection fails to be esstablished an exception is raised.
This patch will allow remote debugging as well as more seemless
debugging environments with IDEs like pycharm.
Change-Id: I7d3f60e373632b256f0e914f18204ce223a3486e
		
	
		
			
				
	
	
		
			434 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			434 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # vim: tabstop=4 shiftwidth=4 softtabstop=4
 | |
| 
 | |
| # Copyright 2010 United States Government as represented by the
 | |
| # Administrator of the National Aeronautics and Space Administration.
 | |
| # All Rights Reserved.
 | |
| #
 | |
| #    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | |
| #    not use this file except in compliance with the License. You may obtain
 | |
| #    a copy of the License at
 | |
| #
 | |
| #         http://www.apache.org/licenses/LICENSE-2.0
 | |
| #
 | |
| #    Unless required by applicable law or agreed to in writing, software
 | |
| #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | |
| #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | |
| #    License for the specific language governing permissions and limitations
 | |
| #    under the License.
 | |
| 
 | |
| """
 | |
| System-level utilities and helper functions.
 | |
| """
 | |
| 
 | |
| import errno
 | |
| 
 | |
| try:
 | |
|     from eventlet import sleep
 | |
| except ImportError:
 | |
|     from time import sleep
 | |
| 
 | |
| import functools
 | |
| import os
 | |
| import platform
 | |
| import subprocess
 | |
| import sys
 | |
| 
 | |
| from webob import exc
 | |
| 
 | |
| from glance.common import exception
 | |
| from glance.openstack.common import cfg
 | |
| import glance.openstack.common.log as logging
 | |
| 
 | |
| 
 | |
| LOG = logging.getLogger(__name__)
 | |
| 
 | |
| FEATURE_BLACKLIST = ['content-length', 'content-type', 'x-image-meta-size']
 | |
| 
 | |
| 
 | |
| def chunkreadable(iter, chunk_size=65536):
 | |
|     """
 | |
|     Wrap a readable iterator with a reader yielding chunks of
 | |
|     a preferred size, otherwise leave iterator unchanged.
 | |
| 
 | |
|     :param iter: an iter which may also be readable
 | |
|     :param chunk_size: maximum size of chunk
 | |
|     """
 | |
|     return chunkiter(iter, chunk_size) if hasattr(iter, 'read') else iter
 | |
| 
 | |
| 
 | |
| def chunkiter(fp, chunk_size=65536):
 | |
|     """
 | |
|     Return an iterator to a file-like obj which yields fixed size chunks
 | |
| 
 | |
|     :param fp: a file-like object
 | |
|     :param chunk_size: maximum size of chunk
 | |
|     """
 | |
|     while True:
 | |
|         chunk = fp.read(chunk_size)
 | |
|         if chunk:
 | |
|             yield chunk
 | |
|         else:
 | |
|             break
 | |
| 
 | |
| 
 | |
| def cooperative_iter(iter):
 | |
|     """
 | |
|     Return an iterator which schedules after each
 | |
|     iteration. This can prevent eventlet thread starvation.
 | |
| 
 | |
|     :param iter: an iterator to wrap
 | |
|     """
 | |
|     try:
 | |
|         for chunk in iter:
 | |
|             sleep(0)
 | |
|             yield chunk
 | |
|     except Exception, err:
 | |
|         msg = _("Error: cooperative_iter exception %s") % err
 | |
|         LOG.error(msg)
 | |
|         raise
 | |
| 
 | |
| 
 | |
| def cooperative_read(fd):
 | |
|     """
 | |
|     Wrap a file descriptor's read with a partial function which schedules
 | |
|     after each read. This can prevent eventlet thread starvation.
 | |
| 
 | |
|     :param fd: a file descriptor to wrap
 | |
|     """
 | |
|     def readfn(*args):
 | |
|         result = fd.read(*args)
 | |
|         sleep(0)
 | |
|         return result
 | |
|     return readfn
 | |
| 
 | |
| 
 | |
| class CooperativeReader(object):
 | |
|     """
 | |
|     An eventlet thread friendly class for reading in image data.
 | |
| 
 | |
|     When accessing data either through the iterator or the read method
 | |
|     we perform a sleep to allow a co-operative yield. When there is more than
 | |
|     one image being uploaded/downloaded this prevents eventlet thread
 | |
|     starvation, ie allows all threads to be scheduled periodically rather than
 | |
|     having the same thread be continuously active.
 | |
|     """
 | |
|     def __init__(self, fd):
 | |
|         """
 | |
|         :param fd: Underlying image file object
 | |
|         """
 | |
|         self.fd = fd
 | |
|         self.iterator = None
 | |
|         # NOTE(markwash): if the underlying supports read(), overwrite the
 | |
|         # default iterator-based implementation with cooperative_read which
 | |
|         # is more straightforward
 | |
|         if hasattr(fd, 'read'):
 | |
|             self.read = cooperative_read(fd)
 | |
| 
 | |
|     def read(self, length=None):
 | |
|         """Return the next chunk of the underlying iterator.
 | |
| 
 | |
|         This is replaced with cooperative_read in __init__ if the underlying
 | |
|         fd already supports read().
 | |
|         """
 | |
|         if self.iterator is None:
 | |
|             self.iterator = self.__iter__()
 | |
|         try:
 | |
|             return self.iterator.next()
 | |
|         except StopIteration:
 | |
|             return ''
 | |
| 
 | |
|     def __iter__(self):
 | |
|         return cooperative_iter(self.fd.__iter__())
 | |
| 
 | |
| 
 | |
| class LimitingReader(object):
 | |
|     """
 | |
|     Reader designed to fail when reading image data past the configured
 | |
|     allowable amount.
 | |
|     """
 | |
|     def __init__(self, data, limit):
 | |
|         """
 | |
|         :param data: Underlying image data object
 | |
|         :param limit: maximum number of bytes the reader should allow
 | |
|         """
 | |
|         self.data = data
 | |
|         self.limit = limit
 | |
|         self.bytes_read = 0
 | |
| 
 | |
|     def __iter__(self):
 | |
|         for chunk in self.data:
 | |
|             self.bytes_read += len(chunk)
 | |
|             if self.bytes_read > self.limit:
 | |
|                 raise exception.ImageSizeLimitExceeded()
 | |
|             else:
 | |
|                 yield chunk
 | |
| 
 | |
|     def read(self, i):
 | |
|         result = self.data.read(i)
 | |
|         self.bytes_read += len(result)
 | |
|         if self.bytes_read > self.limit:
 | |
|             raise exception.ImageSizeLimitExceeded()
 | |
|         return result
 | |
| 
 | |
| 
 | |
| def image_meta_to_http_headers(image_meta):
 | |
|     """
 | |
|     Returns a set of image metadata into a dict
 | |
|     of HTTP headers that can be fed to either a Webob
 | |
|     Request object or an httplib.HTTP(S)Connection object
 | |
| 
 | |
|     :param image_meta: Mapping of image metadata
 | |
|     """
 | |
|     headers = {}
 | |
|     for k, v in image_meta.items():
 | |
|         if v is not None:
 | |
|             if k == 'properties':
 | |
|                 for pk, pv in v.items():
 | |
|                     if pv is not None:
 | |
|                         headers["x-image-meta-property-%s"
 | |
|                                 % pk.lower()] = unicode(pv)
 | |
|             else:
 | |
|                 headers["x-image-meta-%s" % k.lower()] = unicode(v)
 | |
|     return headers
 | |
| 
 | |
| 
 | |
| def add_features_to_http_headers(features, headers):
 | |
|     """
 | |
|     Adds additional headers representing glance features to be enabled.
 | |
| 
 | |
|     :param headers: Base set of headers
 | |
|     :param features: Map of enabled features
 | |
|     """
 | |
|     if features:
 | |
|         for k, v in features.items():
 | |
|             if k.lower() in FEATURE_BLACKLIST:
 | |
|                 raise exception.UnsupportedHeaderFeature(feature=k)
 | |
|             if v is not None:
 | |
|                 headers[k.lower()] = unicode(v)
 | |
| 
 | |
| 
 | |
| def get_image_meta_from_headers(response):
 | |
|     """
 | |
|     Processes HTTP headers from a supplied response that
 | |
|     match the x-image-meta and x-image-meta-property and
 | |
|     returns a mapping of image metadata and properties
 | |
| 
 | |
|     :param response: Response to process
 | |
|     """
 | |
|     result = {}
 | |
|     properties = {}
 | |
| 
 | |
|     if hasattr(response, 'getheaders'):  # httplib.HTTPResponse
 | |
|         headers = response.getheaders()
 | |
|     else:  # webob.Response
 | |
|         headers = response.headers.items()
 | |
| 
 | |
|     for key, value in headers:
 | |
|         key = str(key.lower())
 | |
|         if key.startswith('x-image-meta-property-'):
 | |
|             field_name = key[len('x-image-meta-property-'):].replace('-', '_')
 | |
|             properties[field_name] = value or None
 | |
|         elif key.startswith('x-image-meta-'):
 | |
|             field_name = key[len('x-image-meta-'):].replace('-', '_')
 | |
|             result[field_name] = value or None
 | |
|     result['properties'] = properties
 | |
|     if 'size' in result:
 | |
|         try:
 | |
|             result['size'] = int(result['size'])
 | |
|         except ValueError:
 | |
|             raise exception.Invalid
 | |
|     for key in ('is_public', 'deleted', 'protected'):
 | |
|         if key in result:
 | |
|             result[key] = bool_from_string(result[key])
 | |
|     return result
 | |
| 
 | |
| 
 | |
| def bool_from_string(subject):
 | |
|     """Interpret a string as a boolean-like value."""
 | |
|     if isinstance(subject, bool):
 | |
|         return subject
 | |
|     elif isinstance(subject, int):
 | |
|         return subject == 1
 | |
|     if hasattr(subject, 'startswith'):  # str or unicode...
 | |
|         if subject.strip().lower() in ('true', 'on', '1', 'yes', 'y'):
 | |
|             return True
 | |
|     return False
 | |
| 
 | |
| 
 | |
| def safe_mkdirs(path):
 | |
|     try:
 | |
|         os.makedirs(path)
 | |
|     except OSError, e:
 | |
|         if e.errno != errno.EEXIST:
 | |
|             raise
 | |
| 
 | |
| 
 | |
| def safe_remove(path):
 | |
|     try:
 | |
|         os.remove(path)
 | |
|     except OSError, e:
 | |
|         if e.errno != errno.ENOENT:
 | |
|             raise
 | |
| 
 | |
| 
 | |
| class PrettyTable(object):
 | |
|     """Creates an ASCII art table for use in bin/glance
 | |
| 
 | |
|     Example:
 | |
| 
 | |
|         ID  Name              Size         Hits
 | |
|         --- ----------------- ------------ -----
 | |
|         122 image                       22     0
 | |
|     """
 | |
|     def __init__(self):
 | |
|         self.columns = []
 | |
| 
 | |
|     def add_column(self, width, label="", just='l'):
 | |
|         """Add a column to the table
 | |
| 
 | |
|         :param width: number of characters wide the column should be
 | |
|         :param label: column heading
 | |
|         :param just: justification for the column, 'l' for left,
 | |
|                      'r' for right
 | |
|         """
 | |
|         self.columns.append((width, label, just))
 | |
| 
 | |
|     def make_header(self):
 | |
|         label_parts = []
 | |
|         break_parts = []
 | |
|         for width, label, _ in self.columns:
 | |
|             # NOTE(sirp): headers are always left justified
 | |
|             label_part = self._clip_and_justify(label, width, 'l')
 | |
|             label_parts.append(label_part)
 | |
| 
 | |
|             break_part = '-' * width
 | |
|             break_parts.append(break_part)
 | |
| 
 | |
|         label_line = ' '.join(label_parts)
 | |
|         break_line = ' '.join(break_parts)
 | |
|         return '\n'.join([label_line, break_line])
 | |
| 
 | |
|     def make_row(self, *args):
 | |
|         row = args
 | |
|         row_parts = []
 | |
|         for data, (width, _, just) in zip(row, self.columns):
 | |
|             row_part = self._clip_and_justify(data, width, just)
 | |
|             row_parts.append(row_part)
 | |
| 
 | |
|         row_line = ' '.join(row_parts)
 | |
|         return row_line
 | |
| 
 | |
|     @staticmethod
 | |
|     def _clip_and_justify(data, width, just):
 | |
|         # clip field to column width
 | |
|         clipped_data = str(data)[:width]
 | |
| 
 | |
|         if just == 'r':
 | |
|             # right justify
 | |
|             justified = clipped_data.rjust(width)
 | |
|         else:
 | |
|             # left justify
 | |
|             justified = clipped_data.ljust(width)
 | |
| 
 | |
|         return justified
 | |
| 
 | |
| 
 | |
| def get_terminal_size():
 | |
| 
 | |
|     def _get_terminal_size_posix():
 | |
|         import fcntl
 | |
|         import struct
 | |
|         import termios
 | |
| 
 | |
|         height_width = None
 | |
| 
 | |
|         try:
 | |
|             height_width = struct.unpack('hh', fcntl.ioctl(sys.stderr.fileno(),
 | |
|                                          termios.TIOCGWINSZ,
 | |
|                                          struct.pack('HH', 0, 0)))
 | |
|         except:
 | |
|             pass
 | |
| 
 | |
|         if not height_width:
 | |
|             try:
 | |
|                 p = subprocess.Popen(['stty', 'size'],
 | |
|                                      shell=False,
 | |
|                                      stdout=subprocess.PIPE,
 | |
|                                      stderr=open(os.devnull, 'w'))
 | |
|                 result = p.communicate()
 | |
|                 if p.returncode == 0:
 | |
|                     return tuple(int(x) for x in result[0].split())
 | |
|             except:
 | |
|                 pass
 | |
| 
 | |
|         return height_width
 | |
| 
 | |
|     def _get_terminal_size_win32():
 | |
|         try:
 | |
|             from ctypes import windll, create_string_buffer
 | |
|             handle = windll.kernel32.GetStdHandle(-12)
 | |
|             csbi = create_string_buffer(22)
 | |
|             res = windll.kernel32.GetConsoleScreenBufferInfo(handle, csbi)
 | |
|         except:
 | |
|             return None
 | |
|         if res:
 | |
|             import struct
 | |
|             unpack_tmp = struct.unpack("hhhhHhhhhhh", csbi.raw)
 | |
|             (bufx, bufy, curx, cury, wattr,
 | |
|              left, top, right, bottom, maxx, maxy) = unpack_tmp
 | |
|             height = bottom - top + 1
 | |
|             width = right - left + 1
 | |
|             return (height, width)
 | |
|         else:
 | |
|             return None
 | |
| 
 | |
|     def _get_terminal_size_unknownOS():
 | |
|         raise NotImplementedError
 | |
| 
 | |
|     func = {'posix': _get_terminal_size_posix,
 | |
|             'win32': _get_terminal_size_win32}
 | |
| 
 | |
|     height_width = func.get(platform.os.name, _get_terminal_size_unknownOS)()
 | |
| 
 | |
|     if height_width is None:
 | |
|         raise exception.Invalid()
 | |
| 
 | |
|     for i in height_width:
 | |
|         if not isinstance(i, int) or i <= 0:
 | |
|             raise exception.Invalid()
 | |
| 
 | |
|     return height_width[0], height_width[1]
 | |
| 
 | |
| 
 | |
| def mutating(func):
 | |
|     """Decorator to enforce read-only logic"""
 | |
|     @functools.wraps(func)
 | |
|     def wrapped(self, req, *args, **kwargs):
 | |
|         if req.context.read_only:
 | |
|             msg = _("Read-only access")
 | |
|             LOG.debug(msg)
 | |
|             raise exc.HTTPForbidden(msg, request=req,
 | |
|                                     content_type="text/plain")
 | |
|         return func(self, req, *args, **kwargs)
 | |
|     return wrapped
 | |
| 
 | |
| 
 | |
| def setup_remote_pydev_debug(host, port):
 | |
| 
 | |
|         error_msg = ('Error setting up the debug environment.  Verify that the'
 | |
|                      ' option pydev_worker_debug_port is pointing to a valid '
 | |
|                      'hostname or IP on which a pydev server is listening on'
 | |
|                      ' the port indicated by pydev_worker_debug_port.')
 | |
| 
 | |
|         try:
 | |
|             from pydev import pydevd
 | |
| 
 | |
|             pydevd.settrace(host,
 | |
|                             port=port,
 | |
|                             stdoutToServer=True,
 | |
|                             stderrToServer=True)
 | |
|             return True
 | |
|         except:
 | |
|             LOG.exception(error_msg)
 | |
|             raise
 |