Skip to content

Commit 0dc78cf

Browse files
committed
Adding list zones request to Bigtable client.
Also adding test helper for faking behavior from gRPC stubs.
1 parent 3cd0755 commit 0dc78cf

4 files changed

Lines changed: 128 additions & 7 deletions

File tree

gcloud/bigtable/client.py

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,20 @@
1919
In the hierarchy of API concepts
2020
2121
* a :class:`Client` owns a :class:`.Cluster`
22-
* a :class:`.Cluster` owns a :class:`Table <gcloud_bigtable.table.Table>`
23-
* a :class:`Table <gcloud_bigtable.table.Table>` owns a
22+
* a :class:`.Cluster` owns a :class:`Table <gcloud.bigtable.table.Table>`
23+
* a :class:`Table <gcloud.bigtable.table.Table>` owns a
2424
:class:`ColumnFamily <.column_family.ColumnFamily>`
25-
* a :class:`Table <gcloud_bigtable.table.Table>` owns a :class:`Row <.row.Row>`
25+
* a :class:`Table <gcloud.bigtable.table.Table>` owns a :class:`Row <.row.Row>`
2626
(and all the cells in the row)
2727
"""
2828

2929

3030
import copy
3131

32+
from gcloud.bigtable._generated import bigtable_cluster_data_pb2 as data_pb2
3233
from gcloud.bigtable._generated import bigtable_cluster_service_pb2
34+
from gcloud.bigtable._generated import (
35+
bigtable_cluster_service_messages_pb2 as messages_pb2)
3336
from gcloud.bigtable._generated import bigtable_service_pb2
3437
from gcloud.bigtable._generated import bigtable_table_service_pb2
3538
from gcloud.bigtable._generated import operations_pb2
@@ -192,7 +195,7 @@ def project_name(self):
192195
193196
The project name is of the form
194197
195-
``"projects/{project_id}"``
198+
``"projects/{project}"``
196199
197200
:rtype: str
198201
:returns: The project name to be used with the Cloud Bigtable Admin
@@ -375,3 +378,30 @@ def cluster(self, zone, cluster_id, display_name=None, serve_nodes=3):
375378
"""
376379
return Cluster(zone, cluster_id, self,
377380
display_name=display_name, serve_nodes=serve_nodes)
381+
382+
def list_zones(self, timeout_seconds=None):
383+
"""Lists zones associated with project.
384+
385+
:type timeout_seconds: int
386+
:param timeout_seconds: Number of seconds for request time-out.
387+
If not passed, defaults to value set on client.
388+
389+
:rtype: list
390+
:returns: The names (as :class:`str`) of the zones
391+
:raises: :class:`ValueError <exceptions.ValueError>` if one of the
392+
zones is not in ``OK`` state.
393+
"""
394+
request_pb = messages_pb2.ListZonesRequest(name=self.project_name)
395+
timeout_seconds = timeout_seconds or self.timeout_seconds
396+
response = self._cluster_stub.ListZones.async(request_pb,
397+
timeout_seconds)
398+
# We expect a `.messages_pb2.ListZonesResponse`
399+
list_zones_response = response.result()
400+
401+
result = []
402+
for zone in list_zones_response.zones:
403+
if zone.status != data_pb2.Zone.OK:
404+
raise ValueError('Zone %s not in OK state' % (
405+
zone.display_name,))
406+
result.append(zone.display_name)
407+
return result

gcloud/bigtable/column_family.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class ColumnFamily(object):
2222
:param column_family_id: The ID of the column family. Must be of the
2323
form ``[_a-zA-Z0-9][-_.a-zA-Z0-9]*``.
2424
25-
:type table: :class:`Table <gcloud_bigtable.table.Table>`
25+
:type table: :class:`Table <gcloud.bigtable.table.Table>`
2626
:param table: The table that owns the column family.
2727
"""
2828

gcloud/bigtable/row.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class Row(object):
2424
:type row_key: bytes
2525
:param row_key: The key for the current row.
2626
27-
:type table: :class:`Table <gcloud_bigtable.table.Table>`
27+
:type table: :class:`Table <gcloud.bigtable.table.Table>`
2828
:param table: The table that owns the row.
2929
"""
3030

