Skip to content

Commit 356dcb1

Browse files
andrewsgJon Wayne Parrott
authored and
Jon Wayne Parrott
committed
Add samples for Cloud Tasks (#1068)
* Add samples for Cloud Tasks * Respond to tasks sample review * Update app engine queues samples * Address review feedback * Address review issues and convert pull queue sample to not use API key auth * Reform pull queues to match appengine queues changes to auth, command line input, readme * flake8 and fix comment
1 parent ec5bbad commit 356dcb1

12 files changed

+566
-0
lines changed

appengine/flexible/tasks/README.md

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Google Cloud Tasks App Engine Queue Samples
2+
3+
Sample command-line program for interacting with the Cloud Tasks API
4+
using App Engine queues.
5+
6+
App Engine queues push tasks to an App Engine HTTP target. This directory
7+
contains both the App Engine app to deploy, as well as the snippets to run
8+
locally to push tasks to it, which could also be called on App Engine.
9+
10+
`app_engine_queue_snippets.py` is a simple command-line program to create tasks
11+
to be pushed to the App Engine app.
12+
13+
`main.py` is the main App Engine app. This app serves as an endpoint to receive
14+
App Engine task attempts.
15+
16+
`app.yaml` configures the App Engine app.
17+
18+
19+
## Prerequisites to run locally:
20+
21+
Please refer to [Setting Up a Python Development Environment](https://cloud.google.com/python/setup).
22+
23+
## Authentication
24+
25+
To set up authentication, please refer to our
26+
[authentication getting started guide](https://cloud.google.com/docs/authentication/getting-started).
27+
28+
## Creating a queue
29+
30+
To create a queue using the Cloud SDK, use the following gcloud command:
31+
32+
gcloud alpha tasks queues create-app-engine-queue my-appengine-queue
33+
34+
Note: A newly created queue will route to the default App Engine service and
35+
version unless configured to do otherwise. Read the online help for the
36+
`create-app-engine-queue` or the `update-app-engine-queue` commands to learn
37+
about routing overrides for App Engine queues.
38+
39+
## Deploying the App Engine app
40+
41+
Deploy the App Engine app with gcloud:
42+
43+
gcloud app deploy
44+
45+
Verify the index page is serving:
46+
47+
gcloud app browse
48+
49+
The App Engine app serves as a target for the push requests. It has an
50+
endpoint `/log_payload` that reads the payload (i.e., the request body) of the
51+
HTTP POST request and logs it. The log output can be viewed with:
52+
53+
gcloud app logs read
54+
55+
## Running the Samples
56+
57+
Set environment variables:
58+
59+
First, your project ID:
60+
61+
export PROJECT_ID=my-project-id
62+
63+
Then the queue ID, as specified at queue creation time. Queue IDs already
64+
created can be listed with `gcloud alpha tasks queues list`.
65+
66+
export QUEUE_ID=my-appengine-queue
67+
68+
And finally the location ID, which can be discovered with
69+
`gcloud alpha tasks queues describe $QUEUE_ID`, with the location embedded in
70+
the "name" value (for instance, if the name is
71+
"projects/my-project/locations/us-central1/queues/my-appengine-queue", then the
72+
location is "us-central1").
73+
74+
export LOCATION_ID=us-central1
75+
76+
Create a task, targeted at the `log_payload` endpoint, with a payload specified:
77+
78+
python create_app_engine_queue_task.py --project=$PROJECT_ID --queue=$QUEUE_ID --location=$LOCATION_ID --payload=hello
79+
80+
Now view that the payload was received and verify the payload:
81+
82+
gcloud app logs read
83+
84+
Create a task that will be scheduled for a time in the future using the
85+
`--in_seconds` flag:
86+
87+
python create_app_engine_queue_task.py --project=$PROJECT_ID --queue=$QUEUE_ID --location=$LOCATION_ID --payload=hello --in_seconds=30

appengine/flexible/tasks/app.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
runtime: python
2+
env: flex
3+
entrypoint: gunicorn -b :$PORT main:app
4+
5+
runtime_config:
6+
python_version: 3
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# Copyright 2017 Google Inc. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from __future__ import print_function
16+
17+
import argparse
18+
import base64
19+
import datetime
20+
import json
21+
22+
from googleapiclient import discovery
23+
24+
25+
def seconds_from_now_to_rfc3339_datetime(seconds):
26+
"""Return an RFC 3339 datetime string for a number of seconds from now."""
27+
d = datetime.datetime.utcnow() + datetime.timedelta(seconds=seconds)
28+
return d.isoformat('T') + 'Z'
29+
30+
31+
def create_task(project, queue, location, payload=None, in_seconds=None):
32+
"""Create a task for a given queue with an arbitrary payload."""
33+
34+
# Create a client.
35+
DISCOVERY_URL = (
36+
'https://cloudtasks.googleapis.com/$discovery/rest?version=v2beta2')
37+
client = discovery.build(
38+
'cloudtasks', 'v2beta2', discoveryServiceUrl=DISCOVERY_URL)
39+
40+
url = '/log_payload'
41+
body = {
42+
'task': {
43+
'app_engine_task_target': {
44+
'http_method': 'POST',
45+
'relative_url': url
46+
}
47+
}
48+
}
49+
50+
if payload is not None:
51+
# Payload is a string (unicode), and must be encoded for base64.
52+
# The finished request body is JSON, which requires unicode.
53+
body['task']['app_engine_task_target']['payload'] = base64.b64encode(
54+
payload.encode()).decode()
55+
56+
if in_seconds is not None:
57+
scheduled_time = seconds_from_now_to_rfc3339_datetime(in_seconds)
58+
body['task']['schedule_time'] = scheduled_time
59+
60+
queue_name = 'projects/{}/locations/{}/queues/{}'.format(
61+
project, location, queue)
62+
63+
print('Sending task {}'.format(json.dumps(body)))
64+
65+
response = client.projects().locations().queues().tasks().create(
66+
parent=queue_name, body=body).execute()
67+
68+
# By default CreateTaskRequest.responseView is BASIC, so not all
69+
# information is retrieved by default because some data, such as payloads,
70+
# might be desirable to return only when needed because of its large size
71+
# or because of the sensitivity of data that it contains.
72+
print('Created task {}'.format(response['name']))
73+
return response
74+
75+
76+
if __name__ == '__main__':
77+
parser = argparse.ArgumentParser(
78+
description=create_task.__doc__,
79+
formatter_class=argparse.RawDescriptionHelpFormatter)
80+
81+
parser.add_argument(
82+
'--project',
83+
help='Project of the queue to add the task to.'
84+
)
85+
86+
parser.add_argument(
87+
'--queue',
88+
help='ID (short name) of the queue to add the task to.'
89+
)
90+
91+
parser.add_argument(
92+
'--location',
93+
help='Location of the queue to add the task to.'
94+
)
95+
96+
parser.add_argument(
97+
'--payload',
98+
help='Optional payload to attach to the push queue.'
99+
)
100+
101+
parser.add_argument(
102+
'--in_seconds',
103+
help='The number of seconds from now to schedule task attempt.'
104+
)
105+
106+
args = parser.parse_args()
107+
108+
create_task(
109+
args.project, args.queue, args.location,
110+
args.payload, args.in_seconds)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Copyright 2016 Google Inc. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import mock
16+
17+
import create_app_engine_queue_task
18+
19+
TEST_PROJECT = 'mock-project'
20+
TEST_LOCATION = 'us-central1'
21+
TEST_QUEUE = 'my-appengine-queue'
22+
23+
24+
@mock.patch('googleapiclient.discovery.build')
25+
def test_create_task(build):
26+
projects = build.return_value.projects.return_value
27+
locations = projects.locations.return_value
28+
create_function = locations.queues.return_value.tasks.return_value.create
29+
execute_function = create_function.return_value.execute
30+
execute_function.return_value = {'name': 'task_name'}
31+
create_app_engine_queue_task.create_task(
32+
TEST_PROJECT, TEST_QUEUE, TEST_LOCATION)
33+
assert execute_function.called

appengine/flexible/tasks/main.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Copyright 2016 Google Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""App Engine app to serve as an endpoint for App Engine queue samples."""
16+
17+
import logging
18+
19+
from flask import Flask, request
20+
21+
app = Flask(__name__)
22+
23+
24+
@app.route('/log_payload', methods=['POST'])
25+
def log_payload():
26+
"""Log the request payload."""
27+
payload = request.data or "empty payload"
28+
logging.warn(payload)
29+
return 'Logged request payload: {}'.format(payload)
30+
31+
32+
@app.route('/')
33+
def hello():
34+
"""Basic index to verify app is serving."""
35+
return 'Hello World!'
36+
37+
38+
if __name__ == '__main__':
39+
# This is used when running locally. Gunicorn is used to run the
40+
# application on Google App Engine. See entrypoint in app.yaml.
41+
app.run(host='127.0.0.1', port=8080, debug=True)

appengine/flexible/tasks/main_test.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Copyright 2016 Google Inc. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import mock
16+
import pytest
17+
18+
19+
@pytest.fixture
20+
def app():
21+
import main
22+
main.app.testing = True
23+
return main.app.test_client()
24+
25+
26+
def test_index(app):
27+
r = app.get('/')
28+
assert r.status_code == 200
29+
30+
31+
@mock.patch('logging.warn')
32+
def test_log_payload(logging_mock, app):
33+
payload = 'hello'
34+
35+
r = app.post('/log_payload', payload)
36+
assert r.status_code == 200
37+
38+
assert logging_mock.called
39+
40+
41+
@mock.patch('logging.warn')
42+
def test_empty_payload(logging_mock, app):
43+
r = app.post('/log_payload')
44+
assert r.status_code == 200
45+
46+
assert logging_mock.called
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Flask==0.11.1
2+
google-api-python-client==1.6.0
3+
google-cloud-datastore==0.22.0
4+
gunicorn==19.6.0

tasks/README.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Google Cloud Tasks Pull Queue Samples
2+
3+
Sample command-line program for interacting with the Google Cloud Tasks API
4+
using pull queues.
5+
6+
Pull queues let you add tasks to a queue, then programatically remove and
7+
interact with them. Tasks can be added or processed in any environment,
8+
such as on Google App Engine or Google Compute Engine.
9+
10+
`pull_queue_snippets.py` is a simple command-line program to demonstrate listing queues,
11+
creating tasks, and pulling and acknowledging tasks.
12+
13+
## Prerequisites to run locally:
14+
15+
Please refer to [Setting Up a Python Development Environment](https://cloud.google.com/python/setup).
16+
17+
## Authentication
18+
19+
To set up authentication, please refer to our
20+
[authentication getting started guide](https://cloud.google.com/docs/authentication/getting-started).
21+
22+
## Creating a queue
23+
24+
To create a queue using the Cloud SDK, use the following gcloud command:
25+
26+
gcloud alpha tasks queues create-pull-queue my-pull-queue
27+
28+
## Running the Samples
29+
30+
Set the environment variables:
31+
32+
Set environment variables:
33+
34+
First, your project ID:
35+
36+
export PROJECT_ID=my-project-id
37+
38+
Then the queue ID, as specified at queue creation time. Queue IDs already
39+
created can be listed with `gcloud alpha tasks queues list`.
40+
41+
export QUEUE_ID=my-pull-queue
42+
43+
And finally the location ID, which can be discovered with
44+
`gcloud alpha tasks queues describe $QUEUE_ID`, with the location embedded in
45+
the "name" value (for instance, if the name is
46+
"projects/my-project/locations/us-central1/queues/my-pull-queue", then the
47+
location is "us-central1").
48+
49+
export LOCATION_ID=us-central1
50+
51+
Create a task for a queue:
52+
53+
python pull_queue_snippets.py create-task --project=$PROJECT_ID --queue=$QUEUE_ID --location=$LOCATION_ID
54+
55+
Pull and acknowledge a task:
56+
57+
python pull_queue_snippets.py pull-and-ack-task --project=$PROJECT_ID --queue=$QUEUE_ID --location=$LOCATION_ID
58+
59+
Note that usually, there would be a processing step in between pulling a task and acknowledging it.

0 commit comments

Comments
 (0)