This repository has been archived by the owner on Sep 3, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 88
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #84 from radu-stefan-dt/synthetic-sftp
Add SFTP Third-party Synthetic ActiveGate extension
- Loading branch information
Showing
6 changed files
with
468 additions
and
0 deletions.
There are no files selected for viewing
12 changes: 12 additions & 0 deletions
12
third-party-synthetic/active-gate-extensions/extension-third-party-sftp/README.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,12 @@ | ||
# Synthetic SFTP extension | ||
|
||
Each endpoint is a Synthetic Test representing the performance of an SFTP server | ||
|
||
### How to use | ||
|
||
1. Unzip `custom.remote.python.thirdparty_sftp.zip` to the `plugin_deployment` folder of the ActiveGate | ||
- By default on Linux: `unzip custom.remote.python.thirdparty_linux_commands.zip -d /opt/dynatrace/remotepluginmodule/plugin_deployment` | ||
- On windows: `C:\Program Files\dynatrace\remotepluginmodule\plugin_deployment` | ||
2. Upload the extension to Dynatrace | ||
- Settings > Monitored technologies > Custom extensions > Upload extension | ||
3. Configure your endpoints on the same screen |
Binary file added
BIN
+8.54 MB
...ctive-gate-extensions/extension-third-party-sftp/custom.remote.python.thirdparty_sftp.zip
Binary file not shown.
Binary file added
BIN
+15.3 KB
third-party-synthetic/active-gate-extensions/extension-third-party-sftp/sftp.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
233 changes: 233 additions & 0 deletions
233
third-party-synthetic/active-gate-extensions/extension-third-party-sftp/src/plugin.json
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,233 @@ | ||
{ | ||
"name": "custom.remote.python.thirdparty_sftp", | ||
"version": "1.015", | ||
"type": "python", | ||
"entity": "CUSTOM_DEVICE", | ||
"technologies": [ | ||
"SFTP" | ||
], | ||
"metricGroup": "tech.SFTP", | ||
"source": { | ||
"package": "sftp_extension", | ||
"className": "SFTPExtension", | ||
"install_requires": [ | ||
"paramiko", | ||
"dt" | ||
] | ||
}, | ||
"activation": "Remote", | ||
"properties": [ | ||
{ | ||
"key": "api_url", | ||
"type": "String" | ||
}, | ||
{ | ||
"key": "api_token", | ||
"type": "Password" | ||
}, | ||
{ | ||
"key": "test_name", | ||
"type": "String" | ||
}, | ||
{ | ||
"key": "hostname", | ||
"type": "String" | ||
}, | ||
{ | ||
"key": "username", | ||
"type": "String" | ||
}, | ||
{ | ||
"key": "password", | ||
"type": "Password" | ||
}, | ||
{ | ||
"key": "ssh_key_file", | ||
"type": "String" | ||
}, | ||
{ | ||
"key": "ssh_key_passphrase", | ||
"type": "Password" | ||
}, | ||
{ | ||
"key": "frequency", | ||
"type": "String", | ||
"defaultValue": "15" | ||
}, | ||
{ | ||
"key": "port", | ||
"type": "String", | ||
"defaultValue": "22" | ||
}, | ||
{ | ||
"key": "location", | ||
"type": "String", | ||
"defaultValue": "ActiveGate" | ||
}, | ||
{ | ||
"key": "remote_dir", | ||
"type": "String" | ||
}, | ||
{ | ||
"key": "local_file", | ||
"type": "String" | ||
}, | ||
{ | ||
"key": "test_read", | ||
"type": "Boolean" | ||
}, | ||
{ | ||
"key": "test_put", | ||
"type": "Boolean" | ||
}, | ||
{ | ||
"key": "log_level", | ||
"type": "Dropdown", | ||
"dropdownValues": [ | ||
"INFO", | ||
"DEBUG" | ||
], | ||
"defaultValue": "INFO" | ||
}, | ||
{ | ||
"key": "proxy_address", | ||
"type": "String" | ||
}, | ||
{ | ||
"key": "proxy_username", | ||
"type": "String" | ||
}, | ||
{ | ||
"key": "proxy_password", | ||
"type": "Password" | ||
}, | ||
{ | ||
"key": "failure_count", | ||
"type": "Integer", | ||
"defaultValue": 1 | ||
} | ||
], | ||
"configUI": { | ||
"displayName": "SFTP", | ||
"properties": [ | ||
{ | ||
"key": "test_name", | ||
"displayName": "(Optional) Synthetic test name", | ||
"displayHint": "Custom test name. Defaults to \"<hostname>:<port>\"", | ||
"displayOrder": 1 | ||
}, | ||
{ | ||
"key": "api_url", | ||
"displayName": "Dynatrace tenant URL", | ||
"displayHint": "https://localhost:9999/e/<environment_id> or https://<my.managed.host>/e/<environment_id> or https://<environment_id>.live.dynatrace.com", | ||
"displayOrder": 2 | ||
}, | ||
{ | ||
"key": "api_token", | ||
"displayName": "Dynatrace API Token", | ||
"displayHint": "Requires \"Create and read synthetic monitors, locations, and nodes\" permission", | ||
"displayOrder": 3 | ||
}, | ||
{ | ||
"key": "hostname", | ||
"displayName": "Hostname", | ||
"displayOrder": 4 | ||
}, | ||
{ | ||
"key": "port", | ||
"displayName": "SSH Port", | ||
"displayOrder": 5 | ||
}, | ||
{ | ||
"key": "username", | ||
"displayName": "Username", | ||
"displayOrder": 6 | ||
}, | ||
{ | ||
"key": "password", | ||
"displayName": "Password", | ||
"displayOrder": 7 | ||
}, | ||
{ | ||
"key": "ssh_key_file", | ||
"displayName": "(Optional) Private key file", | ||
"displayHint": "Path to a private key file to use for authentication", | ||
"displayOrder": 8 | ||
}, | ||
{ | ||
"key": "ssh_key_passphrase", | ||
"displayName": "(Optional) Private key passphrase", | ||
"displayHint": "The private key passphrase if the key file uses one", | ||
"displayOrder": 9 | ||
}, | ||
{ | ||
"key": "test_read", | ||
"displayName": "Test read", | ||
"displayHint": "Check if you want to test directory read", | ||
"displayOrder": 10 | ||
}, | ||
{ | ||
"key": "test_put", | ||
"displayName": "Test put", | ||
"displayHint": "Check if you want to test uploading a file", | ||
"displayOrder": 11 | ||
}, | ||
{ | ||
"key": "remote_dir", | ||
"displayName": "Remote directory", | ||
"displayHint": "Remote directory path used for reading and/or uploading a file to.", | ||
"displayOrder": 12 | ||
}, | ||
{ | ||
"key": "local_file", | ||
"displayName": "Local file for upload", | ||
"displayHint": "A local file (under 100KBs) that can be uploaded remotely for testing.", | ||
"displayOrder": 13 | ||
}, | ||
{ | ||
"key": "frequency", | ||
"displayName": "Execution frequency", | ||
"displayHint": "Command execution frequency (in minutes).", | ||
"displayOrder": 14 | ||
}, | ||
{ | ||
"key": "location", | ||
"displayName": "Synthetic test location", | ||
"displayHint": "A custom name for the Synthetic Test location.", | ||
"displayOrder": 15 | ||
}, | ||
{ | ||
"key": "failure_count", | ||
"displayName": "Failure count", | ||
"displayHint": "The number of consecutive failures required for an Outage to be reported", | ||
"defaultValue": 1, | ||
"displayOrder": 16 | ||
}, | ||
{ | ||
"key": "proxy_address", | ||
"displayName": "(Optional) Proxy address", | ||
"defaultValue": "Only needed if the Dynatrace tenant must be accessed through a proxy.", | ||
"displayOrder": 17 | ||
}, | ||
{ | ||
"key": "proxy_username", | ||
"displayName": "(Optional) Proxy username", | ||
"defaultValue": "Only needed if the Dynatrace tenant must be accessed through a proxy.", | ||
"displayOrder": 18 | ||
}, | ||
{ | ||
"key": "proxy_password", | ||
"displayName": "(Optional) Proxy password", | ||
"defaultValue": "Only needed if the Dynatrace tenant must be accessed through a proxy.", | ||
"displayOrder": 19 | ||
}, | ||
{ | ||
"key": "log_level", | ||
"displayName": "Log level", | ||
"displayOrder": 20 | ||
} | ||
] | ||
}, | ||
"metrics": [], | ||
"ui": {} | ||
} |
91 changes: 91 additions & 0 deletions
91
third-party-synthetic/active-gate-extensions/extension-third-party-sftp/src/sftp_client.py
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,91 @@ | ||
import os | ||
import time | ||
import logging | ||
import paramiko | ||
from typing import Optional, Union | ||
|
||
|
||
class SFTPClient: | ||
def __init__( | ||
self, | ||
hostname: str, | ||
port: int, | ||
username: str, | ||
password: Optional[str] = None, | ||
key: Optional[str] = None, | ||
passphrase: Optional[str] = None, | ||
log: Optional[logging.Logger] = None | ||
): | ||
self.hostname = hostname | ||
self.port = port | ||
self.username = username | ||
self.password = password | ||
self.key = key | ||
self.passphrase = passphrase | ||
self.log = log if log is not None else logging.getLogger(__name__) | ||
self.connected = False | ||
self.client: Optional[paramiko.SFTPClient] = None | ||
self.transport: Optional[paramiko.Transport] = None | ||
self.connect_time = time.time() | ||
|
||
def __enter__(self) -> "SFTPClient": | ||
try: | ||
self.transport = paramiko.Transport((self.hostname, self.port)) | ||
if self.key is not None: | ||
with open(file=self.key, mode="r") as f: | ||
self.key = paramiko.RSAKey.from_private_key(f, self.passphrase) | ||
self.transport.connect(None, self.username, self.password, self.key) | ||
self.client = paramiko.SFTPClient.from_transport(self.transport) | ||
except Exception as e: | ||
self.log.exception(e) | ||
self.connected = False | ||
else: | ||
self.connected = True | ||
finally: | ||
self.connect_time = int((time.time() - self.connect_time)*1000) | ||
|
||
return self | ||
|
||
def __exit__(self, e_type, e_value, e_trace) -> bool: | ||
self.connected = False | ||
if self.client is not None: | ||
self.client.close() | ||
if self.transport is not None: | ||
self.transport.close() | ||
|
||
if e_type is not None: | ||
self.log.exception(e_type, e_value, e_trace) | ||
|
||
return True | ||
|
||
def test_connect(self) -> Union[bool, str, int]: | ||
if self.connected: | ||
return True, "", self.connect_time | ||
return False, f"Error connecting to host {self.hostname}", 0 | ||
|
||
def test_read(self, remote_dir: str) -> Union[bool, str, int]: | ||
start = time.time() | ||
try: | ||
self.client.chdir(remote_dir) | ||
self.client.listdir() | ||
except Exception as e: | ||
self.log.exception(e) | ||
return False, f"Could not read directory {remote_dir}", 0 | ||
else: | ||
return True, "", int((time.time() - start)*1000) | ||
|
||
def test_put(self, local_file: str, remote_dir: str) -> Union[bool, str, int]: | ||
start = time.time() | ||
if os.path.getsize(local_file) > 100_000: | ||
return False, "File size too large. Must be under 100KBs", 0 | ||
|
||
try: | ||
filename = local_file.split(os.path.sep)[-1] | ||
dest = f"{remote_dir}/{filename}" | ||
self.log.info(f'Uploading local file "{local_file}" to remote destination "{dest}"') | ||
self.client.put(local_file, dest) | ||
except Exception as e: | ||
self.log.exception(e) | ||
return False, f"Error uploading file {local_file} to {dest}", 0 | ||
else: | ||
return True, "", int((time.time()-start)*1000) |
Oops, something went wrong.