diff --git a/quickstart/hono_commands_fu.py b/quickstart/hono_commands_fu.py new file mode 100644 index 00000000..b7d2c6e3 --- /dev/null +++ b/quickstart/hono_commands_fu.py @@ -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": + 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 diff --git a/web/site/content/docs/concepts/file-upload.md b/web/site/content/docs/concepts/file-upload.md index 6cab15fe..5ff289d7 100644 --- a/web/site/content/docs/concepts/file-upload.md +++ b/web/site/content/docs/concepts/file-upload.md @@ -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. \ No newline at end of file +enabling its transparent monitoring. + +### What's next + +[How to upload files]({{< relref "upload-files" >}}) \ No newline at end of file diff --git a/web/site/content/docs/how-to-guides/_index.md b/web/site/content/docs/how-to-guides/_index.md index ebed9f8a..52fca0af 100644 --- a/web/site/content/docs/how-to-guides/_index.md +++ b/web/site/content/docs/how-to-guides/_index.md @@ -4,4 +4,4 @@ type: docs weight: 4 description: > Explore the functionalities of Eclipse Kanto. ---- +--- \ No newline at end of file diff --git a/web/site/content/docs/how-to-guides/upload-files.md b/web/site/content/docs/how-to-guides/upload-files.md new file mode 100644 index 00000000..5c1c4213 --- /dev/null +++ b/web/site/content/docs/how-to-guides/upload-files.md @@ -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.