Skip to content

Commit

Permalink
Merge pull request #79 from Dynatrace-James-Kitson/tweak-settings
Browse files Browse the repository at this point in the history
settings changes to align with API and rest of project
  • Loading branch information
Dynatrace-James-Kitson authored Jan 12, 2024
2 parents 68b8acd + 67fd8cf commit 3d16f88
Show file tree
Hide file tree
Showing 10 changed files with 299 additions and 133 deletions.
183 changes: 139 additions & 44 deletions dynatrace/environment_v2/settings.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
from typing import Optional, Dict, Any
from typing import Optional, Dict, Any, List, Union
from datetime import datetime

from dynatrace.dynatrace_object import DynatraceObject
from dynatrace.http_client import HttpClient
from dynatrace.pagination import PaginatedList
from dynatrace.utils import int64_to_datetime


class SettingService:
ENDPOINT = "/api/v2/settings/objects"

def __init__(self, http_client: HttpClient):
self.__http_client = http_client

def list_objects(self,schema_id: Optional[str] = None,
scope: Optional[str] = None,external_ids: Optional[str] = None,
fields: Optional[str] = None,
filter:Optional[str] = None, sort:Optional[str] = None, page_size:Optional[str] = None) -> PaginatedList["DynatraceObject"]:

def list_objects(
self,
schema_id: Optional[str] = None,
scope: Optional[str] = None,
external_ids: Optional[str] = None,
fields: Optional[str] = None,
filter: Optional[str] = None,
sort: Optional[str] = None,
page_size: Optional[str] = None,
) -> PaginatedList["SettingsObject"]:
"""Lists settings
:return: a list of settings with details
Expand All @@ -28,64 +36,151 @@ def list_objects(self,schema_id: Optional[str] = None,
"sort": sort,
"pageSize": page_size,
}
return PaginatedList(Settings, self.__http_client, target_url=self.ENDPOINT, list_item="items", target_params=params)

def create_object(self,external_id,object_id,schema_id,schema_version,scope, value,validate_only):
"""Creates a new settings object
return PaginatedList(
SettingsObject,
self.__http_client,
target_url=self.ENDPOINT,
list_item="items",
target_params=params,
)

