-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* initial gcr collector flow * adding the specific google cloud run metrics * small cleanup * adding tests for gcr * add headers to the request * add debug logs for testing * add debug logs for testing * fixing parsing gcr response * update version * changes in regards to PR comments and cleanup * use request mocking in tests * add requests mock in python27 requirements * fixing typo * Update instana/agent/google_cloud_run.py Co-authored-by: Andrey Slotin <andrey.slotin@instana.com> * Update instana/agent/google_cloud_run.py Co-authored-by: Andrey Slotin <andrey.slotin@instana.com> * Update instana/collector/helpers/google_cloud_run/process.py Co-authored-by: Andrey Slotin <andrey.slotin@instana.com> * Update instana/collector/helpers/google_cloud_run/process.py Co-authored-by: Andrey Slotin <andrey.slotin@instana.com> * PR review fixes * fix conflict Co-authored-by: Andrey Slotin <andrey.slotin@instana.com>
- Loading branch information
Showing
26 changed files
with
665 additions
and
57 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
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
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,95 @@ | ||
# (c) Copyright IBM Corp. 2021 | ||
# (c) Copyright Instana Inc. 2021 | ||
|
||
""" | ||
The Instana agent (for GCR) that manages | ||
monitoring state and reporting that data. | ||
""" | ||
import time | ||
from instana.options import GCROptions | ||
from instana.collector.google_cloud_run import GCRCollector | ||
from instana.log import logger | ||
from instana.util import to_json | ||
from instana.agent.base import BaseAgent | ||
from instana.version import VERSION | ||
|
||
|
||
class GCRAgent(BaseAgent): | ||
""" In-process agent for Google Cloud Run """ | ||
|
||
def __init__(self, service, configuration, revision): | ||
super(GCRAgent, self).__init__() | ||
|
||
self.options = GCROptions() | ||
self.collector = None | ||
self.report_headers = None | ||
self._can_send = False | ||
|
||
# Update log level (if INSTANA_LOG_LEVEL was set) | ||
self.update_log_level() | ||
|
||
logger.info("Stan is on the AWS Fargate scene. Starting Instana instrumentation version: %s", VERSION) | ||
|
||
if self._validate_options(): | ||
self._can_send = True | ||
self.collector = GCRCollector(self, service, configuration, revision) | ||
self.collector.start() | ||
else: | ||
logger.warning("Required INSTANA_AGENT_KEY and/or INSTANA_ENDPOINT_URL environment variables not set. " | ||
"We will not be able monitor this GCR cluster.") | ||
|
||
def can_send(self): | ||
""" | ||
Are we in a state where we can send data? | ||
@return: Boolean | ||
""" | ||
return self._can_send | ||
|
||
def get_from_structure(self): | ||
""" | ||
Retrieves the From data that is reported alongside monitoring data. | ||
@return: dict() | ||
""" | ||
return {'hl': True, 'cp': 'gcp', 'e': self.collector.get_instance_id()} | ||
|
||
def report_data_payload(self, payload): | ||
""" | ||
Used to report metrics and span data to the endpoint URL in self.options.endpoint_url | ||
""" | ||
response = None | ||
try: | ||
if self.report_headers is None: | ||
# Prepare request headers | ||
self.report_headers = { | ||
"Content-Type": "application/json", | ||
"X-Instana-Host": "gcp:cloud-run:revision:{revision}".format( | ||
revision=self.collector.revision), | ||
"X-Instana-Key": self.options.agent_key | ||
} | ||
|
||
self.report_headers["X-Instana-Time"] = str(round(time.time() * 1000)) | ||
|
||
response = self.client.post(self.__data_bundle_url(), | ||
data=to_json(payload), | ||
headers=self.report_headers, | ||
timeout=self.options.timeout, | ||
verify=self.options.ssl_verify, | ||
proxies=self.options.endpoint_proxy) | ||
|
||
if response.status_code >= 400: | ||
logger.info("report_data_payload: Instana responded with status code %s", response.status_code) | ||
except Exception as exc: | ||
logger.debug("report_data_payload: connection error (%s)", type(exc)) | ||
return response | ||
|
||
def _validate_options(self): | ||
""" | ||
Validate that the options used by this Agent are valid. e.g. can we report data? | ||
""" | ||
return self.options.endpoint_url is not None and self.options.agent_key is not None | ||
|
||
def __data_bundle_url(self): | ||
""" | ||
URL for posting metrics to the host agent. Only valid when announced. | ||
""" | ||
return "{endpoint_url}/bundle".format(endpoint_url=self.options.endpoint_url) |
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
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
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,140 @@ | ||
# (c) Copyright IBM Corp. 2021 | ||
# (c) Copyright Instana Inc. 2021 | ||
|
||
""" | ||
Google Cloud Run Collector: Manages the periodic collection of metrics & snapshot data | ||
""" | ||
import os | ||
from time import time | ||
import requests | ||
|
||
from instana.log import logger | ||
from instana.collector.base import BaseCollector | ||
from instana.util import DictionaryOfStan, validate_url | ||
from instana.collector.helpers.google_cloud_run.process import GCRProcessHelper | ||
from instana.collector.helpers.google_cloud_run.instance_entity import InstanceEntityHelper | ||
|
||
|
||
class GCRCollector(BaseCollector): | ||
""" Collector for Google Cloud Run """ | ||
|
||
def __init__(self, agent, service, configuration, revision): | ||
super(GCRCollector, self).__init__(agent) | ||
logger.debug("Loading Google Cloud Run Collector") | ||
|
||
# Indicates if this Collector has all requirements to run successfully | ||
self.ready_to_start = True | ||
|
||
self.revision = revision | ||
self.service = service | ||
self.configuration = configuration | ||
# Prepare the URLS that we will collect data from | ||
self._gcr_md_uri = os.environ.get("GOOGLE_CLOUD_RUN_METADATA_ENDPOINT", "http://metadata.google.internal") | ||
|
||
if self._gcr_md_uri == "" or validate_url(self._gcr_md_uri) is False: | ||
logger.warning("GCRCollector: GOOGLE_CLOUD_RUN_METADATA_ENDPOINT not in environment or invalid URL. " | ||
"Instana will not be able to monitor this environment") | ||
self.ready_to_start = False | ||
|
||
self._gcr_md_project_uri = self._gcr_md_uri + '/computeMetadata/v1/project/?recursive=true' | ||
self._gcr_md_instance_uri = self._gcr_md_uri + '/computeMetadata/v1/instance/?recursive=true' | ||
|
||
# Timestamp in seconds of the last time we fetched all GCR metadata | ||
self.__last_gcr_md_full_fetch = 0 | ||
|
||
# How often to do a full fetch of GCR metadata | ||
self.__gcr_md_full_fetch_interval = 300 | ||
|
||
# HTTP client with keep-alive | ||
self._http_client = requests.Session() | ||
|
||
# The fully qualified ARN for this process | ||
self._gcp_arn = None | ||
|
||
# Response from the last call to | ||
# Instance URI | ||
self.instance_metadata = None | ||
|
||
# Response from the last call to | ||
# Project URI | ||
self.project_metadata = None | ||
|
||
# Populate the collection helpers | ||
self.helpers.append(GCRProcessHelper(self)) | ||
self.helpers.append(InstanceEntityHelper(self)) | ||
|
||
def start(self): | ||
if self.ready_to_start is False: | ||
logger.warning("Google Cloud Run Collector is missing requirements and cannot monitor this environment.") | ||
return | ||
|
||
super(GCRCollector, self).start() | ||
|
||
def __get_project_instance_metadata(self): | ||
""" | ||
Get the latest data from the service revision instance entity metadata and store in the class | ||
@return: Boolean | ||
""" | ||
try: | ||
# Refetch the GCR snapshot data | ||
self.__last_gcr_md_full_fetch = int(time()) | ||
headers = {"Metadata-Flavor": "Google"} | ||
# Response from the last call to | ||
# ${GOOGLE_CLOUD_RUN_METADATA_ENDPOINT}/computeMetadata/v1/project/?recursive=true | ||
self.project_metadata = self._http_client.get(self._gcr_md_project_uri, timeout=1, | ||
headers=headers).json() | ||
|
||
# Response from the last call to | ||
# ${GOOGLE_CLOUD_RUN_METADATA_ENDPOINT}/computeMetadata/v1/instance/?recursive=true | ||
self.instance_metadata = self._http_client.get(self._gcr_md_instance_uri, timeout=1, | ||
headers=headers).json() | ||
except Exception: | ||
logger.debug("GoogleCloudRunCollector.get_project_instance_metadata", exc_info=True) | ||
|
||
def should_send_snapshot_data(self): | ||
return int(time()) - self.snapshot_data_last_sent > self.snapshot_data_interval | ||
|
||
def prepare_payload(self): | ||
payload = DictionaryOfStan() | ||
payload["spans"] = [] | ||
payload["metrics"]["plugins"] = [] | ||
|
||
try: | ||
|
||
if not self.span_queue.empty(): | ||
payload["spans"] = self.queued_spans() | ||
|
||
self.fetching_start_time = int(time()) | ||
delta = self.fetching_start_time - self.__last_gcr_md_full_fetch | ||
if delta < self.__gcr_md_full_fetch_interval: | ||
return payload | ||
|
||
with_snapshot = self.should_send_snapshot_data() | ||
|
||
# Fetch the latest metrics | ||
self.__get_project_instance_metadata() | ||
if self.instance_metadata is None and self.project_metadata is None: | ||
return payload | ||
|
||
plugins = [] | ||
for helper in self.helpers: | ||
plugins.extend( | ||
helper.collect_metrics(with_snapshot=with_snapshot, instance_metadata=self.instance_metadata, | ||
project_metadata=self.project_metadata)) | ||
|
||
payload["metrics"]["plugins"] = plugins | ||
|
||
if with_snapshot: | ||
self.snapshot_data_last_sent = int(time()) | ||
except Exception: | ||
logger.debug("collect_snapshot error", exc_info=True) | ||
|
||
return payload | ||
|
||
def get_instance_id(self): | ||
try: | ||
if self.instance_metadata: | ||
return self.instance_metadata.get("id") | ||
except Exception: | ||
logger.debug("get_instance_id error", exc_info=True) | ||
return None |
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
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
Oops, something went wrong.