Ejemplo n.º 1
0
def start_service(api_class, service_class, log,
                  services=None, host='localhost', port=9100, killprocgrp=False):
    """Start a Data API service.

    Args:
        api_class (BaseService): The custom API service class, e.g.,
                    `doekbase.data_api.taxonomy.taxon.api.TaxonService`
        service_class (type): The Thrift auto-generated service class
        log (logging.Logger): Logging object
        services (dict): Service configuration dictionary, passed to
                         constructor of the `api_class`.
        host (str): Service host (will default to 'localhost')
        port (int): Service port, e.g. 9101
        killprocgrp (bool): if True, kill process group on exit
    """
    assert issubclass(api_class, BaseService), \
        'Invalid "api_class": must be a subclass of ' \
        'doekbase.data_api.service_core.BaseService'
    assert hasattr(service_class, 'Processor'), 'Invalid "service_class": ' \
                                                'missing "Processor" attribute'
    assert isinstance(port, int), 'The "port" must be an integer'

    svc_t0 = util.log_start(log, 'start_service',
                            kvp=dict(host=host, port=port))

    # Create server
    services = services or SERVICES_DICT
    handler = api_class(services)
    processor = service_class.Processor(handler)
    pfactory = TBinaryProtocol.TBinaryProtocolFactory()
    resource = TTwisted.ThriftResource(processor, pfactory, pfactory)
    site = twisted.web.server.Site(resource=resource)
    twisted.internet.reactor.listenTCP(port, site, interface=host)

    # Kill entire process group on shutdown
    if killprocgrp:
        twisted.internet.reactor.addSystemEventTrigger('before', 'shutdown',
                                                       functools.partial(
                                                           kill_process_group,
                                                           log=log))

    # Run server
    sname = api_class.__name__
    shost = host or 'localhost'
    util.log_start(log, 'server', kvp=dict(name=sname, host=shost, port=port))
    t0 = util.log_start(log, 'twisted.internet.reactor.run',
                        level=logging.DEBUG)
    try:
        twisted.internet.reactor.run()
    except Exception as err:
        log.error('msg="Abort {} server on error"'.format(sname))
        util.log_end(log, t0, 'twisted.internet.reactor.run',
                     status_code=1, level=logging.ERROR, kvp=dict(msg=err))
        raise
    finally:
        util.log_end(log, t0, 'twisted.internet.reactor.run')

    util.log_end(log, svc_t0, 'start_service',
                            kvp=dict(host=host, port=port))
    return 0
Ejemplo n.º 2
0
    def __init__(self, client, ref):
        """Create API instance with an object reference and client

        Args:
          client (object):  Implementation of thrift_service.Iface
          ref (str): Object reference
        """
        assert isinstance(client, thrift_service.Iface)
        if not REF_PATTERN.match(ref):
            raise ValueError('Format error for "{}"'.format(ref))
        self._client, self._ref = client, ref

        # Set up client
        t0 = log_start(_log, 'client.init')
        auth_info = ttypes.AuthInfo(token=get_auth_token())
        self._client.init(auth_info)
        log_end(_log, t0, 'client.init')

        # Create internal state
        t0 = log_start(_log, 'client.get_info', kvp=dict(ref=ref))
        self._info = self._client.get_info(ref)
        log_end(_log, t0, 'client.get_info')
        self._id = self._info.object_id
        self._name = self._info.object_name
        self._typestring = self._info.type_string
        self._version = self._info.version
