Skip to content

Commit 48dce46

Browse files
committed
Adding Bigtable Cluster.from_pb factory.
For creating a Cluster object from a protobuf cluster returned from the API. Also adding a helper for making sure properties are set on a protobuf object.
1 parent b54f725 commit 48dce46

2 files changed

Lines changed: 179 additions & 0 deletions

File tree

‎gcloud/bigtable/cluster.py‎

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,51 @@
1515
"""User friendly container for Google Cloud Bigtable Cluster."""
1616

1717

18+
import re
19+
1820
from gcloud.bigtable.table import Table
1921

2022

23+
_CLUSTER_NAME_RE = re.compile(r'^projects/(?P<project>[^/]+)/'
24+
r'zones/(?P<zone>[^/]+)/clusters/'
25+
r'(?P<cluster_id>[a-z][-a-z0-9]*)$')
26+
27+
28+
def _require_pb_property(message_pb, property_name, value):
29+
"""Check that a property agrees with the value on the message.
30+
31+
:type message_pb: :class:`google.protobuf.message.Message`
32+
:param message_pb: The message to check for ``property_name``.
33+
34+
:type property_name: str
35+
:param property_name: The property value to check against.
36+
37+
:type value: object or :data:`NoneType <types.NoneType>`
38+
:param value: The value to check against the cluster. If :data:`None`,
39+
will not be checked.
40+
41+
:rtype: object
42+
:returns: The value of ``property_name`` set on ``message_pb``.
43+
:raises: :class:`ValueError <exceptions.ValueError>` if the result returned
44+
from the ``message_pb`` does not contain the ``property_name``
45+
value or if the value returned disagrees with the ``value``
46+
passed with the request (if that value is not null).
47+
"""
48+
# Make sure `property_name` is set on the response.
49+
# NOTE: HasField() doesn't work in protobuf>=3.0.0a3
50+
all_fields = set([field.name for field in message_pb._fields])
51+
if property_name not in all_fields:
52+
raise ValueError('Message does not contain %s.' % (property_name,))
53+
property_val = getattr(message_pb, property_name)
54+
if value is None:
55+
value = property_val
56+
elif value != property_val:
57+
raise ValueError('Message returned %s value disagreeing '
58+
'with value passed in.' % (property_name,))
59+
60+
return value
61+
62+
2163
class Cluster(object):
2264
"""Representation of a Google Cloud Bigtable Cluster.
2365
@@ -60,3 +102,37 @@ def table(self, table_id):
60102
:returns: The table owned by this cluster.
61103
"""
62104
return Table(table_id, self)
105+
106+
def _update_from_pb(self, cluster_pb):
107+
self.display_name = _require_pb_property(
108+
cluster_pb, 'display_name', None)
109+
self.serve_nodes = _require_pb_property(
110+
cluster_pb, 'serve_nodes', None)
111+
112+
@classmethod
113+
def from_pb(cls, cluster_pb, client):
114+
"""Creates a cluster instance from a protobuf.
115+
116+
:type cluster_pb: :class:`bigtable_cluster_data_pb2.Cluster`
117+
:param cluster_pb: A cluster protobuf object.
118+
119+
:type client: :class:`.client.Client`
120+
:param client: The client that owns the cluster.
121+
122+
:rtype: :class:`Cluster`
123+
:returns: The cluster parsed from the protobuf response.
124+
:raises: :class:`ValueError <exceptions.ValueError>` if the cluster
125+
name does not match :data:`_CLUSTER_NAME_RE` or if the parsed
126+
project ID does not match the project ID on the client.
127+
"""
128+
match = _CLUSTER_NAME_RE.match(cluster_pb.name)
129+
if match is None:
130+
raise ValueError('Cluster protobuf name was not in the '
131+
'expected format.', cluster_pb.name)
132+
if match.group('project') != client.project:
133+
raise ValueError('Project ID on cluster does not match the '
134+
'project ID on the client')
135+
136+
result = cls(match.group('zone'), match.group('cluster_id'), client)
137+
result._update_from_pb(cluster_pb)
138+
return result

