xds-k8s: Link xDS test client and test server logs in Logs Explorer (#26844)

pull/26853/head
Sergii Tkachenko 3 years ago committed by GitHub
parent bc09e792c8
commit bf186156bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      tools/run_tests/xds_k8s_test_driver/config/common.cfg
  2. 13
      tools/run_tests/xds_k8s_test_driver/framework/helpers/datetime.py
  3. 11
      tools/run_tests/xds_k8s_test_driver/framework/infrastructure/gcp/api.py
  4. 52
      tools/run_tests/xds_k8s_test_driver/framework/test_app/base_runner.py
  5. 17
      tools/run_tests/xds_k8s_test_driver/framework/test_app/client_app.py
  6. 13
      tools/run_tests/xds_k8s_test_driver/framework/test_app/server_app.py

@ -2,3 +2,6 @@
--td_bootstrap_image=gcr.io/grpc-testing/td-grpc-bootstrap:2558ec79df06984ed0d37e9e69f34688ffe301bb
--logger_levels=__main__:DEBUG,framework:INFO
--verbosity=0
# Google projects: remove if console.cloud.google.com redirects to Logs Explorer
# ref: https://github.com/grpc/grpc/pull/26844#discussion_r680224772
--gcp_ui_url=pantheon.corp.google.com

@ -24,6 +24,19 @@ def utc_now() -> datetime.datetime:
return datetime.datetime.now(datetime.timezone.utc)
def shorten_utc_zone(utc_datetime_str: str) -> str:
"""Replace ±00:00 timezone designator with Z (zero offset AKA Zulu time)."""
return RE_ZERO_OFFSET.sub('Z', utc_datetime_str)
def iso8601_utc_time(timedelta: datetime.timedelta = None) -> str:
"""Return datetime relative to current in ISO-8601 format, UTC tz."""
time: datetime.datetime = utc_now()
if timedelta:
time += timedelta
return shorten_utc_zone(time.isoformat())
def datetime_suffix(*, seconds: bool = False) -> str:
"""Return current UTC date, and time in a format useful for resource naming.

@ -44,6 +44,9 @@ COMPUTE_V1_DISCOVERY_FILE = flags.DEFINE_string(
"compute_v1_discovery_file",
default=None,
help="Load compute v1 from discovery file")
GCP_UI_URL = flags.DEFINE_string("gcp_ui_url",
default="console.cloud.google.com",
help="Override GCP UI URL.")
# Type aliases
_HttpError = googleapiclient.errors.HttpError
@ -59,13 +62,15 @@ class GcpApiManager:
v1_discovery_uri=None,
v2_discovery_uri=None,
compute_v1_discovery_file=None,
private_api_key_secret_name=None):
private_api_key_secret_name=None,
gcp_ui_url=None):
self.v1_discovery_uri = v1_discovery_uri or V1_DISCOVERY_URI.value
self.v2_discovery_uri = v2_discovery_uri or V2_DISCOVERY_URI.value
self.compute_v1_discovery_file = (compute_v1_discovery_file or
COMPUTE_V1_DISCOVERY_FILE.value)
self.private_api_key_secret_name = (private_api_key_secret_name or
PRIVATE_API_KEY_SECRET_NAME.value)
self.gcp_ui_url = gcp_ui_url or GCP_UI_URL.value
# TODO(sergiitk): add options to pass google Credentials
self._exit_stack = contextlib.ExitStack()
@ -80,10 +85,10 @@ class GcpApiManager:
Return API key credential that identifies a GCP project allow-listed for
accessing private API discovery documents.
https://pantheon.corp.google.com/apis/credentials
https://console.cloud.google.com/apis/credentials
This method lazy-loads the content of the key from the Secret Manager.
https://pantheon.corp.google.com/security/secret-manager
https://console.cloud.google.com/security/secret-manager
"""
if not self.private_api_key_secret_name:
raise ValueError('private_api_key_secret_name must be set to '

@ -12,18 +12,37 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import contextlib
import datetime
import logging
import pathlib
from typing import Optional
from typing import Dict, Optional
import urllib.parse
import mako.template
import yaml
import framework.helpers.datetime
from framework.infrastructure import gcp
from framework.infrastructure import k8s
logger = logging.getLogger(__name__)
# Type aliases
_helper_datetime = framework.helpers.datetime
timedelta = datetime.timedelta
def _logs_explorer_query(query: Dict[str, str]) -> str:
return '\n'.join(f'{k}="{v}"' for k, v in query.items())
def _logs_explorer_request(req: Dict[str, str]) -> str:
return ';'.join(f'{k}={_logs_explorer_quote(v)}' for k, v in req.items())
def _logs_explorer_quote(value: str):
return urllib.parse.quote_plus(value, safe=':')
class RunnerError(Exception):
"""Error running app"""
@ -287,8 +306,35 @@ class KubernetesBaseRunner:
logger.info("Service %s: detected NEG=%s in zones=%s", name, neg_name,
neg_zones)
@classmethod
def _make_namespace_name(cls, resource_prefix: str, resource_suffix: str,
@staticmethod
def _logs_explorer_link(*,
deployment_name: str,
namespace_name: str,
gcp_project: str,
gcp_ui_url: str,
end_delta: timedelta = None) -> None:
"""Output the link to test server/client logs in GCP Logs Explorer."""
if end_delta is None:
end_delta = timedelta(hours=1)
time_now = _helper_datetime.iso8601_utc_time()
time_end = _helper_datetime.iso8601_utc_time(end_delta)
query = _logs_explorer_query({
'resource.type': 'k8s_container',
'resource.labels.project_id': gcp_project,
'resource.labels.container_name': deployment_name,
'resource.labels.namespace_name': namespace_name,
})
req = _logs_explorer_request({
'query': query,
'timeRange': f'{time_now}/{time_end}',
})
link = f'https://{gcp_ui_url}/logs/query;{req}?project={gcp_project}'
logger.info("GCP Logs Explorer link to %s:\n%s", deployment_name, link)
@staticmethod
def _make_namespace_name(resource_prefix: str, resource_suffix: str,
name: str) -> str:
"""A helper to make consistent test app kubernetes namespace name
for given resource prefix and suffix."""

@ -20,7 +20,7 @@ modules.
import datetime
import functools
import logging
from typing import Iterable, List, Optional, Tuple
from typing import Iterable, List, Optional
from framework.helpers import retryers
from framework.infrastructure import gcp
@ -261,6 +261,9 @@ class KubernetesClientRunner(base_runner.KubernetesBaseRunner):
# Kubernetes service account
self.service_account_name = service_account_name or deployment_name
self.service_account_template = service_account_template
# GCP.
self.gcp_project = gcp_project
self.gcp_ui_url = gcp_api_manager.gcp_ui_url
# GCP service account to map to Kubernetes service account
self.gcp_service_account = gcp_service_account
# GCP IAM API used to grant allow workload service accounts permission
@ -272,6 +275,7 @@ class KubernetesClientRunner(base_runner.KubernetesBaseRunner):
self.service_account: Optional[k8s.V1ServiceAccount] = None
self.port_forwarder = None
# TODO(sergiitk): make rpc UnaryCall enum or get it from proto
def run(self,
*,
server_target,
@ -280,8 +284,17 @@ class KubernetesClientRunner(base_runner.KubernetesBaseRunner):
metadata='',
secure_mode=False,
print_response=False) -> XdsTestClient:
logger.info(
'Deploying xDS test client "%s" to k8s namespace %s: '
'server_target=%s rpc=%s qps=%s metadata=%r secure_mode=%s '
'print_response=%s', self.deployment_name, self.k8s_namespace.name,
server_target, rpc, qps, metadata, secure_mode, print_response)
self._logs_explorer_link(deployment_name=self.deployment_name,
namespace_name=self.k8s_namespace.name,
gcp_project=self.gcp_project,
gcp_ui_url=self.gcp_ui_url)
super().run()
# TODO(sergiitk): make rpc UnaryCall enum or get it from proto
# Allow Kubernetes service account to use the GCP service account
# identity.

@ -180,6 +180,9 @@ class KubernetesServerRunner(base_runner.KubernetesBaseRunner):
# Kubernetes service account
self.service_account_name = service_account_name or deployment_name
self.service_account_template = service_account_template
# GCP.
self.gcp_project = gcp_project
self.gcp_ui_url = gcp_api_manager.gcp_ui_url
# GCP service account to map to Kubernetes service account
self.gcp_service_account = gcp_service_account
# GCP IAM API used to grant allow workload service accounts permission
@ -217,6 +220,16 @@ class KubernetesServerRunner(base_runner.KubernetesBaseRunner):
isinstance(maintenance_port, int)):
raise TypeError('Port numbers must be integer')
logger.info(
'Deploying xDS test server "%s" to k8s namespace %s: test_port=%s '
'maintenance_port=%s secure_mode=%s server_id=%s replica_count=%s',
self.deployment_name, self.k8s_namespace.name, test_port,
maintenance_port, secure_mode, server_id, replica_count)
self._logs_explorer_link(deployment_name=self.deployment_name,
namespace_name=self.k8s_namespace.name,
gcp_project=self.gcp_project,
gcp_ui_url=self.gcp_ui_url)
# Create namespace.
super().run()

Loading…
Cancel
Save