Ejemplo n.º 3
0
    def __init__(self, client, ref):
        """Create API instance with an object reference and client

        Args:
          client (object):  Implementation of thrift_service.Iface
          ref (str): Object reference
        """
        assert isinstance(client, thrift_service.Iface)
        if not REF_PATTERN.match(ref):
            raise ValueError('Format error for "{}"'.format(ref))
        self._client, self._ref = client, ref

        # Set up client
        t0 = log_start(_log, 'client.init')
        auth_info = ttypes.AuthInfo(token=get_auth_token())
        self._client.init(auth_info)
        log_end(_log, t0, 'client.init')

        # Create internal state
        t0 = log_start(_log, 'client.get_info', kvp=dict(ref=ref))
        self._info = self._client.get_info(ref)
        log_end(_log, t0, 'client.get_info')
        self._id = self._info.object_id
        self._name = self._info.object_name
        self._typestring = self._info.type_string
        self._version = self._info.version
Ejemplo n.º 4
0
 def get_data(self):
     t0 = log_start(_log, 'get_data')
     s = ''
     try:
         t1 = log_start(_log, 'get_data.query')
         data_dict = self.ws_client.get_objects([
             {"ref": self.ref}])[0]["data"]
         log_end(_log, t1, 'get_data.query')
         t1 = log_start(_log, 'get_data.dump')
         s = json.dumps(data_dict)
         log_end(_log, t1, 'get_data.dump')
     except Exception as err:
         print("@@ died in .dumps: {}".format(err))
     log_end(_log, t0, 'get_data')
     return s
Ejemplo n.º 5
0
 def _init_ws_from_files(self, path):
     ext = '.msgpack'
     extlen = len(ext)
     WorkspaceFile.use_msgpack = True
     client = WorkspaceFile(path)
     num_loaded = 0
     for name in os.listdir(path):
         if name.endswith(ext):
             ref = name[:-extlen]
             t0 = log_start(_log,
                            'load',
                            level=logging.DEBUG,
                            kvp={'ref': ref})
             client.load(ref)
             log_end(_log,
                     t0,
                     'client.load',
                     level=logging.DEBUG,
                     kvp={'ref': ref})
         num_loaded += 1
     if num_loaded == 0:
         raise ValueError(
             'No files with extension "{e}" found in path {p}'.format(
                 e=ext, p=path))
     return client
Ejemplo n.º 6
0
 def _get_annotation(ga):
     t0 = log_start(_log, "get_annotation")
     try:
         feature_types = ga.get_feature_types()
     except Exception as err:
         raise RuntimeError("Cannot get feature_types: {}".format(err))
     ann = {
         "feature_" + k: getattr(ga, "get_feature_" + k)(feature_types) for k in ("type_descriptions", "type_counts")
     }
     ann["feature_types"] = feature_types
     log_end(_log, t0, "get_annotation")
     return ann
Ejemplo n.º 7
0
 def _get_annotation(ga):
     t0 = log_start(_log, 'get_annotation')
     try:
         feature_types = ga.get_feature_types()
     except Exception as err:
         raise RuntimeError('Cannot get feature_types: {}'.format(err))
     ann = {
         'feature_' + k: getattr(ga, 'get_feature_' + k)(feature_types)
         for k in ('type_descriptions', 'type_counts')
     }
     ann['feature_types'] = feature_types
     log_end(_log, t0, 'get_annotation')
     return ann
Ejemplo n.º 8
0
 def _get_taxon(ga):
     t0 = log_start(_log, 'get_taxon')
     try:
         taxon = ga.get_taxon()
     except Exception as err:
         raise RuntimeError('Cannot get taxon: {}'.format(err))
     txn = {
         k: getattr(taxon, 'get_' + k)()
         for k in ('taxonomic_id', 'kingdom', 'domain', 'genetic_code',
                   'scientific_name', 'aliases', 'scientific_lineage')
     }
     txn['lineage_list'] = txn['scientific_lineage'].split(';')
     log_end(_log, t0, 'get_taxon')
     return txn
