Skip to content

Commit

Permalink
Merge pull request #1253 from dhermes/bigtable-cluster-reload
Browse files Browse the repository at this point in the history
Implementing Cluster.reload().
  • Loading branch information
dhermes committed Dec 1, 2015
2 parents a7b395a + 8feea17 commit 13f8693
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 45 deletions.
2 changes: 2 additions & 0 deletions gcloud/bigtable/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@
'to set your LD_LIBRARY_PATH variable to help '
'Python locate the libraries.', file=sys.stderr)
raise

from gcloud.bigtable.client import Client
57 changes: 57 additions & 0 deletions gcloud/bigtable/_testing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Copyright 2015 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Mocks used to emulate gRPC generated objects."""


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

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

def __enter__(self):
self._entered += 1
return 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 API method attached to a gRPC stub.
In the beta implementation, these are of type.
:class:`grpc.framework.crust.implementations._UnaryUnaryMultiCallable`
"""

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

def __call__(self, *args, **kwargs):
"""Sync 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 curr_result
2 changes: 1 addition & 1 deletion gcloud/bigtable/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ class Client(_ClientFactoryMixin, _ClientProjectMixin):
:class:`OAuth2Credentials <oauth2client.client.OAuth2Credentials>` or
:data:`NoneType <types.NoneType>`
:param credentials: (Optional) The OAuth2 Credentials to use for this
cluster. If not provided, defaulst to the Google
cluster. If not provided, defaults to the Google
Application Default Credentials.
:type read_only: bool
Expand Down
34 changes: 33 additions & 1 deletion gcloud/bigtable/cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@

import re

from gcloud.bigtable._generated import (
bigtable_cluster_service_messages_pb2 as messages_pb2)
from gcloud.bigtable.table import Table


_CLUSTER_NAME_RE = re.compile(r'^projects/(?P<project>[^/]+)/'
r'zones/(?P<zone>[^/]+)/clusters/'
r'(?P<cluster_id>[a-z][-a-z0-9]*)$')
_DEFAULT_SERVE_NODES = 3


def _get_pb_property_value(message_pb, property_name):
Expand Down Expand Up @@ -74,7 +77,7 @@ class Cluster(object):
"""

def __init__(self, zone, cluster_id, client,
display_name=None, serve_nodes=3):
display_name=None, serve_nodes=_DEFAULT_SERVE_NODES):
self.zone = zone
self.cluster_id = cluster_id
self.display_name = display_name or cluster_id
Expand Down Expand Up @@ -124,6 +127,24 @@ def from_pb(cls, cluster_pb, client):
result._update_from_pb(cluster_pb)
return result

@property
def name(self):
"""Cluster name used in requests.
.. note::
This property will not change if ``zone`` and ``cluster_id`` do not,
but the return value is not cached.
The cluster name is of the form
``"projects/{project}/zones/{zone}/clusters/{cluster_id}"``
:rtype: str
:returns: The cluster name.
"""
return (self._client.project_name + '/zones/' + self.zone +
'/clusters/' + self.cluster_id)

def __eq__(self, other):
if not isinstance(other, self.__class__):
return False
Expand All @@ -139,3 +160,14 @@ def __eq__(self, other):

def __ne__(self, other):
return not self.__eq__(other)

def reload(self):
"""Reload the metadata for this cluster."""
request_pb = messages_pb2.GetClusterRequest(name=self.name)
# We expect a `._generated.bigtable_cluster_data_pb2.Cluster`.
cluster_pb = self._client._cluster_stub.GetCluster(
request_pb, self._client.timeout_seconds)

# NOTE: _update_from_pb does not check that the project, zone and
# cluster ID on the response match the request.
self._update_from_pb(cluster_pb)
47 changes: 5 additions & 42 deletions gcloud/bigtable/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@ def test_is_started(self):

def _start_method_helper(self, admin):
from gcloud._testing import _Monkey
from gcloud.bigtable._testing import _FakeStub
from gcloud.bigtable import client as MUT

