def GET(self, namespace=None): """ List supported Cloudwatch namespaces :: GET /_metrics/cloudwatch/namespaces Returns: :: {'namespace-name1': {...}, 'namespace-name2': {...} ,... } OR List supported Cloudwatch metrics for a given namespace :: GET /_metrics/cloudwatch/{namespace-name}` Returns: :: { 'namespace-name': { 'metrics': ['metric-name',...], 'dimensions': ['dimension-name',...] } } """ adapter = datasource_adapter_factory.createCloudwatchDatasourceAdapter( ) resources = adapter.describeSupportedMetrics() namespaces = _translateResourcesIntoNamespaces(resources) # Adding Autostacks namespaces to this list for now, to maintain API # backwards-compatibility during adapter refactor namespaces["Autostacks"] = {"metrics": ["InstanceCount"]} if namespace is None: self.addStandardHeaders() return utils.jsonEncode(namespaces) if not namespace in namespaces: raise web.NotFound("Namespace '%s' was not found" % namespace) self.addStandardHeaders() return utils.jsonEncode({str(namespace): namespaces[namespace]})
def setUpClass(cls): cls.autostack = Mock(uid="blahblahblah", region="bogus", filters=jsonEncode({"tag:Name":["Bogus"]})) cls.autostack.name = "Test" cls.jsonAutostack = jsonEncode({"uid":"blahblahblah", "name":"Test", "region":"bogus", "filters":{"tag:Name":["Bogus"]}})
def GET(self, namespace=None): """ List supported Cloudwatch namespaces :: GET /_metrics/cloudwatch/namespaces Returns: :: {'namespace-name1': {...}, 'namespace-name2': {...} ,... } OR List supported Cloudwatch metrics for a given namespace :: GET /_metrics/cloudwatch/{namespace-name}` Returns: :: { 'namespace-name': { 'metrics': ['metric-name',...], 'dimensions': ['dimension-name',...] } } """ adapter = datasource_adapter_factory.createCloudwatchDatasourceAdapter() resources = adapter.describeSupportedMetrics() namespaces = _translateResourcesIntoNamespaces(resources) # Adding Autostacks namespaces to this list for now, to maintain API # backwards-compatibility during adapter refactor namespaces["Autostacks"] = {"metrics": ["InstanceCount"]} if namespace is None: self.addStandardHeaders() return utils.jsonEncode(namespaces) if not namespace in namespaces: raise web.NotFound("Namespace '%s' was not found" % namespace) self.addStandardHeaders() return utils.jsonEncode({str(namespace): namespaces[namespace]})
def testAddAnnotationIncomplete(self, _): """ Test Failed to Create incomplete Annotation Response:: HTTP Status 400 Missing "field" in request """ # Annotation without timestamp badRequest = self.request.copy() del badRequest["timestamp"] with self.assertRaises(AppError) as e: self.app.post("", app_utils.jsonEncode(badRequest), headers=self.headers) error = e.exception self.assertRegexpMatches(error.message, "Missing 'timestamp' in request") # Annotation without device badRequest = self.request.copy() del badRequest["device"] with self.assertRaises(AppError) as e: self.app.post("", app_utils.jsonEncode(badRequest), headers=self.headers) error = e.exception self.assertRegexpMatches(error.message, "Missing 'device' in request") # Annotation without server badRequest = self.request.copy() del badRequest["server"] with self.assertRaises(AppError) as e: self.app.post("", app_utils.jsonEncode(badRequest), headers=self.headers) error = e.exception self.assertRegexpMatches(error.message, "Missing 'server' in request") # Annotation without user badRequest = self.request.copy() del badRequest["user"] with self.assertRaises(AppError) as e: self.app.post("", app_utils.jsonEncode(badRequest), headers=self.headers) error = e.exception self.assertRegexpMatches(error.message, "Missing 'user' in request") # Annotation without data and message badRequest = self.request.copy() del badRequest["message"] del badRequest["data"] with self.assertRaises(AppError) as e: self.app.post("", app_utils.jsonEncode(badRequest), headers=self.headers) error = e.exception self.assertRegexpMatches(error.message, "Annotation must contain either 'message' or 'data'")
def DELETE(self): """ Delete models for multiple instances :: DELETE /_instances DELETE data: :: [ "{region}/{namespace}/{instanceId}", ... ] Returns: :: { "result": "success" } """ try: instances = utils.jsonDecode(web.data()) except: raise InvalidRequestResponse({"result": "Invalid request"}) if not instances: raise InvalidRequestResponse( {"result": ("Missing instances in DELETE" " request")}) deleted = [] if instances: for server in instances: with web.ctx.connFactory() as conn: modelIds = repository.listMetricIDsForInstance( conn, server) if modelIds: for modelId in modelIds: ModelHandler.deleteModel(modelId) deleted.append(server) if instances == deleted: self.addStandardHeaders() return utils.jsonEncode({"result": "success"}) raise web.notfound( "Not able to delete %s" % utils.jsonEncode(list(set(instances) - set(deleted))))
def setUpClass(cls): cls.autostack = Mock(uid="blahblahblah", region="bogus", filters=jsonEncode({"tag:Name": ["Bogus"]})) cls.autostack.name = "Test" cls.jsonAutostack = jsonEncode({ "uid": "blahblahblah", "name": "Test", "region": "bogus", "filters": { "tag:Name": ["Bogus"] } })
def setUpClass(cls): with open( os.path.join( htm.it.app.HTM_IT_HOME, "tests/py/data/app/webservices/models_list.json")) as fileObj: cls.model_list = json.load(fileObj) cls.autostack = Mock(uid="blahblahblah", region="bogus", filters=jsonEncode({"tag:Name":["Bogus"]})) cls.autostack.name = "Test" cls.jsonAutostack = jsonEncode({"uid":"blahblahblah", "name":"Test", "region":"bogus", "filters":{"tag:Name":["Bogus"]}}) cls.metric = Mock(uid="cebe9fab-f416-4845-8dab-02d292244112", datasource="autostack", description="The number of database connections in use " "by Amazon RDS database", server="htm-itdb2", location="us-east-1", parameters=jsonEncode( {"region":"us-east-1", "DBInstanceIdentifier":"htm-itdb2"}), status=1, message=None, collector_error=None, last_timestamp="2013-08-15 21:25:00", poll_interval=60, tag_name=None, model_params=None, last_rowid=20277) cls.metric.name = "AWS/RDS/DatabaseConnections" cls.jsonMetric = jsonEncode( {"uid":cls.metric.uid, "datasource":cls.metric.datasource, "name":cls.metric.name, "description":cls.metric.description, "server":cls.metric.server, "location":cls.metric.location, "parameters":jsonDecode(cls.metric.parameters), "status":cls.metric.status, "message":cls.metric.message, "last_timestamp":cls.metric.last_timestamp, "poll_interval":cls.metric.poll_interval, "tag_name":cls.metric.tag_name, "last_rowid":cls.metric.last_rowid, "display_name":cls.metric.server})
def DELETE(self): """ Delete models for multiple instances :: DELETE /_instances DELETE data: :: [ "{region}/{namespace}/{instanceId}", ... ] Returns: :: { "result": "success" } """ try: instances = utils.jsonDecode(web.data()) except: raise InvalidRequestResponse({"result": "Invalid request"}) if not instances: raise InvalidRequestResponse({"result": ("Missing instances in DELETE" " request")}) deleted = [] if instances: for server in instances: with web.ctx.connFactory() as conn: modelIds = repository.listMetricIDsForInstance(conn, server) if modelIds: for modelId in modelIds: ModelHandler.deleteModel(modelId) deleted.append(server) if instances == deleted: self.addStandardHeaders() return utils.jsonEncode({"result": "success"}) raise web.notfound("Not able to delete %s" % utils.jsonEncode(list(set(instances)-set(deleted))))
def testMonitorMetricViaModelSpec(self): """ Happy path testing for the route "/_models" with new modelSpec format """ modelSpec = { "datasource": "cloudwatch", "metricSpec": { "region": "us-west-2", "namespace": "AWS/EC2", "metric": "CPUUtilization", "dimensions": {"InstanceId": "i-12d67826"}, }, "modelParams": {"min": 0, "max": 100}, # optional # optional } # create a model response = self.app.post("/", utils.jsonEncode(modelSpec), headers=self.headers) assertions.assertSuccess(self, response, code=201) postResult = utils.jsonDecode(response.body) self.assertEqual(len(postResult), 1) self._checkCreateModelResult(postResult[0], modelSpec["metricSpec"]) # get model that was previously created uid = postResult[0]["uid"] response = self.app.get("/%s" % uid, headers=self.headers) assertions.assertSuccess(self, response) getModelResult = utils.jsonDecode(response.body) self.assertItemsEqual(getModelResult[0].keys(), self.modelsTestData["get_response"].keys()) # get all models in the system response = self.app.get("/", headers=self.headers) assertions.assertSuccess(self, response) allModelsResult = utils.jsonDecode(response.body) self.assertItemsEqual(allModelsResult[0].keys(), self.modelsTestData["get_response"].keys()) self.assertItemsEqual(allModelsResult[0].keys(), self.modelsTestData["get_response"].keys()) self.assertEqual(len(allModelsResult), 1) # Repeat the request to monitor same metric and verify that it returns the # same model uid instead of creating a new one response = self.app.post("/", utils.jsonEncode(modelSpec), headers=self.headers) assertions.assertSuccess(self, response, code=201) postResult = utils.jsonDecode(response.body) self.assertEqual(postResult[0]["uid"], uid) self.assertEqual(len(postResult), 1) self._checkCreateModelResult(postResult[0], modelSpec["metricSpec"]) # Unmonitor the metric response = self.app.delete("/%s" % uid, headers=self.headers) assertions.assertDeleteSuccessResponse(self, response)
def testGetAnnotations(self, getAnnotations, _): """ Test Get Annotation Request:: GET /_annotations?device={device}&user={user}&server={server} &from={from}&to={to} Response:: HTTP 200 Ok [ { "uid": "2a123bb1dd4d46e7a806d62efc29cbb9", "device", "1231AC32FE", "created":"2013-08-27 16:46:51", "timestamp":"2013-08-27 16:45:00", "user":"******", "server":" AWS/EC2/i-53f52b67", "message":" The CPU Utilization was high ...", "data": { Optional JSON Object } }, ... ] """ getAnnotations.return_value = [self.annotation] response = self.app.get("?%s=%s" % ("server", "dummy"), headers=self.headers) self.assertEqual(response.status, 200) actual = json.loads(response.body) expected = json.loads(app_utils.jsonEncode(self.annotation)) self.assertDictEqual(expected, actual[0])
def testPOSTAutostacks(self, adapterMock, quotaRepositoryMock, _repositoryMock): name = "Test" region = "Bogus" filters = {"tag:Name":["Bogus"]} quotaRepositoryMock.getInstanceCount.return_value = 0 autostackMock = Mock() autostackMock.items.return_value = [("name", name), ("region", region), ("filters", filters)] adapterMock.return_value.createAutostack.return_value = autostackMock response = self.app.post("/", app_utils.jsonEncode({"name": name, "region": region, "filters": filters}), headers=self.headers) self.assertEqual(response.status, 201) result = json.loads(response.body) self.assertEqual(result["name"], "Test") self.assertTrue(adapterMock.return_value.createAutostack.called)
def testPUTNotificationSettingsCreate(self, repositoryMock, engineMock): """ Test PUT notification settings (create) """ repositoryMock.getDeviceNotificationSettings.side_effect = ( ObjectNotFoundError("No settings yet")) update = { "windowsize": 3601, "sensitivity": 0.999999, "email_addr": "*****@*****.**"} response = self.app.put("/%s/settings" % self.notification["device"], app_utils.jsonEncode(update), headers=self.headers) self.assertEqual(response.status, 201) self.assertFalse(response.body) self.assertTrue(repositoryMock.getDeviceNotificationSettings.called) repositoryMock.addDeviceNotificationSettings.assert_called_with( engineMock.return_value.connect.return_value.__enter__.return_value, self.notification["device"], update["windowsize"], update["sensitivity"], update["email_addr"])
def GET(self, deviceId, notificationId): """ Get single notification :: GET /_notifications/{deviceId}/{notificationId} Returns: :: { "uid": "e78599c4-758b-4c6e-87b1-daabaccff798", "timestamp": "2014-02-07 16:26:44", "metric": "e5511f295a474037a75bc966d02b67d2", "acknowledged": 0, "seen": 0, "windowsize": 3600, "device": "9a90eaf2-6374-4230-aa96-0830c0a737fe" } :param uid: Notification ID :type uid: str :param timestamp: Notification timestamp :type timestamp: timestamp :param metric: Metric that triggered notification :type metric: str :param acknowledged: Acknowledged status :type acknowledged: int :param seen: Seen status :type seen: int :param windowsize: Notification window in seconds during which no other notifications for a given instance should be sent to a given device :type windowsize: int :param device: Device ID :type device: str """ # Update the last_timestamp for the notification device. try: with web.ctx.connFactory() as conn: repository.updateNotificationDeviceTimestamp(conn, deviceId) except ObjectNotFoundError: return web.notfound("Notification device not found: %s" % deviceId) try: with web.ctx.connFactory() as conn: notificationRow = repository.getNotification(conn, notificationId, NotificationHandler.fields) notificationDict = dict([(col, notificationRow[col]) for col in NotificationHandler.fields]) if notificationRow["device"] != deviceId: return web.notfound("Device not found: %s" % deviceId) self.addStandardHeaders() return utils.jsonEncode(notificationDict) except ObjectNotFoundError: return web.notfound("Notification not found: %s" % notificationId)
def GET(self): # pylint: disable=R0201 """ Returns list of supported Clouwdwatch regions :: GET /_metrics/cloudwatch/regions Returns: :: { 'region-name': 'region-description',...} Sample output: :: { "ap-northeast-1": "Asia Pacific (Tokyo) Region", "ap-southeast-1": "Asia Pacific (Singapore) Region", "ap-southeast-2": "Asia Pacific (Sydney) Region", "eu-west-1": "EU (Ireland) Region", "sa-east-1": "South America (Sao Paulo) Region", "us-east-1": "US East (Northern Virginia) Region", "us-west-1": "US West (Northern California) Region", "us-west-2": "US West (Oregon) Region" } """ adapter = datasource_adapter_factory.createCloudwatchDatasourceAdapter() AuthenticatedBaseHandler.addStandardHeaders() return utils.jsonEncode(dict(adapter.describeRegions()))
def GET(self): # pylint: disable=C0103 """ Get list of autostacks Example request:: GET /_autostacks Example response:: [ { "name": {name} "region": {region} "filters": { "tag:{Name}": ["{value}", "{value}", ...], "tag:{Description}": ["{value}", "{value}", ...], "tag:{etc}": ["{value}", "{value}", ...] }, "uid": {uid} } ... ] """ self.addStandardHeaders() with web.ctx.connFactory() as conn: autostackRows = repository.getAutostackList(conn) autostackList = [{"uid":autostack.uid, "name":autostack.name, "region":autostack.region, "filters":utils.jsonDecode(autostack.filters)} for autostack in autostackRows] return utils.jsonEncode(autostackList)
def testPOSTAutostackMetricsNoMinMax( self, addMetricMock, getMetricMock, adapterMock, *_args): getMetricMock.return_value = self.metric addMetricMock.return_value = self.metric response = self.app.post("/" + self.autostack.uid + "/metrics", app_utils.jsonEncode([{"metric": "CPUUtilization", "namespace": "AWS/EC2"}, {"metric": "NetworkIn", "namespace": "AWS/EC2"}]), headers=self.headers) self.assertEqual(response.status, 201) self.assertEqual(adapterMock.return_value.monitorMetric.call_count, 2) metricSpec = ( adapterMock.return_value.monitorMetric .call_args_list[0][0][0]["metricSpec"]) self.assertEqual(metricSpec["slaveMetric"]["metric"], "CPUUtilization") self.assertEqual(metricSpec["slaveMetric"]["namespace"], "AWS/EC2") metricSpec = ( adapterMock.return_value.monitorMetric .call_args_list[1][0][0]["metricSpec"]) self.assertEqual(metricSpec["slaveMetric"]["metric"], "NetworkIn") self.assertEqual(metricSpec["slaveMetric"]["namespace"], "AWS/EC2")
def setUp(self): self.headers = getDefaultHTTPHeaders(htm.it.app.config) self.app = TestApp(models_api.app.wsgifunc()) metric = Mock( uid="cebe9fab-f416-4845-8dab-02d292244112", datasource="cloudwatch", description="The number of database connections in use by " "Amazon RDS database", server="htm-itdb2", location="us-east-1", parameters=app_utils.jsonEncode({ "region": "us-east-1", "DBInstanceIdentifier": "htm-itdb2" }), status=1, message=None, collector_error=None, last_timestamp="2013-08-15 21:25:00", poll_interval=60, tag_name=None, model_params=None, last_rowid=20277) metric.name = "AWS/RDS/DatabaseConnections" self.metric = metric
def GET(self): # pylint: disable=R0201 """ Describe Cloudwatch datasource, listing all supported regions, namespaces and metrics :: GET /_metrics/cloudwatch Returns: :: { 'regions': { 'region-name": 'region-description',...}, 'namespaces': { 'namespace-name': { 'metrics': ['metric-name',...], 'dimensions': ['dimension-name',...] }, .... } } """ web.header('Content-Type', 'application/json; charset=UTF-8', True) adapter = datasource_adapter_factory.createCloudwatchDatasourceAdapter() resources = adapter.describeSupportedMetrics() return utils.jsonEncode( {"regions": dict(adapter.describeRegions()), "namespaces": _translateResourcesIntoNamespaces(resources)})
def GET(self): """ Get all instances :: GET /_instances Sample Output: :: [ { "location": "us-west-2", "message": null, "name": "jenkins-master", "namespace": "AWS/EC2", "server": "i-12345678", "status": 2 "parameters": { "region": "us-west-2", "AutoScalingGroupName": "groksolutions-com-ssl" }, }, ... ] """ with web.ctx.connFactory() as conn: instances = repository.getInstances(conn) self.addStandardHeaders() return utils.jsonEncode(instances)
def testModelExportHandlerGETAutostack(self, getMetricMock, createDatasourceAdapterMock, _engineMock): metric = Mock(uid="1f76ba824fe147c8a4bc1c59565d8490", datasource="cloudwatch", description="CPUUtilization on EC2 instance i-ab15a19d in " "us-west-2 region", server="i-ab15a19d", location="us-west-2", parameters=app_utils.jsonEncode({"name": "test1", "filters": { "tag:Name": ["*d*"] }}), last_timestamp=datetime.datetime(2013, 11, 23, 18, 36), poll_interval=300L, status=1L, message=None, tag_name="jenkins-master", model_params={}, last_rowid=1440L) metric.name = "AWS/EC2/CPUUtilization" getMetricMock.return_value = metric createDatasourceAdapterMock.return_value.exportModel.return_value = ( metric.parameters) response = self.app.get("/1f76ba824fe147c8a4bc1c59565d8490/export", headers=self.headers) self.assertEqual(response.status, 200) self.assertEqual([metric.parameters], json.loads(response.body))
def GET(self): """ Get model data stats :: GET /_models/data/stats Returns: :: { "processing_time_remaining": 37 } """ with repository.engineFactory().connect() as conn: unprocessedDataCount = repository.getUnprocessedModelDataCount(conn) processingTimeRemaining = int(math.ceil( unprocessedDataCount * _PROCESSING_TIME_PER_RECORD)) self.addStandardHeaders() return utils.jsonEncode({ "processing_time_remaining": processingTimeRemaining, })
def GET(self): """ Get model data stats :: GET /_models/data/stats Returns: :: { "processing_time_remaining": 37 } """ with repository.engineFactory().connect() as conn: unprocessedDataCount = repository.getUnprocessedModelDataCount( conn) processingTimeRemaining = int( math.ceil(unprocessedDataCount * _PROCESSING_TIME_PER_RECORD)) self.addStandardHeaders() return utils.jsonEncode({ "processing_time_remaining": processingTimeRemaining, })
def testCreateModelWithEmptyInstanceIdArg(self): """ Test for the empty dimension field in json. """ data = utils.jsonEncode(self.modelsTestData["create_empty_instanceid_data"]) response = self.app.put("/", data, status="*", headers=self.headers) assertions.assertInvalidArgumentsError(self, response, "json")
def testPUTNotificationSettingsUpdate(self, repositoryMock, engineMock): """ Test PUT notification settings (update) """ repositoryMock.getDeviceNotificationSettings = Mock( return_value=self.settings) update = { "windowsize": 3601, "sensitivity": 0.999999, "email_addr": "*****@*****.**" } response = self.app.put("/%s/settings" % self.notification["device"], app_utils.jsonEncode(update), headers=self.headers) self.assertEqual(response.status, 204) self.assertFalse(response.body) repositoryMock.updateDeviceNotificationSettings.assert_called_with( engineMock.return_value.connect.return_value.__enter__. return_value, self.notification["device"], { "windowsize": 3601, "sensitivity": 0.999999, "email_addr": "*****@*****.**" })
def deleteModel(metricId): try: with web.ctx.connFactory() as conn: metricRow = repository.getMetric(conn, metricId) except app_exceptions.ObjectNotFoundError: raise web.notfound( "ObjectNotFoundError Metric not found: Metric ID: %s" % metricId) if metricRow.datasource == "autostack": raise NotAllowedResponse({ "result": ("Not a standalone model=%s; datasource=%s. Unable" " to DELETE from this endpoint") % ( metricId, metricRow.datasource, ) }) log.debug("Deleting model for %s metric=%s", metricRow.datasource, metricId) with web.ctx.connFactory() as conn: repository.deleteModel(conn, metricId) # NOTE: this is the new way using datasource adapters try: createDatasourceAdapter( metricRow.datasource).unmonitorMetric(metricId) except app_exceptions.ObjectNotFoundError: raise web.notfound( "ObjectNotFoundError Metric not found: Metric ID: %s" % (metricId, )) return utils.jsonEncode({'result': 'success'})
def testCreateModelWithEmptyMetricArg(self): """ Test for the missing empty metric field in json. """ data = utils.jsonEncode(self.modelsTestData["create_empty_metric_data"]) response = self.app.put("/", data, status="*", headers=self.headers) assertions.assertInvalidArgumentsError(self, response, "json")
def deleteModel(metricId): try: with web.ctx.connFactory() as conn: metricRow = repository.getMetric(conn, metricId) except app_exceptions.ObjectNotFoundError: raise web.notfound("ObjectNotFoundError Metric not found: Metric ID: %s" % metricId) if metricRow.datasource == "autostack": raise NotAllowedResponse( {"result": ("Not a standalone model=%s; datasource=%s. Unable" " to DELETE from this endpoint") % (metricId, metricRow.datasource,) }) log.debug("Deleting model for %s metric=%s", metricRow.datasource, metricId) with web.ctx.connFactory() as conn: repository.deleteModel(conn, metricId) # NOTE: this is the new way using datasource adapters try: createDatasourceAdapter(metricRow.datasource).unmonitorMetric(metricId) except app_exceptions.ObjectNotFoundError: raise web.notfound( "ObjectNotFoundError Metric not found: Metric ID: %s" % (metricId,)) return utils.jsonEncode({'result': 'success'})
def GET(self, autostackId, *args): # pylint: disable=C0103,W0613 """ Get Metrics associated with autostack :: GET /_autostacks/{autostackId}/metrics NOTE: args is ignored. Function signature for all method handlers must be compatible with the regexp pattern that matches. POST optionally takes a second argument, DELETE requires it. """ try: self.addStandardHeaders() engine = repository.engineFactory() metricRows = repository.getAutostackMetrics( engine, autostackId, getMetricDisplayFields(engine)) metricsList = [ convertMetricRowToMetricDict(metricRow) for metricRow in metricRows ] return utils.jsonEncode(metricsList) except ObjectNotFoundError: raise web.notfound("Autostack not found: Autostack ID: %s" % autostackId) except web.HTTPError as ex: if bool(re.match(r"([45][0-9][0-9])\s?", web.ctx.status)): # Log 400-599 status codes as errors, ignoring 200-399 log.error(str(ex) or repr(ex)) raise except Exception as ex: raise web.internalerror(str(ex) or repr(ex))
def testGETAnnotationById(self, getAnnotationById, _): """ Test Get Annotation Request:: GET /_annotations/{uid} Response:: HTTP 200 Ok [ { "uid": "2a123bb1dd4d46e7a806d62efc29cbb9", "device", "1231AC32FE", "created":"2013-08-27 16:46:51", "timestamp":"2013-08-27 16:45:00", "user":"******", "server":" AWS/EC2/i-53f52b67", "message":" The CPU Utilization was high ...", "data": { Optional JSON Object } } ] """ getAnnotationById.return_value = self.annotation response = self.app.get("/%s" % self.annotation["uid"], headers=self.headers) self.assertEqual(response.status, 200) actual = json.loads(response.body) expected = json.loads(app_utils.jsonEncode(self.annotation)) self.assertDictEqual(expected, actual[0])
def testPOSTAutostacks(self, adapterMock, quotaRepositoryMock, _repositoryMock): name = "Test" region = "Bogus" filters = {"tag:Name": ["Bogus"]} quotaRepositoryMock.getInstanceCount.return_value = 0 autostackMock = Mock() autostackMock.items.return_value = [("name", name), ("region", region), ("filters", filters)] adapterMock.return_value.createAutostack.return_value = autostackMock response = self.app.post("/", app_utils.jsonEncode({ "name": name, "region": region, "filters": filters }), headers=self.headers) self.assertEqual(response.status, 201) result = json.loads(response.body) self.assertEqual(result["name"], "Test") self.assertTrue(adapterMock.return_value.createAutostack.called)
def GET(self): """ Get all instances :: GET /_instances Sample Output: :: [ { "location": "us-west-2", "message": null, "name": "jenkins-master", "namespace": "AWS/EC2", "server": "i-12345678", "status": 2 "parameters": { "region": "us-west-2", "AutoScalingGroupName": "htm-itsolutions-com-ssl" }, }, ... ] """ with web.ctx.connFactory() as conn: instances = repository.getInstances(conn) self.addStandardHeaders() return utils.jsonEncode(instances)
def testCreateModelWithInvalidDatasourceArg(self): """ Test for the invalid metric field in json. """ data = utils.jsonEncode(self.modelsTestData["create_invalid_ds_data"]) response = self.app.put("/", data, status="*", headers=self.headers) assertions.assertInvalidArgumentsError(self, response, "json")
def GET(self): # pylint: disable=R0201 """ Describe Cloudwatch datasource, listing all supported regions, namespaces and metrics :: GET /_metrics/cloudwatch Returns: :: { 'regions': { 'region-name": 'region-description',...}, 'namespaces': { 'namespace-name': { 'metrics': ['metric-name',...], 'dimensions': ['dimension-name',...] }, .... } } """ web.header('Content-Type', 'application/json; charset=UTF-8', True) adapter = datasource_adapter_factory.createCloudwatchDatasourceAdapter( ) resources = adapter.describeSupportedMetrics() return utils.jsonEncode({ "regions": dict(adapter.describeRegions()), "namespaces": _translateResourcesIntoNamespaces(resources) })
def testCreateModelWithInvalidRegionKey(self): """ Test for the invalid region field in json. """ data = utils.jsonEncode(self.modelsTestData["create_invalid_region_key"]) response = self.app.put("/", data, status="*", headers=self.headers) assertions.assertInvalidArgumentsError(self, response, "json")
def testPOSTAutostackMetricsNoMinMax(self, addMetricMock, getMetricMock, adapterMock, *_args): getMetricMock.return_value = self.metric addMetricMock.return_value = self.metric response = self.app.post("/" + self.autostack.uid + "/metrics", app_utils.jsonEncode([{ "metric": "CPUUtilization", "namespace": "AWS/EC2" }, { "metric": "NetworkIn", "namespace": "AWS/EC2" }]), headers=self.headers) self.assertEqual(response.status, 201) self.assertEqual(adapterMock.return_value.monitorMetric.call_count, 2) metricSpec = (adapterMock.return_value.monitorMetric.call_args_list[0] [0][0]["metricSpec"]) self.assertEqual(metricSpec["slaveMetric"]["metric"], "CPUUtilization") self.assertEqual(metricSpec["slaveMetric"]["namespace"], "AWS/EC2") metricSpec = (adapterMock.return_value.monitorMetric.call_args_list[1] [0][0]["metricSpec"]) self.assertEqual(metricSpec["slaveMetric"]["metric"], "NetworkIn") self.assertEqual(metricSpec["slaveMetric"]["namespace"], "AWS/EC2")
def GET(self): # pylint: disable=R0201 """ Returns list of supported Clouwdwatch regions :: GET /_metrics/cloudwatch/regions Returns: :: { 'region-name': 'region-description',...} Sample output: :: { "ap-northeast-1": "Asia Pacific (Tokyo) Region", "ap-southeast-1": "Asia Pacific (Singapore) Region", "ap-southeast-2": "Asia Pacific (Sydney) Region", "eu-west-1": "EU (Ireland) Region", "sa-east-1": "South America (Sao Paulo) Region", "us-east-1": "US East (Northern Virginia) Region", "us-west-1": "US West (Northern California) Region", "us-west-2": "US West (Oregon) Region" } """ adapter = datasource_adapter_factory.createCloudwatchDatasourceAdapter( ) AuthenticatedBaseHandler.addStandardHeaders() return utils.jsonEncode(dict(adapter.describeRegions()))
def GET(self, autostackId, *args): # pylint: disable=C0103,W0613 """ Get Metrics associated with autostack :: GET /_autostacks/{autostackId}/metrics NOTE: args is ignored. Function signature for all method handlers must be compatible with the regexp pattern that matches. POST optionally takes a second argument, DELETE requires it. """ try: self.addStandardHeaders() engine = repository.engineFactory() metricRows = repository.getAutostackMetrics(engine, autostackId, getMetricDisplayFields(engine)) metricsList = [convertMetricRowToMetricDict(metricRow) for metricRow in metricRows] return utils.jsonEncode(metricsList) except ObjectNotFoundError: raise web.notfound("Autostack not found: Autostack ID: %s" % autostackId) except web.HTTPError as ex: if bool(re.match(r"([45][0-9][0-9])\s?", web.ctx.status)): # Log 400-599 status codes as errors, ignoring 200-399 log.error(str(ex) or repr(ex)) raise except Exception as ex: raise web.internalerror(str(ex) or repr(ex))
def testDeleteInstancesHandlerEmptyData(self, listMetricIDsMock, _engineFactoryMock): """ Test for Delete "/_instances" with with empty input data response is validated for appropriate headers, body and status """ params = [] response = self.app.delete("", params=app_utils.jsonEncode(params), headers=self.headers, status="*") assertions.assertBadRequest(self, response, "json") self.assertFalse(listMetricIDsMock.called)
def testCreateModelWithInvalidDimensionsArg(self): """ Test for the invalid dimension field in json. """ data = utils.jsonEncode(self.modelsTestData["create_invalid_dimension_data"]) response = self.app.put("/", data, status="*", headers=self.headers) assertions.assertBadRequest(self, response, "json") response = self.app.get("/", headers=self.headers) self.assertFalse(json.loads(response.body), "Model actually created with invalid dimension")
def testPOSTSectionInvalid(self): """ Test for POST for '/_settings', Set some invalid setting resoponse is validated for appropriate headers and body """ with self.assertRaises(AppError) as e: self.app.post("/foo", app_utils.jsonEncode( {"aws_access_key_id" : "dummy_aws_key2"}), headers=self.headers) self.assertIn("Bad response: 400 Bad Request (not 200 OK or 3xx redirect" " for /foo)\nFailed to update configuration settings", str(e.exception))
def testPostMultipleWithInstanceToIncorrectRegion(self): """ Test for post '/_instances' response is validated for appropriate headers, body and status invoke post with valid instance id to incorrect region """ params = [VALID_EC2_INSTANCES["jenkins-master"]] response = self.app.post("/us-east-1/AWS/EC2", params=app_utils.jsonEncode(params), headers=self.headers, status="*") assertions.assertBadRequest(self, response, "json") self.assertIn("InvalidArgumentsError", response.body)
def GET(self, region): """ List all existing Cloudwatch metrics for a given region :: GET /_metrics/cloudwatch/regions/{region} Returns: :: [ { 'name': 'tag-or-empty-string', 'region': 'region-name', 'namespace': 'namespace-name', 'datasource': 'cloudwatch', 'identifier': 'id-from-dimension', 'metric': 'metric-name', 'dimensions': { ... } },... ] """ adapter = datasource_adapter_factory.createCloudwatchDatasourceAdapter( ) resources = adapter.describeSupportedMetrics() def translateResourcesIntoMetrics(): for resource, metrics in resources.items(): for specificResource in adapter.describeResources( region, resource): for metric, cloudwatchParams in metrics.items(): yield { "datasource": "cloudwatch", "dimensions": { cloudwatchParams["dimensionGroups"][0][0]: specificResource["resID"] }, "identifier": specificResource["resID"], "metric": metric, "name": specificResource["name"], "namespace": cloudwatchParams["namespace"], "region": region } if region not in dict(adapter.describeRegions()): raise web.NotFound("Region '%s' was not found" % region) self.addStandardHeaders() return utils.jsonEncode(list(translateResourcesIntoMetrics()))
def testCreateModelWithInvalidMetricArg(self): """ Test for the invalid metric field in json. """ data = utils.jsonEncode(self.modelsTestData["create_invalid_metric_data"]) response = self.app.put("/", data, status="*", headers=self.headers) assertions.assertBadRequest(self, response, "json") response = self.app.get("/", headers=self.headers) self.assertFalse(json.loads(response.body), "Model actually created with invalid metric")
def testDeleteMultipleWithInvalidInstanceId(self): """ Test for post '/_instances' response is validated for appropriate headers, body and status invoke delete with invalid Instance id """ params = ["abcd1234"] response = self.app.delete("", params=app_utils.jsonEncode(params), headers=self.headers, status="*") assertions.assertNotFound(self, response) self.assertIn("Not able to delete", response.body)
def testPostMultipleWithEmptyData(self): """ Test for post '/_instances' response is validated for appropriate headers, body and status Invoke post with empty data """ params = [] response = self.app.post("/us-west-2/AWS/EC2", params=app_utils.jsonEncode(params), headers=self.headers, status="*") assertions.assertBadRequest(self, response, "json") self.assertIn("InvalidArgumentsError", response.body)
def _handleMetricCollectionError(self, engine, metricObj, startTime, error): """ Update the metric's collector_error member and promote the metric to ERROR state if needed :param engine: SQLAlchemy engine object :type engine: sqlalchemy.engine.Engine :param metricObj: Metric instance :param startTime: metric collection start time; unix epoch timestamp :param error: exception object that is the result of metric data collection or (possibly pending) resource status check """ # Quarantine the metric temporarily self._metricInfoCache[metricObj.uid].quarantineEndTime = ( startTime + metricObj.poll_interval * self._METRIC_QUARANTINE_DURATION_RATIO) try: if metricObj.collector_error is None: # TODO: unit-test # Begin error grace period for this metric deadline = time.time() + self._metricErrorGracePeriod with engine.connect() as conn: repository.retryOnTransientErrors( repository.setMetricCollectorError)( conn, metricObj.uid, utils.jsonEncode( dict(deadline=deadline, message=repr(error)))) self._log.error( "Started error grace period on metric=<%r> through %sZ due to " "error=%r", metricObj, datetime.utcfromtimestamp(deadline).isoformat(), error) elif (time.time() > utils.jsonDecode( metricObj.collector_error)["deadline"]): # TODO: unit-test # Error grace period expired: promote the metric to ERROR state with engine.connect() as conn: repository.retryOnTransientErrors( repository.setMetricStatus)(conn, metricObj.uid, MetricStatus.ERROR, repr(error)) self._log.error( "Metric Collector: grace period expired; placed metric=<%r> in " "ERROR state due to error=%r", metricObj, error) except app_exceptions.ObjectNotFoundError: # TODO: unit-test self._log.warning("Metric deleted? metric=%r", metricObj, exc_info=True)