Ejemplo n.º 9
0
 def _get_assembly(ga):
     t0 = log_start(_log, 'get_assembly')
     try:
         assembly = ga.get_assembly()
     except Exception as err:
         raise RuntimeError('Cannot get assembly: {}'.format(err))
     asy = {
         k1: getattr(assembly, 'get_' + k2)()
         for k1, k2 in (('number_of_contigs',
                         'number_contigs'), ('total_length', 'dna_size'),
                        ('total_gc_content',
                         'gc_content'), ('contig_length', 'contig_lengths'),
                        ('contig_gc_content', 'contig_gc_content'))
     }
     log_end(_log, t0, 'get_assembly')
     return asy
Ejemplo n.º 10
0
def kill_process_group(log):
    """Kill entire process group on Twisted shutdown.

    Args:
        log (logging.Logger): Logger
    """
    pid = os.getpid()  # my pid
    grpid = -os.getpgid(pid)  # my process group
    signo = signal.SIGINT  # the signal to send
    t = util.log_start(log, 'kill_process_group', level=logging.WARN,
                       kvp=dict(pid=pid, group_pid=grpid, signal=signo))
    # ignore signal in this process (Twisted is already shutting down)
    signal.signal(signo, signal.SIG_IGN)
    # send the signal to my process group
    os.kill(grpid, signo)
    util.log_end(log, t, 'kill_process_group', level=logging.WARN,
                 kvp=dict(pid=pid, group_pid=grpid, signal=signo))
Ejemplo n.º 11
0
 def _get_assembly(ga):
     t0 = log_start(_log, "get_assembly")
     try:
         assembly = ga.get_assembly()
     except Exception as err:
         raise RuntimeError("Cannot get assembly: {}".format(err))
     asy = {
         k1: getattr(assembly, "get_" + k2)()
         for k1, k2 in (
             ("number_of_contigs", "number_contigs"),
             ("total_length", "dna_size"),
             ("total_gc_content", "gc_content"),
             ("contig_length", "contig_lengths"),
             ("contig_gc_content", "contig_gc_content"),
         )
     }
     log_end(_log, t0, "get_assembly")
     return asy
Ejemplo n.º 12
0
 def _init_ws_from_files(self, path):
     ext = '.msgpack'
     extlen = len(ext)
     WorkspaceFile.use_msgpack = True
     client = WorkspaceFile(path)
     num_loaded = 0
     for name in os.listdir(path):
         if name.endswith(ext):
             ref = name[:-extlen]
             t0 = log_start(_log, 'load', level=logging.DEBUG,
                            kvp={'ref': ref})
             client.load(ref)
             log_end(_log, t0, 'client.load', level=logging.DEBUG,
                     kvp={'ref': ref})
         num_loaded += 1
     if num_loaded == 0:
         raise ValueError('No files with extension "{e}" found in path {p}'
                          .format(e=ext, p=path))
     return client
Ejemplo n.º 13
0
 def _get_taxon(ga):
     t0 = log_start(_log, "get_taxon")
     try:
         taxon = ga.get_taxon()
     except Exception as err:
         raise RuntimeError("Cannot get taxon: {}".format(err))
     txn = {
         k: getattr(taxon, "get_" + k)()
         for k in (
             "taxonomic_id",
             "kingdom",
             "domain",
             "genetic_code",
             "scientific_name",
             "aliases",
             "scientific_lineage",
         )
     }
     txn["lineage_list"] = txn["scientific_lineage"].split(";")
     log_end(_log, t0, "get_taxon")
     return txn
Ejemplo n.º 14
0
def kill_process_group(log):
    """Kill entire process group on Twisted shutdown.

    Args:
        log (logging.Logger): Logger
    """
    pid = os.getpid()  # my pid
    grpid = -os.getpgid(pid)  # my process group
    signo = signal.SIGINT  # the signal to send
    t = util.log_start(log,
                       'kill_process_group',
                       level=logging.WARN,
                       kvp=dict(pid=pid, group_pid=grpid, signal=signo))
    # ignore signal in this process (Twisted is already shutting down)
    signal.signal(signo, signal.SIG_IGN)
    # send the signal to my process group
    os.kill(grpid, signo)
    util.log_end(log,
                 t,
                 'kill_process_group',
                 level=logging.WARN,
                 kvp=dict(pid=pid, group_pid=grpid, signal=signo))
