Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

services API + simple toy service example #383

Merged
merged 126 commits into from
May 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
126 commits
Select commit Hold shift + click to select a range
20c989a
+ http server example
shadeofblue Mar 23, 2021
948fd65
add another service example
shadeofblue Mar 24, 2021
9880630
simple service debug session etc ...
shadeofblue Mar 29, 2021
982d7df
add `WorkContext.send_bytes` command
shadeofblue Mar 29, 2021
69bcdfc
Merge branch 'blue/send_bytes' into blue/services-prototype
shadeofblue Mar 29, 2021
0665865
fix the PollingBatch with the current version of yagna
shadeofblue Mar 29, 2021
adc5972
Merge branch 'blue/polling-batch-timeout-argument' into blue/services…
shadeofblue Mar 29, 2021
3e4b0a8
black, additional comment
shadeofblue Mar 29, 2021
3524e5b
- ","
shadeofblue Mar 29, 2021
cab9f94
use aiohttp's general `_request_timeout`
shadeofblue Mar 30, 2021
f92b448
Merge branch 'blue/polling-batch-timeout-argument' into blue/services…
shadeofblue Mar 30, 2021
5a776a6
Merge branch 'master' into blue/polling-batch-timeout-argument
shadeofblue Mar 30, 2021
f851124
black...
shadeofblue Mar 30, 2021
942f46b
Merge branch 'blue/polling-batch-timeout-argument' of github.com:gole…
shadeofblue Mar 30, 2021
32ae650
black...
shadeofblue Mar 30, 2021
4a2081d
bring back the "proper" version of the service
shadeofblue Mar 30, 2021
ccb307e
.
shadeofblue Mar 30, 2021
eec0510
a more fleshed-out example
shadeofblue Mar 31, 2021
ccedf05
`WorkContext.download_bytes` and `WorkContext.download_json` methods
shadeofblue Mar 31, 2021
f8eeeda
`WorkContext.download_bytes` and `WorkContext.download_json` methods
shadeofblue Mar 31, 2021
9854fc4
refine the simple_service.py
shadeofblue Apr 2, 2021
8f1065e
+ tests, comments
shadeofblue Apr 6, 2021
a55f667
black
shadeofblue Apr 6, 2021
338eceb
make the `on_download` callback async
shadeofblue Apr 7, 2021
6d171a3
fix assert message
shadeofblue Apr 7, 2021
e6563fd
Merge branch 'master' into blue/download-bytes
shadeofblue Apr 7, 2021
d6e5a35
Set maximum client-side timeout to 5 seconds, continue on client-side…
filipgolem Apr 7, 2021
10de416
mypy
shadeofblue Apr 7, 2021
474565b
Decrease timeout
filipgolem Apr 7, 2021
554abcc
Merge branch 'master' into blue/polling-batch-timeout-argument
shadeofblue Apr 7, 2021
b05ccc6
Merge branch 'blue/download-bytes' into blue/services-prototype
shadeofblue Apr 7, 2021
9afefd5
make callbacks async in `simple_service.py`
shadeofblue Apr 7, 2021
e1fdffe
Merge branch 'blue/polling-batch-timeout-argument' into blue/services…
shadeofblue Apr 7, 2021
ac1e43a
Merge branch 'master' into blue/services-prototype
shadeofblue Apr 8, 2021
8f46907
remove the non-working http server example
shadeofblue Apr 8, 2021
1ff0c4b
some comments
shadeofblue Apr 8, 2021
4467388
comment to the exception handler
shadeofblue Apr 13, 2021
cbf48af
Merge branch 'master' into blue/services-prototype
shadeofblue Apr 14, 2021
196a60b
add yapapi.payload
shadeofblue Apr 19, 2021
caf803b
* `turbogeth` example stub
shadeofblue Apr 22, 2021
75aa653
dataclass info
shadeofblue Apr 26, 2021
27ffa59
Merge branch 'master' into blue/services-prototype
shadeofblue Apr 26, 2021
2e7d5f6
limit workers to 1
shadeofblue Apr 26, 2021
a75a263
rename Executor.package to Executor.payload
shadeofblue Apr 27, 2021
3589478
mock of the WorkContext change that makes deploy and start more explicit
shadeofblue Apr 27, 2021
dafe1ed
a very draft "strawman" try at the services API
shadeofblue Apr 27, 2021
1904e4a
- commented-out code
shadeofblue Apr 27, 2021
cc2ca55
comments
shadeofblue Apr 27, 2021
c70e845
rename `Swarm` to `Cluster`
shadeofblue Apr 28, 2021
8b81071
Merge branch 'master' into blue/turbogeth-mockup
shadeofblue May 5, 2021
78ae7b6
Merge branch 'master' into blue/payload
shadeofblue May 5, 2021
d2f81ab
Merge branch 'blue/payload' into blue/turbogeth-mockup
shadeofblue May 5, 2021
7893761
s/state str/state enum/
shadeofblue May 5, 2021
ccb3076
- some notes that were already completed
shadeofblue May 5, 2021
b40b775
update with remarks from @stranger80
shadeofblue May 6, 2021
50ccdb6
more or less _final_ draft
shadeofblue May 7, 2021
166c4d6
remove callbacks from deploy/start
shadeofblue May 7, 2021
ae5613a
Merge branch 'master' into blue/payload
shadeofblue May 11, 2021
a028595
- comment
shadeofblue May 11, 2021
94df0ee
make `Payload` an AutodecoratingModel
shadeofblue May 11, 2021
8a5f0ef
black
shadeofblue May 11, 2021
621946a
update with @stranger80's remarks
shadeofblue May 11, 2021
b700dc6
address concerns from conversation with @stranger80
shadeofblue May 11, 2021
377c29f
DON'T busy-loop by default :/
shadeofblue May 11, 2021
18de396
two more debug messages for illustration
shadeofblue May 12, 2021
d750ffa
actually, `Golem`
shadeofblue May 12, 2021
91f19a4
+ comment on a potential issue + solution
shadeofblue May 12, 2021
d6fb7cb
Merge branch 'blue/payload' into blue/turbogeth-mockup
shadeofblue May 12, 2021
4c10b73
+ `test_payload`
shadeofblue May 12, 2021
47182bc
+ tests for AutodecoratingModel
shadeofblue May 12, 2021
6d683ac
Merge branch 'blue/payload' into blue/turbogeth-mockup
shadeofblue May 12, 2021
3c150f6
improve the payload definition ;)
shadeofblue May 12, 2021
b25bbca
Merge branch 'master' into blue/turbogeth-mockup
shadeofblue May 13, 2021
b1ab842
black
shadeofblue May 13, 2021
b5f64e9
Merge branch 'blue/executor-split' into blue/turbogeth-mockup
shadeofblue May 18, 2021
43885c5
move the easy part to `executor/services.py`, add comments
shadeofblue May 18, 2021
49125cd
unneeded `dataclass`
shadeofblue May 18, 2021
dd2a66a
Merge branch 'blue/executor-split' into blue/services-api
shadeofblue May 18, 2021
0bc7030
Merge branch 'blue/executor-split' into blue/services-api
shadeofblue May 18, 2021
74774ac
implement the HL Services API
shadeofblue May 19, 2021
c57c63e
Merge branch 'blue/executor-split' into blue/services-api
shadeofblue May 19, 2021
a3dcd75
fixes
shadeofblue May 19, 2021
9b00016
black
shadeofblue May 19, 2021
9f71db2
Merge branch 'blue/services-api' into blue/services-prototype
shadeofblue May 19, 2021
2a2717d
mv examples/service examples/simple-service-poc
shadeofblue May 19, 2021
7277961
make `get_payload` async
shadeofblue May 19, 2021
3861b3a
Merge branch 'blue/services-api' into blue/services-prototype
shadeofblue May 19, 2021
f809854
if the user issues "start" or "deploy" explictly, disable "implicit i…
shadeofblue May 19, 2021
9e64ffc
add `Cluster.stop` method
shadeofblue May 19, 2021
73ba1fd
Merge branch 'blue/services-api' into blue/services-prototype
shadeofblue May 19, 2021
abf2211
update the toy example with the HL Services API
shadeofblue May 19, 2021
9eee66f
.
shadeofblue May 19, 2021
b821a55
- deprecation warning
shadeofblue May 19, 2021
2e5f575
make the log unique
shadeofblue May 19, 2021
99a7845
fix messages
shadeofblue May 19, 2021
e053519
ARGH ...
shadeofblue May 19, 2021
0ee6c70
make `ctx` and `cluster` private on `Service`
shadeofblue May 20, 2021
4606988
Merge branch 'blue/executor-split' into blue/services-api
shadeofblue May 20, 2021
cafd93c
Merge branch 'blue/services-api' into blue/services-prototype
shadeofblue May 20, 2021
e431502
Merge branch 'blue/executor-split' into blue/services-api
shadeofblue May 20, 2021
290a305
Merge branch 'blue/services-api' into blue/services-prototype
shadeofblue May 20, 2021
033e5db
fix plot downloading in simple_service
shadeofblue May 20, 2021
f365e43
Merge branch 'blue/executor-split' into blue/services-api
shadeofblue May 20, 2021
b7b197a
Merge branch 'blue/services-api' into blue/services-prototype
shadeofblue May 20, 2021
f2de825
fix logging for services
shadeofblue May 20, 2021
43c0146
Merge branch 'blue/services-api' into blue/services-prototype
shadeofblue May 20, 2021
cdadb30
optimize the example to eliminate the callbacks
shadeofblue May 20, 2021
487d80e
fix the task id for services
shadeofblue May 20, 2021
8037417
Merge branch 'blue/services-api' into blue/services-prototype
shadeofblue May 20, 2021
8172ddd
Merge branch 'master' into blue/services-api
shadeofblue May 24, 2021
a9f4915
Merge branch 'blue/services-api' into blue/services-prototype
shadeofblue May 24, 2021
11a3655
Shutdown Golem's services & jobs as part of closing the exit stack
azawlocki May 26, 2021
dbefecd
Terminate agreements in Cluster.__aexit__()
azawlocki May 26, 2021
c7cbd5b
Merge branch 'blue/services-api' into blue/services-prototype
azawlocki May 26, 2021
30074a4
Fix mypy issues
azawlocki May 26, 2021
0ed9598
more sensible example behavior (waiting for start, etc)
shadeofblue May 27, 2021
ae7b8c0
add README.md to the Docker image directory, add appropriate annotati…
shadeofblue May 27, 2021
57f2c48
prevent the example from finishing before we've established whether t…
shadeofblue May 27, 2021
ab9d278
some comments to clarify the service example
shadeofblue May 27, 2021
44f826e
make the ctl script a constant, add some comments to the handlers
shadeofblue May 27, 2021
1d1647d
Merge pull request #303 from golemfactory/blue/services-prototype
shadeofblue May 27, 2021
17493a2
Merge branch 'master' into blue/services-api
shadeofblue May 27, 2021
73b3036
Adjust expected log messages in blender/yacat integration tests
azawlocki May 27, 2021
71a2542
black
shadeofblue May 27, 2021
6d37753
Merge branch 'blue/services-api' of github.com:golemfactory/yapapi in…
shadeofblue May 27, 2021
92acf35
Merge branch 'master' into blue/services-api
shadeofblue May 27, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions examples/erigon/erigon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import asyncio

