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

Provide How-to guide for uploading a log file to a simple HTTP server #66

Merged
merged 7 commits into from
Aug 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
172 changes: 172 additions & 0 deletions quickstart/hono_commands_fu.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# Copyright (c) 2022 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# http://www.eclipse.org/legal/epl-2.0
#
# SPDX-License-Identifier: EPL-2.0

import getopt
import json
import os
import signal
import sys
import threading
import time
import uuid
from string import Template

from proton import Message
from proton._reactor import AtLeastOnce
from proton.handlers import MessagingHandler
from proton.reactor import Container

ditto_live_inbox_msg_template = Template("""
{
"topic": "$namespace/$name/things/live/messages/$action",
"headers": {
"content-type": "application/json",
"correlation-id": "$correlation_id",
"response-required": true
},
"path": "/features/AutoUploadable/inbox/messages/$action",
"value": $value
}
""")


class CommandResponsesHandler(MessagingHandler):
def __init__(self, server, address):
super(CommandResponsesHandler, self).__init__()
self.server = server
self.address = address

def on_start(self, event):
conn = event.container.connect(self.server, user="consumer@HONO", password="verysecret")
event.container.create_receiver(conn, self.address)
print('[response handler connected]')

def on_message(self, event):
print('[got response]')
response = json.loads(event.message.body)
print(json.dumps(response, indent=2))
if response["status"] == 204:
print('[ok]', "fu")
else:
print('[error]')
os.kill(os.getpid(), signal.SIGINT)


class CommandsInvoker(MessagingHandler):
def __init__(self, server, address, action, correlation_id=None):
super(CommandsInvoker, self).__init__()
self.server = server
self.address = address
self.action = action
self.correlation_id = correlation_id

def on_start(self, event):
conn = event.container.connect(self.server, sasl_enabled=True, allowed_mechs="PLAIN", allow_insecure_mechs=True,
user="consumer@HONO", password="verysecret")
event.container.create_sender(conn, self.address)

def on_sendable(self, event):
print('[sending command]')
correlation_id = str(uuid.uuid4())
namespaced_id = device_id.split(':', 1)
if self.action == "trigger":
value = json.dumps(dict(correlationId=self.correlation_id))
else:
upload_options = {"https.url": "http://localhost:8080/suite-connector.log"}
value = json.dumps(dict(correlationId=self.correlation_id, options=upload_options))

payload = ditto_live_inbox_msg_template.substitute(namespace=namespaced_id[0], name=namespaced_id[1],
action=self.action, correlation_id=correlation_id,
value=value)
print(payload)
msg = Message(body=payload, address='{}/{}'.format(self.address, device_id), content_type="application/json",
subject="fu", reply_to=reply_to_address, correlation_id=correlation_id, id=str(uuid.uuid4()))
event.sender.send(msg)
event.sender.close()
event.connection.close()
print('[sent]')


class EventsHandler(MessagingHandler):
def __init__(self, server, receiver_address, correlation_id):
super(EventsHandler, self).__init__()
self.server = server
self.receiver_address = receiver_address
self.correlation_id = correlation_id

def on_start(self, event):
conn = event.container.connect(self.server, user="consumer@HONO", password="verysecret")
event.container.create_receiver(conn, source=self.receiver_address, options=[AtLeastOnce()])
print('[events handler connected]')

def on_message(self, event):
if event.message.body is not None:
body = json.loads(event.message.body)
if body["topic"].split("/")[-1] == "request":
print('[request event received]')
print(json.dumps(body, indent=2))
request_correlation_id = body["value"]["correlationId"]
Container(CommandsInvoker(uri, command_address, "start", correlation_id=request_correlation_id)).run()
elif body["topic"].split("/")[-1] == "modify" and body["path"].split("/")[-1] == "lastUpload":
if body["value"]["correlationId"] == self.correlation_id:
print('[last upload event received]')
print(json.dumps(body, indent=2))
if body["value"]["state"] == "SUCCESS":
e-grigorov marked this conversation as resolved.
Show resolved Hide resolved
print('[successful upload]')
os.kill(os.getpid(), signal.SIGINT)
elif body["value"]["state"] == "FAILED":
print('[failed upload]')
os.kill(os.getpid(), signal.SIGINT)


# Parse command line args
options, reminder = getopt.getopt(sys.argv[1:], 't:d:')
opts_dict = dict(options)
tenant_id = os.environ.get("TENANT") or opts_dict['-t']
device_id = os.environ.get("DEVICE_ID") or opts_dict['-d']

# AMQP global configurations
uri = 'amqp://hono.eclipseprojects.io:15672'
command_address = 'command/{}'.format(tenant_id)
event_address = 'event/{}'.format(tenant_id)
reply_to_address = 'command_response/{}/replies'.format(tenant_id)

