Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
33 changes: 29 additions & 4 deletions gcloud/bigtable/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,20 @@
In the hierarchy of API concepts

* a :class:`Client` owns a :class:`.Cluster`
* a :class:`.Cluster` owns a :class:`Table <gcloud_bigtable.table.Table>`
* a :class:`Table <gcloud_bigtable.table.Table>` owns a
* a :class:`.Cluster` owns a :class:`Table <gcloud.bigtable.table.Table>`
* a :class:`Table <gcloud.bigtable.table.Table>` owns a
:class:`ColumnFamily <.column_family.ColumnFamily>`
* a :class:`Table <gcloud_bigtable.table.Table>` owns a :class:`Row <.row.Row>`
* a :class:`Table <gcloud.bigtable.table.Table>` owns a :class:`Row <.row.Row>`
(and all the cells in the row)
"""


import copy

from gcloud.bigtable._generated import bigtable_cluster_data_pb2 as data_pb2
from gcloud.bigtable._generated import bigtable_cluster_service_pb2
from gcloud.bigtable._generated import (
bigtable_cluster_service_messages_pb2 as messages_pb2)
from gcloud.bigtable._generated import bigtable_service_pb2
from gcloud.bigtable._generated import bigtable_table_service_pb2
from gcloud.bigtable._generated import operations_pb2
Expand Down Expand Up @@ -192,7 +195,7 @@ def project_name(self):

The project name is of the form

``"projects/{project_id}"``
``"projects/{project}"``

:rtype: str
:returns: The project name to be used with the Cloud Bigtable Admin
Expand Down Expand Up @@ -375,3 +378,25 @@ def cluster(self, zone, cluster_id, display_name=None, serve_nodes=3):
"""
return Cluster(zone, cluster_id, self,
display_name=display_name, serve_nodes=serve_nodes)

def list_zones(self):
"""Lists zones associated with project.

:rtype: list
:returns: The names (as :class:`str`) of the zones
:raises: :class:`ValueError <exceptions.ValueError>` if one of the
zones is not in ``OK`` state.
"""
request_pb = messages_pb2.ListZonesRequest(name=self.project_name)
response = self._cluster_stub.ListZones.async(request_pb,
self.timeout_seconds)
# We expect a `.messages_pb2.ListZonesResponse`
list_zones_response = response.result()

result = []
for zone in list_zones_response.zones:
if zone.status != data_pb2.Zone.OK:
raise ValueError('Zone %s not in OK state' % (
zone.display_name,))
result.append(zone.display_name)
return result
2 changes: 1 addition & 1 deletion gcloud/bigtable/column_family.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class ColumnFamily(object):
:param column_family_id: The ID of the column family. Must be of the
form ``[_a-zA-Z0-9][-_.a-zA-Z0-9]*``.

:type table: :class:`Table <gcloud_bigtable.table.Table>`
:type table: :class:`Table <gcloud.bigtable.table.Table>`
:param table: The table that owns the column family.
"""

Expand Down
2 changes: 1 addition & 1 deletion gcloud/bigtable/row.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class Row(object):
:type row_key: bytes
:param row_key: The key for the current row.

:type table: :class:`Table <gcloud_bigtable.table.Table>`
:type table: :class:`Table <gcloud.bigtable.table.Table>`
:param table: The table that owns the row.
"""

Expand Down
93 changes: 92 additions & 1 deletion gcloud/bigtable/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,59 @@ def test_cluster_factory(self):
self.assertEqual(cluster.serve_nodes, serve_nodes)
self.assertTrue(cluster._client is client)

def _list_zones_helper(self, zone_status):
from gcloud.bigtable._generated import (
bigtable_cluster_data_pb2 as data_pb2)
from gcloud.bigtable._generated import (
bigtable_cluster_service_messages_pb2 as messages_pb2)

credentials = _Credentials()
project = 'PROJECT'
timeout_seconds = 281330
client = self._makeOne(project=project, credentials=credentials,
admin=True, timeout_seconds=timeout_seconds)

# Create request_pb
request_pb = messages_pb2.ListZonesRequest(
name='projects/' + project,
)

# Create response_pb
zone1 = 'foo'
zone2 = 'bar'
response_pb = messages_pb2.ListZonesResponse(
zones=[
data_pb2.Zone(display_name=zone1, status=zone_status),
data_pb2.Zone(display_name=zone2, status=zone_status),
],
)

# Patch the stub used by the API method.
client._cluster_stub_internal = stub = _FakeStub(response_pb)

# Create expected_result.
expected_result = [zone1, zone2]

# Perform the method and check the result.
result = client.list_zones()
self.assertEqual(result, expected_result)
self.assertEqual(stub.method_calls, [(
'ListZones',
(request_pb, timeout_seconds),
{},
)])

def test_list_zones(self):
from gcloud.bigtable._generated import (
bigtable_cluster_data_pb2 as data_pb2)
self._list_zones_helper(data_pb2.Zone.OK)

def test_list_zones_failure(self):
from gcloud.bigtable._generated import (
bigtable_cluster_data_pb2 as data_pb2)
with self.assertRaises(ValueError):
self._list_zones_helper(data_pb2.Zone.EMERGENCY_MAINENANCE)


class _Credentials(object):

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


class _FakeStub(object):
"""Acts as a gPRC stub."""

def __init__(self):
def __init__(self, *results):
self.results = results
self.method_calls = []
self._entered = 0
self._exited = []

Expand All @@ -523,3 +579,38 @@ def __enter__(self):
def __exit__(self, exc_type, exc_val, exc_tb):
self._exited.append((exc_type, exc_val, exc_tb))
return True

def __getattr__(self, name):
# We need not worry about attributes set in constructor
# since __getattribute__ will handle them.
return _MethodMock(name, self)


class _MethodMock(object):
"""Mock for :class:`grpc.framework.alpha._reexport._UnaryUnarySyncAsync`.

May need to be callable and needs to (in our use) have an
``async`` method.
"""

def __init__(self, name, factory):
self._name = name
self._factory = factory

def async(self, *args, **kwargs):
"""Async method meant to mock a gRPC stub request."""
self._factory.method_calls.append((self._name, args, kwargs))
curr_result, self._factory.results = (self._factory.results[0],
self._factory.results[1:])
return _AsyncResult(curr_result)


class _AsyncResult(object):
"""Result returned from a ``_MethodMock.async`` call."""

def __init__(self, result):
self._result = result

def result(self):
"""Result method on an asyc object."""
return self._result