from dataclasses import dataclass

from yapapi.props.base import prop, constraint
from yapapi.props import inf

from yapapi.payload import Payload
from yapapi.executor import Golem
from yapapi.executor.services import Service

from yapapi.log import enable_default_logger, log_summary, log_event_repr # noqa


TURBOGETH_RUNTIME_NAME = "turbogeth-managed"
PROP_ERIGON_ETHEREUM_NETWORK = "golem.srv.app.eth.network"


@dataclass
class ErigonPayload(Payload):
network: str = prop(PROP_ERIGON_ETHEREUM_NETWORK)

runtime: str = constraint(inf.INF_RUNTIME_NAME, default=TURBOGETH_RUNTIME_NAME)
min_mem_gib: float = constraint(inf.INF_MEM, operator=">=", default=16)
min_storage_gib: float = constraint(inf.INF_STORAGE, operator=">=", default=1024)


class ErigonService(Service):
credentials = None

def post_init(self):
self.credentials = {}

def __repr__(self):
srv_repr = super().__repr__()
return f"{srv_repr}, credentials: {self.credentials}"

@staticmethod
async def get_payload():
return ErigonPayload(network="rinkeby")

async def start(self):
deploy_idx = self.ctx.deploy()
self.ctx.start()
future_results = yield self.ctx.commit()
results = await future_results
self.credentials = "RECEIVED" or results[deploy_idx] # (NORMALLY, WOULD BE PARSED)

