diff --git a/quickstart/hono_commands_fb.py b/quickstart/hono_commands_fb.py new file mode 100644 index 00000000..0e537011 --- /dev/null +++ b/quickstart/hono_commands_fb.py @@ -0,0 +1,215 @@ +# 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 +# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +# which is available at https://www.apache.org/licenses/LICENSE-2.0. +# +# SPDX-License-Identifier: EPL-2.0 OR Apache-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.handlers import MessagingHandler +from proton.reactor import Container, AtLeastOnce + +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/BackupAndRestore/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]', "fb") + else: + print('[error]') + event.receiver.close() + event.connection.close() + + def on_connection_closed(self, event): + print('[connection closed]') + 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 == CLI_OPT_BACKUP_CMD: + # backup command + value = json.dumps(dict(correlationId=self.correlation_id)) + else: + # start and restore command + opts = {"https.url": "http://{}:8080/data.zip".format(host)} + value = json.dumps(dict(correlationId=self.correlation_id, options=opts)) + + 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=self.action, 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": + # received request event for uploading the file + 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": + # upload feature updated + 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 backup]') + event.receiver.close() + event.connection.close() + elif body["value"]["state"] == "FAILED": + print('[failed backup]') + event.receiver.close() + event.connection.close() + elif body["topic"].split("/")[-1] == "modify" and body["path"].split("/")[-1] == "lastOperation": + # operation (backup/restore) feature updated + if body["value"]["correlationId"] == self.correlation_id: + print('[last operation event received]') + print(json.dumps(body, indent=2)) + if body["value"]["state"] == "RESTORE_FINISHED": + print('[successful restore]') + event.receiver.close() + event.connection.close() + elif body["value"]["state"] == "RESTORE_FAILED": + print('[failed restore]') + event.receiver.close() + event.connection.close() + + def on_connection_closed(self, event): + print('[connection closed]') + os.kill(os.getpid(), signal.SIGINT) + + +CLI_OPT_BACKUP_CMD = "backup" +CLI_OPT_RESTORE_CMD = "restore" + +# Parse command line args +options, reminder = getopt.getopt(sys.argv[2:], 't:d:h:') +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'] +host = os.environ.get("HOST") or opts_dict['-h'] +command = sys.argv[1] +if command == CLI_OPT_BACKUP_CMD: + action = "backup" +elif command == CLI_OPT_RESTORE_CMD: + action = "restore" +else: + print('[error] unsupported command', command) + exit(1) + +# 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 backup and restore app for tenant [{}], device [{}] at [{}]' + .format(tenant_id, device_id, uri)) + +# Create command invoker and handler +upload_correlation_id = "demo.backup.and.restore" + 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, action, 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 backup and restore 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/how-to-guides/backup-restore-files.md b/web/site/content/docs/how-to-guides/backup-restore-files.md new file mode 100644 index 00000000..c8957981 --- /dev/null +++ b/web/site/content/docs/how-to-guides/backup-restore-files.md @@ -0,0 +1,137 @@ +--- +title: "Back up and restore files" +type: docs +description: > + Back up and restore a file from and to your edge device. +weight: 3 +--- + +Following the steps below you will back up a simple text file to an HTTP file server +and then restore it back 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 backing up and restoring files. + +### 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 and downloads. + 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_fb.py" %}} + file backup and restore 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_fb.py + ``` + +### Back up + +By default, all directories in `/var/tmp/file-backup/` or the directory itself can be backed up. +For this example, create a file `data.txt` which will be later backed up: + +```shell +sudo mkdir -p /var/tmp/file-backup && sudo echo "This is the first line in the file!" >> /var/tmp/file-backup/data.txt +``` + +You can verify that the file was successfully created by executing the following command: + +```shell +cat /var/tmp/file-backup/data.txt +``` + +This should produce `This is the first line in the file!` as an output. + +Choose a directory where the text file will be uploaded, open a new terminal there and run `servefile` +with the flag `-u` to enable a file upload: + +```shell +servefile -u . +``` + +To explore the file backup, 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 text file backup from the edge via executing the application that requires the command +to execute (`backup`), Eclipse Hono tenant (`-t`), the device identifier (`-d`) and the host where the backup will +be uploaded to: + +```shell +python3 hono_commands_fb.py backup -t demo -d demo:device -h localhost +``` + +You can check out that the backup file `data.zip` is on your HTTP file server by +listing the content of the `servefile` working directory. + +### Restore + +To explore the restore capabilities you will first modify the `data.txt` file, and then you will restore it to +the version before the changes by using the backup, that was created earlier. + +You can modify the `data.txt` file with the following command: + +```shell +sudo echo "This is the second line in the file!" >> /var/tmp/file-backup/data.txt +``` + +You can verify that the file was successfully updated by executing the following command: + +```shell +cat /var/tmp/file-backup/data.txt +``` + +This output should be: +```text +This is the first line in the file! +This is the second line in the file! +``` + +Navigate to the terminal where `servefile` was started and terminate it. +Start it again with the flag `-l` to enable a file download: + +```shell +servefile -l . +``` + +To explore the file restore, 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 text file restore from the edge via executing the application that requires the command +to execute (`restore`), Eclipse Hono tenant (`-t`), the device identifier (`-d`) and the host where the backup file +will be downloaded from: + +```shell +python3 hono_commands_fb.py restore -t demo -d demo:device -h localhost +``` + +### Verify + +You can check out that the original file is restored by executing the following command: + +```shell +cat /var/tmp/file-backup/data.txt +``` + +This should produce `This is the first line in the file!` as an output. + +### Clean up + +Stop `servefile` and clean up its working directory. +Remove the `data.txt` file from the `/var/tmp/file-backup` directory. diff --git a/web/site/content/docs/how-to-guides/system-metrics.md b/web/site/content/docs/how-to-guides/system-metrics.md index abed9d35..6c9abe41 100644 --- a/web/site/content/docs/how-to-guides/system-metrics.md +++ b/web/site/content/docs/how-to-guides/system-metrics.md @@ -3,7 +3,7 @@ title: "Monitor system metrics" type: docs description: > Monitor system metrics from your edge device. -weight: 3 +weight: 4 --- Following the steps below you will be able to monitor the system metrics from your edge device