gcloud/bigtable/test_client.py

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,59 @@ def test_cluster_factory(self):
494494
self.assertEqual(cluster.serve_nodes, serve_nodes)
495495
self.assertTrue(cluster._client is client)
496496

497+
def _list_zones_helper(self, zone_status):
498+
from gcloud.bigtable._generated import (
499+
bigtable_cluster_data_pb2 as data_pb2)
500+
from gcloud.bigtable._generated import (
501+
bigtable_cluster_service_messages_pb2 as messages_pb2)
502+
503+
credentials = _Credentials()
504+
project = 'PROJECT'
505+
client = self._makeOne(project=project,
506+
credentials=credentials, admin=True)
507+
508+
# Create request_pb
509+
request_pb = messages_pb2.ListZonesRequest(
510+
name='projects/' + project,
511+
)
512+
513+
# Create response_pb
514+
zone1 = 'foo'
515+
zone2 = 'bar'
516+
response_pb = messages_pb2.ListZonesResponse(
517+
zones=[
518+
data_pb2.Zone(display_name=zone1, status=zone_status),
519+
data_pb2.Zone(display_name=zone2, status=zone_status),
520+
],
521+
)
522+
523+
# Patch the stub used by the API method.
524+
client._cluster_stub_internal = stub = _FakeStub(response_pb)
525+
526+
# Create expected_result.
527+
expected_result = [zone1, zone2]
528+
529+
# Perform the method and check the result.
530+
timeout_seconds = 281330
531+
result = client.list_zones(timeout_seconds=timeout_seconds)
532+
self.assertEqual(result, expected_result)
533+
self.assertEqual(stub.method_calls, [(
534+
'ListZones',
535+
(request_pb, timeout_seconds),
536+
{},
537+
)])
538+
539+
def test_list_zones(self):
540+
from gcloud.bigtable._generated import (
541+
bigtable_cluster_data_pb2 as data_pb2)
542+
self._list_zones_helper(data_pb2.Zone.OK)
543+
544+
def test_list_zones_failure(self):
545+
from gcloud.bigtable._generated import (
546+
bigtable_cluster_data_pb2 as data_pb2)
547+
with self.assertRaises(ValueError):
548+
self._list_zones_helper(data_pb2.Zone.EMERGENCY_MAINENANCE)
549+
497550

498551
class _Credentials(object):
499552

@@ -511,8 +564,11 @@ def __eq__(self, other):
511564

512565

513566
class _FakeStub(object):
567+
"""Acts as a gPRC stub."""
514568

515-
def __init__(self):
569+
def __init__(self, *results):
570+
self.results = results
571+
self.method_calls = []
516572
self._entered = 0
517573
self._exited = []
518574

@@ -523,3 +579,38 @@ def __enter__(self):
523579
def __exit__(self, exc_type, exc_val, exc_tb):
524580
self._exited.append((exc_type, exc_val, exc_tb))
525581
return True
582+
583+
def __getattr__(self, name):
584+
# We need not worry about attributes set in constructor
585+
# since __getattribute__ will handle them.
586+
return _MethodMock(name, self)
587+
588+
589+
class _MethodMock(object):
590+
"""Mock for :class:`grpc.framework.alpha._reexport._UnaryUnarySyncAsync`.
591+
592+
May need to be callable and needs to (in our use) have an
593+
``async`` method.
594+
"""
595+
596+
def __init__(self, name, factory):
597+
self._name = name
598+
self._factory = factory
599+
600+
def async(self, *args, **kwargs):
601+
"""Async method meant to mock a gRPC stub request."""
602+
self._factory.method_calls.append((self._name, args, kwargs))
603+
curr_result, self._factory.results = (self._factory.results[0],
604+
self._factory.results[1:])
605+
return _AsyncResult(curr_result)
606+
607+
608+
class _AsyncResult(object):
609+
"""Result returned from a ``_MethodMock.async`` call."""
610+
611+
def __init__(self, result):
612+
self._result = result
613+
614+
def result(self):
615+
"""Result method on an asyc object."""
616+
return self._result

0 commit comments

Comments
 (0)