Giter Club home page Giter Club logo

ya-httpx-client's Introduction

ya-httpx-client

Communicate with a provider-based HTTP server the way you communicate with any other HTTP server.

Introduction

The documentation assumes reader knows what Golem is and understand basics of the development of yapapi-based services.

Features:

  1. Deploy an HTTP server on a Golem provider in an easy way. There are no requirements on the server implementation (doesn't even have to be in python) except it must be capable of running in the Golem provider image.
  2. Send requests to the provider-based server using httpx.AsyncClient, the same way you would send them to any other HTTP server. httpx is similar to more popular requests, but with async support.
  3. Restart the service on a new provider every time it stops (for whatever reason), in a seamless way that ensures no request is ever lost.
  4. Maintain multiple providers running the same server (efficient load balancing included).
  5. Change the number of providers running the server in a dynamic way.

NOTE: features 3-5 are useful only if your server is stateless (i.e. request/response history never matters).

This library is built on top of yapapi, there is nothing here that couldn't be done with the pure yapapi.

Installation

pip3 install git+https://github.com/golemfactory/ya-httpx-client.git#egg=ya-httpx-client[requestor]

How to

Every ya-httpx-client application is made up of 3 main components:

  • HTTP server code (that has nothing to do with Golem at all)
  • Dockerfile that will serve as a base for the Golem provider image
  • Requestor agent that initializes ya-httpx-client.Session, starts server(s) on provider(s), and uses them

Examples

Calculator

There is a simple complete application here. It consists of:

  • calculator_server.py - a Flask-based http server that answers to requests like GET base_url/add/7/8
  • calculator.Dockerfile - base of a Golem provider image that includes calculator_server.py
  • run_calculator.py - requestor agent that starts the calculator on provider and sends some requests

Usage: python3 examples/calculator/run_calculator.py

Requestor proxy

Second example is a generic HTTP server running on the requestor side that:

  • On startup starts a http server on provider(s). There are no assumptions about this server, in our example it is a simple echo server.
  • Accepts any request
  • Forwards every request to the provider and responds with the response received

So, in other words, requestor serves as a proxy to the server running on a provider.

Usage:

# Install additonal requestor-side dependencies (Quart)
pip3 install -r examples/requestor_proxy/requirements.txt

# Start the server
python3 examples/requestor_proxy/requestor_proxy.py devnet-beta

# (other console) use the "local" server
curl http://localhost:5000/add/3/4

For the details on the configuration, check comments in the code.

Requestor agent quickstart

#   1.  Initialize the session with yapapi.Golem configuration. This should be done exactly once. 
executor_cfg = {'budget': 10, 'subnet_tag': 'devnet-beta'}
session = Session(executor_cfg)

#   2.  Define a service. You may define as many services as you want, provided they have different urls.
session.add_url(
    #   All HTTP requests directed to host "some_name" will be processed by ...
    url='http://some_name',
    #   ...a http server running on provider, in VM based on this image ...
    image_hash='25f09e17c34433f979331edf4f3b47b2ca330ba2f8acbfe2e3dbd9c3',
    #   ... that will be started with this command (this should start the server in the background,
    #   service will be operative only after this command finished) ...
    entrypoint=("sh", "-c", "start_my_http_server.sh"),
    #   ... and to be more exact, by one of indistinguishable services running on different providers.
    #   This is the initial number of services that can be changed at any time by session.set_cluster_size().
    #   Also check "load balancing" section.
    init_cluster_size=1
)

#   3.  Use the service(s)
async def run():
    async with session.client() as client:
        #   Note 'http://some_name' prefix - this is our url from @session.startup
        req_url = 'http://some_name/do/something'
        res = await client.post(req_url, json={'foo': 'bar'})
        assert res.status_code == 200

    #   This is necessary for a graceful shutdown
    await session.close()

#   4.  Optional: change the number of services. Check "load balancing" section for more details.
session.set_cluster_size('http://some_name', 7)

Communication protocol

There are two communication protocols, determined by ya_httpx_client.cluster.Cluster.USE_VPN value, VPN and FileSerialization. Differences:

  • VPN is faster
  • VPN requires provider supporting vpn capability (-> yagna 0.8.0 or higher)
  • FileSerialization requires ya-httpx-client[provider] installed on provider (--> check some example Dockerfile for details)
  • Http servers running on providers should listen on 0.0.0.0:80 for VPN and on unix:///tmp/golem.sock for FileSerialization

VPN is the default mode, other one is legacy - it will probably be removed one day.

Load balancing

By default, a single instance of a service is created. We can change this by setting init_cluster_size in @session.startup to a different value, or by calling session.set_cluster_size. This value doesn't have to be an integer, it might also be a callable that takes a ya_httpx_client.Cluster object as an argument and returns an integer:

def calculate_new_size(cluster):
    if cluster.request_queue.qsize() > 7:
        return 2
    else:
        return 1
session.set_cluster_size('http://some_name', calculate_new_size)

or even better, anything that could be evaluated as an integer:

class LoadBalancer:
    def __init__(self, cluster):
        self.cluster = cluster
    
    def __int__(self):
        if self.cluster.request_queue.empty():
            return 0
        return 1
session.set_cluster_size('http://some_name', LoadBalancer)

If the last case, we have no control on when and how often int(load_balancer_object) will be called, so the implementation should be a little more clever, at least to avoid too frequent changes - check SimpleLoadBalancer for an example.

NOTE: setting size to anything other than an integer should be considered an experimental feature.

Future development

Currently, when the service stops, it is restarted on another provider. This makes sense only for stateless services, but there is no way to turn this off. The developer should decide if they want to restart the service or not (or maybe stop the execution?).

ya-httpx-client's People

Stargazers

Kevin Alexander Scott Jellis avatar

Watchers

James Cloos avatar Viggith avatar Piotr Chromiec avatar  avatar  avatar

ya-httpx-client's Issues

two instances when a single one was ordered

  1. Have a cluster with size == 1
  2. Wait for
[2021-09-28T12:29:32.908+0200 WARNING yapapi.services] Instance failed when starting, trying to create another one...
Replacing service on mf - it is failed

in logs. (I've no idea how to cause this, sorry).
3. Observe two instances created instead of one.

I think we want respawn_unstarted_instances=False in golem.run_service, but maybe this would be worth some investigation?

goth tests

Currently goth tests are almost ready, but there are few small things missing.

PR is here: #16

goth tests of file-serialization-based communication

Currently we have tests only for VPN.
This shouldn't be hard.
Also this wouldn't be really useful, because why would anyone use file serialization? But either way, if there's such option, it would be nice to test it.

ya-httpx-client installation fails

Name: blue
yagna version: 0.8.0
OS+lang+version (if applicable): linux, python 3.6.12

$ pip3 install .[requestor]
Processing /home/blue/ya-httpx-client
  Preparing metadata (setup.py) ... done
Collecting yapapi-service-manager@ git+https://github.com/golemfactory/yapapi-service-manager.git
  Cloning https://github.com/golemfactory/yapapi-service-manager.git to /tmp/pip-install-vwps1gzg/yapapi-service-manager_3010580acb044d8f8ef787a9acadd1bd
  Running command git clone --filter=blob:none -q https://github.com/golemfactory/yapapi-service-manager.git /tmp/pip-install-vwps1gzg/yapapi-service-manager_3010580acb044d8f8ef787a9acadd1bd
  Resolved https://github.com/golemfactory/yapapi-service-manager.git to commit 5e9b2aa3002d830d040681b06b1c78e5c764e21d
  Preparing metadata (setup.py) ... done
Collecting httpx==0.18.2
  Using cached httpx-0.18.2-py3-none-any.whl (76 kB)
Collecting httpcore<0.14.0,>=0.13.3
  Using cached httpcore-0.13.7-py3-none-any.whl (58 kB)
Collecting async-generator
  Using cached async_generator-1.10-py3-none-any.whl (18 kB)
Collecting rfc3986[idna2008]<2,>=1.3
  Using cached rfc3986-1.5.0-py2.py3-none-any.whl (31 kB)
Collecting certifi
  Using cached certifi-2021.10.8-py2.py3-none-any.whl (149 kB)
Collecting sniffio
  Using cached sniffio-1.2.0-py3-none-any.whl (10 kB)
Collecting yapapi@ git+https://[email protected]/golemfactory/[email protected]
  Cloning https://****@github.com/golemfactory/yapapi.git (to revision b0.7) to /tmp/pip-install-vwps1gzg/yapapi_4e6bd9b303354c78bdc76f8e4b804724
  Running command git clone --filter=blob:none -q 'https://****@github.com/golemfactory/yapapi.git' /tmp/pip-install-vwps1gzg/yapapi_4e6bd9b303354c78bdc76f8e4b804724
  Running command git checkout -b b0.7 --track origin/b0.7
  Switched to a new branch 'b0.7'
  Branch 'b0.7' set up to track remote branch 'b0.7' from 'origin'.
^CERROR: Operation cancelled by user

Consider: move to `yapapi.contrib`

WHY? Because this just fits there.
WHAT?

  • use (internally) yapapi.contrib.service.http_proxy
  • preserve the cool interface
  • close this repo

Python 3.6 support

This line:

from contextlib import asynccontextmanager

in ya_httpx_client.session fails on python 3.6. Maybe there are also some other lines that fail as well.

Either:
A) fix this, so python3.6 works
B) mention in README that higher python is required

Remove deprecations & improve the interface

Since work_context/script decoupling in yapapi, we could do just

session.add_service(
    http://some_url,
    ...,
    start_commands = [
        Run(/something/),
    ]
)

This would be clean & convenient.
Also, this part either way needs reworking to fix the deprecations (it can't be done in a really easy way).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.