async def run(self):

while True:
print(f"service {self.ctx.id} running on {self.ctx.provider_name} ... ")
signal = self._listen_nowait()
if signal and signal.message == "go":
self.ctx.run("go!")
yield self.ctx.commit()
else:
await asyncio.sleep(1)
yield

async def shutdown(self):
self.ctx.download_file("some/service/state", "temp/path")
yield self.ctx.commit()


async def main(subnet_tag, driver=None, network=None):

async with Golem(
budget=10.0,
subnet_tag=subnet_tag,
driver=driver,
network=network,
event_consumer=log_summary(log_event_repr),
) as golem:
cluster = await golem.run_service(
ErigonService,
num_instances=1,
)

def instances():
return [{s.ctx.id, s.state.value} for s in cluster.instances]

def still_running():
return any([s for s in cluster.instances if s.is_available])

cnt = 0
while cnt < 10:
print(f"instances: {instances()}")
await asyncio.sleep(3)
cnt += 1
if cnt == 3:
if len(cluster.instances) > 1:
cluster.instances[0].send_message_nowait("go")

for s in cluster.instances:
cluster.stop_instance(s)

print(f"instances: {instances()}")

cnt = 0
while cnt < 10 and still_running():
print(f"instances: {instances()}")
await asyncio.sleep(1)