Ejemplo n.º 15
0
    def load(self, ref):
        """Load data from a given reference.

        The reference will be translated into a file to load,
        using the following formula:
        ``<working_directory> + '/' + <ref> + <ext>`,
        where ``<working_directory>`` is the path given to the class
        constructor, ``<ref>`` is the reference given to this
        function, and
        ``<ext>`` is a file extension '.msgpack' if
        `use_msgpack` is True and '.json' otherwise.

        Thus, for ``WorkspaceFile('/tmp/data').load('foo_bar')``,
        the path loaded would be '/tmp/data/foo_bar.msgpack'.

        See class documentation on format of input data.

        Args:
          ref (str): The reference

        Notes:
          * Post-condition: Object is loaded if and only if that reference was
            not loaded previously. Modification timestamp of the underlying
            file is NOT checked, you must manually invalidate modified
            data with :meth:`unload(ref)`.

        Raises:
          IOError: file not found or not readable.
          ValueError: parsing failed.
        """
        # log start
        t0 = log_start(_log, 'WorkspaceFile.load', level=logging.INFO,
                       kvp=dict(ref=ref))
        # stop if already loaded in the past
        if ref in self._loaded:
            # log done and return
            log_end(_log, t0, 'WorkspaceFile.load', level=logging.INFO,
                    kvp=dict(ref=ref, cached='yes'))
            return
        # create the full path from the reference
        ext = 'msgpack' if self.use_msgpack else 'json'
        full_path = '{}.{}'.format(os.path.join(self._wd, ref), ext)
        # open the file; raises IOError on failure
        f = open(full_path)
        # parse the file
        try:
            record = msgpack.load(f) if self.use_msgpack else json.load(f)
        except Exception as err:
            raise ValueError('Loading {}: {}'.format(full_path, err))
        finally:
            f.close()
        # cache the parsed data, both by reference and by 'name'
        # (if name is not the same as reference)
        #print("@@ REF={r} RECORD[ref]={rr} RECORD[name]={n}"
        #      .format(r=ref, rr=record['ref'], n=record['name']))
        self._loaded[ref] = record
        self._loaded[record['ref']] = record
        self._loaded[record['name']] = record
        #print('@@ STORE RECORD BY ({},{})'.format(record['ref'], record['name']))
        # insert the parsed data into mongomock
        self.collection.insert(record)
        # log done
        log_end(_log, t0, 'WorkspaceFile.load', level=logging.INFO,
                kvp=dict(ref=ref, cached='no'))
