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 #16 from pawelnalezyty/feature/add-external-tester
Added external syntetic testing utility
- Loading branch information
Showing
21 changed files
with
1,556 additions
and
0 deletions.
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,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. |
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,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 |
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,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
105
external-synthetic/external-tester/examples/file_exists_test.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,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 |
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 @@ | ||
from .resultsreporter import ResultsReporter |
62 changes: 62 additions & 0 deletions
62
external-synthetic/external-tester/reporting/api_constants.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,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' |
Oops, something went wrong.