credentials = _Credentials()
Expand Down Expand Up @@ -429,6 +430,8 @@ def test_start_while_started(self):
self.assertEqual(client._data_stub_internal, data_stub)

def _stop_method_helper(self, admin):
from gcloud.bigtable._testing import _FakeStub

credentials = _Credentials()
project = 'PROJECT'
client = self._makeOne(project=project, credentials=credentials,
Expand Down Expand Up @@ -499,6 +502,7 @@ def _list_zones_helper(self, zone_status):
bigtable_cluster_data_pb2 as data_pb2)
from gcloud.bigtable._generated import (
bigtable_cluster_service_messages_pb2 as messages_pb2)
from gcloud.bigtable._testing import _FakeStub

credentials = _Credentials()
project = 'PROJECT'
Expand Down Expand Up @@ -552,6 +556,7 @@ def test_list_clusters(self):
bigtable_cluster_data_pb2 as data_pb2)
from gcloud.bigtable._generated import (
bigtable_cluster_service_messages_pb2 as messages_pb2)
from gcloud.bigtable._testing import _FakeStub

credentials = _Credentials()
project = 'PROJECT'
Expand Down Expand Up @@ -625,45 +630,3 @@ def create_scoped(self, scope):

def __eq__(self, other):
return self.value == other.value


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

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

def __enter__(self):
self._entered += 1
return 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 API method attached to a gRPC stub.
In the beta implementation, these are of type.
:class:`grpc.framework.crust.implementations._UnaryUnaryMultiCallable`
"""

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

def __call__(self, *args, **kwargs):
"""Sync 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 curr_result
66 changes: 65 additions & 1 deletion gcloud/bigtable/test_cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,17 @@ def test_from_pb_project_mistmatch(self):
with self.assertRaises(ValueError):
klass.from_pb(cluster_pb, client)

def test_name_property(self):
project = 'PROJECT'
zone = 'zone'
cluster_id = 'cluster-id'
client = _Client(project=project)

cluster = self._makeOne(zone, cluster_id, client)
cluster_name = ('projects/' + project + '/zones/' + zone +
'/clusters/' + cluster_id)
self.assertEqual(cluster.name, cluster_name)

def test___eq__(self):
zone = 'zone'
cluster_id = 'cluster_id'
Expand Down Expand Up @@ -148,6 +159,57 @@ def test___ne__(self):
cluster2 = self._makeOne('zone2', 'cluster_id2', 'client2')
self.assertNotEqual(cluster1, cluster2)

def test_reload(self):
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)
from gcloud.bigtable._testing import _FakeStub
from gcloud.bigtable.cluster import _DEFAULT_SERVE_NODES

project = 'PROJECT'
zone = 'zone'
cluster_id = 'cluster-id'
timeout_seconds = 123
client = _Client(project=project, timeout_seconds=timeout_seconds)
cluster = self._makeOne(zone, cluster_id, client)

# Create request_pb
cluster_name = ('projects/' + project + '/zones/' + zone +
'/clusters/' + cluster_id)
request_pb = messages_pb2.GetClusterRequest(name=cluster_name)

# Create response_pb
serve_nodes = 31
display_name = u'hey-hi-hello'
response_pb = data_pb2.Cluster(
display_name=display_name,
serve_nodes=serve_nodes,
)

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

# Create expected_result.
expected_result = None # reload() has no return value.

# Check Cluster optional config values before.
self.assertEqual(cluster.serve_nodes, _DEFAULT_SERVE_NODES)
self.assertEqual(cluster.display_name, cluster_id)

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

# Check Cluster optional config values before.
self.assertEqual(cluster.serve_nodes, serve_nodes)
self.assertEqual(cluster.display_name, display_name)


class Test__get_pb_property_value(unittest2.TestCase):

Expand All @@ -173,5 +235,7 @@ def test_with_value_unset_on_pb(self):

class _Client(object):

def __init__(self, project):
def __init__(self, project, timeout_seconds=None):
self.project = project
self.project_name = 'projects/' + self.project
self.timeout_seconds = timeout_seconds

0 comments on commit 13f8693

Please sign in to comment.