Skip to content

Commit 039629e

Browse files
authored
Introduce tutorial to deploy python runtimes locally (#101)
The introduced tutorial uses Docker and a tranfers data tool (e.g. curl, wget or Postman). A summary of the tutorial steps are: 1. Create docker container based on core/python3ActionLoop Dockerfile 2. Deploy/run the container locally 3. Create function that resides in a json file 4. Initialize the function against the deployed container using either curl, wget or Postman 5. Call/trigger function using either curl, wget or Postman Signed-off-by: Igor Braga <higorb1@gmail.com>
1 parent c2d9a6a commit 039629e

File tree

1 file changed

+311
-0
lines changed

1 file changed

+311
-0
lines changed

README.md

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,317 @@
2323

2424
## Build Runtimes
2525

26+
### You have 2 options to build the Python runtime:
27+
- Building locally
28+
- Using OpenWhisk Actions.
29+
### This README walks you through how to do both
30+
31+
# Building Python Runtime Locally
32+
33+
### Pre-requisites
34+
- [Docker](https://www.docker.com/)
35+
- [curl](https://curl.se/), [wget](https://www.gnu.org/software/wget/), or [Postman](https://www.postman.com/)
36+
37+
0. Choose/create a folder of your liking
38+
1. Clone this repo:
39+
```
40+
git clone https://github.com/apache/openwhisk-runtime-python
41+
cd openwhisk-runtime-python
42+
```
43+
44+
2. Build docker
45+
46+
Build using Python 3.7 (recommended)
47+
```
48+
docker build -t actionloop-python-v3.7:1.0-SNAPSHOT $(pwd)/core/python3ActionLoop
49+
```
50+
This tutorial assumes you're building with python 3.7. But if you want to use python 2.7 you can use:
51+
```
52+
docker build -t actionloop-python-v2.7:1.0-SNAPSHOT $(pwd)/core/python2ActionLoop
53+
```
54+
55+
2.1. Check docker `IMAGE ID` (3rd column) for repository `actionloop-python-v3.7`
56+
```
57+
docker images
58+
```
59+
You should see an image that looks something like:
60+
```
61+
actionloop-python-v3.7 1.0-SNAPSHOT ...
62+
```
63+
64+
2.2. Tag image (Optional step). Required if you’re pushing your docker image to a registry e.g. dockerHub
65+
```
66+
docker tag <docker_image_ID> <dockerHub_username>/actionloop-python-v3.7:1.0-SNAPSHOT
67+
```
68+
69+
3. Run docker on localhost with either the following commands:
70+
```
71+
docker run -p 127.0.0.1:80:8080/tcp --name=bloom_whisker --rm -it actionloop-python-v3.7:1.0-SNAPSHOT
72+
```
73+
Or run the container in the background (Add -d (detached) to the command above)
74+
```
75+
docker run -d -p 127.0.0.1:80:8080/tcp --name=bloom_whisker --rm -it actionloop-python-v3.7:1.0-SNAPSHOT
76+
```
77+
Note: If you run your docker container in the background you'll want to stop it with:
78+
```
79+
docker stop <container_id>
80+
```
81+
Where `<container_id>` is obtained from `docker ps` command bellow
82+
83+
Lists all running containers
84+
```
85+
docker ps
86+
```
87+
or
88+
```
89+
docker ps -a
90+
```
91+
You shoulkd see a container named `bloom_whisker` being run
92+
93+
4. Create your function (note that each container can only hold one function)
94+
In this first example we'll be creating a very simple function
95+
Create a json file called `python-data-init-run.json` which will contain the function that looks something like the following:
96+
NOTE: value of code is the actual payload and must match the syntax of the target runtime language, in this case `python`
97+
```json
98+
{
99+
"value": {
100+
"name" : "python-helloworld",
101+
"main" : "main",
102+
"binary" : false,
103+
"code" : "def main(args): return {'payload': 'Hello World!'}"
104+
}
105+
}
106+
```
107+
108+
To issue the action against the running runtime, we must first make a request against the `init` API
109+
We need to issue `POST` requests to init our function
110+
Using curl (the option `-d` signifies we're issuing a POST request)
111+
```
112+
curl -d "@python-data-init-run.json" -H "Content-Type: application/json" http://localhost/init
113+
```
114+
Using wget (the option `--post-file` signifies we're issuing a POST request)
115+
```
116+
wget --post-file=python-data-init-run.json --header="Content-Type: application/json" http://localhost/init
117+
```
118+
The above can also be achieved with [Postman](https://www.postman.com/) by setting the headers and body accordingly
119+
120+
Client expected response:
121+
```
122+
{"ok":true}
123+
```
124+
Server will remain silent in this case
125+
126+
Now we can invoke/run our function agains the `run` API with:
127+
Using curl `POST` request
128+
```
129+
curl -d "@python-data-init-run.json" -H "Content-Type: application/json" http://localhost/run
130+
```
131+
Or using `GET` request
132+
```
133+
curl --data-binary "@python-data-init-run.json" -H "Content-Type: application/json" http://localhost/run
134+
```
135+
Or
136+
Using wget `POST` request. The `-O-` is to redirect `wget` response to `stdout`.
137+
```
138+
wget -O- --post-file=python-data-init-run.json --header="Content-Type: application/json" http://localhost/run
139+
```
140+
Or using `GET` request
141+
```
142+
wget -O- --body-file=python-data-init-run.json --method=GET --header="Content-Type: application/json" http://localhost/run
143+
```
144+
145+
The above can also be achieved with [Postman](https://www.postman.com/) by setting the headers and body accordingly.
146+
147+
You noticed that we’re passing the same file `python-data-init-run.json` from function initialization request to trigger the function. That’s not necessary and not recommended since to trigger a function all we need is to pass the parameters of the function. So in the above example, it's prefered if we create a file called `python-data-params.json` that looks like the following:
148+
```json
149+
{
150+
"value": {}
151+
}
152+
```
153+
And trigger the function with the following (it also works with wget and postman equivalents):
154+
```
155+
curl --data-binary "@python-data-params.json" -H "Content-Type: application/json" http://localhost/run
156+
```
157+
158+
#### You can perform the same steps as above using [Postman](https://www.postman.com/) application. Make sure you have the correct request type set and the respective body. Also set the correct headers key value pairs, which for us is "Content-Type: application/json"
159+
160+
After you trigger the function with one of the above commands you should expect the following client response:
161+
```
162+
{"payload": "Hello World!"}
163+
```
164+
And Server expected response:
165+
```
166+
XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
167+
XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
168+
```
169+
170+
## Creating functions with arguments
171+
If your container still running from the previuous example you must stop it and re-run it before proceding. Remember that each python runtime can only hold one function (which cannot be overrided due to security reasons)
172+
Create a json file called `python-data-init-params.json` which will contain the function to be initialized that looks like the following:
173+
```json
174+
{
175+
"value": {
176+
"name": "python-helloworld-with-params",
177+
"main" : "main",
178+
"binary" : false,
179+
"code" : "def main(args): return {'payload': 'Hello ' + args.get('name') + ' from ' + args.get('place') + '!!!'}"
180+
}
181+
}
182+
```
183+
Also create a json file `python-data-run-params.json` which will contain the parameters to the function used to trigger it. Notice here we're creating 2 separate file from the beginning since this is good practice to make the disticntion between what needs to be send via the `init` API and what needs to be sent via the `run` API:
184+
```json
185+
{
186+
"value": {
187+
"name": "UFO",
188+
"place": "Mars"
189+
}
190+
}
191+
```
192+
193+
Now, all we have to do is initialize and trigger our function.
194+
First, to initialize our function make sure your python runtime container is running if not, spin the container by following step 3.
195+
Issue a `POST` request against the `init` API with the following command:
196+
Using curl:
197+
```
198+
curl -d "@python-data-init-params.json" -H "Content-Type: application/json" http://localhost/init
199+
```
200+
Using wget:
201+
```
202+
wget --post-file=python-data-init-params.json --header="Content-Type: application/json" http://localhost/init
203+
```
204+
Client expected response:
205+
```
206+
{"ok":true}
207+
```
208+
Server will remain silent in this case
209+
210+
Second, to run/trigger the function issue requests against the `run` API with the following command:
211+
Using curl with `POST`:
212+
```
213+
curl -d "@python-data-run-params.json" -H "Content-Type: application/json" http://localhost/run
214+
```
215+
Or using curl with `GET`:
216+
```
217+
curl --data-binary "@python-data-run-params.json" -H "Content-Type: application/json" http://localhost/run
218+
```
219+
Or
220+
Using wget with `POST`:
221+
```
222+
wget -O- --post-file=python-data-run-params.json --header="Content-Type: application/json" http://localhost/run
223+
```
224+
Or using wget with `GET`:
225+
```
226+
wget -O- --body-file=python-data-run-params.json --method=GET --header="Content-Type: application/json" http://localhost/run
227+
```
228+
229+
After you trigger the function with one of the above commands you should expect the following client response:
230+
```
231+
{"payload": "Hello UFO from Mars!!!"}
232+
```
233+
234+
And Server expected response:
235+
```
236+
XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
237+
XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
238+
```
239+
240+
## Now let's create a more interesting function
241+
### This function will calculate the nth Fibonacci number
242+
This is the function we’re trying to create. It calculates the nth number of the Fibonacci sequence recursively in `O(n)` time
243+
```python
244+
def fibonacci(n, mem):
245+
if (n == 0 or n == 1):
246+
return 1
247+
if (mem[n] == -1):
248+
mem[n] = fibonacci(n-1, mem) + fibonacci(n-2, mem)
249+
return mem[n]
250+
251+
def main(args):
252+
n = int(args.get('fib_n'))
253+
mem = [-1 for i in range(n+1)]
254+
result = fibonacci(n, mem)
255+
key = 'Fibonacci of n == ' + str(n)
256+
return {key: result}
257+
```
258+
259+
Create a json file called `python-fib-init.json` to initialize our fibonacci function and insert the following. (It’s the same code as above but since we can’t have a string span multiple lines in JSON we need to put all this code in one line and this is how we do it. It’s ugly but not much we can do here)
260+
```json
261+
{
262+
"value": {
263+
"name": "python-recursive-fibonacci",
264+
"main" : "main",
265+
"binary" : false,
266+
"code" : "def fibonacci(n, mem):\n\tif (n == 0 or n == 1):\n\t\treturn 1\n\tif (mem[n] == -1):\n\t\tmem[n] = fibonacci(n-1, mem) + fibonacci(n-2, mem)\n\treturn mem[n]\n\ndef main(args):\n\tn = int(args.get('fib_n'))\n\tmem = [-1 for i in range(n+1)]\n\tresult = fibonacci(n, mem)\n\tkey = 'Fibonacci of n == ' + str(n)\n\treturn {key: result}"
267+
}
268+
}
269+
```
270+
Create a json file called `python-fib-run.json` which will be used to run/trigger our function with the appropriate argument:
271+
```json
272+
{
273+
"value": {
274+
"fib_n": "40"
275+
}
276+
}
277+
```
278+
279+
Now we’re all set.
280+
Make sure your python runtime container is running if not, spin the container by following step 3.
281+
Initialize our fibonacci function by issuing a `POST` request against the `init` API with the following command:
282+
Using curl:
283+
```
284+
curl -d "@python-fib-init.json" -H "Content-Type: application/json" http://localhost/init
285+
```
286+
Using wget:
287+
```
288+
wget --post-file=python-fib-init.json --header="Content-Type: application/json" http://localhost/init
289+
```
290+
Client expected response:
291+
```
292+
{"ok":true}
293+
```
294+
You've noticed by now that `init` API always returns `{"ok":true}` for a successful initialized function. And the server, again, will remain silent
295+
296+
Trigger the function by running/triggering the function with a request against the `run` API with the following command:
297+
Using curl with `POST`:
298+
```
299+
curl -d "@python-fib-run.json" -H "Content-Type: application/json" http://localhost/run
300+
```
301+
Using curl with `GET`:
302+
```
303+
curl --data-binary "@python-fib-run.json" -H "Content-Type: application/json" http://localhost/run
304+
```
305+
Using wget with `POST`:
306+
```
307+
wget -O- --post-file=python-fib-run.json --header="Content-Type: application/json" http://localhost/run
308+
```
309+
Using wget with `GET`:
310+
```
311+
wget -O- --body-file=python-fib-run.json --method=GET --header="Content-Type: application/json" http://localhost/run
312+
```
313+
314+
After you trigger the function with one of the above commands you should expect the following client response:
315+
```
316+
{"Fibonacci of n == 40": 165580141}
317+
```
318+
319+
And Server expected response:
320+
```
321+
XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
322+
XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
323+
```
324+
325+
#### At this point you can edit python-fib-run.json an try other `fib_n` values. All you have to do is save `python-fib-run.json` and trigger the function again. Notice that here we're just modifying the parameters of our function; therefore, there's no need to re-run/re-initialize our container that contains our Python runtime.
326+
327+
#### You can also automate most of this process through [docker actions](https://github.com/apache/openwhisk/tree/master/tools/actionProxy) by using `invoke.py`
328+
329+
# Building Python Runtime using OpenWhisk Actions
330+
331+
### Pre-requisites
332+
- [Gradle](https://gradle.org/)
333+
- [Docker](https://www.docker.com/)
334+
- [OpenWhisk CLI wsk](https://github.com/apache/openwhisk-cli/releases)
335+
336+
26337
The runtimes are built using Gradle.
27338
The file [settings.gradle](settings.gradle) lists the images that are build by default.
28339
To build all those images, run the following command.

0 commit comments

Comments
 (0)