1414# limitations under the License.
1515#
1616import pytest
17- import uuid
1817import os
18+ import uuid
1919import datetime
2020
2121TEST_FAMILY = "test-family"
2222TEST_FAMILY_2 = "test-family-2"
2323TEST_AGGREGATE_FAMILY = "test-aggregate-family"
2424
25+ # authorized view subset to allow all qualifiers
26+ ALLOW_ALL = ""
27+ ALL_QUALIFIERS = {"qualifier_prefixes" : [ALLOW_ALL ]}
28+
2529
2630class SystemTestRunner :
2731 """
@@ -30,6 +34,16 @@ class SystemTestRunner:
3034 used by standard system tests, and metrics tests
3135 """
3236
37+ @pytest .fixture (scope = "session" )
38+ def init_table_id (self , start_timestamp ):
39+ """
40+ The table_id to use when creating a new test table
41+
42+ Args
43+ start_timestamp: accessed when building table to ensure timestamp data is loaded before tests are run
44+ """
45+ return f"test-table-{ uuid .uuid4 ().hex } "
46+
3347 @pytest .fixture (scope = "session" )
3448 def cluster_config (self , project_id ):
3549 """
@@ -72,11 +86,180 @@ def start_timestamp(self):
7286 return datetime .datetime .now (datetime .timezone .utc )
7387
7488 @pytest .fixture (scope = "session" )
75- def init_table_id (self , start_timestamp ):
89+ def admin_client (self ):
7690 """
77- The table_id to use when creating a new test table
91+ Client for interacting with Table and Instance admin APIs
92+ """
93+ from google .cloud .bigtable .client import Client
7894
79- Args
80- start_timestamp: accessed when building table to ensure timestamp data is loaded before tests are run
95+ client = Client (admin = True )
96+ yield client
97+
98+ @pytest .fixture (scope = "session" )
99+ def instance_id (self , admin_client , project_id , cluster_config ):
81100 """
82- return f"test-table-{ uuid .uuid4 ().hex } "
101+ Returns BIGTABLE_TEST_INSTANCE if set, otherwise creates a new temporary instance for the test session
102+ """
103+ from google .cloud .bigtable_admin_v2 import types
104+ from google .api_core import exceptions
105+ from google .cloud .environment_vars import BIGTABLE_EMULATOR
106+
107+ # use user-specified instance if available
108+ user_specified_instance = os .getenv ("BIGTABLE_TEST_INSTANCE" )
109+ if user_specified_instance :
110+ print ("Using user-specified instance: {}" .format (user_specified_instance ))
111+ yield user_specified_instance
112+ return
113+
114+ # create a new temporary test instance
115+ instance_id = f"python-bigtable-tests-{ uuid .uuid4 ().hex [:6 ]} "
116+ if os .getenv (BIGTABLE_EMULATOR ):
117+ # don't create instance if in emulator mode
118+ yield instance_id
119+ else :
120+ try :
121+ operation = admin_client .instance_admin_client .create_instance (
122+ parent = f"projects/{ project_id } " ,
123+ instance_id = instance_id ,
124+ instance = types .Instance (
125+ display_name = "Test Instance" ,
126+ # labels={"python-system-test": "true"},
127+ ),
128+ clusters = cluster_config ,
129+ )
130+ operation .result (timeout = 240 )
131+ except exceptions .AlreadyExists :
132+ pass
133+ yield instance_id
134+ admin_client .instance_admin_client .delete_instance (
135+ name = f"projects/{ project_id } /instances/{ instance_id } "
136+ )
137+
138+ @pytest .fixture (scope = "session" )
139+ def column_split_config (self ):
140+ """
141+ specify initial splits to create when creating a new test table
142+ """
143+ return [(num * 1000 ).to_bytes (8 , "big" ) for num in range (1 , 10 )]
144+
145+ @pytest .fixture (scope = "session" )
146+ def table_id (
147+ self ,
148+ admin_client ,
149+ project_id ,
150+ instance_id ,
151+ column_family_config ,
152+ init_table_id ,
153+ column_split_config ,
154+ ):
155+ """
156+ Returns BIGTABLE_TEST_TABLE if set, otherwise creates a new temporary table for the test session
157+
158+ Args:
159+ - admin_client: Client for interacting with the Table Admin API. Supplied by the admin_client fixture.
160+ - project_id: The project ID of the GCP project to test against. Supplied by the project_id fixture.
161+ - instance_id: The ID of the Bigtable instance to test against. Supplied by the instance_id fixture.
162+ - init_column_families: A list of column families to initialize the table with, if pre-initialized table is not given with BIGTABLE_TEST_TABLE.
163+ Supplied by the init_column_families fixture.
164+ - init_table_id: The table ID to give to the test table, if pre-initialized table is not given with BIGTABLE_TEST_TABLE.
165+ Supplied by the init_table_id fixture.
166+ - column_split_config: A list of row keys to use as initial splits when creating the test table.
167+ """
168+ from google .api_core import exceptions
169+ from google .api_core import retry
170+
171+ # use user-specified instance if available
172+ user_specified_table = os .getenv ("BIGTABLE_TEST_TABLE" )
173+ if user_specified_table :
174+ print ("Using user-specified table: {}" .format (user_specified_table ))
175+ yield user_specified_table
176+ return
177+
178+ retry = retry .Retry (
179+ predicate = retry .if_exception_type (exceptions .FailedPrecondition )
180+ )
181+ try :
182+ parent_path = f"projects/{ project_id } /instances/{ instance_id } "
183+ print (f"Creating table: { parent_path } /tables/{ init_table_id } " )
184+ admin_client .table_admin_client .create_table (
185+ request = {
186+ "parent" : parent_path ,
187+ "table_id" : init_table_id ,
188+ "table" : {"column_families" : column_family_config },
189+ "initial_splits" : [{"key" : key } for key in column_split_config ],
190+ },
191+ retry = retry ,
192+ )
193+ except exceptions .AlreadyExists :
194+ pass
195+ yield init_table_id
196+ print (f"Deleting table: { parent_path } /tables/{ init_table_id } " )
197+ try :
198+ admin_client .table_admin_client .delete_table (
199+ name = f"{ parent_path } /tables/{ init_table_id } "
200+ )
201+ except exceptions .NotFound :
202+ print (f"Table { init_table_id } not found, skipping deletion" )
203+
204+ @pytest .fixture (scope = "session" )
205+ def authorized_view_id (
206+ self ,
207+ admin_client ,
208+ project_id ,
209+ instance_id ,
210+ table_id ,
211+ ):
212+ """
213+ Creates and returns a new temporary authorized view for the test session
214+
215+ Args:
216+ - admin_client: Client for interacting with the Table Admin API. Supplied by the admin_client fixture.
217+ - project_id: The project ID of the GCP project to test against. Supplied by the project_id fixture.
218+ - instance_id: The ID of the Bigtable instance to test against. Supplied by the instance_id fixture.
219+ - table_id: The ID of the table to create the authorized view for. Supplied by the table_id fixture.
220+ """
221+ from google .api_core import exceptions
222+ from google .api_core import retry
223+
224+ retry = retry .Retry (
225+ predicate = retry .if_exception_type (exceptions .FailedPrecondition )
226+ )
227+ new_view_id = uuid .uuid4 ().hex [:8 ]
228+ parent_path = f"projects/{ project_id } /instances/{ instance_id } /tables/{ table_id } "
229+ new_path = f"{ parent_path } /authorizedViews/{ new_view_id } "
230+ try :
231+ print (f"Creating view: { new_path } " )
232+ admin_client .table_admin_client .create_authorized_view (
233+ request = {
234+ "parent" : parent_path ,
235+ "authorized_view_id" : new_view_id ,
236+ "authorized_view" : {
237+ "subset_view" : {
238+ "row_prefixes" : [ALLOW_ALL ],
239+ "family_subsets" : {
240+ TEST_FAMILY : ALL_QUALIFIERS ,
241+ TEST_FAMILY_2 : ALL_QUALIFIERS ,
242+ TEST_AGGREGATE_FAMILY : ALL_QUALIFIERS ,
243+ },
244+ },
245+ },
246+ },
247+ retry = retry ,
248+ )
249+ except exceptions .AlreadyExists :
250+ pass
251+ except exceptions .MethodNotImplemented :
252+ # will occur when run in emulator. Pass empty id
253+ new_view_id = None
254+ yield new_view_id
255+ if new_view_id :
256+ print (f"Deleting view: { new_path } " )
257+ try :
258+ admin_client .table_admin_client .delete_authorized_view (name = new_path )
259+ except exceptions .NotFound :
260+ print (f"View { new_view_id } not found, skipping deletion" )
261+
262+ @pytest .fixture (scope = "session" )
263+ def project_id (self , client ):
264+ """Returns the project ID from the client."""
265+ yield client .project
0 commit comments