Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SNOW-921045 Add wiremock tests support #2170

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0e15481
basic wiremock test
sfc-gh-mkubik Feb 10, 2025
25136a4
remove print statements, fail with runtime error when wiremock connec…
sfc-gh-mkubik Feb 10, 2025
6e9f7b1
change log format
sfc-gh-mkubik Feb 10, 2025
f83d412
add debug catch for wiremock not starting properly in some environments
sfc-gh-mkubik Feb 10, 2025
6187a42
Optimize import time: Directly lookup target distributions instead of…
sfc-gh-rebeling Feb 10, 2025
978ef2e
Merge branch 'main' into SNOW-921045-add-wiremock-support
sfc-gh-mkubik Feb 10, 2025
9d92d93
uncomment subprocess arguments, catch import error in olddrivers
sfc-gh-mkubik Feb 10, 2025
68a2e6c
increase wiremock timeout, change healthcheck endpoint from mappings …
sfc-gh-mkubik Feb 10, 2025
f49bb9a
add ca-cert, add wiremock to FIPS tests, check for status
sfc-gh-mkubik Feb 11, 2025
e238169
remove mkdir from build_test action as it's now included in the repos…
sfc-gh-mkubik Feb 11, 2025
22e5b28
increase timeout
sfc-gh-mkubik Feb 11, 2025
19c2662
Merge remote-tracking branch 'origin' into SNOW-921045-add-wiremock-s…
sfc-gh-mkubik Feb 11, 2025
b9836e5
enable wiremock in Docker containers
sfc-gh-mkubik Feb 11, 2025
1e0f7bb
create wiremock dir in fips builds
sfc-gh-mkubik Feb 12, 2025
ee16b8b
move FIPS wiremock configuration to docker related configs
sfc-gh-mkubik Feb 12, 2025
8e79c39
remove yum install from dockerfile
sfc-gh-mkubik Feb 12, 2025
e0aca9c
increase wiremock timeout for FIPS container, install java in test do…
sfc-gh-mkubik Feb 13, 2025
0cebb42
skip wiremock tests on jenkins, change java package for FIPS GH action
sfc-gh-mkubik Feb 13, 2025
c4ea277
simplify test case, remove wiremock fetching from test linux and oldd…
sfc-gh-mkubik Feb 13, 2025
af61ce2
handle import error in test file
sfc-gh-mkubik Feb 13, 2025
925d1fb
handle RUNNING_ON_JENKINS for olddriver
sfc-gh-mkubik Feb 13, 2025
835104a
olddriver handling
sfc-gh-mkubik Feb 13, 2025
d5575de
Merge branch 'main' into SNOW-921045-add-wiremock-support
sfc-gh-mkubik Feb 17, 2025
8b9d348
Merge branch 'main' into SNOW-921045-add-wiremock-support
sfc-gh-mkubik Feb 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/build_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,15 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Display Python version
run: python -c "import sys; print(sys.version)"
- name: Set up Java
uses: actions/setup-java@v4 # for wiremock
with:
java-version: 11
distribution: 'temurin'
java-package: 'jre'
- name: Fetch Wiremock
shell: bash
run: curl https://repo1.maven.org/maven2/org/wiremock/wiremock-standalone/3.11.0/wiremock-standalone-3.11.0.jar --output .wiremock/wiremock-standalone.jar
- name: Setup parameters file
shell: bash
env:
Expand Down
Binary file added .wiremock/ca-cert.jks
Binary file not shown.
1 change: 1 addition & 0 deletions DESCRIPTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Source code is also available at: https://github.com/snowflakedb/snowflake-conne
- Fixed a bug where privatelink OCSP Cache url could not be determined if privatelink account name was specified in uppercase.
- Added support for iceberg tables to `write_pandas`.
- Fixed base64 encoded private key tests.
- Added Wiremock tests.