print(f"instances: {instances()}")


asyncio.run(main(None))
216 changes: 216 additions & 0 deletions examples/simple-service-poc/simple_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
#!/usr/bin/env python3
"""
the requestor agent controlling and interacting with the "simple service"
"""
import asyncio
from datetime import datetime, timedelta, timezone
import pathlib
import random
import string
import sys


from yapapi import (
NoPaymentAccountError,
__version__ as yapapi_version,
windows_event_loop_fix,
)
from yapapi.executor import Golem
from yapapi.executor.services import Service, ServiceState

from yapapi.log import enable_default_logger, log_summary, log_event_repr, pluralize # noqa
from yapapi.payload import vm

examples_dir = pathlib.Path(__file__).resolve().parent.parent
sys.path.append(str(examples_dir))

from utils import (
build_parser,
TEXT_COLOR_CYAN,
TEXT_COLOR_DEFAULT,
TEXT_COLOR_RED,
TEXT_COLOR_YELLOW,
)

NUM_INSTANCES = 1
STARTING_TIMEOUT = timedelta(minutes=4)


class SimpleService(Service):
STATS_PATH = "/golem/out/stats"
PLOT_INFO_PATH = "/golem/out/plot"
SIMPLE_SERVICE = "/golem/run/simple_service.py"
SIMPLE_SERVICE_CTL = "/golem/run/simulate_observations_ctl.py"

@staticmethod
async def get_payload():
return await vm.repo(
image_hash="8b11df59f84358d47fc6776d0bb7290b0054c15ded2d6f54cf634488",
min_mem_gib=0.5,
min_storage_gib=2.0,
)

async def start(self):
# handler responsible for starting the service
self._ctx.run(self.SIMPLE_SERVICE_CTL, "--start")
yield self._ctx.commit()

async def run(self):
# handler responsible for providing the required interactions while the service is running
while True:
await asyncio.sleep(10)
self._ctx.run(self.SIMPLE_SERVICE, "--stats") # idx 0
self._ctx.run(self.SIMPLE_SERVICE, "--plot", "dist") # idx 1

future_results = yield self._ctx.commit()
results = await future_results
stats = results[0].stdout.strip()
plot = results[1].stdout.strip().strip('"')

print(f"{TEXT_COLOR_CYAN}stats: {stats}{TEXT_COLOR_DEFAULT}")

plot_filename = "".join(random.choice(string.ascii_letters) for _ in range(10)) + ".png"
print(
f"{TEXT_COLOR_CYAN}downloading plot: {plot} to {plot_filename}{TEXT_COLOR_DEFAULT}"
)
self._ctx.download_file(
plot, str(pathlib.Path(__file__).resolve().parent / plot_filename)
)

steps = self._ctx.commit()
yield steps

async def shutdown(self):
# handler reponsible for executing operations on shutdown
self._ctx.run(self.SIMPLE_SERVICE_CTL, "--stop")
yield self._ctx.commit()


async def main(subnet_tag, driver=None, network=None):
async with Golem(
budget=1.0,
subnet_tag=subnet_tag,
driver=driver,
network=network,
event_consumer=log_summary(log_event_repr),
) as golem:

print(
f"yapapi version: {TEXT_COLOR_YELLOW}{yapapi_version}{TEXT_COLOR_DEFAULT}\n"
f"Using subnet: {TEXT_COLOR_YELLOW}{subnet_tag}{TEXT_COLOR_DEFAULT}, "
f"payment driver: {TEXT_COLOR_YELLOW}{golem.driver}{TEXT_COLOR_DEFAULT}, "
f"and network: {TEXT_COLOR_YELLOW}{golem.network}{TEXT_COLOR_DEFAULT}\n"
)

commissioning_time = datetime.now()

print(
f"{TEXT_COLOR_YELLOW}starting {pluralize(NUM_INSTANCES, 'instance')}{TEXT_COLOR_DEFAULT}"
)

# start the service

cluster = await golem.run_service(
SimpleService,
num_instances=NUM_INSTANCES,
expiration=datetime.now(timezone.utc) + timedelta(minutes=120),
)