‎gcloud/bigtable/test_cluster.py‎

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,106 @@ def test_table_factory(self):
6565
self.assertTrue(isinstance(table, Table))
6666
self.assertEqual(table.table_id, table_id)
6767
self.assertEqual(table._cluster, cluster)
68+
69+
def test_from_pb_success(self):
70+
from gcloud.bigtable._generated import (
71+
bigtable_cluster_data_pb2 as data_pb2)
72+
73+
project = 'PROJECT'
74+
zone = 'zone'
75+
cluster_id = 'cluster-id'
76+
client = _Client(project=project)
77+
78+
cluster_name = ('projects/' + project + '/zones/' + zone +
79+
'/clusters/' + cluster_id)
80+
cluster_pb = data_pb2.Cluster(
81+
name=cluster_name,
82+
display_name=cluster_id,
83+
serve_nodes=3,
84+
)
85+
86+
klass = self._getTargetClass()
87+
cluster = klass.from_pb(cluster_pb, client)
88+
self.assertTrue(isinstance(cluster, klass))
89+
self.assertEqual(cluster._client, client)
90+
self.assertEqual(cluster.zone, zone)
91+
self.assertEqual(cluster.cluster_id, cluster_id)
92+
93+
def test_from_pb_bad_cluster_name(self):
94+
from gcloud.bigtable._generated import (
95+
bigtable_cluster_data_pb2 as data_pb2)
96+
97+
cluster_name = 'INCORRECT_FORMAT'
98+
cluster_pb = data_pb2.Cluster(name=cluster_name)
99+
100+
klass = self._getTargetClass()
101+
with self.assertRaises(ValueError):
102+
klass.from_pb(cluster_pb, None)
103+
104+
def test_from_pb_project_mistmatch(self):
105+
from gcloud.bigtable._generated import (
106+
bigtable_cluster_data_pb2 as data_pb2)
107+
108+
project = 'PROJECT'
109+
zone = 'zone'
110+
cluster_id = 'cluster-id'
111+
alt_project = 'ALT_PROJECT'
112+
client = _Client(project=alt_project)
113+
114+
self.assertNotEqual(project, alt_project)
115+
116+
cluster_name = ('projects/' + project + '/zones/' + zone +
117+
'/clusters/' + cluster_id)
118+
cluster_pb = data_pb2.Cluster(name=cluster_name)
119+
120+
klass = self._getTargetClass()
121+
with self.assertRaises(ValueError):
122+
klass.from_pb(cluster_pb, client)
123+
124+
125+
class Test__require_pb_property(unittest2.TestCase):
126+
127+
def _callFUT(self, message_pb, property_name, value):
128+
from gcloud.bigtable.cluster import _require_pb_property
129+
return _require_pb_property(message_pb, property_name, value)
130+
131+
def test_it(self):
132+
from gcloud.bigtable._generated import (
133+
bigtable_cluster_data_pb2 as data_pb2)
134+
serve_nodes = 119
135+
cluster_pb = data_pb2.Cluster(serve_nodes=serve_nodes)
136+
result = self._callFUT(cluster_pb, 'serve_nodes', serve_nodes)
137+
self.assertEqual(result, serve_nodes)
138+
139+
def test_with_null_value_passed_in(self):
140+
from gcloud.bigtable._generated import (
141+
bigtable_cluster_data_pb2 as data_pb2)
142+
serve_nodes = None
143+
actual_serve_nodes = 119
144+
cluster_pb = data_pb2.Cluster(serve_nodes=actual_serve_nodes)
145+
result = self._callFUT(cluster_pb, 'serve_nodes', serve_nodes)
146+
self.assertEqual(result, actual_serve_nodes)
147+
148+
def test_with_value_unset_on_pb(self):
149+
from gcloud.bigtable._generated import (
150+
bigtable_cluster_data_pb2 as data_pb2)
151+
serve_nodes = 119
152+
cluster_pb = data_pb2.Cluster()
153+
with self.assertRaises(ValueError):
154+
self._callFUT(cluster_pb, 'serve_nodes', serve_nodes)
155+
156+
def test_with_values_disagreeing(self):
157+
from gcloud.bigtable._generated import (
158+
bigtable_cluster_data_pb2 as data_pb2)
159+
serve_nodes = 119
160+
other_serve_nodes = 1000
161+
self.assertNotEqual(serve_nodes, other_serve_nodes)
162+
cluster_pb = data_pb2.Cluster(serve_nodes=other_serve_nodes)
163+
with self.assertRaises(ValueError):
164+
self._callFUT(cluster_pb, 'serve_nodes', serve_nodes)
165+
166+
167+
class _Client(object):
168+
169+
def __init__(self, project):
170+
self.project = project

0 commit comments

Comments
 (0)