Skip to content
This repository has been archived by the owner on Sep 3, 2024. It is now read-only.

Commit

Permalink
Merge pull request #84 from radu-stefan-dt/synthetic-sftp
Browse files Browse the repository at this point in the history
Add SFTP Third-party Synthetic ActiveGate extension
  • Loading branch information
Tim Haasdyk authored May 19, 2022
2 parents 0d0b0b5 + af58cf5 commit 150f599
Show file tree
Hide file tree
Showing 6 changed files with 468 additions and 0 deletions.
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 not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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": {}
}
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)
Loading

0 comments on commit 150f599

Please sign in to comment.