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 #16 from pawelnalezyty/feature/add-external-tester
Browse files Browse the repository at this point in the history
Added external syntetic testing utility
  • Loading branch information
Wolfgang Beer authored Feb 20, 2019
2 parents cf6602c + 3a52b4e commit 7611030
Show file tree
Hide file tree
Showing 21 changed files with 1,556 additions and 0 deletions.
13 changes: 13 additions & 0 deletions external-synthetic/external-tester/LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Copyright 2018 Dynatrace

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
175 changes: 175 additions & 0 deletions external-synthetic/external-tester/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# External synthetic example tester

This is a simple python application that periodically executes user defined tests
and sends results to Dynatrace cluster with few examples included.
This script should be used as an example.
If you're not familiar with Dynatrace API check out the
[documentation](https://www.dynatrace.com/support/help/dynatrace-api/ "Dynatrace API"),
especially the
[external synthetic call](https://www.dynatrace.com/support/help/dynatrace-api/environment/synthetic-api/external-synthetic-api/)
so you can build your own tests the way you need it.

## Getting started
These instructions will get you a copy of the project up and running on a windows or linux machine.

### Prerequisites
* Python3 runtime (with pip): [Download](https://www.python.org/downloads/)
* Dynatrace Tenant: [Get your Saas Trial Tenant](https://www.dynatrace.com/trial/)

### Installing
To install the tester in your machine you only need to clone the repository
and then download the python dependencies with pip.

#### Clone the Dynatrace api repository

git clone https://github.com/Dynatrace/dynatrace-api.git

#### Navigate to tool location

cd dynatrace-api/external-synthetic/external-tester

#### Download and install the requirements with pip
To install python project dependencies simply run the following command:

pip install -r requirements.txt

##### Or run installer
Alternatively tester can be installed/build by running following command in the terminal:

./setup.py install/build

Bash completion can be enabled by running:

eval "$(register-python-argcomplete synthetic-external-tester)"

or by globally activating `argcomplete` for python scripts.

activate-global-python-argcomplete


### Running tests
Tester checks which tests are available by looking for all subclasses of
[Test](tests/test.py) class. Each such subclass is identified by its `TEST_NAME` field.
To list all available tests simply run:

./synthetic-external-tester --help

Running specific test:

./synthetic-external-tester test-name --test-args...

To get more information about specific test use `--help` flag.

./synthetic-external-tester test-name --help

Every test will require those basic parameters:

* _interval_ - time in seconds between test executions

* _locationId_ - Id of the location tests will be executed from

* _locationName_ - Name of the location tests will be executed from

* _dynatraceUrl_ - Base Dynatrace communication url for tenant
(SaaS example: https://{TENANT_ID}.live.dynatrace.com).

* _apiToken_ - Token used to authenticate request. Make sure ***Create and configure
synthetic monitors*** flag is enabled for the token.

Depending on type of test, setting more parameters may be required.


### Config file
Instead of supplying arguments by command line they can be set in config file.
Config file can be writen in _ini_ or _yaml_ style.
Each argument starting with double dash '--' can be set in config file.
Command line values supersede those from config file. Example config.ini file:

```
; Interval in seconds between test executions
interval = 10
; Dynatrace tenant URL
dynatraceUrl = https://********.live.dynatrace.com
; Api token for authentication.
apiToken = ********
; Location ID and name from which tests will run
locationId = 1
locationName = Custom location
; Example list argument (arg which has action='append' or nargs='+')
listArg = [val_1, val_2, val_3]
```

Check out [ConfigArgParse](https://pypi.org/project/ConfigArgParse/) documentation for
full config syntax.

### Create your own test
The easiest way to make your own test is to copy [example_test](examples/file_exists_test.py)
into [testdefinitions](tests/testdefinitions) folder and edit it as described in the comments.

In more detail, to create your own test you'll need to:

* Create a subclass of [Test](tests/test.py) base class and save it in
[testdefinitions](tests/testdefinitions) folder.

* Set `TEST_NAME` and make sure its value is unique.

* Set other values (`TEST_HELP_DESCRIPTION`, `dynatrace_test_name`).

* Add additional argument to your test append [TestArgument](tests/test_argument.py)
instance to `TEST_ARGUMENTS` list. This class just holds parameters for `argparse.add_argument`
method so check its [documentation](https://docs.python.org/3/library/argparse.html#the-add-argument-method)
for more information about them.

* Configure steps for your test. To do so create subclass of [TestStep](tests/test_step.py)
base class for each step. Set `test_step_name` values and `__call__` method
and populate `steps` list in test class.

New test should be accessible by running `./synthetic-external-tester {TEST_NAME}`.
Test specific help message will be automatically generated.

### Exit codes
Tester will return exit code `1` when two different test definitions have the same `TEST_NAME`
defined or when `ping_test` on unix system is not executed by `root`.

When configured correctly `synthetic-external-tester` will lunch defined test in infinite loop
at specified intervals. Exceptions raised by executed test will be caught and logged.
Main loop will only stop on user request (eg. by `SIGTERM`).

### Project structure & description

```
synthetic-external-tester
├── examples Folder with example test.
│   └── file_exists_test.py Example test.
├── reporting Folder with classes dedicated to sending results.
│   ├── api_constants.py Python representation of external test api.
│   └── resultsreporter.py Class responsible for sending test reports.
├── syntester Folder with main application class.
│   └── syntester.py Main application class.
├── tests Folder with classes dedicated to running tests.
│   ├── test_argument.py Class representing additional test arguments.
│   ├── test.py Base Test class. All tests must be a subclass of it.
│   └── test_step.py Base step class. Represents one step in test.
│   └── testdefinitions Folder containing specific test definitions.
│      ├── db_test.py Test class for checking database connection.
│      ├── dns_test.py Test class for checking dns lookup.
│      ├── ping_test.py Test class for checking ping response from host.
│      └── tcp_test.py Test class for checking tcp connection to host.
├── config.ini Default configuration file.
├── LICENSE.md License.
├── README.md This readme file.
├── requirements.txt Python project dependencies.
├── setup.py Setup script.
└── synthetic-external-tester Main application
```

## License
This project is licensed under the Apache License - see the [LICENSE](LICENSE.md) file for details
16 changes: 16 additions & 0 deletions external-synthetic/external-tester/config.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
; All flags starting with double dash ('--') can be also defined in this config
; Command line arguments supersede values defined in this config file.


; Interval in seconds between test executions
interval = 10

; Dynatrace tenant URL
dynatraceUrl =

; Api token for authentication.
apiToken =

; Location information
locationId =
locationName =
105 changes: 105 additions & 0 deletions external-synthetic/external-tester/examples/file_exists_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
from tests import Test, TestArgument, TestStep

from datetime import datetime
from pathlib import Path


class FileExistsTest(Test):
"""
Simple example test class
This example test (if added to tests/testdefinitions/ package)
will check if file exists at given path.
"""

# TEST_NAME must be unique and defined for test to be available
TEST_NAME = 'example-test'

# Description added to command line help message for this test
TEST_HELP_DESCRIPTION = "This is example test. "

# Flag used to set additional argument
FILE_PATH_FLAG = 'filepath'

# Definitions of additional arguments required by test
# To add test specific arguments append TestArgument instances to this list
TEST_ARGUMENTS = [
TestArgument(
flag_names=['--' + FILE_PATH_FLAG],
flag_args={
'required': True,
'nargs': 1,
'metavar': 'PATH',
'help': "Path to the file whose existence will be tested."
}
),
]

def __init__(self, args):
"""Create an FileExistsTest class instance.
Args:
args: Command line arguments in dict form
"""

super().__init__()

# Parse test specific flags from supplied arguments
file_path = args[self.FILE_PATH_FLAG][0]

# Dynatrace test name shows up in UI as synthetic monitor name so make sure it is meaningful
self.dynatrace_test_name = 'Example test. Checks if file exists at: {}'.format(file_path)

# Append steps the test consists of
self.steps.append(self.FileExistsTestStep(file_path))

class FileExistsTestStep(TestStep):
"""
FileExistsTest test step class.
TestStep defines one step preformed in test.
Tests can contain many steps.
"""

def __init__(self, file_path):
"""
Create FileExistsTest class instance.
Args:
file_path: Path to file which test check if exists
"""

# Set up step name, it will show up in monitor UI
test_step_name = 'Checking if file exists at: {}'.format(file_path)
super().__init__(test_step_name)

self.file_path = file_path

def __call__(self):
"""
Execute the test step.
Overrides the base class implementation.
"""

# Mark that step execution has started
self.set_started()
self.logger.info('Checking if file exists at: {}'.format(self.file_path))

# If step raises Exception it will be caught and logged in Test.run method

start_time = datetime.now()

# Check if file exists
file = Path(self.file_path)
is_file = file.is_file()
end_time = datetime.now()

if is_file:
# If file exists set step as successful
self.set_passed()
else:
self.set_failed()

# Set up measured duration of the test step
self.duration = end_time - start_time
1 change: 1 addition & 0 deletions external-synthetic/external-tester/reporting/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .resultsreporter import ResultsReporter
62 changes: 62 additions & 0 deletions external-synthetic/external-tester/reporting/api_constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
class ApiConstants:
"""
A holder of all Dynatrace API constants.
The structure of this class represents the structure of Dynatrace API.
Each static field represents a single property in the root of the API JSON,
while nested classes represent nested objects and their properties.
Please refer to Dynatrace API documentation for more details.
"""

MESSAGE_TIMESTAMP = 'messageTimestamp'
SYNTHETIC_ENGINE = 'syntheticEngine'
SYNTHETIC_ENGINE_NAME = 'syntheticEngineName'
SYNTHETIC_ENGINE_ICON_URL = 'syntheticEngineIconUrl'
LOCATIONS = 'locations'
TESTS = 'tests'
TEST_RESULTS = 'testResults'

class Locations:
"""Constants for .locations"""
ID = 'id'
NAME = 'name'

class Tests:
"""Constants for .tests"""
ID = 'id'
TITLE = 'title'
TEST_SETUP = 'testSetup'
ENABLED = 'enabled'
LOCATIONS = 'locations'
STEPS = 'steps'
SCHEDULE_INTERVAL_IN_SECONDS = 'scheduleIntervalInSeconds'

class Locations:
"""Constants for .tests.locations"""
ID = 'id'
ENABLED = 'enabled'

class Steps:
"""Constants for .tests.steps"""
ID = 'id'
TITLE = 'title'

class TestResults:
"""Constants for .testResults"""
ID = 'id'
SCHEDULE_INTERVAL_IN_SECONDS = 'scheduleIntervalInSeconds'
TOTAL_STEP_COUNT = 'totalStepCount'
LOCATION_RESULTS = 'locationResults'

class LocationResults:
"""Constants for .testResults.locationResults"""
ID = 'id'
START_TIMESTAMP = 'startTimestamp'
SUCCESS = 'success'
STEP_RESULTS = 'stepResults'

class StepResults:
"""Constants for .testResults.locationResults.StepResults"""
ID = 'id'
START_TIMESTAMP = 'startTimestamp'
RESPONSE_TIME_MILLIS = 'responseTimeMillis'
Loading

0 comments on commit 7611030

Please sign in to comment.