Skip to content

Commit

Permalink
OpenAI Mock Backend (newrelic#929)
Browse files Browse the repository at this point in the history
* Add mock external openai server

* Add mocked OpenAI server fixtures

* Set up recorded responses.

* Clean mock server to depend on http server

* Linting

* Pin flask version for flask restx tests. (newrelic#931)

* Ignore new redis methods. (newrelic#932)

Co-authored-by: Lalleh Rafeei <84813886+lrafeei@users.noreply.github.com>

* Remove approved paths

* Update CI Image (newrelic#930)

* Update available python versions in CI

* Update makefile with overrides

* Fix default branch detection for arm builds

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>

* Add mocking for embedding endpoint

* [Mega-Linter] Apply linters fixes

* Add ratelimit headers

* [Mega-Linter] Apply linters fixes

* Only get package version once (newrelic#928)

* Only get package version once

* Add disconnect method

* Add disconnect method

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>

* Add datalib dependency for embedding testing.

* Add OpenAI Test Infrastructure (newrelic#926)

* Add openai to tox

* Add OpenAI test files.

* Add test functions.

* [Mega-Linter] Apply linters fixes

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <mergify[bot]@users.noreply.github.com>

* Add mock external openai server

* Add mocked OpenAI server fixtures

* Set up recorded responses.

* Clean mock server to depend on http server

* Linting

* Remove approved paths

* Add mocking for embedding endpoint

* [Mega-Linter] Apply linters fixes

* Add ratelimit headers

* [Mega-Linter] Apply linters fixes

* Add datalib dependency for embedding testing.

---------

Co-authored-by: Uma Annamalai <uannamalai@newrelic.com>
Co-authored-by: Lalleh Rafeei <84813886+lrafeei@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: TimPansino <TimPansino@users.noreply.github.com>
Co-authored-by: Hannah Stepanek <hstepanek@newrelic.com>
Co-authored-by: mergify[bot] <mergify[bot]@users.noreply.github.com>
  • Loading branch information
7 people authored Oct 10, 2023
1 parent db63d45 commit 2834663
Show file tree
Hide file tree
Showing 11 changed files with 367 additions and 161 deletions.
2 changes: 1 addition & 1 deletion .github/containers/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ RUN echo 'eval "$(pyenv init -)"' >>$HOME/.bashrc && \
pyenv update

# Install Python
ARG PYTHON_VERSIONS="3.10 3.9 3.8 3.7 3.11 2.7 pypy2.7-7.3.12 pypy3.8-7.3.11"
ARG PYTHON_VERSIONS="3.11 3.10 3.9 3.8 3.7 3.12 2.7 pypy2.7-7.3.12 pypy3.8-7.3.11"
COPY --chown=1000:1000 --chmod=+x ./install-python.sh /tmp/install-python.sh
RUN /tmp/install-python.sh && \
rm /tmp/install-python.sh
Expand Down
61 changes: 42 additions & 19 deletions .github/containers/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,37 +12,60 @@
# See the License for the specific language governing permissions and
# limitations under the License.

# Repository root for mounting into container.
MAKEFILE_DIR:=$(dir $(realpath $(firstword $(MAKEFILE_LIST))))
REPO_ROOT:=$(realpath $(MAKEFILE_DIR)../../)
# Override constants
PLATFORM_OVERRIDE:=
PYTHON_VERSIONS_OVERRIDE:=

# Computed variables
IMAGE_NAME:=ghcr.io/newrelic/newrelic-python-agent-ci
MAKEFILE_DIR:=$(dir $(realpath $(firstword ${MAKEFILE_LIST})))
REPO_ROOT:=$(realpath ${MAKEFILE_DIR}../../)
UNAME_P:=$(shell uname -p)
PLATFORM_AUTOMATIC:=$(if $(findstring arm,${UNAME_P}),linux/arm64,linux/amd64)
PLATFORM:=$(if ${PLATFORM_OVERRIDE},${PLATFORM_OVERRIDE},${PLATFORM_AUTOMATIC})
PYTHON_VERSIONS_AUTOMATIC:=3.10 2.7
PYTHON_VERSIONS:=$(if ${PYTHON_VERSIONS_OVERRIDE},${PYTHON_VERSIONS_OVERRIDE},${PYTHON_VERSIONS_AUTOMATIC})

.PHONY: default
default: test

# Perform a shortened build for testing
.PHONY: build
build:
@docker build $(MAKEFILE_DIR) \
-t ghcr.io/newrelic/newrelic-python-agent-ci:local \
--build-arg='PYTHON_VERSIONS=3.10 2.7'

# Ensure python versions are usable
.PHONY: test
test: build
@docker run --rm ghcr.io/newrelic/python-agent-ci:local /bin/bash -c '\
python3.10 --version && \
python2.7 --version && \
touch tox.ini && tox --version && \
echo "Success! Python versions installed."'
@docker build ${MAKEFILE_DIR} \
--platform=${PLATFORM} \
-t ${IMAGE_NAME}:local \
--build-arg='PYTHON_VERSIONS=${PYTHON_VERSIONS}'

# Run the local tag as a container.
.PHONY: run
run: build
run: run.local

# Run a specific tag as a container.
# Usage: make run.<tag>
# Defaults to run.local, but can instead be run.latest or any other tag.
.PHONY: run.%
run.%:
# Build image if local was specified, else pull latest
@if [[ "$*" = "local" ]]; then cd ${MAKEFILE_DIR} && $(MAKE) build; else docker pull ${IMAGE_NAME}:$*; fi
@docker run --rm -it \
--mount type=bind,source="$(REPO_ROOT)",target=/home/github/python-agent \
--platform=${PLATFORM} \
--mount type=bind,source="${REPO_ROOT}",target=/home/github/python-agent \
--workdir=/home/github/python-agent \
--add-host=host.docker.internal:host-gateway \
-e NEW_RELIC_HOST="${NEW_RELIC_HOST}" \
-e NEW_RELIC_LICENSE_KEY="${NEW_RELIC_LICENSE_KEY}" \
-e NEW_RELIC_DEVELOPER_MODE="${NEW_RELIC_DEVELOPER_MODE}" \
-e GITHUB_ACTIONS="true" \
ghcr.io/newrelic/newrelic-python-agent-ci:local /bin/bash
${IMAGE_NAME}:$* /bin/bash

# Ensure python versions are usable. Cannot be automatically used with PYTHON_VERSIONS_OVERRIDE.
.PHONY: test
test: build
@docker run --rm \
--platform=${PLATFORM} \
ghcr.io/newrelic/python-agent-ci:local \
/bin/bash -c '\
python3.10 --version && \
python2.7 --version && \
touch tox.ini && tox --version && \
echo "Success! Python versions installed."'
2 changes: 1 addition & 1 deletion .github/workflows/build-ci-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,6 @@ jobs:
with:
push: ${{ github.event_name != 'pull_request' }}
context: .github/containers
platforms: ${{ (github.ref == 'refs/head/main') && 'linux/amd64,linux/arm64' || 'linux/amd64' }}
platforms: ${{ (format('refs/heads/{0}', github.event.repository.default_branch) == github.ref) && 'linux/amd64,linux/arm64' || 'linux/amd64' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
8 changes: 5 additions & 3 deletions newrelic/hooks/datastore_aioredis.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
_redis_operation_re,
)

AIOREDIS_VERSION = get_package_version_tuple("aioredis")


def _conn_attrs_to_dict(connection):
host = getattr(connection, "host", None)
Expand Down Expand Up @@ -58,14 +60,13 @@ def _nr_wrapper_AioRedis_method_(wrapped, instance, args, kwargs):
# Check for transaction and return early if found.
# Method will return synchronously without executing,
# it will be added to the command stack and run later.
aioredis_version = get_package_version_tuple("aioredis")

# This conditional is for versions of aioredis that are outside
# New Relic's supportability window but will still work. New
# Relic does not provide testing/support for this. In order to
# keep functionality without affecting coverage metrics, this
# segment is excluded from coverage analysis.
if aioredis_version and aioredis_version < (2,): # pragma: no cover
if AIOREDIS_VERSION and AIOREDIS_VERSION < (2,): # pragma: no cover
# AioRedis v1 uses a RedisBuffer instead of a real connection for queueing up pipeline commands
from aioredis.commands.transaction import _RedisBuffer

Expand All @@ -75,7 +76,7 @@ def _nr_wrapper_AioRedis_method_(wrapped, instance, args, kwargs):
return wrapped(*args, **kwargs)
else:
# AioRedis v2 uses a Pipeline object for a client and internally queues up pipeline commands
if aioredis_version:
if AIOREDIS_VERSION:
from aioredis.client import Pipeline
if isinstance(instance, Pipeline):
return wrapped(*args, **kwargs)
Expand Down Expand Up @@ -139,6 +140,7 @@ async def wrap_Connection_send_command(wrapped, instance, args, kwargs):
):
return await wrapped(*args, **kwargs)


# This wrapper is for versions of aioredis that are outside
# New Relic's supportability window but will still work. New
# Relic does not provide testing/support for this. In order to
Expand Down
114 changes: 60 additions & 54 deletions tests/datastore_redis/test_custom_conn_pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,22 @@
# See the License for the specific language governing permissions and
# limitations under the License.

''' The purpose of these tests is to confirm that using a non-standard
""" The purpose of these tests is to confirm that using a non-standard
connection pool that does not have a `connection_kwargs` attribute
will not result in an error.
'''
"""

import pytest
import redis

from newrelic.api.background_task import background_task
from newrelic.common.package_version_utils import get_package_version_tuple

from testing_support.fixtures import override_application_settings
from testing_support.validators.validate_transaction_metrics import validate_transaction_metrics
from testing_support.db_settings import redis_settings
from testing_support.fixtures import override_application_settings
from testing_support.util import instance_hostname
from testing_support.validators.validate_transaction_metrics import (
validate_transaction_metrics,
)

from newrelic.api.background_task import background_task
from newrelic.common.package_version_utils import get_package_version_tuple

DB_SETTINGS = redis_settings()[0]
REDIS_PY_VERSION = get_package_version_tuple("redis")
Expand All @@ -45,13 +45,17 @@ def get_connection(self, name, *keys, **options):
def release(self, connection):
self.connection.disconnect()

def disconnect(self):
self.connection.disconnect()


# Settings

_enable_instance_settings = {
'datastore_tracer.instance_reporting.enabled': True,
"datastore_tracer.instance_reporting.enabled": True,
}
_disable_instance_settings = {
'datastore_tracer.instance_reporting.enabled': False,
"datastore_tracer.instance_reporting.enabled": False,
}

# Metrics
Expand All @@ -61,98 +65,100 @@ def release(self, connection):
datastore_all_metric_count = 5 if REDIS_PY_VERSION >= (5, 0) else 3

_base_scoped_metrics = [
('Datastore/operation/Redis/get', 1),
('Datastore/operation/Redis/set', 1),
('Datastore/operation/Redis/client_list', 1),
("Datastore/operation/Redis/get", 1),
("Datastore/operation/Redis/set", 1),
("Datastore/operation/Redis/client_list", 1),
]
# client_setinfo was introduced in v5.0.0 and assigns info displayed in client_list output
if REDIS_PY_VERSION >= (5, 0):
_base_scoped_metrics.append(('Datastore/operation/Redis/client_setinfo', 2),)
_base_scoped_metrics.append(
("Datastore/operation/Redis/client_setinfo", 2),
)

_base_rollup_metrics = [
('Datastore/all', datastore_all_metric_count),
('Datastore/allOther', datastore_all_metric_count),
('Datastore/Redis/all', datastore_all_metric_count),
('Datastore/Redis/allOther', datastore_all_metric_count),
('Datastore/operation/Redis/get', 1),
('Datastore/operation/Redis/set', 1),
('Datastore/operation/Redis/client_list', 1),
("Datastore/all", datastore_all_metric_count),
("Datastore/allOther", datastore_all_metric_count),
("Datastore/Redis/all", datastore_all_metric_count),
("Datastore/Redis/allOther", datastore_all_metric_count),
("Datastore/operation/Redis/get", 1),
("Datastore/operation/Redis/set", 1),
("Datastore/operation/Redis/client_list", 1),
]
if REDIS_PY_VERSION >= (5, 0):
_base_rollup_metrics.append(('Datastore/operation/Redis/client_setinfo', 2),)
_base_rollup_metrics.append(
("Datastore/operation/Redis/client_setinfo", 2),
)

_host = instance_hostname(DB_SETTINGS['host'])
_port = DB_SETTINGS['port']
_host = instance_hostname(DB_SETTINGS["host"])
_port = DB_SETTINGS["port"]

_instance_metric_name = 'Datastore/instance/Redis/%s/%s' % (_host, _port)
_instance_metric_name = "Datastore/instance/Redis/%s/%s" % (_host, _port)

instance_metric_count = 5 if REDIS_PY_VERSION >= (5, 0) else 3

_enable_rollup_metrics = _base_rollup_metrics.append(
(_instance_metric_name, instance_metric_count)
)
_enable_rollup_metrics = _base_rollup_metrics.append((_instance_metric_name, instance_metric_count))

_disable_rollup_metrics = _base_rollup_metrics.append(
(_instance_metric_name, None)
)
_disable_rollup_metrics = _base_rollup_metrics.append((_instance_metric_name, None))

# Operations


def exercise_redis(client):
client.set('key', 'value')
client.get('key')
client.execute_command('CLIENT', 'LIST', parse='LIST')
client.set("key", "value")
client.get("key")
client.execute_command("CLIENT", "LIST", parse="LIST")


# Tests

@pytest.mark.skipif(REDIS_PY_VERSION < (2, 7),
reason='Client list command introduced in 2.7')

@pytest.mark.skipif(REDIS_PY_VERSION < (2, 7), reason="Client list command introduced in 2.7")
@override_application_settings(_enable_instance_settings)
@validate_transaction_metrics(
'test_custom_conn_pool:test_fake_conn_pool_enable_instance',
scoped_metrics=_base_scoped_metrics,
rollup_metrics=_enable_rollup_metrics,
background_task=True)
"test_custom_conn_pool:test_fake_conn_pool_enable_instance",
scoped_metrics=_base_scoped_metrics,
rollup_metrics=_enable_rollup_metrics,
background_task=True,
)
@background_task()
def test_fake_conn_pool_enable_instance():
client = redis.StrictRedis(host=DB_SETTINGS['host'],
port=DB_SETTINGS['port'], db=0)
client = redis.StrictRedis(host=DB_SETTINGS["host"], port=DB_SETTINGS["port"], db=0)

# Get a real connection

conn = client.connection_pool.get_connection('GET')
conn = client.connection_pool.get_connection("GET")

# Replace the original connection pool with one that doesn't
# have the `connection_kwargs` attribute.

fake_pool = FakeConnectionPool(conn)
client.connection_pool = fake_pool
assert not hasattr(client.connection_pool, 'connection_kwargs')
assert not hasattr(client.connection_pool, "connection_kwargs")

exercise_redis(client)

@pytest.mark.skipif(REDIS_PY_VERSION < (2, 7),
reason='Client list command introduced in 2.7')

@pytest.mark.skipif(REDIS_PY_VERSION < (2, 7), reason="Client list command introduced in 2.7")
@override_application_settings(_disable_instance_settings)
@validate_transaction_metrics(
'test_custom_conn_pool:test_fake_conn_pool_disable_instance',
scoped_metrics=_base_scoped_metrics,
rollup_metrics=_disable_rollup_metrics,
background_task=True)
"test_custom_conn_pool:test_fake_conn_pool_disable_instance",
scoped_metrics=_base_scoped_metrics,
rollup_metrics=_disable_rollup_metrics,
background_task=True,
)
@background_task()
def test_fake_conn_pool_disable_instance():
client = redis.StrictRedis(host=DB_SETTINGS['host'],
port=DB_SETTINGS['port'], db=0)
client = redis.StrictRedis(host=DB_SETTINGS["host"], port=DB_SETTINGS["port"], db=0)

# Get a real connection

conn = client.connection_pool.get_connection('GET')
conn = client.connection_pool.get_connection("GET")

# Replace the original connection pool with one that doesn't
# have the `connection_kwargs` attribute.

fake_pool = FakeConnectionPool(conn)
client.connection_pool = fake_pool
assert not hasattr(client.connection_pool, 'connection_kwargs')
assert not hasattr(client.connection_pool, "connection_kwargs")

exercise_redis(client)
2 changes: 2 additions & 0 deletions tests/datastore_redis/test_uninstrumented_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"append_no_scale",
"append_values_and_weights",
"append_weights",
"auto_close_connection_pool",
"batch_indexer",
"BatchIndexer",
"bulk",
Expand All @@ -55,6 +56,7 @@
"edges",
"execute_command",
"flush",
"from_pool",
"from_url",
"get_connection_kwargs",
"get_encoder",
Expand Down
Loading

0 comments on commit 2834663

Please sign in to comment.