From 1d9a24293171fcd0de0ca444aa35f21dbd1c2b5b Mon Sep 17 00:00:00 2001 From: Sergii Tkachenko Date: Fri, 13 Aug 2021 14:01:22 -0400 Subject: [PATCH] xds-k8s: PSM Security tests upgrade networksecurity, networkservices to v1beta1 (#27005) * xds-k8s: PSM Security uses Network Services v1beta1 * xds-k8s: PSM Security uses Network Security v1beta1 * Explain assumptions v1alpha1 extending v1beta1 --- .../framework/infrastructure/gcp/api.py | 4 + .../infrastructure/gcp/network_security.py | 166 +++++++++++------- .../infrastructure/gcp/network_services.py | 122 +++++++------ .../infrastructure/traffic_director.py | 20 ++- 4 files changed, 190 insertions(+), 122 deletions(-) diff --git a/tools/run_tests/xds_k8s_test_driver/framework/infrastructure/gcp/api.py b/tools/run_tests/xds_k8s_test_driver/framework/infrastructure/gcp/api.py index c5184a84d59..f3a7afcb2c4 100644 --- a/tools/run_tests/xds_k8s_test_driver/framework/infrastructure/gcp/api.py +++ b/tools/run_tests/xds_k8s_test_driver/framework/infrastructure/gcp/api.py @@ -125,6 +125,8 @@ class GcpApiManager: version, api_key=self.private_api_key, visibility_labels=['NETWORKSECURITY_ALPHA']) + elif version == 'v1beta1': + return self._build_from_discovery_v2(api_name, version) raise NotImplementedError(f'Network Security {version} not supported') @@ -137,6 +139,8 @@ class GcpApiManager: version, api_key=self.private_api_key, visibility_labels=['NETWORKSERVICES_ALPHA']) + elif version == 'v1beta1': + return self._build_from_discovery_v2(api_name, version) raise NotImplementedError(f'Network Services {version} not supported') diff --git a/tools/run_tests/xds_k8s_test_driver/framework/infrastructure/gcp/network_security.py b/tools/run_tests/xds_k8s_test_driver/framework/infrastructure/gcp/network_security.py index 9d93cc06681..e97b1779b42 100644 --- a/tools/run_tests/xds_k8s_test_driver/framework/infrastructure/gcp/network_security.py +++ b/tools/run_tests/xds_k8s_test_driver/framework/infrastructure/gcp/network_security.py @@ -11,8 +11,11 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + +import abc import dataclasses import logging +from typing import Any, Dict from google.rpc import code_pb2 import tenacity @@ -21,28 +24,53 @@ from framework.infrastructure import gcp logger = logging.getLogger(__name__) - -class NetworkSecurityV1Alpha1(gcp.api.GcpStandardCloudApiResource): - SERVER_TLS_POLICIES = 'serverTlsPolicies' - CLIENT_TLS_POLICIES = 'clientTlsPolicies' - - @dataclasses.dataclass(frozen=True) - class ServerTlsPolicy: - url: str - name: str - server_certificate: dict - mtls_policy: dict - update_time: str - create_time: str - - @dataclasses.dataclass(frozen=True) - class ClientTlsPolicy: - url: str - name: str - client_certificate: dict - server_validation_ca: list - update_time: str - create_time: str +# Type aliases +GcpResource = gcp.compute.ComputeV1.GcpResource + + +@dataclasses.dataclass(frozen=True) +class ServerTlsPolicy: + url: str + name: str + server_certificate: dict + mtls_policy: dict + update_time: str + create_time: str + + @classmethod + def from_response(cls, name: str, response: Dict[str, + Any]) -> 'ServerTlsPolicy': + return cls(name=name, + url=response['name'], + server_certificate=response.get('serverCertificate', {}), + mtls_policy=response.get('mtlsPolicy', {}), + create_time=response['createTime'], + update_time=response['updateTime']) + + +@dataclasses.dataclass(frozen=True) +class ClientTlsPolicy: + url: str + name: str + client_certificate: dict + server_validation_ca: list + update_time: str + create_time: str + + @classmethod + def from_response(cls, name: str, response: Dict[str, + Any]) -> 'ClientTlsPolicy': + return cls(name=name, + url=response['name'], + client_certificate=response.get('clientCertificate', {}), + server_validation_ca=response.get('serverValidationCa', []), + create_time=response['createTime'], + update_time=response['updateTime']) + + +class _NetworkSecurityBase(gcp.api.GcpStandardCloudApiResource, + metaclass=abc.ABCMeta): + """Base class for NetworkSecurity APIs.""" def __init__(self, api_manager: gcp.api.GcpApiManager, project: str): super().__init__(api_manager.networksecurity(self.api_version), project) @@ -53,68 +81,76 @@ class NetworkSecurityV1Alpha1(gcp.api.GcpStandardCloudApiResource): def api_name(self) -> str: return 'networksecurity' + def _execute(self, *args, **kwargs): # pylint: disable=signature-differs + # Workaround TD bug: throttled operations are reported as internal. + # Ref b/175345578 + retryer = tenacity.Retrying( + retry=tenacity.retry_if_exception(self._operation_internal_error), + wait=tenacity.wait_fixed(10), + stop=tenacity.stop_after_delay(5 * 60), + before_sleep=tenacity.before_sleep_log(logger, logging.DEBUG), + reraise=True) + retryer(super()._execute, *args, **kwargs) + + @staticmethod + def _operation_internal_error(exception): + return (isinstance(exception, gcp.api.OperationError) and + exception.error.code == code_pb2.INTERNAL) + + +class NetworkSecurityV1Beta1(_NetworkSecurityBase): + """NetworkSecurity API v1beta1.""" + + SERVER_TLS_POLICIES = 'serverTlsPolicies' + CLIENT_TLS_POLICIES = 'clientTlsPolicies' + @property def api_version(self) -> str: - return 'v1alpha1' + return 'v1beta1' - def create_server_tls_policy(self, name, body: dict): - return self._create_resource(self._api_locations.serverTlsPolicies(), - body, - serverTlsPolicyId=name) + def create_server_tls_policy(self, name: str, body: dict) -> GcpResource: + return self._create_resource( + collection=self._api_locations.serverTlsPolicies(), + body=body, + serverTlsPolicyId=name) def get_server_tls_policy(self, name: str) -> ServerTlsPolicy: - result = self._get_resource( + response = self._get_resource( collection=self._api_locations.serverTlsPolicies(), full_name=self.resource_full_name(name, self.SERVER_TLS_POLICIES)) + return ServerTlsPolicy.from_response(name, response) - return self.ServerTlsPolicy(name=name, - url=result['name'], - server_certificate=result.get( - 'serverCertificate', {}), - mtls_policy=result.get('mtlsPolicy', {}), - create_time=result['createTime'], - update_time=result['updateTime']) - - def delete_server_tls_policy(self, name): + def delete_server_tls_policy(self, name: str) -> bool: return self._delete_resource( collection=self._api_locations.serverTlsPolicies(), full_name=self.resource_full_name(name, self.SERVER_TLS_POLICIES)) - def create_client_tls_policy(self, name, body: dict): - return self._create_resource(self._api_locations.clientTlsPolicies(), - body, - clientTlsPolicyId=name) + def create_client_tls_policy(self, name: str, body: dict) -> GcpResource: + return self._create_resource( + collection=self._api_locations.clientTlsPolicies(), + body=body, + clientTlsPolicyId=name) def get_client_tls_policy(self, name: str) -> ClientTlsPolicy: - result = self._get_resource( + response = self._get_resource( collection=self._api_locations.clientTlsPolicies(), full_name=self.resource_full_name(name, self.CLIENT_TLS_POLICIES)) + return ClientTlsPolicy.from_response(name, response) - return self.ClientTlsPolicy( - name=name, - url=result['name'], - client_certificate=result.get('clientCertificate', {}), - server_validation_ca=result.get('serverValidationCa', []), - create_time=result['createTime'], - update_time=result['updateTime']) - - def delete_client_tls_policy(self, name): + def delete_client_tls_policy(self, name: str) -> bool: return self._delete_resource( collection=self._api_locations.clientTlsPolicies(), full_name=self.resource_full_name(name, self.CLIENT_TLS_POLICIES)) - def _execute(self, *args, **kwargs): # pylint: disable=signature-differs - # Workaround TD bug: throttled operations are reported as internal. - # Ref b/175345578 - retryer = tenacity.Retrying( - retry=tenacity.retry_if_exception(self._operation_internal_error), - wait=tenacity.wait_fixed(10), - stop=tenacity.stop_after_delay(5 * 60), - before_sleep=tenacity.before_sleep_log(logger, logging.DEBUG), - reraise=True) - retryer(super()._execute, *args, **kwargs) - @staticmethod - def _operation_internal_error(exception): - return (isinstance(exception, gcp.api.OperationError) and - exception.error.code == code_pb2.INTERNAL) +class NetworkSecurityV1Alpha1(NetworkSecurityV1Beta1): + """NetworkSecurity API v1alpha1. + + Note: extending v1beta1 class presumes that v1beta1 is just a v1alpha1 API + graduated into a more stable version. This is true in most cases. However, + v1alpha1 class can always override and reimplement incompatible methods. + """ + + @property + def api_version(self) -> str: + return 'v1alpha1' diff --git a/tools/run_tests/xds_k8s_test_driver/framework/infrastructure/gcp/network_services.py b/tools/run_tests/xds_k8s_test_driver/framework/infrastructure/gcp/network_services.py index c209fa21e5e..59cd5655d3d 100644 --- a/tools/run_tests/xds_k8s_test_driver/framework/infrastructure/gcp/network_services.py +++ b/tools/run_tests/xds_k8s_test_driver/framework/infrastructure/gcp/network_services.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import abc import dataclasses import logging from typing import Any, Dict, List, Optional, Tuple @@ -23,8 +24,8 @@ from framework.infrastructure import gcp logger = logging.getLogger(__name__) -_ComputeV1 = gcp.compute.ComputeV1 -GcpResource = _ComputeV1.GcpResource +# Type aliases +GcpResource = gcp.compute.ComputeV1.GcpResource @dataclasses.dataclass(frozen=True) @@ -32,12 +33,25 @@ class EndpointPolicy: url: str name: str type: str - server_tls_policy: Optional[str] traffic_port_selector: dict endpoint_matcher: dict - http_filters: dict update_time: str create_time: str + http_filters: Optional[dict] = None + server_tls_policy: Optional[str] = None + + @classmethod + def from_response(cls, name: str, response: Dict[str, + Any]) -> 'EndpointPolicy': + return cls(name=name, + url=response['name'], + type=response['type'], + server_tls_policy=response.get('serverTlsPolicy', None), + traffic_port_selector=response['trafficPortSelector'], + endpoint_matcher=response['endpointMatcher'], + http_filters=response.get('httpFilters', None), + update_time=response['updateTime'], + create_time=response['createTime']) @dataclasses.dataclass(frozen=True) @@ -161,10 +175,9 @@ class GrpcRoute: ) -class NetworkServicesV1Alpha1(gcp.api.GcpStandardCloudApiResource): - ENDPOINT_POLICIES = 'endpointPolicies' - GRPC_ROUTES = 'grpcRoutes' - ROUTERS = 'routers' +class _NetworkServicesBase(gcp.api.GcpStandardCloudApiResource, + metaclass=abc.ABCMeta): + """Base class for NetworkServices APIs.""" def __init__(self, api_manager: gcp.api.GcpApiManager, project: str): super().__init__(api_manager.networkservices(self.api_version), project) @@ -175,9 +188,30 @@ class NetworkServicesV1Alpha1(gcp.api.GcpStandardCloudApiResource): def api_name(self) -> str: return 'networkservices' + def _execute(self, *args, **kwargs): # pylint: disable=signature-differs + # Workaround TD bug: throttled operations are reported as internal. + # Ref b/175345578 + retryer = tenacity.Retrying( + retry=tenacity.retry_if_exception(self._operation_internal_error), + wait=tenacity.wait_fixed(10), + stop=tenacity.stop_after_delay(5 * 60), + before_sleep=tenacity.before_sleep_log(logger, logging.DEBUG), + reraise=True) + retryer(super()._execute, *args, **kwargs) + + @staticmethod + def _operation_internal_error(exception): + return (isinstance(exception, gcp.api.OperationError) and + exception.error.code == code_pb2.INTERNAL) + + +class NetworkServicesV1Beta1(_NetworkServicesBase): + """NetworkServices API v1beta1.""" + ENDPOINT_POLICIES = 'endpointPolicies' + @property def api_version(self) -> str: - return 'v1alpha1' + return 'v1beta1' def create_endpoint_policy(self, name, body: dict) -> GcpResource: return self._create_resource( @@ -186,47 +220,41 @@ class NetworkServicesV1Alpha1(gcp.api.GcpStandardCloudApiResource): endpointPolicyId=name) def get_endpoint_policy(self, name: str) -> EndpointPolicy: - result = self._get_resource( + response = self._get_resource( collection=self._api_locations.endpointPolicies(), full_name=self.resource_full_name(name, self.ENDPOINT_POLICIES)) - return EndpointPolicy( - name=name, - url=result['name'], - type=result['type'], - server_tls_policy=result.get('serverTlsPolicy', None), - traffic_port_selector=result['trafficPortSelector'], - endpoint_matcher=result['endpointMatcher'], - http_filters=result['httpFilters'], - update_time=result['updateTime'], - create_time=result['createTime']) - - def delete_endpoint_policy(self, name): + return EndpointPolicy.from_response(name, response) + + def delete_endpoint_policy(self, name: str) -> bool: return self._delete_resource( collection=self._api_locations.endpointPolicies(), full_name=self.resource_full_name(name, self.ENDPOINT_POLICIES)) - def _execute(self, *args, **kwargs): # pylint: disable=signature-differs - # Workaround TD bug: throttled operations are reported as internal. - # Ref b/175345578 - retryer = tenacity.Retrying( - retry=tenacity.retry_if_exception(self._operation_internal_error), - wait=tenacity.wait_fixed(10), - stop=tenacity.stop_after_delay(5 * 60), - before_sleep=tenacity.before_sleep_log(logger, logging.DEBUG), - reraise=True) - retryer(super()._execute, *args, **kwargs) + +class NetworkServicesV1Alpha1(NetworkServicesV1Beta1): + """NetworkServices API v1alpha1. + + Note: extending v1beta1 class presumes that v1beta1 is just a v1alpha1 API + graduated into a more stable version. This is true in most cases. However, + v1alpha1 class can always override and reimplement incompatible methods. + """ + + GRPC_ROUTES = 'grpcRoutes' + ROUTERS = 'routers' + + @property + def api_version(self) -> str: + return 'v1alpha1' def create_router(self, name: str, body: dict) -> GcpResource: - return self._create_resource( - self._api_locations.routers(), - body, - routerId=name, - ) + return self._create_resource(collection=self._api_locations.routers(), + body=body, + routerId=name) def get_router(self, name: str) -> Router: + full_name = self.resource_full_name(name, self.ROUTERS) result = self._get_resource(collection=self._api_locations.routers(), - full_name=self.resource_full_name( - name, self.ROUTERS)) + full_name=full_name) return Router.from_response(name, result) def delete_router(self, name: str) -> bool: @@ -235,22 +263,18 @@ class NetworkServicesV1Alpha1(gcp.api.GcpStandardCloudApiResource): name, self.ROUTERS)) def create_grpc_route(self, name: str, body: dict) -> GcpResource: - return self._create_resource(self._api_locations.grpcRoutes(), - body, - grpcRouteId=name) + return self._create_resource( + collection=self._api_locations.grpcRoutes(), + body=body, + grpcRouteId=name) def get_grpc_route(self, name: str) -> GrpcRoute: + full_name = self.resource_full_name(name, self.GRPC_ROUTES) result = self._get_resource(collection=self._api_locations.grpcRoutes(), - full_name=self.resource_full_name( - name, self.GRPC_ROUTES)) + full_name=full_name) return GrpcRoute.from_response(name, result) def delete_grpc_route(self, name: str) -> bool: return self._delete_resource( collection=self._api_locations.grpcRoutes(), full_name=self.resource_full_name(name, self.GRPC_ROUTES)) - - @staticmethod - def _operation_internal_error(exception): - return (isinstance(exception, gcp.api.OperationError) and - exception.error.code == code_pb2.INTERNAL) diff --git a/tools/run_tests/xds_k8s_test_driver/framework/infrastructure/traffic_director.py b/tools/run_tests/xds_k8s_test_driver/framework/infrastructure/traffic_director.py index 2401e9e958f..9de4e2852c0 100644 --- a/tools/run_tests/xds_k8s_test_driver/framework/infrastructure/traffic_director.py +++ b/tools/run_tests/xds_k8s_test_driver/framework/infrastructure/traffic_director.py @@ -14,7 +14,7 @@ import functools import logging import random -from typing import Any, Dict, Iterable, List, Optional, Set +from typing import Any, Dict, List, Optional, Set from framework import xds_flags from framework.infrastructure import gcp @@ -32,12 +32,13 @@ _BackendGRPC = BackendServiceProtocol.GRPC _HealthCheckGRPC = HealthCheckProtocol.GRPC # Network Security -_NetworkSecurityV1Alpha1 = gcp.network_security.NetworkSecurityV1Alpha1 -ServerTlsPolicy = _NetworkSecurityV1Alpha1.ServerTlsPolicy -ClientTlsPolicy = _NetworkSecurityV1Alpha1.ClientTlsPolicy +_NetworkSecurityV1Beta1 = gcp.network_security.NetworkSecurityV1Beta1 +ServerTlsPolicy = gcp.network_security.ServerTlsPolicy +ClientTlsPolicy = gcp.network_security.ClientTlsPolicy # Network Services _NetworkServicesV1Alpha1 = gcp.network_services.NetworkServicesV1Alpha1 +_NetworkServicesV1Beta1 = gcp.network_services.NetworkServicesV1Beta1 EndpointPolicy = gcp.network_services.EndpointPolicy # Testing metadata consts @@ -537,6 +538,8 @@ class TrafficDirectorAppNetManager(TrafficDirectorManager): GRPC_ROUTE_NAME = "grpc-route" ROUTER_NAME = "router" + netsvc: _NetworkServicesV1Alpha1 + def __init__(self, gcp_api_manager: gcp.api.GcpApiManager, project: str, @@ -627,12 +630,14 @@ class TrafficDirectorAppNetManager(TrafficDirectorManager): class TrafficDirectorSecureManager(TrafficDirectorManager): - netsec: Optional[_NetworkSecurityV1Alpha1] SERVER_TLS_POLICY_NAME = "server-tls-policy" CLIENT_TLS_POLICY_NAME = "client-tls-policy" ENDPOINT_POLICY = "endpoint-policy" CERTIFICATE_PROVIDER_INSTANCE = "google_cloud_private_spiffe" + netsec: _NetworkSecurityV1Beta1 + netsvc: _NetworkServicesV1Beta1 + def __init__( self, gcp_api_manager: gcp.api.GcpApiManager, @@ -649,8 +654,8 @@ class TrafficDirectorSecureManager(TrafficDirectorManager): network=network) # API - self.netsec = _NetworkSecurityV1Alpha1(gcp_api_manager, project) - self.netsvc = _NetworkServicesV1Alpha1(gcp_api_manager, project) + self.netsec = _NetworkSecurityV1Beta1(gcp_api_manager, project) + self.netsvc = _NetworkServicesV1Beta1(gcp_api_manager, project) # Managed resources self.server_tls_policy: Optional[ServerTlsPolicy] = None @@ -734,7 +739,6 @@ class TrafficDirectorSecureManager(TrafficDirectorManager): } config = { "type": "GRPC_SERVER", - "httpFilters": {}, "trafficPortSelector": port_selector, "endpointMatcher": { "metadataLabelMatcher": label_matcher_all,