# helper functions to display / filter instances

def instances():
return [(s.provider_name, s.state.value) for s in cluster.instances]

def still_running():
return any([s for s in cluster.instances if s.is_available])

def still_starting():
return len(cluster.instances) < NUM_INSTANCES or any(
[s for s in cluster.instances if s.state == ServiceState.starting]
)

# wait until instances are started

while still_starting() and datetime.now() < commissioning_time + STARTING_TIMEOUT:
print(f"instances: {instances()}")
await asyncio.sleep(5)

if still_starting():
raise Exception(f"Failed to start instances before {STARTING_TIMEOUT} elapsed :( ...")

print("All instances started :)")

# allow the service to run for a short while
# (and allowing its requestor-end handlers to interact with it)

start_time = datetime.now()

while datetime.now() < start_time + timedelta(minutes=2):
print(f"instances: {instances()}")
await asyncio.sleep(5)

print(f"{TEXT_COLOR_YELLOW}stopping instances{TEXT_COLOR_DEFAULT}")
cluster.stop()

# wait for instances to stop

cnt = 0
while cnt < 10 and still_running():
print(f"instances: {instances()}")
await asyncio.sleep(5)

print(f"instances: {instances()}")


if __name__ == "__main__":
parser = build_parser(
"A very simple / POC example of a service running on Golem, utilizing the VM runtime"
)
now = datetime.now().strftime("%Y-%m-%d_%H.%M.%S")
parser.set_defaults(log_file=f"simple-service-yapapi-{now}.log")
args = parser.parse_args()

# This is only required when running on Windows with Python prior to 3.8:
windows_event_loop_fix()

enable_default_logger(
log_file=args.log_file,
debug_activity_api=True,
debug_market_api=True,
debug_payment_api=True,
)

loop = asyncio.get_event_loop()
task = loop.create_task(
main(subnet_tag=args.subnet_tag, driver=args.driver, network=args.network)
)

try:
loop.run_until_complete(task)
except NoPaymentAccountError as e:
handbook_url = (
"https://handbook.golem.network/requestor-tutorials/"
"flash-tutorial-of-requestor-development"
)
print(
f"{TEXT_COLOR_RED}"
f"No payment account initialized for driver `{e.required_driver}` "
f"and network `{e.required_network}`.\n\n"
f"See {handbook_url} on how to initialize payment accounts for a requestor node."
f"{TEXT_COLOR_DEFAULT}"
)
except KeyboardInterrupt:
print(
f"{TEXT_COLOR_YELLOW}"
"Shutting down gracefully, please wait a short while "
"or press Ctrl+C to exit immediately..."
f"{TEXT_COLOR_DEFAULT}"
)
task.cancel()
try:
loop.run_until_complete(task)
print(
f"{TEXT_COLOR_YELLOW}Shutdown completed, thank you for waiting!{TEXT_COLOR_DEFAULT}"
)
except (asyncio.CancelledError, KeyboardInterrupt):
pass
38 changes: 38 additions & 0 deletions examples/simple-service-poc/simple_service/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
This directory contains files used to construct the application Docker image
that's then converted to a GVMI file (a Golem Virtual Machine Image file) and uploaded
to the Yagna image repository.

All Python scripts here are run within a VM on the Provider's end.

The example (`../simple_service.py`) already contains the appropriate image hash
but if you'd like to experiment with it, feel free to re-build it.

## Building the image

You'll need:

* Docker: https://www.docker.com/products/docker-desktop
* gvmkit-build: `pip install gvmkit-build`

Once you have those installed, run the following from this directory:

```bash
docker build -f simple_service.Dockerfile -t simple-service .
gvmkit-build simple-service:latest
gvmkit-build simple-service:latest --push
```

Note the hash link that's presented after the upload finishes.

e.g. `b742b6cb04123d07bacb36a2462f8b2347b20c32223c1ac49664635f`

and update the service's `get_payload` method to point to this image:

```python
async def get_payload():
return await vm.repo(
image_hash="b742b6cb04123d07bacb36a2462f8b2347b20c32223c1ac49664635f",
min_mem_gib=0.5,
min_storage_gib=2.0,
)
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM python:3.8-slim
VOLUME /golem/in /golem/out
COPY simple_service.py /golem/run/simple_service.py
COPY simulate_observations.py /golem/run/simulate_observations.py
COPY simulate_observations_ctl.py /golem/run/simulate_observations_ctl.py
RUN pip install numpy matplotlib
RUN chmod +x /golem/run/*
RUN /golem/run/simple_service.py --init
ENTRYPOINT ["sh"]
Loading