|
13 | 13 | # limitations under the License. |
14 | 14 | import logging |
15 | 15 | import os |
| 16 | +import random |
| 17 | +import string |
16 | 18 | import time |
17 | 19 | from datetime import timedelta, timezone |
18 | 20 | from tempfile import mkstemp |
|
22 | 24 | import pandas as pd |
23 | 25 | import pytest |
24 | 26 | from pytest_lazyfixture import lazy_fixture |
25 | | -from testcontainers.core.container import DockerContainer |
26 | | -from testcontainers.core.waiting_utils import wait_for_logs |
27 | | -from testcontainers.minio import MinioContainer |
28 | 27 | from testcontainers.mysql import MySqlContainer |
| 28 | +from testcontainers.postgres import PostgresContainer |
29 | 29 |
|
30 | 30 | from feast import FeatureService, FileSource, RequestSource |
31 | 31 | from feast.data_format import AvroFormat, ParquetFormat |
@@ -93,158 +93,136 @@ def s3_registry() -> Registry: |
93 | 93 |
|
94 | 94 |
|
95 | 95 | @pytest.fixture(scope="function") |
96 | | -def minio_registry() -> Registry: |
97 | | - bucket_name = "test-bucket" |
| 96 | +def minio_registry(minio_server): |
| 97 | + bucket_name = "".join(random.choices(string.ascii_lowercase, k=10)) |
98 | 98 |
|
99 | | - container = MinioContainer() |
100 | | - container.start() |
101 | | - client = container.get_client() |
| 99 | + client = minio_server.get_client() |
102 | 100 | client.make_bucket(bucket_name) |
103 | 101 |
|
104 | | - container_host = container.get_container_host_ip() |
105 | | - exposed_port = container.get_exposed_port(container.port) |
| 102 | + container_host = minio_server.get_container_host_ip() |
| 103 | + exposed_port = minio_server.get_exposed_port(minio_server.port) |
106 | 104 |
|
107 | 105 | registry_config = RegistryConfig( |
108 | 106 | path=f"s3://{bucket_name}/registry.db", cache_ttl_seconds=600 |
109 | 107 | ) |
110 | 108 |
|
111 | 109 | mock_environ = { |
112 | 110 | "FEAST_S3_ENDPOINT_URL": f"http://{container_host}:{exposed_port}", |
113 | | - "AWS_ACCESS_KEY_ID": container.access_key, |
114 | | - "AWS_SECRET_ACCESS_KEY": container.secret_key, |
| 111 | + "AWS_ACCESS_KEY_ID": minio_server.access_key, |
| 112 | + "AWS_SECRET_ACCESS_KEY": minio_server.secret_key, |
115 | 113 | "AWS_SESSION_TOKEN": "", |
116 | 114 | } |
117 | 115 |
|
118 | 116 | with mock.patch.dict(os.environ, mock_environ): |
119 | 117 | yield Registry("project", registry_config, None) |
120 | 118 |
|
121 | | - container.stop() |
122 | | - |
123 | | - |
124 | | -POSTGRES_USER = "test" |
125 | | -POSTGRES_PASSWORD = "test" |
126 | | -POSTGRES_DB = "test" |
127 | 119 |
|
128 | 120 | logger = logging.getLogger(__name__) |
129 | 121 |
|
130 | 122 |
|
131 | 123 | @pytest.fixture(scope="function") |
132 | | -def pg_registry(): |
133 | | - container = ( |
134 | | - DockerContainer("postgres:latest") |
135 | | - .with_exposed_ports(5432) |
136 | | - .with_env("POSTGRES_USER", POSTGRES_USER) |
137 | | - .with_env("POSTGRES_PASSWORD", POSTGRES_PASSWORD) |
138 | | - .with_env("POSTGRES_DB", POSTGRES_DB) |
139 | | - ) |
140 | | - |
141 | | - container.start() |
142 | | - |
143 | | - registry_config = _given_registry_config_for_pg_sql(container) |
144 | | - |
145 | | - yield SqlRegistry(registry_config, "project", None) |
| 124 | +def pg_registry(postgres_server): |
| 125 | + db_name = "".join(random.choices(string.ascii_lowercase, k=10)) |
146 | 126 |
|
147 | | - container.stop() |
| 127 | + _create_pg_database(postgres_server, db_name) |
148 | 128 |
|
| 129 | + container_port = postgres_server.get_exposed_port(5432) |
| 130 | + container_host = postgres_server.get_container_host_ip() |
149 | 131 |
|
150 | | -@pytest.fixture(scope="function") |
151 | | -def pg_registry_async(): |
152 | | - container = ( |
153 | | - DockerContainer("postgres:latest") |
154 | | - .with_exposed_ports(5432) |
155 | | - .with_env("POSTGRES_USER", POSTGRES_USER) |
156 | | - .with_env("POSTGRES_PASSWORD", POSTGRES_PASSWORD) |
157 | | - .with_env("POSTGRES_DB", POSTGRES_DB) |
| 132 | + registry_config = SqlRegistryConfig( |
| 133 | + registry_type="sql", |
| 134 | + cache_ttl_seconds=2, |
| 135 | + cache_mode="sync", |
| 136 | + # The `path` must include `+psycopg` in order for `sqlalchemy.create_engine()` |
| 137 | + # to understand that we are using psycopg3. |
| 138 | + path=f"postgresql+psycopg://{postgres_server.username}:{postgres_server.password}@{container_host}:{container_port}/{db_name}", |
| 139 | + sqlalchemy_config_kwargs={"echo": False, "pool_pre_ping": True}, |
| 140 | + thread_pool_executor_worker_count=0, |
| 141 | + purge_feast_metadata=False, |
158 | 142 | ) |
159 | 143 |
|
160 | | - container.start() |
161 | | - |
162 | | - registry_config = _given_registry_config_for_pg_sql(container, 2, "thread", 3) |
163 | | - |
164 | 144 | yield SqlRegistry(registry_config, "project", None) |
165 | 145 |
|
166 | | - container.stop() |
167 | 146 |
|
| 147 | +@pytest.fixture(scope="function") |
| 148 | +def pg_registry_async(postgres_server): |
| 149 | + db_name = "".join(random.choices(string.ascii_lowercase, k=10)) |
| 150 | + |
| 151 | + _create_pg_database(postgres_server, db_name) |
168 | 152 |
|
169 | | -def _given_registry_config_for_pg_sql( |
170 | | - container, |
171 | | - cache_ttl_seconds=2, |
172 | | - cache_mode="sync", |
173 | | - thread_pool_executor_worker_count=0, |
174 | | - purge_feast_metadata=False, |
175 | | -): |
176 | | - log_string_to_wait_for = "database system is ready to accept connections" |
177 | | - waited = wait_for_logs( |
178 | | - container=container, |
179 | | - predicate=log_string_to_wait_for, |
180 | | - timeout=30, |
181 | | - interval=10, |
182 | | - ) |
183 | | - logger.info("Waited for %s seconds until postgres container was up", waited) |
184 | | - container_port = container.get_exposed_port(5432) |
185 | | - container_host = container.get_container_host_ip() |
| 153 | + container_port = postgres_server.get_exposed_port(5432) |
| 154 | + container_host = postgres_server.get_container_host_ip() |
186 | 155 |
|
187 | | - return SqlRegistryConfig( |
| 156 | + registry_config = SqlRegistryConfig( |
188 | 157 | registry_type="sql", |
189 | | - cache_ttl_seconds=cache_ttl_seconds, |
190 | | - cache_mode=cache_mode, |
| 158 | + cache_ttl_seconds=2, |
| 159 | + cache_mode="thread", |
191 | 160 | # The `path` must include `+psycopg` in order for `sqlalchemy.create_engine()` |
192 | 161 | # to understand that we are using psycopg3. |
193 | | - path=f"postgresql+psycopg://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{container_host}:{container_port}/{POSTGRES_DB}", |
| 162 | + path=f"postgresql+psycopg://{postgres_server.username}:{postgres_server.password}@{container_host}:{container_port}/{db_name}", |
194 | 163 | sqlalchemy_config_kwargs={"echo": False, "pool_pre_ping": True}, |
195 | | - thread_pool_executor_worker_count=thread_pool_executor_worker_count, |
196 | | - purge_feast_metadata=purge_feast_metadata, |
| 164 | + thread_pool_executor_worker_count=3, |
| 165 | + purge_feast_metadata=False, |
197 | 166 | ) |
198 | 167 |
|
| 168 | + yield SqlRegistry(registry_config, "project", None) |
199 | 169 |
|
200 | | -@pytest.fixture(scope="function") |
201 | | -def mysql_registry(): |
202 | | - container = MySqlContainer("mysql:latest") |
203 | | - container.start() |
204 | 170 |
|
205 | | - registry_config = _given_registry_config_for_mysql(container) |
| 171 | +def _create_mysql_database(container: MySqlContainer, database: str): |
| 172 | + container.exec( |
| 173 | + f"mysql -uroot -p{container.root_password} -e 'CREATE DATABASE {database}; GRANT ALL PRIVILEGES ON {database}.* TO {container.username};'" |
| 174 | + ) |
206 | 175 |
|
207 | | - yield SqlRegistry(registry_config, "project", None) |
208 | 176 |
|
209 | | - container.stop() |
| 177 | +def _create_pg_database(container: PostgresContainer, database: str): |
| 178 | + container.exec(f"psql -U {container.username} -c 'CREATE DATABASE {database}'") |
210 | 179 |
|
211 | 180 |
|
212 | 181 | @pytest.fixture(scope="function") |
213 | | -def mysql_registry_async(): |
214 | | - container = MySqlContainer("mysql:latest") |
215 | | - container.start() |
| 182 | +def mysql_registry(mysql_server): |
| 183 | + db_name = "".join(random.choices(string.ascii_lowercase, k=10)) |
| 184 | + |
| 185 | + _create_mysql_database(mysql_server, db_name) |
216 | 186 |
|
217 | | - registry_config = _given_registry_config_for_mysql(container, 2, "thread", 3) |
| 187 | + connection_url = ( |
| 188 | + "/".join(mysql_server.get_connection_url().split("/")[:-1]) + f"/{db_name}" |
| 189 | + ) |
| 190 | + |
| 191 | + registry_config = SqlRegistryConfig( |
| 192 | + registry_type="sql", |
| 193 | + path=connection_url, |
| 194 | + cache_ttl_seconds=2, |
| 195 | + cache_mode="sync", |
| 196 | + sqlalchemy_config_kwargs={"echo": False, "pool_pre_ping": True}, |
| 197 | + thread_pool_executor_worker_count=0, |
| 198 | + purge_feast_metadata=False, |
| 199 | + ) |
218 | 200 |
|
219 | 201 | yield SqlRegistry(registry_config, "project", None) |
220 | 202 |
|
221 | | - container.stop() |
222 | 203 |
|
| 204 | +@pytest.fixture(scope="function") |
| 205 | +def mysql_registry_async(mysql_server): |
| 206 | + db_name = "".join(random.choices(string.ascii_lowercase, k=10)) |
223 | 207 |
|
224 | | -def _given_registry_config_for_mysql( |
225 | | - container, |
226 | | - cache_ttl_seconds=2, |
227 | | - cache_mode="sync", |
228 | | - thread_pool_executor_worker_count=0, |
229 | | - purge_feast_metadata=False, |
230 | | -): |
231 | | - import sqlalchemy |
| 208 | + _create_mysql_database(mysql_server, db_name) |
232 | 209 |
|
233 | | - engine = sqlalchemy.create_engine( |
234 | | - container.get_connection_url(), pool_pre_ping=True |
| 210 | + connection_url = ( |
| 211 | + "/".join(mysql_server.get_connection_url().split("/")[:-1]) + f"/{db_name}" |
235 | 212 | ) |
236 | | - engine.connect() |
237 | 213 |
|
238 | | - return SqlRegistryConfig( |
| 214 | + registry_config = SqlRegistryConfig( |
239 | 215 | registry_type="sql", |
240 | | - path=container.get_connection_url(), |
241 | | - cache_ttl_seconds=cache_ttl_seconds, |
242 | | - cache_mode=cache_mode, |
| 216 | + path=connection_url, |
| 217 | + cache_ttl_seconds=2, |
| 218 | + cache_mode="thread", |
243 | 219 | sqlalchemy_config_kwargs={"echo": False, "pool_pre_ping": True}, |
244 | | - thread_pool_executor_worker_count=thread_pool_executor_worker_count, |
245 | | - purge_feast_metadata=purge_feast_metadata, |
| 220 | + thread_pool_executor_worker_count=3, |
| 221 | + purge_feast_metadata=False, |
246 | 222 | ) |
247 | 223 |
|
| 224 | + yield SqlRegistry(registry_config, "project", None) |
| 225 | + |
248 | 226 |
|
249 | 227 | @pytest.fixture(scope="session") |
250 | 228 | def sqlite_registry(): |
@@ -339,11 +317,11 @@ def mock_remote_registry(): |
339 | 317 | async_sql_fixtures = [ |
340 | 318 | pytest.param( |
341 | 319 | lazy_fixture("pg_registry_async"), |
342 | | - marks=pytest.mark.xdist_group(name="pg_registry_async"), |
| 320 | + marks=pytest.mark.xdist_group(name="pg_registry"), |
343 | 321 | ), |
344 | 322 | pytest.param( |
345 | 323 | lazy_fixture("mysql_registry_async"), |
346 | | - marks=pytest.mark.xdist_group(name="mysql_registry_async"), |
| 324 | + marks=pytest.mark.xdist_group(name="mysql_registry"), |
347 | 325 | ), |
348 | 326 | ] |
349 | 327 |
|
@@ -1609,45 +1587,61 @@ def local_registry_purge_feast_metadata() -> Registry: |
1609 | 1587 |
|
1610 | 1588 |
|
1611 | 1589 | @pytest.fixture(scope="function") |
1612 | | -def pg_registry_purge_feast_metadata(): |
1613 | | - container = ( |
1614 | | - DockerContainer("postgres:latest") |
1615 | | - .with_exposed_ports(5432) |
1616 | | - .with_env("POSTGRES_USER", POSTGRES_USER) |
1617 | | - .with_env("POSTGRES_PASSWORD", POSTGRES_PASSWORD) |
1618 | | - .with_env("POSTGRES_DB", POSTGRES_DB) |
1619 | | - ) |
| 1590 | +def pg_registry_purge_feast_metadata(postgres_server): |
| 1591 | + db_name = "".join(random.choices(string.ascii_lowercase, k=10)) |
1620 | 1592 |
|
1621 | | - container.start() |
| 1593 | + _create_pg_database(postgres_server, db_name) |
1622 | 1594 |
|
1623 | | - registry_config = _given_registry_config_for_pg_sql(container, 2, "thread", 3, True) |
| 1595 | + container_port = postgres_server.get_exposed_port(5432) |
| 1596 | + container_host = postgres_server.get_container_host_ip() |
1624 | 1597 |
|
1625 | | - yield SqlRegistry(registry_config, "project", None) |
| 1598 | + registry_config = SqlRegistryConfig( |
| 1599 | + registry_type="sql", |
| 1600 | + cache_ttl_seconds=2, |
| 1601 | + cache_mode="thread", |
| 1602 | + # The `path` must include `+psycopg` in order for `sqlalchemy.create_engine()` |
| 1603 | + # to understand that we are using psycopg3. |
| 1604 | + path=f"postgresql+psycopg://{postgres_server.username}:{postgres_server.password}@{container_host}:{container_port}/{db_name}", |
| 1605 | + sqlalchemy_config_kwargs={"echo": False, "pool_pre_ping": True}, |
| 1606 | + thread_pool_executor_worker_count=3, |
| 1607 | + purge_feast_metadata=True, |
| 1608 | + ) |
1626 | 1609 |
|
1627 | | - container.stop() |
| 1610 | + yield SqlRegistry(registry_config, "project", None) |
1628 | 1611 |
|
1629 | 1612 |
|
1630 | 1613 | @pytest.fixture(scope="function") |
1631 | | -def mysql_registry_purge_feast_metadata(): |
1632 | | - container = MySqlContainer("mysql:latest") |
1633 | | - container.start() |
| 1614 | +def mysql_registry_purge_feast_metadata(mysql_server): |
| 1615 | + db_name = "".join(random.choices(string.ascii_lowercase, k=10)) |
1634 | 1616 |
|
1635 | | - registry_config = _given_registry_config_for_mysql(container, 2, "thread", 3, True) |
| 1617 | + _create_mysql_database(mysql_server, db_name) |
1636 | 1618 |
|
1637 | | - yield SqlRegistry(registry_config, "project", None) |
| 1619 | + connection_url = ( |
| 1620 | + "/".join(mysql_server.get_connection_url().split("/")[:-1]) + f"/{db_name}" |
| 1621 | + ) |
1638 | 1622 |
|
1639 | | - container.stop() |
| 1623 | + registry_config = SqlRegistryConfig( |
| 1624 | + registry_type="sql", |
| 1625 | + path=connection_url, |
| 1626 | + cache_ttl_seconds=2, |
| 1627 | + cache_mode="thread", |
| 1628 | + sqlalchemy_config_kwargs={"echo": False, "pool_pre_ping": True}, |
| 1629 | + thread_pool_executor_worker_count=3, |
| 1630 | + purge_feast_metadata=True, |
| 1631 | + ) |
| 1632 | + |
| 1633 | + yield SqlRegistry(registry_config, "project", None) |
1640 | 1634 |
|
1641 | 1635 |
|
1642 | 1636 | purge_feast_metadata_fixtures = [ |
1643 | 1637 | lazy_fixture("local_registry_purge_feast_metadata"), |
1644 | 1638 | pytest.param( |
1645 | 1639 | lazy_fixture("pg_registry_purge_feast_metadata"), |
1646 | | - marks=pytest.mark.xdist_group(name="pg_registry_purge_feast_metadata"), |
| 1640 | + marks=pytest.mark.xdist_group(name="pg_registry"), |
1647 | 1641 | ), |
1648 | 1642 | pytest.param( |
1649 | 1643 | lazy_fixture("mysql_registry_purge_feast_metadata"), |
1650 | | - marks=pytest.mark.xdist_group(name="mysql_registry_purge_feast_metadata"), |
| 1644 | + marks=pytest.mark.xdist_group(name="mysql_registry"), |
1651 | 1645 | ), |
1652 | 1646 | ] |
1653 | 1647 |
|
|
0 commit comments