Ejemplo n.º 16
0
    def load(self, ref):
        """Load data from a given reference.

        The reference will be translated into a file to load,
        using the following formula:
        ``<working_directory> + '/' + <ref> + <ext>`,
        where ``<working_directory>`` is the path given to the class
        constructor, ``<ref>`` is the reference given to this
        function, and
        ``<ext>`` is a file extension '.msgpack' if
        `use_msgpack` is True and '.json' otherwise.

        Thus, for ``WorkspaceFile('/tmp/data').load('foo_bar')``,
        the path loaded would be '/tmp/data/foo_bar.msgpack'.

        See class documentation on format of input data.

        Args:
          ref (str): The reference

        Notes:
          * Post-condition: Object is loaded if and only if that reference was
            not loaded previously. Modification timestamp of the underlying
            file is NOT checked, you must manually invalidate modified
            data with :meth:`unload(ref)`.

        Raises:
          IOError: file not found or not readable.
          ValueError: parsing failed.
        """
        # log start
        t0 = log_start(_log,
                       'WorkspaceFile.load',
                       level=logging.INFO,
                       kvp=dict(ref=ref))
        # stop if already loaded in the past
        if ref in self._loaded:
            # log done and return
            log_end(_log,
                    t0,
                    'WorkspaceFile.load',
                    level=logging.INFO,
                    kvp=dict(ref=ref, cached='yes'))
            return
        # create the full path from the reference
        ext = 'msgpack' if self.use_msgpack else 'json'
        full_path = '{}.{}'.format(os.path.join(self._wd, ref), ext)
        # open the file; raises IOError on failure
        f = open(full_path)
        # parse the file
        try:
            record = msgpack.load(f) if self.use_msgpack else json.load(f)
        except Exception as err:
            raise ValueError('Loading {}: {}'.format(full_path, err))
        finally:
            f.close()
        # cache the parsed data, both by reference and by 'name'
        # (if name is not the same as reference)
        #print("@@ REF={r} RECORD[ref]={rr} RECORD[name]={n}"
        #      .format(r=ref, rr=record['ref'], n=record['name']))
        self._loaded[ref] = record
        self._loaded[record['ref']] = record
        self._loaded[record['name']] = record
        #print('@@ STORE RECORD BY ({},{})'.format(record['ref'], record['name']))
        # insert the parsed data into mongomock
        self.collection.insert(record)
        # log done
        log_end(_log,
                t0,
                'WorkspaceFile.load',
                level=logging.INFO,
                kvp=dict(ref=ref, cached='no'))
Ejemplo n.º 17
0
def start_service(api_class,
                  service_class,
                  log,
                  services=None,
                  host='localhost',
                  port=9100,
                  killprocgrp=False):
    """Start a Data API service.

    Args:
        api_class (BaseService): The custom API service class, e.g.,
                    `doekbase.data_api.taxonomy.taxon.api.TaxonService`
        service_class (type): The Thrift auto-generated service class
        log (logging.Logger): Logging object
        services (dict): Service configuration dictionary, passed to
                         constructor of the `api_class`.
        host (str): Service host (will default to 'localhost')
        port (int): Service port, e.g. 9101
        killprocgrp (bool): if True, kill process group on exit
    """
    assert issubclass(api_class, BaseService), \
        'Invalid "api_class": must be a subclass of ' \
        'doekbase.data_api.service_core.BaseService'
    assert hasattr(service_class, 'Processor'), 'Invalid "service_class": ' \
                                                'missing "Processor" attribute'
    assert isinstance(port, int), 'The "port" must be an integer'

    svc_t0 = util.log_start(log,
                            'start_service',
                            kvp=dict(host=host, port=port))

    # Create server
    services = services or SERVICES_DICT
    handler = api_class(services)
    processor = service_class.Processor(handler)
    pfactory = TBinaryProtocol.TBinaryProtocolFactory()
    resource = TTwisted.ThriftResource(processor, pfactory, pfactory)
    site = twisted.web.server.Site(resource=resource)
    twisted.internet.reactor.listenTCP(port, site, interface=host)

    # Kill entire process group on shutdown
    if killprocgrp:
        twisted.internet.reactor.addSystemEventTrigger(
            'before', 'shutdown', functools.partial(kill_process_group,
                                                    log=log))

    # Run server
    sname = api_class.__name__
    shost = host or 'localhost'
    util.log_start(log, 'server', kvp=dict(name=sname, host=shost, port=port))
    t0 = util.log_start(log,
                        'twisted.internet.reactor.run',
                        level=logging.DEBUG)
    try:
        twisted.internet.reactor.run()
    except Exception as err:
        log.error('msg="Abort {} server on error"'.format(sname))
        util.log_end(log,
                     t0,
                     'twisted.internet.reactor.run',
                     status_code=1,
                     level=logging.ERROR,
                     kvp=dict(msg=err))
        raise
    finally:
        util.log_end(log, t0, 'twisted.internet.reactor.run')

    util.log_end(log, svc_t0, 'start_service', kvp=dict(host=host, port=port))
    return 0