-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Provide How-to guide for backing up and restoring a text file (#142)
[#90] Provide How-to guide for backing up and restoring a text file - created Python scripts that executes the backup and restore operations - created a guide in the Eclipse Kanto Documentation Signed-off-by: Ognian Baruh <[email protected]>
- Loading branch information
Ognian Baruh
authored
Oct 12, 2022
1 parent
6583917
commit 50d55a4
Showing
3 changed files
with
353 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
137 changes: 137 additions & 0 deletions
137
web/site/content/docs/how-to-guides/backup-restore-files.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters