Ejemplo n.º 1
0
 def on_enq(self, data):
     """Calls on <ENQ> message receiving.
     """
     logger.debug('on_enq: %r', data)
     if not self.in_transfer_state:
         self.in_transfer_state = True
         return ACK
     else:
         logger.error('ENQ is not expected')
         return NAK
Ejemplo n.º 2
0
    def post(self, endpoint, payload):
        """Sends a POST request to SENAITE
        """
        url = self.get_url(endpoint)
        try:
            response = self.session.post(url, data=payload)
        except Exception as e:
            message = "Could not send POST to {}".format(url)
            logger.error(message)
            logger.error(e)
            return {}

        return response.json()
Ejemplo n.º 3
0
 def on_message(self, data):
     """Calls on ASTM message receiving."""
     logger.debug('on_message: %r', data)
     if not self.in_transfer_state:
         self.discard_chunked_messages()
         return NAK
     else:
         try:
             self.handle_message(data)
             return ACK
         except Exception as exc:
             logger.error(
                 'Error occurred on message handling. {!r}'.format(exc))
             return NAK
Ejemplo n.º 4
0
    def auth(self):
        logger.info("Starting session with SENAITE ...")
        self.session.auth = (self.username, self.password)

        # try to get the version of the remote JSON API
        version = self.get("version")
        if not version or not version.get("version"):
            logger.error("senaite.jsonapi not found on at {}".format(self.url))
            return False

        # try to get the current logged in user
        user = self.get("users/current")
        user = user.get("items", [{}])[0]
        if not user or user.get("authenticated") is False:
            logger.error("Wrong username/password")
            return False

        logger.info("Session established ('{}') with '{}'".format(
            self.username, self.url))
        return True
Ejemplo n.º 5
0
def post_to_senaite(messages, session, **kwargs):
    """POST ASTM messages to SENAITE
    """
    attempt = 1
    retries = kwargs.get('retries', 3)
    delay = kwargs.get('delay', 5)
    consumer = kwargs.get('consumer', 'senaite.lis2a.import')
    success = False

    while True:
        # Open a session with SENAITE and authenticate
        authenticated = session.auth()
        # Build the POST payload
        payload = {
            'consumer': consumer,
            'messages': messages,
        }
        if authenticated:
            # Send the message
            response = session.post('push', payload)
            success = response.get('success')
            if success:
                break

        # the break here ensures that at least one time is tried
        if attempt >= retries:
            break

        # increase attempts
        attempt += 1

        logger.warn('Could not push. Retrying {}/{}'.format(attempt, retries))

        # Sleep before we retry
        sleep(delay)

    if not success:
        logger.error('Could not push the message')
Ejemplo n.º 6
0
    def get(self, endpoint, timeout=60):
        """Fetch the given url or endpoint and return a parsed JSON object
        """
        url = self.get_url(endpoint)
        try:
            response = self.session.get(url, timeout=timeout)
        except Exception as e:
            message = "Could not connect to {}".format(url)
            logger.error(message)
            logger.error(e)
            return {}

        status = response.status_code
        if status != 200:
            message = "GET for {} returned {}".format(endpoint, status)
            logger.error(message)
            return {}

        return response.json()