:param external_id: External identifier for the object being created
:param object_id: The ID of the settings object that should be replaced. Only applicable if an external identifier
:param object_id: the ID of the object
:param schema_id: The schema on which the object is based
:param schema_version: The version of the schema on which the object is based.
:param scope The scope that the object targets. For more details, please see Dynatrace Documentation.
:param value The value of the setting.
:return: a Settings object
"""
params = {
"validate_only": validate_only,
}
body =[ {
"externalId" : external_id,
"objectId": object_id,
"schemaId": schema_id,
"schemaVersion": schema_version,
"scope": scope,
"value" : value
def create_object(
self,
validate_only: Optional[bool] = False,
body: Union[
Optional[List["SettingsObjectCreate"]], Optional["SettingsObjectCreate"]
] = [],
):
"""
Creates a new settings object or validates the provided settigns object
:param validate_only: If true, the request runs only validation of the submitted settings objects, without saving them
:param body: The JSON body of the request. Contains the settings objects
"""
query_params = {"validateOnly": validate_only}

}]

response = self.__http_client.make_request(self.ENDPOINT,params=body, method="POST",query_params=params).json()
if isinstance(body, SettingsObjectCreate):
body = [body]

body = [o.json() for o in body]

response = self.__http_client.make_request(
self.ENDPOINT, params=body, method="POST", query_params=query_params
).json()
return response



def get_object(self, object_id: str):
"""Gets parameters of specified settings object
:param object_id: the ID of the object
:return: a Settings object
"""
response = self.__http_client.make_request(f"{self.ENDPOINT}/{object_id}").json()
return Settings(raw_element=response)
response = self.__http_client.make_request(
f"{self.ENDPOINT}/{object_id}"
).json()
return SettingsObject(raw_element=response)

def update_object(self, object_id: str, value):
def update_object(
self, object_id: str, value: Optional["SettingsObjectCreate"] = None
):
"""Updates an existing settings object
:param object_id: the ID of the object
:param object_id: the ID of the object
:param value: the JSON body of the request. Contains updated parameters of the settings object.
"""
return self.__http_client.make_request(path=f"{self.ENDPOINT}/{object_id}", params=value, method="PUT")
return self.__http_client.make_request(
f"{self.ENDPOINT}/{object_id}", params=value.json(), method="PUT"
)

def delete_object(self, object_id: str):
def delete_object(self, object_id: str, update_token: Optional[str] = None):
"""Deletes the specified object
:param object_id: the ID of the object
:param update_token: The update token of the object. You can use it to detect simultaneous modifications by different users
:return: HTTP response
"""
return self.__http_client.make_request(path=f"{self.ENDPOINT}/{object_id}", method="DELETE")
query_params = {"updateToken": update_token}
return self.__http_client.make_request(
f"{self.ENDPOINT}/{object_id}",
method="DELETE",
query_params=query_params,
).json()


class ModificationInfo(DynatraceObject):
def _create_from_raw_data(self, raw_element: Dict[str, Any]):
self.deleteable: bool = raw_element.get("deleteable")
self.first: bool = raw_element.get("first")
self.modifiable: bool = raw_element.get("modifiable")
self.modifiable_paths: List[str] = raw_element.get("modifiablePaths", [])
self.movable: bool = raw_element.get("movable")
self.non_modifiable_paths: List[str] = raw_element.get("nonModifiablePaths", [])


class Settings(DynatraceObject):
class SettingsObject(DynatraceObject):
def _create_from_raw_data(self, raw_element: Dict[str, Any]):
# Mandatory
self.objectId: str = raw_element["objectId"]
self.value: str = raw_element["value"]
self.value: dict = raw_element["value"]
# Optional
self.author: str = raw_element.get("author")
self.created: datetime = (
int64_to_datetime(int(raw_element.get("created")))
if raw_element.get("created")
else None
)
self.created_by: str = raw_element.get("createdBy")
self.external_id: str = raw_element.get("externalId")
self.modification_info: ModificationInfo = (
ModificationInfo(
self._http_client, self._headers, raw_element.get("modificationInfo")
)
if raw_element.get("modificationInfo")
else None
)
self.modified: datetime = (
int64_to_datetime(int(raw_element.get("modified")))
if raw_element.get("modified")
else None
)
self.modified_by: str = raw_element.get("modifiedBy")
self.schema_id: str = raw_element.get("schemaId")
self.schema_version: str = raw_element.get("schemaVersion")
self.scope: str = raw_element.get("scope")
self.search_summary: str = raw_element.get("searchSummary")
self.summary: str = raw_element.get("summary")
self.update_token: str = raw_element.get("updateToken")


class SettingsObjectCreate:
def __init__(
self,
schema_id: str,
value: dict,
scope: str,
external_id: Optional[str] = None,
insert_after: Optional[str] = None,
object_id: Optional[str] = None,
schema_version: Optional[str] = None,
):
self.schema_id = schema_id
self.value = value
self.scope = scope
self.external_id = external_id
self.insert_after = insert_after
self.object_id = object_id
self.schema_version = schema_version

def json(self) -> dict:
body = {"schemaId": self.schema_id, "value": self.value, "scope": self.scope}

if self.external_id:
body["externalId"] = self.external_id
if self.insert_after:
body["insertAfter"] = self.insert_after
if self.object_id:
body["objectId"] = self.object_id
if self.schema_version:
body["schemaVersion"] = self.schema_version

return body
70 changes: 46 additions & 24 deletions test/environment_v2/test_settings.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,59 @@
from datetime import datetime

import dynatrace.environment_v2.settings as st
from dynatrace.environment_v2.settings import SettingsObject, SettingsObjectCreate
from dynatrace import Dynatrace
from dynatrace.pagination import PaginatedList
payload = {"additionalInformation": [],
"contactDetails":[{"email": 'unittest@contoso.com', "integrationType": "EMAIL"}],
"description": 'unittest',
"identifier":'unittest',
"links": [],
"name": 'unittest',
"responsibilities": {"development": False,
"infrastructure": False,
"lineOfBusiness": True,
"operations": False,
"security": False},
"supplementaryIdentifiers": [] }

settings_dict = {
"enabled": True,
"summary": "DT API TEST 22",
"queryDefinition": {
"type": "METRIC_KEY",
"metricKey": "netapp.ontap.node.fru.state",
"aggregation": "AVG",
"entityFilter": {
"dimensionKey": "dt.entity.netapp_ontap:fru",
"conditions": [],
},
"dimensionFilter": [],
},
"modelProperties": {
"type": "STATIC_THRESHOLD",
"threshold": 100.0,
"alertOnNoData": False,
"alertCondition": "BELOW",
"violatingSamples": 3,
"samples": 5,
"dealertingSamples": 5,
},
"eventTemplate": {
"title": "OnTap {dims:type} {dims:fru_id} is in Error State",
"description": "OnTap field replaceable unit (FRU) {dims:type} with id {dims:fru_id} on node {dims:node} in cluster {dims:cluster} is in an error state.\n",
"eventType": "RESOURCE",
"davisMerge": True,
"metadata": [],
},
"eventEntityDimensionKey": "dt.entity.netapp_ontap:fru",
}
settings_object = SettingsObjectCreate("builtin:anomaly-detection.metric-events", settings_dict, "environment")
test_object_id = "vu9U3hXa3q0AAAABACdidWlsdGluOmFub21hbHktZGV0ZWN0aW9uLm1ldHJpYy1ldmVudHMABnRlbmFudAAGdGVuYW50ACRiYmYzZWNhNy0zMmZmLTM2ZTEtOTFiOS05Y2QxZjE3OTc0YjC-71TeFdrerQ"

def test_list_objects(dt: Dynatrace):
settings = dt.settings.list_objects(schema_id="builtin:ownership.teams")
settings = dt.settings.list_objects(schema_id="builtin:anomaly-detection.metric-events")
assert isinstance(settings, PaginatedList)
assert len(list(settings)) == 1
assert all(isinstance(s, st.Settings) for s in settings)
assert len(list(settings)) == 2
assert all(isinstance(s, SettingsObject) for s in settings)

def test_get_object(dt: Dynatrace):
setting = dt.settings.get_object(object_id="vu9U3hXa3q0AAAABABdidWlsdGluOm93bmVyc2hpcC50ZWFtcwAGdGVuYW50AAZ0ZW5hbnQAJGVjN2UyNTdhLWM5MTktM2YzMC05NWFiLTliMzNkMmQwZGRkY77vVN4V2t6t")
assert isinstance(setting, st.Settings)
setting = dt.settings.get_object(object_id=test_object_id)
assert isinstance(setting, SettingsObject)
assert setting.schema_version == "1.0.16"

def test_post_object(dt: Dynatrace):

response = dt.settings.create_object(external_id='unittest',object_id='unittest',schema_id="builtin:ownership.teams",schema_version="1.0.6",scope="environment", value=payload,validate_only=False)
response = dt.settings.create_object(body=settings_object)
assert response[0].get("code") == 200
assert response[0].get("code") is not None

def test_put_object(dt: Dynatrace):
payload["identifier"] = "unittestupdate"
response = dt.settings.update_object("unittest",payload)
print(response)
response = dt.settings.update_object(test_object_id, settings_object)
print(response)

69 changes: 69 additions & 0 deletions test/mock_data/GET_api_v2_settings_objects_1eafd8dc0bf2543.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{
"items": [
{
"objectId": "vu9U3hXa3q0AAAABACdidWlsdGluOmFub21hbHktZGV0ZWN0aW9uLm1ldHJpYy1ldmVudHMABnRlbmFudAAGdGVuYW50ACQ4OWNhMmY2Ny0wY2Q3LTM0MzAtYmU5Ny1kOTg4YTRmMWRiYWa-71TeFdrerQ",
"value": {
"enabled": true,
"summary": "OnTap Node Field Replaceable Unit in Error State",
"queryDefinition": {
"type": "METRIC_KEY",
"metricKey": "netapp.ontap.node.fru.state",
"aggregation": "AVG",
"entityFilter": {
"dimensionKey": "dt.entity.netapp_ontap:fru",
"conditions": []
},
"dimensionFilter": []
},
"modelProperties": {
"type": "STATIC_THRESHOLD",
"threshold": 100.0,
"alertOnNoData": false,
"alertCondition": "BELOW",
"violatingSamples": 3,
"samples": 5,
"dealertingSamples": 5
},
"eventTemplate": {
"title": "OnTap {dims:type} {dims:fru_id} is in Error State",
"description": "OnTap field replaceable unit (FRU) {dims:type} with id {dims:fru_id} on node {dims:node} in cluster {dims:cluster} is in an error state.\n",
"eventType": "RESOURCE",
"davisMerge": true,
"metadata": []
},
"eventEntityDimensionKey": "dt.entity.netapp_ontap:fru"
}
},
{
"objectId": "vu9U3hXa3q0AAAABACdidWlsdGluOmFub21hbHktZGV0ZWN0aW9uLm1ldHJpYy1ldmVudHMABnRlbmFudAAGdGVuYW50ACQ5MjRiNjdiYS00OGQ1LTM0NDctODg4Zi05NzUzMzg5NzcxMze-71TeFdrerQ",
"value": {
"enabled": false,
"summary": "High outbound bandwidth utilization",
"queryDefinition": {
"type": "METRIC_SELECTOR",
"metricSelector": "func:com.dynatrace.extension.snmp-generic-cisco-device.if.out.bandwidth"
},
"modelProperties": {
"type": "STATIC_THRESHOLD",
"threshold": 90.0,
"alertOnNoData": false,
"alertCondition": "ABOVE",
"violatingSamples": 3,
"samples": 5,
"dealertingSamples": 5
},
"eventTemplate": {
"title": "High outbound bandwidth utilization",
"description": "The {metricname} value of {severity} was {alert_condition} your custom threshold of {threshold}.",
"eventType": "CUSTOM_ALERT",
"davisMerge": false,
"metadata": []
},
"eventEntityDimensionKey": "dt.entity.snmp:com_dynatrace_extension_snmp_generic_cisco_network_interface",
"legacyId": "E|649d5713-380b-6751-a64f-6904a2c4cd2f"
}
}
],
"totalCount": 25,
"pageSize": 100
}
Loading

0 comments on commit 3d16f88

Please sign in to comment.