print('[starting] demo file upload app for tenant [{}], device [{}] at [{}]'.format(tenant_id, device_id, uri))

# Create command invoker and handler
upload_correlation_id = "demo.upload." + str(uuid.uuid4())
events_handler = Container(EventsHandler(uri, event_address, correlation_id=upload_correlation_id))
response_handler = Container(CommandResponsesHandler(uri, reply_to_address))
commands_invoker = Container(CommandsInvoker(uri, command_address, "trigger", correlation_id=upload_correlation_id))

events_thread = threading.Thread(target=lambda: events_handler.run(), daemon=True)
events_thread.start()
response_thread = threading.Thread(target=lambda: response_handler.run(), daemon=True)
response_thread.start()
# Give it some time to link
time.sleep(2)
# Send the command
commands_invoker.run()


def handler(signum, frame):
print('[stopping] demo file upload app for tenant [{}], device [{}] at [{}]'.format(tenant_id, device_id, uri))
events_handler.stop()
response_handler.stop()
events_thread.join(timeout=5)
response_thread.join(timeout=5)
print('[stopped]')
exit(0)


signal.signal(signal.SIGINT, handler)
while True:
pass
6 changes: 5 additions & 1 deletion web/site/content/docs/concepts/file-upload.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,8 @@ It's not always possible to inline all the data into exchanged messages. For exa

There are different triggers which can initiate the upload operation: periodic or explicit. Once initiated, the request will be sent to the IoT cloud for confirmation or cancellation transferred back to the edge. If starting is confirmed, the files
to upload will be selected according to the specified configuration, their integrity check information can be calculated and the transfer of the binary content will begin. A status report is announced on each step of the upload process
enabling its transparent monitoring.
enabling its transparent monitoring.

### What's next

[How to upload files]({{< relref "upload-files" >}})
2 changes: 1 addition & 1 deletion web/site/content/docs/how-to-guides/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ type: docs
weight: 4
description: >
Explore the functionalities of Eclipse Kanto.
---
---
76 changes: 76 additions & 0 deletions web/site/content/docs/how-to-guides/upload-files.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
---
title: "Upload files"
type: docs
description: >
Upload a log file from your edge device.
weight: 2
---

Following the steps below you will upload an example log file to your HTTP file server
via a publicly available Eclipse Hono sandbox using Eclipse Kanto.
A simple Eclipse Hono northbound business application written in Python is
provided to explore the capabilities for remotely uploading and monitoring.

### Before you begin

To ensure that all steps in this guide can be executed, you need:

* {{% refn "https://github.com/sebageek/servefile/" %}}`servefile`{{% /refn %}} installed

This is a small Python HTTP server used in the example to serve the uploads.
It does not have to be running on your edge device but it has to be accessible from there.
You can install it by executing:

```shell
pip3 install servefile
```

* If you don't have an installed and running Eclipse Kanto on your edge device,
follow {{% relrefn "install" %}} Install Eclipse Kanto {{% /relrefn %}}
* If you don't have a connected Eclipse Kanto to Eclipse Hono sandbox,
follow {{% relrefn "hono" %}} Explore via Eclipse Hono {{% /relrefn %}}

* The {{% refn "https://github.com/eclipse-kanto/kanto/blob/main/quickstart/hono_commands_fu.py" %}}
file upload application {{% /refn %}}

Navigate to the `quickstart` folder where the resources from the {{% relrefn "hono" %}} Explore via Eclipse Hono
{{% /relrefn %}} guide are located and execute the following script:

```shell
wget https://github.com/eclipse-kanto/kanto/raw/main/quickstart/hono_commands_fu.py
```


### Upload log file

By default, all files in `/var/tmp/file-upload/` directory can be uploaded.
For example, grab the suite connector log file and place it in the directory via executing:

```shell
mkdir -p /var/tmp/file-upload/ && sudo cp /var/log/suite-connector/suite-connector.log /var/tmp/file-upload/
```

Choose a directory where the log file will be uploaded, open a new terminal there and run `servefile`:

```shell
servefile -u .
```

To explore the file upload, we will use a Python script to request and monitor the operation.
The location where the Python application will run does not have to be your edge device as it communicates remotely
with Eclipse Hono only.

Now we are ready to request the log file upload from the edge via executing the application
that requires the Eclipse Hono tenant (`-t`) and the device identifier (`-d`):

```shell
python3 hono_commands_fu.py -t demo -d demo:device
```

### Verify

You can check out that the log file is on your HTTP file server listing the content of `servefile` working directory.

### Clean up

Stop `servefile` and clean up its working directory.