Ejemplo n.º 7
0
def main():
    # Argument parser
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)

    # Argument groups
    astm_group = parser.add_argument_group('ASTM SERVER')
    lims_group = parser.add_argument_group('SENAITE LIMS')

    astm_group.add_argument('-l',
                            '--listen',
                            type=str,
                            default='0.0.0.0',
                            help='Listen IP address')

    astm_group.add_argument('-p',
                            '--port',
                            type=str,
                            default='4010',
                            help='Port to connect')

    astm_group.add_argument('-o',
                            '--output',
                            type=str,
                            help='Output directory to write ASTM files')

    lims_group.add_argument(
        '-u',
        '--url',
        type=str,
        help='SENAITE URL address including username and password in the '
        'format: http(s)://<user>:<password>@<senaite_url>')

    lims_group.add_argument('-c',
                            '--consumer',
                            type=str,
                            default='senaite.lis2a.import',
                            help='SENAITE push consumer interface')

    lims_group.add_argument(
        '-r',
        '--retries',
        type=int,
        default=3,
        help='Number of attempts of reconnection when SENAITE '
        'instance is not reachable. Only has effect when '
        'argument --url is set')

    lims_group.add_argument('-d',
                            '--delay',
                            type=int,
                            default=5,
                            help='Time delay in seconds between retries when '
                            'SENAITE instance is not reachable. Only has '
                            'effect when argument --url is set')

    parser.add_argument('-v',
                        '--verbose',
                        action='store_true',
                        help='Verbose logging')

    parser.add_argument('--logfile',
                        default=LOGFILE,
                        help='Path to store log files')

    # Parse Arguments
    args = parser.parse_args()

    if args.logfile:
        handler = logging.handlers.RotatingFileHandler(args.logfile,
                                                       maxBytes=5,
                                                       backupCount=0)
        # Format each log message like this
        formatter = logging.Formatter(
            '%(asctime)s %(levelname)-8s %(message)s')
        # Attach the formatter to the handler
        handler.setFormatter(formatter)
        # Attach the handler to the logger
        logger.addHandler(handler)

    # Get the current event loop.
    loop = asyncio.get_event_loop()

    # Set logging
    if args.verbose:
        logger.setLevel(logging.DEBUG)
    else:
        logger.setLevel(logging.INFO)
    logger.addHandler(logging.StreamHandler())

    # Validate output path
    output = args.output
    if output and not os.path.isdir(args.output):
        logger.error('Output path must be an existing directory')
        return sys.exit(-1)

    # Validate SENAITE URL
    url = args.url
    if url:
        session = lims.Session(url)
        logger.info('Checking connection to SENAITE ...')
        if not session.auth():
            return sys.exit(-1)

    def dispatch_astm_message(message):
        """Dispatch astm message
        """
        logger.debug('Dispatching ASTM Message')
        if output:
            path = os.path.abspath(output)
            loop.create_task(asyncio.to_thread(write_message, message, path))
        if url:
            session = lims.Session(url)
            session_args = {
                'delay': args.delay,
                'retries': args.retries,
                'consumer': args.consumer,
            }
            loop.create_task(
                asyncio.to_thread(post_to_senaite, message, session,
                                  **session_args))

    # Bridges communication between the protocol and server
    queue = asyncio.Queue()

    # Create a TCP server coroutine listening on port of the host address.
    server_coro = loop.create_server(lambda: ASTMProtocol(loop, queue),
                                     host=args.listen,
                                     port=args.port)

    # Run until the future (an instance of Future) has completed.
    server = loop.run_until_complete(server_coro)

    for socket in server.sockets:
        ip, port = socket.getsockname()
        logger.info('Starting server on {}:{}'.format(ip, port))
        logger.info('ASTM server ready to handle connections ...')

    # Create a ASTM message consumer task to be scheduled concurrently.
    loop.create_task(consume(queue, callback=dispatch_astm_message))

    # Run the event loop until stop() is called.
    try:
        loop.run_forever()
    except KeyboardInterrupt:
        logger.info('Shutting down server...')
        all_tasks = asyncio.gather(*asyncio.all_tasks(loop),
                                   return_exceptions=True)
        all_tasks.cancel()
        with contextlib.suppress(asyncio.CancelledError):
            loop.run_until_complete(all_tasks)
        loop.run_until_complete(loop.shutdown_asyncgens())
    finally:
        loop.close()
        logger.info('Server is now down...')
Ejemplo n.º 8
0
 def default_handler(self, data):
     # raise ValueError('Unable to dispatch data: %r', data)
     logger.error('Unable to dispatch data: %r', data)