diff --git a/README.md b/README.md index 3e8f224..c0172ed 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ The default location of the configuration file used by collectd-cloudwatch plugi * __credentials_path__ - Used to point to AWS account configuration file * __region__ - Manual override for [region](http://docs.aws.amazon.com/general/latest/gr/rande.html#cw_region) used to publish metrics * __host__ - Manual override for EC2 Instance ID and Host information propagated by collectd + * __ec2_endpoint_url__ - Manual override for EC2 endpoint + * __monitoring_endpoint_url__ - Manual override for Monitoring endpoint * __proxy_server_name__ - Manual override for proxy server name, used by plugin to connect aws cloudwatch at *.amazonaws.com. * __proxy_server_port__ - Manual override for proxy server port, used by plugin to connect aws cloudwatch at *.amazonaws.com. * __enable_high_resolution_metrics__ - The storage resolution is for high resolution support diff --git a/src/cloudwatch/modules/client/baserequestbuilder.py b/src/cloudwatch/modules/client/baserequestbuilder.py index 8bac432..12551f8 100644 --- a/src/cloudwatch/modules/client/baserequestbuilder.py +++ b/src/cloudwatch/modules/client/baserequestbuilder.py @@ -1,6 +1,9 @@ -from ..awsutils import get_aws_timestamp, get_datestamp -from signer import Signer +import re + from querystringbuilder import QuerystringBuilder +from signer import Signer +from ..awsutils import get_aws_timestamp, get_datestamp + class BaseRequestBuilder(object): """ @@ -17,7 +20,8 @@ class BaseRequestBuilder(object): _ALGORITHM = "AWS4-HMAC-SHA256" _V4_TERMINATOR = "aws4_request" - def __init__(self, credentials, region, service, action, api_version, enable_high_resolution_metrics=False): + def __init__(self, endpoint, credentials, region, service, action, api_version, enable_high_resolution_metrics=False): + self.endpoint = endpoint self.credentials = credentials self.region = region self.datestamp = None @@ -61,4 +65,16 @@ def _get_request_map(self): } if self.credentials.token: canonical_map["X-Amz-Security-Token"] = self.credentials.token - return canonical_map \ No newline at end of file + return canonical_map + + def _get_host(self): + """ Returns the endpoint's hostname derived from the endpoint """ + match = re.search(r"http://(.*)/", self.endpoint) + if match: + return match.group(1) + + match = re.search(r"https://(.*)/", self.endpoint) + if match: + return match.group(1) + + raise ValueError("Cannot extract endpoint hostname") diff --git a/src/cloudwatch/modules/client/ec2getclient.py b/src/cloudwatch/modules/client/ec2getclient.py index f1530ef..d87a62a 100644 --- a/src/cloudwatch/modules/client/ec2getclient.py +++ b/src/cloudwatch/modules/client/ec2getclient.py @@ -25,7 +25,7 @@ class EC2GetClient(object): _TOTAL_RETRIES = 1 def __init__(self, config_helper, connection_timeout=_DEFAULT_CONNECTION_TIMEOUT, response_timeout=_DEFAULT_RESPONSE_TIMEOUT): - self.request_builder = EC2RequestBuilder(config_helper.credentials, config_helper.region) + self.request_builder = EC2RequestBuilder(config_helper.ec2_endpoint, config_helper.credentials, config_helper.region) self._validate_and_set_endpoint(config_helper.ec2_endpoint) self.timeout = (connection_timeout, response_timeout) diff --git a/src/cloudwatch/modules/client/ec2requestbuilder.py b/src/cloudwatch/modules/client/ec2requestbuilder.py index 073cd1f..fcca783 100644 --- a/src/cloudwatch/modules/client/ec2requestbuilder.py +++ b/src/cloudwatch/modules/client/ec2requestbuilder.py @@ -13,8 +13,8 @@ class EC2RequestBuilder(BaseRequestBuilder): _ACTION = "DescribeTags" _API_VERSION = "2016-11-15" - def __init__(self, credentials, region): - super(self.__class__, self).__init__(credentials, region, self._SERVICE, self._ACTION, self._API_VERSION) + def __init__(self, ec2_endpoint, credentials, region): + super(self.__class__, self).__init__(ec2_endpoint, credentials, region, self._SERVICE, self._ACTION, self._API_VERSION) def create_signed_request(self, request_map): """ Creates a ready to send request with metrics from the metric list passed as parameter """ @@ -32,11 +32,3 @@ def _create_canonical_querystring(self, request_map): http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html """ return self.querystring_builder.build_querystring_from_map(request_map, self._get_request_map()) - - def _get_host(self): - """ Returns the endpoint's hostname derived from the region """ - if self.region == "localhost": - return "localhost" - elif self.region.startswith("cn-"): - return "ec2." + self.region + ".amazonaws.com.cn" - return "ec2." + self.region + ".amazonaws.com" diff --git a/src/cloudwatch/modules/client/putclient.py b/src/cloudwatch/modules/client/putclient.py index 4fea5c1..080a0af 100644 --- a/src/cloudwatch/modules/client/putclient.py +++ b/src/cloudwatch/modules/client/putclient.py @@ -29,7 +29,7 @@ class PutClient(object): _LOG_FILE_MAX_SIZE = 10*1024*1024 def __init__(self, config_helper, connection_timeout=_DEFAULT_CONNECTION_TIMEOUT, response_timeout=_DEFAULT_RESPONSE_TIMEOUT): - self.request_builder = RequestBuilder(config_helper.credentials, config_helper.region, config_helper.enable_high_resolution_metrics) + self.request_builder = RequestBuilder(config_helper.endpoint, config_helper.credentials, config_helper.region, config_helper.enable_high_resolution_metrics) self._validate_and_set_endpoint(config_helper.endpoint) self.timeout = (connection_timeout, response_timeout) self.proxy_server_name = config_helper.proxy_server_name diff --git a/src/cloudwatch/modules/client/requestbuilder.py b/src/cloudwatch/modules/client/requestbuilder.py index 85e3896..e512b2a 100644 --- a/src/cloudwatch/modules/client/requestbuilder.py +++ b/src/cloudwatch/modules/client/requestbuilder.py @@ -13,8 +13,8 @@ class RequestBuilder(BaseRequestBuilder): _ACTION = "PutMetricData" _API_VERSION = "2010-08-01" - def __init__(self, credentials, region, enable_high_resolution_metrics): - super(self.__class__, self).__init__(credentials, region, self._SERVICE, self._ACTION, self._API_VERSION, enable_high_resolution_metrics) + def __init__(self, endpoint, credentials, region, enable_high_resolution_metrics): + super(self.__class__, self).__init__(endpoint, credentials, region, self._SERVICE, self._ACTION, self._API_VERSION, enable_high_resolution_metrics) self.namespace = "" def create_signed_request(self, namespace, metric_list): @@ -44,11 +44,3 @@ def _get_namespace_request_map(self): if (self.namespace): canonical_map["Namespace"] = self.namespace return canonical_map - - def _get_host(self): - """ Returns the endpoint's hostname derived from the region """ - if self.region == "localhost": - return "localhost" - elif self.region.startswith("cn-"): - return "monitoring." + self.region + ".amazonaws.com.cn" - return "monitoring." + self.region + ".amazonaws.com" diff --git a/src/cloudwatch/modules/configuration/confighelper.py b/src/cloudwatch/modules/configuration/confighelper.py index f013085..8b099bf 100644 --- a/src/cloudwatch/modules/configuration/confighelper.py +++ b/src/cloudwatch/modules/configuration/confighelper.py @@ -139,7 +139,11 @@ def _load_hostname(self): " Using host information provided by Collectd.") def _set_ec2_endpoint(self): - """ Creates endpoint from region information """ + """ Creates endpoint from region information (if the endpoint not explicitly set via ec2_endpoint_url) """ + if self.config_reader.ec2_endpoint_url: + self.ec2_endpoint = self.config_reader.ec2_endpoint_url + return + if self.region is "localhost": self.ec2_endpoint = "http://" + self.region + "/" elif self.region.startswith("cn-"): @@ -179,7 +183,11 @@ def _load_flush_interval_in_seconds(self): self._LOGGER.warning("flush_interval_in_seconds in configuration is invalid: " + str(self.config_reader.flush_interval_in_seconds) + " use the default value: " + self.flush_interval_in_seconds) def _set_endpoint(self): - """ Creates endpoint from region information """ + """ Creates endpoint from region information (if the endpoint not explicitly set via monitoring_endpoint_url) """ + if self.config_reader.monitoring_endpoint_url: + self.endpoint = self.config_reader.monitoring_endpoint_url + return + if self.region is "localhost": self.endpoint = "http://" + self.region + "/" elif self.region.startswith("cn-"): diff --git a/src/cloudwatch/modules/configuration/configreader.py b/src/cloudwatch/modules/configuration/configreader.py index 1da5a65..26df242 100644 --- a/src/cloudwatch/modules/configuration/configreader.py +++ b/src/cloudwatch/modules/configuration/configreader.py @@ -39,6 +39,8 @@ class ConfigReader(object): PROXY_SERVER_PORT_KEY = "proxy_server_port" ENABLE_HIGH_DEFINITION_METRICS = "enable_high_resolution_metrics" FLUSH_INTERVAL_IN_SECONDS = "flush_interval_in_seconds" + EC2_ENDPOINT_URL = "ec2_endpoint_url" + MONITORING_ENDPOINT_URL = "monitoring_endpoint_url" def __init__(self, config_path): self.config_path = config_path @@ -78,3 +80,5 @@ def _parse_config_file(self): self.push_asg = self.reader_utils.try_get_boolean(self.PUSH_ASG_KEY, self._PUSH_ASG_DEFAULT_VALUE) self.push_constant = self.reader_utils.try_get_boolean(self.PUSH_CONSTANT_KEY, self._PUSH_CONSTANT_DEFAULT_VALUE) self.constant_dimension_value = self.reader_utils.get_string(self.CONSTANT_DIMENSION_KEY) + self.ec2_endpoint_url = self.reader_utils.get_string(self.EC2_ENDPOINT_URL) + self.monitoring_endpoint_url = self.reader_utils.get_string(self.MONITORING_ENDPOINT_URL) diff --git a/test/test_requestbuilder.py b/test/test_requestbuilder.py index 41e2310..60bdb5c 100644 --- a/test/test_requestbuilder.py +++ b/test/test_requestbuilder.py @@ -12,13 +12,13 @@ def setUp(self): self.region = "test_region" self.namespace = "test_namespace" self.credentials = AWSCredentials("access_key", "secret_key") - self.builder = RequestBuilder(self.credentials, self.region, "10") + self.builder = RequestBuilder("https://monitoring." + self.region + ".amazonaws.com/", self.credentials, self.region, "10") def test_get_host(self): self.assertEquals("monitoring." + self.region +".amazonaws.com", self.builder._get_host()) - def test_get_host_for_localhost_region(self): - self.builder.region = "localhost" + def test_get_host_for_localhost_endpoint(self): + self.builder.endpoint = "http://localhost/" self.assertEquals("localhost", self.builder._get_host()) def test_get_signed_headers(self): @@ -54,7 +54,7 @@ def test_create_signed_request_generates_all_required_parameters(self): self.assertTrue("X-Amz-SignedHeaders" in request) def test_create_signed_request_with_iam_role_has_token_parameter(self): - self.builder = RequestBuilder(AWSCredentials("access_key", "secret_key", "token"), self.region, "10") + self.builder = RequestBuilder("https://localhost/", AWSCredentials("access_key", "secret_key", "token"), self.region, "10") metric = MetricDataStatistic("test_metric", statistic_values=MetricDataStatistic.Statistics(20)) request = self.builder.create_signed_request(self.namespace, [metric]) self.assertTrue("X-Amz-Security-Token" in request)