def ssh_execute(ssh, cmd, process_input=None, addl_env=None, check_exit_code=True): LOG.debug('Running cmd (SSH): %s', cmd) if addl_env: raise InvalidArgumentError(_('Environment not supported over SSH')) if process_input: # This is (probably) fixable if we need it... raise InvalidArgumentError(_('process_input not supported over SSH')) stdin_stream, stdout_stream, stderr_stream = ssh.exec_command(cmd) channel = stdout_stream.channel # NOTE(justinsb): This seems suspicious... # ...other SSH clients have buffering issues with this approach stdout = stdout_stream.read() stderr = stderr_stream.read() stdin_stream.close() exit_status = channel.recv_exit_status() # exit_status == -1 if no exit code was returned if exit_status != -1: LOG.debug('Result was %s' % exit_status) if check_exit_code and exit_status != 0: raise ProcessExecutionError(exit_code=exit_status, stdout=stdout, stderr=stderr, cmd=cmd) return (stdout, stderr)
def get_size(self, location, context=None): """ Takes a `glance.store.location.Location` object that indicates where to find the image file and returns the image size :param location `glance.store.location.Location` object, supplied from glance.store.location.get_location_from_uri() :raises `glance.store.exceptions.NotFound` if image does not exist :rtype int """ loc = location.store_location try: self._check_context(context) volume = get_cinderclient(self.conf, context).volumes.get(loc.volume_id) # GB unit convert to byte return volume.size * (1024**3) except cinder_exception.NotFound as e: reason = _("Failed to get image size due to " "volume can not be found: %s") % self.volume_id LOG.error(reason) raise exceptions.NotFound(reason) except Exception as e: LOG.exception(_("Failed to get image size due to " "internal error: %s") % e) return 0
def configure_add(self): """ Configure the Store to use the stored configuration options Any store that needs special configuration should implement this method. If the store was not able to successfully configure itself, it should raise `exceptions.BadStoreConfiguration` """ try: self.chunk_size = CONF.sheepdog_store_chunk_size * units.Mi self.addr = CONF.sheepdog_store_address self.port = CONF.sheepdog_store_port except cfg.ConfigFileValueError as e: reason = _("Error in store configuration: %s") % e LOG.error(reason) raise exceptions.BadStoreConfiguration(store_name='sheepdog', reason=reason) try: processutils.execute("collie", shell=True) except processutils.ProcessExecutionError as exc: reason = _("Error in store configuration: %s") % exc LOG.error(reason) raise exceptions.BadStoreConfiguration(store_name='sheepdog', reason=reason)
def parse_uri(self, uri): if not uri.startswith('cinder://'): reason = _("URI must start with cinder://") LOG.error(reason) raise exceptions.BadStoreUri(uri, reason) self.scheme = 'cinder' self.volume_id = uri[9:] if not utils.is_uuid_like(self.volume_id): reason = _("URI contains invalid volume ID: %s") % self.volume_id LOG.error(reason) raise exceptions.BadStoreUri(uri, reason)
def deprecated(self, msg, *args, **kwargs): """Call this method when a deprecated feature is used. If the system is configured for fatal deprecations then the message is logged at the 'critical' level and :class:`DeprecatedConfig` will be raised. Otherwise, the message will be logged (once) at the 'warn' level. :raises: :class:`DeprecatedConfig` if the system is configured for fatal deprecations. """ stdmsg = _("Deprecated: %s") % msg if CONF.fatal_deprecations: self.critical(stdmsg, *args, **kwargs) raise DeprecatedConfig(msg=stdmsg) # Using a list because a tuple with dict can't be stored in a set. sent_args = self._deprecated_messages_sent.setdefault(msg, list()) if args in sent_args: # Already logged this message, so don't log it again. return sent_args.append(args) self.warn(stdmsg, *args, **kwargs)
def bool_from_string(subject, strict=False, default=False): """Interpret a string as a boolean. A case-insensitive match is performed such that strings matching 't', 'true', 'on', 'y', 'yes', or '1' are considered True and, when `strict=False`, anything else returns the value specified by 'default'. Useful for JSON-decoded stuff and config file parsing. If `strict=True`, unrecognized values, including None, will raise a ValueError which is useful when parsing values passed in from an API call. Strings yielding False are 'f', 'false', 'off', 'n', 'no', or '0'. """ if not isinstance(subject, six.string_types): subject = six.text_type(subject) lowered = subject.strip().lower() if lowered in TRUE_STRINGS: return True elif lowered in FALSE_STRINGS: return False elif strict: acceptable = ', '.join( "'%s'" % s for s in sorted(TRUE_STRINGS + FALSE_STRINGS)) msg = _("Unrecognized value '%(val)s', acceptable values are:" " %(acceptable)s") % {'val': subject, 'acceptable': acceptable} raise ValueError(msg) else: return default
def _check_context(self, context): """ Configure the Store to use the stored configuration options Any store that needs special configuration should implement this method. If the store was not able to successfully configure itself, it should raise `exceptions.BadStoreConfiguration` """ if context is None: reason = _("Cinder storage requires a context.") raise exceptions.BadStoreConfiguration(store_name="cinder", reason=reason) if context.service_catalog is None: reason = _("Cinder storage requires a service catalog.") raise exceptions.BadStoreConfiguration(store_name="cinder", reason=reason)
def string_to_bytes(text, unit_system='IEC', return_int=False): """Converts a string into an float representation of bytes. The units supported for IEC :: Kb(it), Kib(it), Mb(it), Mib(it), Gb(it), Gib(it), Tb(it), Tib(it) KB, KiB, MB, MiB, GB, GiB, TB, TiB The units supported for SI :: kb(it), Mb(it), Gb(it), Tb(it) kB, MB, GB, TB Note that the SI unit system does not support capital letter 'K' :param text: String input for bytes size conversion. :param unit_system: Unit system for byte size conversion. :param return_int: If True, returns integer representation of text in bytes. (default: decimal) :returns: Numerical representation of text in bytes. :raises ValueError: If text has an invalid value. """ try: base, reg_ex = UNIT_SYSTEM_INFO[unit_system] except KeyError: msg = _('Invalid unit system: "%s"') % unit_system raise ValueError(msg) match = reg_ex.match(text) if match: magnitude = float(match.group(1)) unit_prefix = match.group(2) if match.group(3) in ['b', 'bit']: magnitude /= 8 else: msg = _('Invalid string format: %s') % text raise ValueError(msg) if not unit_prefix: res = magnitude else: res = magnitude * pow(base, UNIT_PREFIX_EXPONENT[unit_prefix]) if return_int: return int(math.ceil(res)) return res
def __init__(self, stdout=None, stderr=None, exit_code=None, cmd=None, description=None): self.exit_code = exit_code self.stderr = stderr self.stdout = stdout self.cmd = cmd self.description = description if description is None: description = _("Unexpected error while running command.") if exit_code is None: exit_code = '-' message = _('%(description)s\n' 'Command: %(cmd)s\n' 'Exit code: %(exit_code)s\n' 'Stdout: %(stdout)r\n' 'Stderr: %(stderr)r') % {'description': description, 'cmd': cmd, 'exit_code': exit_code, 'stdout': stdout, 'stderr': stderr} super(ProcessExecutionError, self).__init__(message)
def _delete_image(self, image_name, snapshot_name=None, context=None): """ Delete RBD image and snapshot. :param image_name Image's name :param snapshot_name Image snapshot's name :raises NotFound if image does not exist; InUseByStore if image is in use or snapshot unprotect failed """ with rados.Rados(conffile=self.conf_file, rados_id=self.user) as conn: with conn.open_ioctx(self.pool) as ioctx: try: # First remove snapshot. if snapshot_name is not None: with rbd.Image(ioctx, image_name) as image: try: image.unprotect_snap(snapshot_name) except rbd.ImageBusy: log_msg = _("snapshot %(image)s@%(snap)s " "could not be unprotected because " "it is in use") LOG.debug(log_msg % {'image': image_name, 'snap': snapshot_name}) raise exceptions.InUseByStore() image.remove_snap(snapshot_name) # Then delete image. rbd.RBD().remove(ioctx, image_name) except rbd.ImageNotFound: raise exceptions.NotFound(message= _("RBD image %s does not exist") % image_name) except rbd.ImageBusy: log_msg = _("image %s could not be removed " "because it is in use") LOG.debug(log_msg % image_name) raise exceptions.InUseByStore()
def get_size(self, location): """ Takes a `glance.store.location.Location` object that indicates where to find the image file and returns the image size :param location `glance.store.location.Location` object, supplied from glance.store.location.get_location_from_uri() :raises `glance.store.exceptions.NotFound` if image does not exist :rtype int """ loc = location.store_location image = SheepdogImage(self.addr, self.port, loc.image, self.chunk_size) if not image.exist(): raise exceptions.NotFound(_("Sheepdog image %s does not exist") % image.name) return image.get_size()
def __iter__(self): try: with rados.Rados(conffile=self.conf_file, rados_id=self.user) as conn: with conn.open_ioctx(self.pool) as ioctx: with rbd.Image(ioctx, self.name) as image: img_info = image.stat() size = img_info['size'] bytes_left = size while bytes_left > 0: length = min(self.chunk_size, bytes_left) data = image.read(size - bytes_left, length) bytes_left -= len(data) yield data raise StopIteration() except rbd.ImageNotFound: raise exceptions.NotFound( _('RBD image %s does not exist') % self.name)
def delete(self, location): """ Takes a `glance.store.location.Location` object that indicates where to find the image file to delete :location `glance.store.location.Location` object, supplied from glance.store.location.get_location_from_uri() :raises NotFound if image does not exist """ loc = location.store_location image = SheepdogImage(self.addr, self.port, loc.image, self.chunk_size) if not image.exist(): raise exceptions.NotFound(_("Sheepdog image %s does not exist") % loc.image) image.delete()
def get_cinderclient(conf, context): if conf.glance_store.cinder_endpoint_template: url = conf.glance_store.cinder_endpoint_template % context.to_dict() else: info = conf.glance_store.cinder_catalog_info service_type, service_name, endpoint_type = info.split(':') # extract the region if set in configuration if conf.glance_store.os_region_name: attr = 'region' filter_value = conf.glance_store.os_region_name else: attr = None filter_value = None # FIXME: the cinderclient ServiceCatalog object is mis-named. # It actually contains the entire access blob. # Only needed parts of the service catalog are passed in, see # nova/context.py. compat_catalog = { 'access': {'serviceCatalog': context.service_catalog or []}} sc = service_catalog.ServiceCatalog(compat_catalog) url = sc.url_for(attr=attr, filter_value=filter_value, service_type=service_type, service_name=service_name, endpoint_type=endpoint_type) LOG.debug(_('Cinderclient connection created using URL: %s') % url) c = cinderclient.Client(context.user, context.auth_tok, project_id=context.tenant, auth_url=url, insecure=conf.glance_store.cinder_api_insecure, retries=conf.glance_store.cinder_http_retries, cacert=conf.glance_store.cinder_ca_certificates_file) # noauth extracts user_id:project_id from auth_token c.client.auth_token = context.auth_tok or '%s:%s' % (context.user, context.tenant) c.client.management_url = url return c
def test_exception_to_unicode(self): class FakeException(Exception): def __str__(self): raise UnicodeError() exc = Exception('error message') ret = driver._exception_to_unicode(exc) self.assertIsInstance(ret, unicode) self.assertEqual(ret, 'error message') exc = Exception('\xa5 error message') ret = driver._exception_to_unicode(exc) self.assertIsInstance(ret, unicode) self.assertEqual(ret, ' error message') exc = FakeException('\xa5 error message') ret = driver._exception_to_unicode(exc) self.assertIsInstance(ret, unicode) self.assertEqual(ret, _("Caught '%(exception)s' exception.") % {'exception': 'FakeException'})
def add(self, image_id, image_file, image_size): """ Stores an image file with supplied identifier to the backend storage system and returns a tuple containing information about the stored image. :param image_id: The opaque image identifier :param image_file: The image data to write, as a file-like object :param image_size: The size of the image data to write, in bytes :retval tuple of URL in backing store, bytes written, and checksum :raises `glance.store.exceptions.Duplicate` if the image already existed """ image = SheepdogImage(self.addr, self.port, image_id, self.chunk_size) if image.exist(): raise exceptions.Duplicate(_("Sheepdog image %s already exists") % image_id) location = StoreLocation({'image': image_id}) checksum = hashlib.md5() image.create(image_size) try: total = left = image_size while left > 0: length = min(self.chunk_size, left) data = image_file.read(length) image.write(data, total - left, length) left -= length checksum.update(data) except Exception: # Note(zhiyan): clean up already received data when # error occurs such as ImageSizeLimitExceeded exceptions. with excutils.save_and_reraise_exception(): image.delete() return (location.get_uri(), image_size, checksum.hexdigest(), {})
def configure_add(self): """ Configure the Store to use the stored configuration options Any store that needs special configuration should implement this method. If the store was not able to successfully configure itself, it should raise `exceptions.BadStoreConfiguration` """ try: chunk = self.conf.glance_store.rbd_store_chunk_size self.chunk_size = chunk * 1024^2 # these must not be unicode since they will be passed to a # non-unicode-aware C library self.pool = str(self.conf.glance_store.rbd_store_pool) self.user = str(self.conf.glance_store.rbd_store_user) self.conf_file = str(self.conf.glance_store.rbd_store_ceph_conf) except cfg.ConfigFileValueError as e: reason = _("Error in store configuration: %s") % e LOG.error(reason) raise exceptions.BadStoreConfiguration(store_name='rbd', reason=reason)
def get_size(self, location, context=None): """ Takes a `glance.store.location.Location` object that indicates where to find the image file, and returns the size :param location `glance.store.location.Location` object, supplied from glance.store.location.get_location_from_uri() :raises `glance.store.exceptions.NotFound` if image does not exist """ loc = location.store_location with rados.Rados(conffile=self.conf_file, rados_id=self.user) as conn: with conn.open_ioctx(self.pool) as ioctx: try: with rbd.Image(ioctx, loc.image, snapshot=loc.snapshot) as image: img_info = image.stat() return img_info['size'] except rbd.ImageNotFound: msg = _('RBD image %s does not exist') % loc.get_uri() LOG.debug(msg) raise exceptions.NotFound(msg)
def _find_facility_from_conf(): facility_names = logging.handlers.SysLogHandler.facility_names facility = getattr(logging.handlers.SysLogHandler, CONF.syslog_log_facility, None) if facility is None and CONF.syslog_log_facility in facility_names: facility = facility_names.get(CONF.syslog_log_facility) if facility is None: valid_facilities = facility_names.keys() consts = ['LOG_AUTH', 'LOG_AUTHPRIV', 'LOG_CRON', 'LOG_DAEMON', 'LOG_FTP', 'LOG_KERN', 'LOG_LPR', 'LOG_MAIL', 'LOG_NEWS', 'LOG_AUTH', 'LOG_SYSLOG', 'LOG_USER', 'LOG_UUCP', 'LOG_LOCAL0', 'LOG_LOCAL1', 'LOG_LOCAL2', 'LOG_LOCAL3', 'LOG_LOCAL4', 'LOG_LOCAL5', 'LOG_LOCAL6', 'LOG_LOCAL7'] valid_facilities.extend(consts) raise TypeError(_('syslog facility must be one of: %s') % ', '.join("'%s'" % fac for fac in valid_facilities)) return facility
def parse_uri(self, uri): prefix = 'rbd://' if not uri.startswith(prefix): reason = _('URI must start with rbd://') msg = (_("Invalid URI: %(uri)s: %(reason)s") % {'uri': uri, 'reason': reason}) LOG.debug(msg) raise exceptions.BadStoreUri(message=reason) # convert to ascii since librbd doesn't handle unicode try: ascii_uri = str(uri) except UnicodeError: reason = _('URI contains non-ascii characters') msg = (_("Invalid URI: %(uri)s: %(reason)s") % {'uri': uri, 'reason': reason}) LOG.debug(msg) raise exceptions.BadStoreUri(message=reason) pieces = ascii_uri[len(prefix):].split('/') if len(pieces) == 1: self.fsid, self.pool, self.image, self.snapshot = \ (None, None, pieces[0], None) elif len(pieces) == 4: self.fsid, self.pool, self.image, self.snapshot = \ map(urllib.unquote, pieces) else: reason = _('URI must have exactly 1 or 4 components') msg = (_("Invalid URI: %(uri)s: %(reason)s") % {'uri': uri, 'reason': reason}) LOG.debug(msg) raise exceptions.BadStoreUri(message=reason) if any(map(lambda p: p == '', pieces)): reason = _('URI cannot contain empty components') msg = (_("Invalid URI: %(uri)s: %(reason)s") % {'uri': uri, 'reason': reason}) LOG.debug(msg) raise exceptions.BadStoreUri(message=reason)
except ImportError: rados = None rbd = None DEFAULT_POOL = 'images' DEFAULT_CONFFILE = '/etc/ceph/ceph.conf' DEFAULT_USER = None # let librados decide based on the Ceph conf file DEFAULT_CHUNKSIZE = 8 # in MiB DEFAULT_SNAPNAME = 'snap' LOG = logging.getLogger(__name__) _RBD_OPTS = [ cfg.IntOpt('rbd_store_chunk_size', default=DEFAULT_CHUNKSIZE, help=_('RADOS images will be chunked into objects of this size ' '(in megabytes). For best performance, this should be ' 'a power of two.')), cfg.StrOpt('rbd_store_pool', default=DEFAULT_POOL, help=_('RADOS pool in which images are stored.')), cfg.StrOpt('rbd_store_user', default=DEFAULT_USER, help=_('RADOS user to authenticate as (only applicable if ' 'using Cephx. If <None>, a default will be chosen based ' 'on the client. section in rbd_store_ceph_conf)')), cfg.StrOpt('rbd_store_ceph_conf', default=DEFAULT_CONFFILE, help=_('Ceph configuration file path. ' 'If <None>, librados will locate the default config. ' 'If using cephx authentication, this file should ' 'include a reference to the right keyring ' 'in a client.<USER> section')), ]
def add(self, image_id, image_file, image_size, context=None): """ Stores an image file with supplied identifier to the backend storage system and returns a tuple containing information about the stored image. :param image_id: The opaque image identifier :param image_file: The image data to write, as a file-like object :param image_size: The size of the image data to write, in bytes :retval tuple of URL in backing store, bytes written, checksum and a dictionary with storage system specific information :raises `glance.store.exceptions.Duplicate` if the image already existed """ checksum = hashlib.md5() image_name = str(image_id) with rados.Rados(conffile=self.conf_file, rados_id=self.user) as conn: fsid = None if hasattr(conn, 'get_fsid'): fsid = conn.get_fsid() with conn.open_ioctx(self.pool) as ioctx: order = int(math.log(self.chunk_size, 2)) LOG.debug('creating image %s with order %d and size %d', image_name, order, image_size) if image_size == 0: LOG.warning(_("since image size is zero we will be doing " "resize-before-write for each chunk which " "will be considerably slower than normal")) try: loc = self._create_image(fsid, ioctx, image_name, image_size, order) except rbd.ImageExists: raise exceptions.Duplicate(message= _('RBD image %s already exists') % image_id) try: with rbd.Image(ioctx, image_name) as image: bytes_written = 0 offset = 0 chunks = utils.chunkreadable(image_file, self.chunk_size) for chunk in chunks: # If the image size provided is zero we need to do # a resize for the amount we are writing. This will # be slower so setting a higher chunk size may # speed things up a bit. if image_size == 0: chunk_length = len(chunk) length = offset + chunk_length bytes_written += chunk_length LOG.debug(_("resizing image to %s KiB") % (length / 1024)) image.resize(length) LOG.debug(_("writing chunk at offset %s") % (offset)) offset += image.write(chunk, offset) checksum.update(chunk) if loc.snapshot: image.create_snap(loc.snapshot) image.protect_snap(loc.snapshot) except Exception as exc: # Delete image if one was created try: self._delete_image(loc.image, loc.snapshot) except exceptions.NotFound: pass raise exc # Make sure we send back the image size whether provided or inferred. if image_size == 0: image_size = bytes_written return (loc.get_uri(), image_size, checksum.hexdigest(), {})
def execute(*cmd, **kwargs): """Helper method to shell out and execute a command through subprocess. Allows optional retry. :param cmd: Passed to subprocess.Popen. :type cmd: string :param process_input: Send to opened process. :type process_input: string :param env_variables: Environment variables and their values that will be set for the process. :type env_variables: dict :param check_exit_code: Single bool, int, or list of allowed exit codes. Defaults to [0]. Raise :class:`ProcessExecutionError` unless program exits with one of these code. :type check_exit_code: boolean, int, or [int] :param delay_on_retry: True | False. Defaults to True. If set to True, wait a short amount of time before retrying. :type delay_on_retry: boolean :param attempts: How many times to retry cmd. :type attempts: int :param run_as_root: True | False. Defaults to False. If set to True, the command is prefixed by the command specified in the root_helper kwarg. :type run_as_root: boolean :param root_helper: command to prefix to commands called with run_as_root=True :type root_helper: string :param shell: whether or not there should be a shell used to execute this command. Defaults to false. :type shell: boolean :param loglevel: log level for execute commands. :type loglevel: int. (Should be logging.DEBUG or logging.INFO) :returns: (stdout, stderr) from process execution :raises: :class:`UnknownArgumentError` on receiving unknown arguments :raises: :class:`ProcessExecutionError` """ process_input = kwargs.pop('process_input', None) env_variables = kwargs.pop('env_variables', None) check_exit_code = kwargs.pop('check_exit_code', [0]) ignore_exit_code = False delay_on_retry = kwargs.pop('delay_on_retry', True) attempts = kwargs.pop('attempts', 1) run_as_root = kwargs.pop('run_as_root', False) root_helper = kwargs.pop('root_helper', '') shell = kwargs.pop('shell', False) loglevel = kwargs.pop('loglevel', logging.DEBUG) if isinstance(check_exit_code, bool): ignore_exit_code = not check_exit_code check_exit_code = [0] elif isinstance(check_exit_code, int): check_exit_code = [check_exit_code] if kwargs: raise UnknownArgumentError(_('Got unknown keyword args ' 'to utils.execute: %r') % kwargs) if run_as_root and hasattr(os, 'geteuid') and os.geteuid() != 0: if not root_helper: raise NoRootWrapSpecified( message=_('Command requested root, but did not ' 'specify a root helper.')) cmd = shlex.split(root_helper) + list(cmd) cmd = map(str, cmd) while attempts > 0: attempts -= 1 try: LOG.log(loglevel, 'Running cmd (subprocess): %s', strutils.mask_password(' '.join(cmd))) _PIPE = subprocess.PIPE # pylint: disable=E1101 if os.name == 'nt': preexec_fn = None close_fds = False else: preexec_fn = _subprocess_setup close_fds = True obj = subprocess.Popen(cmd, stdin=_PIPE, stdout=_PIPE, stderr=_PIPE, close_fds=close_fds, preexec_fn=preexec_fn, shell=shell, env=env_variables) result = None for _i in six.moves.range(20): # NOTE(russellb) 20 is an arbitrary number of retries to # prevent any chance of looping forever here. try: if process_input is not None: result = obj.communicate(process_input) else: result = obj.communicate() except OSError as e: if e.errno in (errno.EAGAIN, errno.EINTR): continue raise break obj.stdin.close() # pylint: disable=E1101 _returncode = obj.returncode # pylint: disable=E1101 LOG.log(loglevel, 'Result was %s' % _returncode) if not ignore_exit_code and _returncode not in check_exit_code: (stdout, stderr) = result raise ProcessExecutionError(exit_code=_returncode, stdout=stdout, stderr=stderr, cmd=' '.join(cmd)) return result except ProcessExecutionError: if not attempts: raise else: LOG.log(loglevel, '%r failed. Retrying.', cmd) if delay_on_retry: greenthread.sleep(random.randint(20, 200) / 100.0) finally: # NOTE(termie): this appears to be necessary to let the subprocess # call clean something up in between calls, without # it two execute calls in a row hangs the second one greenthread.sleep(0)
import glance.store.driver import glance.store.location LOG = logging.getLogger(__name__) DEFAULT_ADDR = 'localhost' DEFAULT_PORT = 7000 DEFAULT_CHUNKSIZE = 64 # in MiB LOG = logging.getLogger(__name__) sheepdog_opts = [ cfg.IntOpt('sheepdog_store_chunk_size', default=DEFAULT_CHUNKSIZE, help=_('Images will be chunked into objects of this size ' '(in megabytes). For best performance, this should be ' 'a power of two.')), cfg.IntOpt('sheepdog_store_port', default=DEFAULT_PORT, help=_('Port of sheep daemon.')), cfg.StrOpt('sheepdog_store_address', default=DEFAULT_ADDR, help=_('IP address of sheep daemon.')) ] CONF = cfg.CONF CONF.register_opts(sheepdog_opts) class SheepdogImage: """Class describing an image stored in Sheepdog storage.""" def __init__(self, addr, port, name, chunk_size):