Skip to content

Commit 2c43048

Browse files
authored
Merge pull request #1 from arturgontijo/feature/basic-service
[Repo] SNET Example Service (gRPC)
2 parents e2a91c5 + c3f2504 commit 2c43048

10 files changed

Lines changed: 460 additions & 0 deletions

Dockerfile

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
FROM ubuntu:18.04
2+
3+
ENV SINGNET_REPOS=/opt/singnet
4+
5+
RUN mkdir -p ${SINGNET_REPOS}
6+
7+
RUN apt-get update && \
8+
apt-get install -y \
9+
apt-utils \
10+
nano \
11+
git \
12+
wget \
13+
curl \
14+
zip \
15+
libudev-dev \
16+
libusb-1.0-0-dev
17+
18+
RUN apt-get install -y nodejs npm
19+
RUN apt-get install -y python3 python3-pip
20+
21+
RUN cd ${SINGNET_REPOS} && \
22+
git clone https://github.com/singnet/snet-cli && \
23+
cd snet-cli && \
24+
./scripts/blockchain install && \
25+
pip3 install -e .
26+
27+
RUN cd ${SINGNET_REPOS} && \
28+
mkdir snet-daemon && \
29+
cd snet-daemon && \
30+
wget https://github.com/singnet/snet-daemon/releases/download/v0.1.3/snetd-0.1.3.tar.gz && \
31+
tar -xvf snetd-0.1.3.tar.gz && \
32+
mv linux-amd64/snetd /usr/bin/snetd
33+
34+
RUN cd ${SINGNET_REPOS} && \
35+
git clone https://github.com/singnet/example-service.git && \
36+
cd example-service && \
37+
sh buildproto.sh
38+
39+
WORKDIR ${SINGNET_REPOS}/example-service