- v3.13.2(January 29, 2025)
- Changed not to use scoped temporary objects.
Expand Down
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ exclude license_header.txt
exclude tox.ini
exclude mypy.ini
exclude .clang-format
exclude .wiremock/*

prune ci
prune benchmark
Expand Down
1 change: 1 addition & 0 deletions ci/docker/connector_test_fips/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ RUN sed -i s/mirror.centos.org/vault.centos.org/g /etc/yum.repos.d/*.repo && \
RUN yum clean all && \
yum install -y redhat-rpm-config gcc libffi-devel openssl openssl-devel && \
yum install -y python38 python38-devel && \
yum install -y java-11-openjdk && \
yum clean all && \
rm -rf /var/cache/yum
RUN python3 -m pip install --user --upgrade pip setuptools wheel
3 changes: 3 additions & 0 deletions ci/test_fips.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ THIS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
CONNECTOR_DIR="$( dirname "${THIS_DIR}")"
CONNECTOR_WHL="$(ls $CONNECTOR_DIR/dist/*cp38*manylinux2014*.whl | sort -r | head -n 1)"

# fetch wiremock
curl https://repo1.maven.org/maven2/org/wiremock/wiremock-standalone/3.11.0/wiremock-standalone-3.11.0.jar --output "${CONNECTOR_DIR}/.wiremock/wiremock-standalone.jar"

python3.8 -m venv fips_env
source fips_env/bin/activate
pip install -U setuptools pip
Expand Down
52 changes: 52 additions & 0 deletions test/unit/test_wiremock_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#
# Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved.
#

from typing import Any, Generator, Union

import pytest

# old driver support
try:
from snowflake.connector.vendored import requests
from src.snowflake.connector.test_util import RUNNING_ON_JENKINS
except ImportError:
import os

import requests

RUNNING_ON_JENKINS = os.getenv("JENKINS_HOME") is not None


from ..wiremock.wiremock_utils import WiremockClient


@pytest.fixture(scope="session")
def wiremock_client() -> Generator[Union[WiremockClient, Any], Any, None]:
with WiremockClient() as client:
yield client


@pytest.mark.skipif(RUNNING_ON_JENKINS, reason="jenkins doesn't support wiremock tests")
def test_wiremock(wiremock_client):
connection_reset_by_peer_mapping = {
"mappings": [
{
"scenarioName": "Basic example",
"requiredScenarioState": "Started",
"request": {"method": "GET", "url": "/endpoint"},
"response": {"status": 200},
}
],
"importOptions": {"duplicatePolicy": "IGNORE", "deleteAllNotInImport": True},
}
wiremock_client.import_mapping(connection_reset_by_peer_mapping)

response = requests.get(
f"http://{wiremock_client.wiremock_host}:{wiremock_client.wiremock_http_port}/endpoint"
)

assert response is not None, "response is None"
assert (
response.status_code == requests.codes.ok
), f"response status is not 200, received status {response.status_code}"
3 changes: 3 additions & 0 deletions test/wiremock/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#
# Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved.
#
152 changes: 152 additions & 0 deletions test/wiremock/wiremock_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
#
# Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved.
#

import json
import logging
import os.path
import pathlib
import socket
import subprocess
from time import sleep
from typing import Optional

try:
from snowflake.connector.vendored import requests
except ImportError:
import requests

WIREMOCK_START_MAX_RETRY_COUNT = 12
LOGGER = logging.getLogger(__name__)


def _get_mapping_str(mapping):
if isinstance(mapping, str):
return mapping
if isinstance(mapping, dict):
return json.dumps(mapping)
if os.path.isfile(str(mapping)):
with open(mapping) as f:
return f.read()

raise RuntimeError(f"Mapping {mapping} is of an invalid type")


class WiremockClient:
def __init__(self):
self.wiremock_filename = "wiremock-standalone.jar"
self.wiremock_host = "localhost"
self.wiremock_http_port = None
self.wiremock_https_port = None

self.wiremock_dir = pathlib.Path(__file__).parent.parent.parent / ".wiremock"
assert self.wiremock_dir.exists(), f"{self.wiremock_dir} does not exist"

self.wiremock_jar_path = self.wiremock_dir / self.wiremock_filename
assert (
self.wiremock_jar_path.exists()
), f"{self.wiremock_jar_path} does not exist"

def _start_wiremock(self):
self.wiremock_http_port = self._find_free_port()
self.wiremock_https_port = self._find_free_port()
self.wiremock_process = subprocess.Popen(
[
"java",
"-jar",
self.wiremock_jar_path,
"--root-dir",
self.wiremock_dir,
"--enable-browser-proxying", # work as forward proxy
"--proxy-pass-through",
"false", # pass through only matched requests
"--port",
str(self.wiremock_http_port),
"--https-port",
str(self.wiremock_https_port),
"--https-keystore",
self.wiremock_dir / "ca-cert.jks",
"--ca-keystore",
self.wiremock_dir / "ca-cert.jks",
]
)
self._wait_for_wiremock()

def _stop_wiremock(self):
self.wiremock_process.kill()

def _wait_for_wiremock(self):
retry_count = 0
while retry_count < WIREMOCK_START_MAX_RETRY_COUNT:
if self._health_check():
return
retry_count += 1
sleep(1)

raise TimeoutError(
f"WiremockClient did not respond within {WIREMOCK_START_MAX_RETRY_COUNT} seconds"
)

def _health_check(self):
mappings_endpoint = (
f"http://{self.wiremock_host}:{self.wiremock_http_port}/__admin/health"
)
try:
response = requests.get(mappings_endpoint)
except requests.exceptions.RequestException as e:
LOGGER.warning(f"Wiremock healthcheck failed with exception: {e}")
return False
if (
response.status_code == requests.codes.ok
and response.json()["status"] == "healthy"
):
LOGGER.debug(f"Wiremock healthcheck failed with response: {response}")
else:
LOGGER.warning(
f"Wiremock healthcheck failed with status code: {response.status_code}"
)
return False
return True

def _reset_wiremock(self):
reset_endpoint = (
f"http://{self.wiremock_host}:{self.wiremock_http_port}/__admin/reset"
)
response = self._wiremock_post(reset_endpoint)
if response.status_code != requests.codes.ok:
raise RuntimeError("Failed to reset WiremockClient")

def _wiremock_post(
self, endpoint: str, body: Optional[str] = None
) -> requests.Response:
headers = {"Accept": "application/json", "Content-Type": "application/json"}
return requests.post(endpoint, data=body, headers=headers)

def import_mapping(self, mapping):
self._reset_wiremock()
import_mapping_endpoint = f"http://{self.wiremock_host}:{self.wiremock_http_port}/__admin/mappings/import"
mapping_str = _get_mapping_str(mapping)
response = self._wiremock_post(import_mapping_endpoint, mapping_str)
if response.status_code != requests.codes.ok:
raise RuntimeError("Failed to import mapping")

def add_mapping(self, mapping):
add_mapping_endpoint = (
f"http://{self.wiremock_host}:{self.wiremock_http_port}/__admin/mappings"
)
mapping_str = _get_mapping_str(mapping)
response = self._wiremock_post(add_mapping_endpoint, mapping_str)
if response.status_code != requests.codes.created:
raise RuntimeError("Failed to add mapping")

def _find_free_port(self) -> int:
with socket.socket() as sock:
sock.bind((self.wiremock_host, 0))
return sock.getsockname()[1]

def __enter__(self):
self._start_wiremock()
return self

def __exit__(self, exc_type, exc_val, exc_tb):
self._stop_wiremock()
Loading