Skip to content

Commit

Permalink
Provide How-to guide for backing up and restoring a text file (#142)
Browse files Browse the repository at this point in the history
[#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
Show file tree
Hide file tree
Showing 3 changed files with 353 additions and 1 deletion.
215 changes: 215 additions & 0 deletions quickstart/hono_commands_fb.py
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 web/site/content/docs/how-to-guides/backup-restore-files.md
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.
2 changes: 1 addition & 1 deletion web/site/content/docs/how-to-guides/system-metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 50d55a4

Please sign in to comment.