README.md

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# example-service
2+
3+
[![CircleCI](https://circleci.com/gh/singnet/example-service.svg?style=svg)](https://circleci.com/gh/singnet/example-service)
4+
5+
Simple arithmetic service compatible with SingularityNET
6+
7+
## Getting Started
8+
9+
### Prerequisites
10+
11+
* [Python 3.6.5](https://www.python.org/downloads/release/python-365/)
12+
13+
### Installing
14+
15+
* Clone the git repository:
16+
17+
```
18+
$ git clone git@github.com:singnet/example-service.git
19+
$ cd example-service
20+
```
21+
22+
* Install the dependencies and compile the protobuf file:
23+
24+
```
25+
$ pip3 install -r requirements.txt
26+
$ sh buildproto.sh
27+
```
28+
29+
### Running
30+
31+
#### Standalone
32+
33+
* Run the example service directly (without `SNET Daemon`):
34+
35+
```
36+
$ python3 run_example_service.py --no-daemon
37+
```
38+
39+
* To test it run the script:
40+
41+
```
42+
$ python3 test_example_service.py
43+
```
44+
45+
#### With SingularityNET Daemon
46+
47+
##### SingularityNET Daemon Configuration
48+
49+
Now you must follow the [howToPublishService](https://github.com/singnet/wiki/tree/master/tutorials/howToPublishService)
50+
tutorial to publish this service and set the `snetd.config.json` file.
51+
52+
Then, be sure that you have the `snetd` in your PATH.
53+
54+
Create the `snetd.config.json` file using this template:
55+
56+
```
57+
{
58+
"DAEMON_END_POINT": "http://DAEMON_HOST:DAEMON_PORT",
59+
"ETHEREUM_JSON_RPC_ENDPOINT": "https://kovan.infura.io",
60+
"IPFS_END_POINT": "http://ipfs.singularitynet.io:80",
61+
"REGISTRY_ADDRESS_KEY": "0x2e4b2f2b72402b9b2d6a7851e37c856c329afe38",
62+
"PASSTHROUGH_ENABLED": true,
63+
"PASSTHROUGH_ENDPOINT": "http://SERVICE_GRPC_HOST:SERVICE_GRPC_PORT",
64+
"ORGANIZATION_NAME": "ORGANIZATION_NAME",
65+
"SERVICE_NAME": "SERVICE_NAME",
66+
"LOG": {
67+
"LEVEL": "debug",
68+
"OUTPUT": {
69+
"TYPE": "stdout"
70+
}
71+
}
72+
}
73+
```
74+
75+
For example, replace tags with:
76+
77+
- `http://DAEMON_HOST:DAEMON_PORT`: http://localhost:7000
78+
- `http://SERVICE_GRPC_HOST:SERVICE_GRPC_PORT`: http://localhost:7003
79+
- `ORGANIZATION_NAME`: example-organization
80+
- `SERVICE_NAME`: example-service
81+
82+
See [SingularityNet daemon configuration](https://github.com/singnet/snet-daemon/blob/master/README.md#configuration) for detailed configuration description.
83+
84+
##### Running Service + Daemon on Host
85+
86+
* Run the script without flag to launch both `SNET Daemon` and the service
87+
88+
```
89+
$ python3 run_example_service.py
90+
```
91+
92+
##### Running Service + Daemon in Docker Container
93+
94+
* Build the docker image and run a Container from it:
95+
96+
```
97+
$ docker build -t snet_example_service https://github.com/singnet/example-service.git#master
98+
$ docker run -p 7000:7000 -ti snet_example_service bash
99+
```
100+
101+
From this point we follow the tutorial in the Docker Container's prompt.
102+
103+
After this, run the service (with `SNET Daemon`):
104+
105+
```
106+
# python3 run_example_service.py &
107+
```
108+
109+
### Testing
110+
111+
* Invoke the test script (from the same Docker Container):
112+
113+
```
114+
# python3 test_example_service.py
115+
```
116+
117+
## License
118+
119+
This project is licensed under the MIT License - see the
120+
[LICENSE](https://github.com/singnet/example-service/blob/master/LICENSE) file for details.

buildproto.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#! /bin/bash
2+
python3.6 -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. service/service_spec/example_service.proto

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
grpcio>=1.14.2
2+
grpcio-tools>=1.14.1

run_example_service.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import sys
2+
import subprocess
3+
import logging
4+
import pathlib
5+
import argparse
6+
7+
from service import registry
8+
9+
logging.basicConfig(level=10, format="%(asctime)s - [%(levelname)8s] - %(name)s - %(message)s")
10+
log = logging.getLogger("run_example_service")
11+
12+
13+
def main():
14+
parser = argparse.ArgumentParser(description="Run services")
15+
parser.add_argument("--no-daemon", action="store_false", dest="run_daemon", help="do not start the daemon")
16+
args = parser.parse_args()
17+
root_path = pathlib.Path(__file__).absolute().parent
18+
19+
# All services modules go here
20+
service_modules = ["service.example_service"]
21+
22+
# Call for all the services listed in service_modules
23+
all_p = start_all_services(root_path, service_modules, args.run_daemon)
24+
25+
# Wait for all subprocesses
26+
try:
27+
for p in all_p:
28+
p.wait()
29+
except Exception as e:
30+
log.error(e)
31+
raise
32+
33+
34+
def start_all_services(cwd, service_modules, run_daemon):
35+
"""
36+
Loop through all service_modules and start them.
37+
For each one, an instance of Daemon "snetd" is created.
38+
snetd will start with configs from "snetd.config.json"
39+
"""
40+
all_p = []
41+
for i, service_module in enumerate(service_modules):
42+
service_name = service_module.split(".")[-1]
43+
log.info("Launching {} on port {}".format(str(registry[service_name]), service_module))
44+
all_p += start_service(cwd, service_module, run_daemon)
45+
return all_p
46+
47+
48+
def start_service(cwd, service_module, run_daemon):
49+
"""
50+
Starts SNET Daemon ("snetd") and the python module of the service
51+
at the passed gRPC port.
52+
"""
53+
all_p = []
54+
if run_daemon:
55+
all_p.append(start_snetd(str(cwd)))
56+
service_name = service_module.split(".")[-1]
57+
grpc_port = registry[service_name]["grpc"]
58+
p = subprocess.Popen([sys.executable, "-m", service_module, "--grpc-port", str(grpc_port)], cwd=str(cwd))
59+
all_p.append(p)
60+
return all_p
61+
62+
63+
def start_snetd(cwd):
64+
"""
65+
Starts the Daemon "snetd":
66+
"""
67+
cmd = ["snetd", "serve"]
68+
return subprocess.Popen(cmd, cwd=str(cwd))
69+
70+
71+
if __name__ == "__main__":
72+
main()

service/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
registry = {
2+
"example_service": {
3+
"grpc": 7003,
4+
},
5+
}

service/common.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import argparse
2+
import os.path
3+
import time
4+
5+
from service import registry
6+
7+
8+
def common_parser(script_name):
9+
parser = argparse.ArgumentParser(prog=script_name)
10+
service_name = os.path.splitext(os.path.basename(script_name))[0]
11+
parser.add_argument("--grpc-port",
12+
help="port to bind gRPC service to",
13+
default=registry[service_name]['grpc'],
14+
type=int,
15+
required=False)
16+
return parser
17+
18+
19+
# From gRPC docs:
20+
# Because start() does not block you may need to sleep-loop if there is nothing
21+
# else for your code to do while serving.
22+
def main_loop(grpc_handler, args):
23+
server = grpc_handler(port=args.grpc_port)
24+
server.start()
25+
try:
26+
while True:
27+
time.sleep(1)
28+
except KeyboardInterrupt:
29+
server.stop(0)

service/example_service.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import sys
2+
import logging
3+
4+
import grpc
5+
import concurrent.futures as futures
6+
7+
import service.common
8+
9+
# Importing the generated codes from buildproto.sh
10+
import service.service_spec.example_service_pb2_grpc as grpc_bt_grpc
11+
from service.service_spec.example_service_pb2 import Result
12+
13+
logging.basicConfig(level=10, format="%(asctime)s - [%(levelname)8s] - %(name)s - %(message)s")
14+
log = logging.getLogger("example_service")
15+
16+
17+
"""
18+
Simple arithmetic service to test the Snet Daemon (gRPC), dApp and/or Snet-CLI.
19+
The user must provide the method (arithmetic operation) and
20+
two numeric inputs: "a" and "b".
21+
22+
e.g:
23+
With dApp: 'method': mul
24+
'params': {"a": 12.0, "b": 77.0}
25+
Resulting: response:
26+
value: 924.0
27+
28+
29+
Full snet-cli cmd:
30+
$ snet client call mul '{"a":12.0, "b":77.0}'
31+
32+
Result:
33+
(Transaction info)
34+
Signing job...
35+
36+
Read call params from cmdline...
37+
38+
Calling service...
39+
40+
response:
41+
value: 924.0
42+
"""
43+
44+
45+
# Create a class to be added to the gRPC server
46+
# derived from the protobuf codes.
47+
class CalculatorServicer(grpc_bt_grpc.CalculatorServicer):
48+
def __init__(self):
49+
self.a = 0
50+
self.b = 0
51+
self.result = 0
52+
# Just for debugging purpose.
53+
log.debug("CalculatorServicer created")
54+
55+
# The method that will be exposed to the snet-cli call command.
56+
# request: incoming data
57+
# context: object that provides RPC-specific information (timeout, etc).
58+
def add(self, request, context):
59+
# In our case, request is a Numbers() object (from .proto file)
60+
self.a = request.a
61+
self.b = request.b
62+
63+
# To respond we need to create a Result() object (from .proto file)
64+
self.result = Result()
65+
66+
self.result.value = self.a + self.b
67+
log.debug("add({},{})={}".format(self.a, self.b, self.result.value))
68+
return self.result
69+
70+
def sub(self, request, context):
71+
self.a = request.a
72+
self.b = request.b
73+
74+
self.result = Result()
75+
self.result.value = self.a - self.b
76+
log.debug("sub({},{})={}".format(self.a, self.b, self.result.value))
77+
return self.result
78+
79+
def mul(self, request, context):
80+
self.a = request.a
81+
self.b = request.b
82+
83+
self.result = Result()
84+
self.result.value = self.a * self.b
85+
log.debug("mul({},{})={}".format(self.a, self.b, self.result.value))
86+
return self.result
87+
88+
def div(self, request, context):
89+
self.a = request.a
90+
self.b = request.b
91+
92+
self.result = Result()
93+
self.result.value = self.a / self.b
94+
log.debug("div({},{})={}".format(self.a, self.b, self.result.value))
95+
return self.result
96+
97+
98+
# The gRPC serve function.
99+
#
100+
# Params:
101+
# max_workers: pool of threads to execute calls asynchronously
102+
# port: gRPC server port
103+
#
104+
# Add all your classes to the server here.
105+
# (from generated .py files by protobuf compiler)
106+
def serve(max_workers=10, port=7777):
107+
server = grpc.server(futures.ThreadPoolExecutor(max_workers=max_workers))
108+
grpc_bt_grpc.add_CalculatorServicer_to_server(CalculatorServicer(), server)
109+
server.add_insecure_port("[::]:{}".format(port))
110+
return server
111+
112+
113+
if __name__ == "__main__":
114+
"""
115+
Runs the gRPC server to communicate with the Snet Daemon.
116+
"""
117+
parser = service.common.common_parser(__file__)
118+
args = parser.parse_args(sys.argv[1:])
119+
service.common.main_loop(serve, args)

0 commit comments

Comments
 (0)