You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@aurora.apache.org by mc...@apache.org on 2014/02/06 20:43:37 UTC

git commit: Add a "quota" noun for querying role quotas.

Updated Branches:
  refs/heads/master 1545e7f56 -> d2dc5715b


Add a "quota" noun for querying role quotas.

Reviewed in https://reviews.apache.org/r/17332/


Project: http://git-wip-us.apache.org/repos/asf/incubator-aurora/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-aurora/commit/d2dc5715
Tree: http://git-wip-us.apache.org/repos/asf/incubator-aurora/tree/d2dc5715
Diff: http://git-wip-us.apache.org/repos/asf/incubator-aurora/diff/d2dc5715

Branch: refs/heads/master
Commit: d2dc5715b551c81c02dfe87645010c4db70fd2eb
Parents: 1545e7f
Author: Mark Chu-Carroll <mc...@twopensource.com>
Authored: Thu Feb 6 14:39:40 2014 -0500
Committer: Mark Chu-Carroll <mc...@twitter.com>
Committed: Thu Feb 6 14:39:40 2014 -0500

----------------------------------------------------------------------
 src/main/python/apache/aurora/client/cli/BUILD  |  10 +-
 .../python/apache/aurora/client/cli/__init__.py |   2 +
 .../python/apache/aurora/client/cli/options.py  |  16 +++
 .../python/apache/aurora/client/cli/quota.py    | 108 +++++++++++++++++++
 src/test/python/apache/aurora/client/cli/BUILD  |  14 ++-
 .../apache/aurora/client/cli/test_quota.py      |  90 ++++++++++++++++
 .../python/apache/aurora/client/cli/util.py     |  14 +++
 7 files changed, 247 insertions(+), 7 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/d2dc5715/src/main/python/apache/aurora/client/cli/BUILD
----------------------------------------------------------------------
diff --git a/src/main/python/apache/aurora/client/cli/BUILD b/src/main/python/apache/aurora/client/cli/BUILD
index 1019e00..63954d6 100644
--- a/src/main/python/apache/aurora/client/cli/BUILD
+++ b/src/main/python/apache/aurora/client/cli/BUILD
@@ -16,15 +16,13 @@
 
 python_binary(
   name='client',
-  entry_point = 'apache.aurora.cli:main',
-  dependencies = [
-    pants(':cli'),
-  ],
-)
+  entry_point = 'apache.aurora.client.cli:main',
+  dependencies = [ pants(':cli') ],
+  )
 
 python_library(
   name='cli',
-  sources = [ '__init__.py', 'context.py', 'jobs.py', 'options.py' ],
+  sources = globs('*.py'),
   dependencies = [
     pants('3rdparty/python:argparse'),
     pants('3rdparty/python:twitter.common.python'),

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/d2dc5715/src/main/python/apache/aurora/client/cli/__init__.py
----------------------------------------------------------------------
diff --git a/src/main/python/apache/aurora/client/cli/__init__.py b/src/main/python/apache/aurora/client/cli/__init__.py
index 20ecbcf..14a6676 100644
--- a/src/main/python/apache/aurora/client/cli/__init__.py
+++ b/src/main/python/apache/aurora/client/cli/__init__.py
@@ -223,6 +223,8 @@ class AuroraCommandLine(CommandLine):
   def register_nouns(self):
     from .jobs import Job
     self.register_noun(Job())
+    from .quota import Quota
+    self.register_noun(Quota())
 
 
 def main():

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/d2dc5715/src/main/python/apache/aurora/client/cli/options.py
----------------------------------------------------------------------
diff --git a/src/main/python/apache/aurora/client/cli/options.py b/src/main/python/apache/aurora/client/cli/options.py
index b45af32..5d6eba2 100644
--- a/src/main/python/apache/aurora/client/cli/options.py
+++ b/src/main/python/apache/aurora/client/cli/options.py
@@ -18,6 +18,15 @@ from apache.aurora.client.cli import CommandOption
 from apache.aurora.common.aurora_job_key import AuroraJobKey
 
 
+def parse_qualified_role(rolestr):
+  if rolestr is None:
+    raise ValueError('Role argument cannot be empty!')
+  role_parts = rolestr.split('/')
+  if len(role_parts) != 2:
+    raise ValueError('Role argument must be a CLUSTER/NAME pair')
+  return role_parts
+
+
 def parse_instances(instances):
   """Parse lists of instances or instance ranges into a set().
      Examples:
@@ -83,6 +92,13 @@ JSON_WRITE_OPTION = CommandOption('--write_json', default=False, dest='write_jso
     help='Generate command output in JSON format')
 
 
+ROLE_ARGUMENT = CommandOption('role', type=parse_qualified_role,
+    help='Rolename to retrieve information about, in CLUSTER/NAME format')
+
+
 WATCH_OPTION = CommandOption('--watch_secs', type=int, default=30,
     help='Minimum number of seconds a shard must remain in RUNNING state before considered a '
          'success.')
+
+
+

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/d2dc5715/src/main/python/apache/aurora/client/cli/quota.py
----------------------------------------------------------------------
diff --git a/src/main/python/apache/aurora/client/cli/quota.py b/src/main/python/apache/aurora/client/cli/quota.py
new file mode 100644
index 0000000..a7bcfbe
--- /dev/null
+++ b/src/main/python/apache/aurora/client/cli/quota.py
@@ -0,0 +1,108 @@
+#
+# Copyright 2013 Apache Software Foundation
+#
+# 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.
+#
+
+from __future__ import print_function
+
+from apache.aurora.client.cli import (
+    EXIT_INVALID_PARAMETER,
+    EXIT_OK,
+    Noun,
+    Verb,
+)
+from apache.aurora.client.cli.context import AuroraCommandContext
+from apache.aurora.client.cli.options import (
+    JSON_WRITE_OPTION,
+    ROLE_ARGUMENT,
+)
+
+from gen.apache.aurora.ttypes import ResponseCode
+
+from thrift.protocol import TJSONProtocol
+from thrift.TSerialization import serialize
+
+
+class GetQuotaCmd(Verb):
+  @property
+  def name(self):
+    return 'get'
+
+  @property
+  def help(self):
+    return """Usage: aurora quota get cluster/role
+
+    Prints information about quotas for a role
+    """
+
+  def setup_options_parser(self, parser):
+    self.add_option(parser, JSON_WRITE_OPTION)
+    self.add_option(parser, ROLE_ARGUMENT)
+
+  def render_quota(self, write_json, quota_resp):
+    def get_quota_json(quota):
+      result = {}
+      result['cpu'] = quota.numCpus
+      result['ram'] = float(quota.ramMb) / 1024
+      result['disk'] = float(quota.diskMb) / 1024
+      return result
+
+    def get_quota_str(quota):
+      result = []
+      result.append('  CPU: %s' % quota.numCpus)
+      result.append('  RAM: %f GB' % (float(quota.ramMb) / 1024))
+      result.append('  Disk: %f GB' % (float(quota.diskMb) / 1024))
+      return result
+
+    if write_json:
+      return serialize(quota_resp.result.getQuotaResult,
+          protocol_factory=TJSONProtocol.TSimpleJSONProtocolFactory())
+    else:
+      result = ['Allocated:']
+      result += get_quota_str(quota_resp.result.getQuotaResult.quota)
+      if quota_resp.result.getQuotaResult.consumed:
+        result.append('Consumed:')
+        result += get_quota_str(quota_resp.result.getQuotaResult.consumed)
+      return '\n'.join(result)
+
+  def execute(self, context):
+    (cluster, role) = context.options.role
+    api = context.get_api(cluster)
+    resp = api.get_quota(role)
+    if resp.responseCode == ResponseCode.ERROR:
+      raise context.CommandError(EXIT_INVALID_PARAMETER, 'Role %s not found' % role)
+    elif resp.responseCode == ResponseCode.INVALID_REQUEST:
+      raise context.CommandError(EXIT_COMMAND_FAILURE, 'Error retrieving quota for role %s' % role)
+    elif resp.responseCode == ResponseCode.AUTH_FAILED:
+      raise context.CommandError(EXIT_COMMAND_FAILURE, 'Invalid authorization to retrieve quota for role %s' % role)
+    context.print_out(self.render_quota(context.options.write_json, resp))
+    return EXIT_OK
+
+
+class Quota(Noun):
+  @property
+  def name(self):
+    return 'quota'
+
+  @property
+  def help(self):
+    return "Work with quota settings for an Apache Aurora cluster"
+
+  @classmethod
+  def create_context(cls):
+    return AuroraCommandContext()
+
+  def __init__(self):
+    super(Quota, self).__init__()
+    self.register_verb(GetQuotaCmd())

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/d2dc5715/src/test/python/apache/aurora/client/cli/BUILD
----------------------------------------------------------------------
diff --git a/src/test/python/apache/aurora/client/cli/BUILD b/src/test/python/apache/aurora/client/cli/BUILD
index 5ba08ad..51cc380 100644
--- a/src/test/python/apache/aurora/client/cli/BUILD
+++ b/src/test/python/apache/aurora/client/cli/BUILD
@@ -16,7 +16,7 @@
 
 python_test_suite(
   name = 'all',
-  dependencies = [ pants(':job') ]
+  dependencies = [ pants(':job'), pants(':quota') ]
 )
 
 python_library(
@@ -47,3 +47,15 @@ python_tests(
     pants('src/test/python/apache/aurora/client/commands:util')
   ]
 )
+
+python_tests(
+  name = 'quota',
+  sources = [ 'test_quota.py' ],
+  dependencies = [
+    pants(':util'),
+    pants('3rdparty/python:mock'),
+    pants('3rdparty/python:twitter.common.contextutil'),
+    pants('src/main/python/apache/aurora/client/cli'),
+    pants('src/test/python/apache/aurora/client/commands:util')
+  ]
+)

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/d2dc5715/src/test/python/apache/aurora/client/cli/test_quota.py
----------------------------------------------------------------------
diff --git a/src/test/python/apache/aurora/client/cli/test_quota.py b/src/test/python/apache/aurora/client/cli/test_quota.py
new file mode 100644
index 0000000..d582fce
--- /dev/null
+++ b/src/test/python/apache/aurora/client/cli/test_quota.py
@@ -0,0 +1,90 @@
+#
+# Copyright 2013 Apache Software Foundation
+#
+# 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.
+#
+
+import contextlib
+
+from apache.aurora.client.cli import AuroraCommandLine
+from apache.aurora.client.cli.util import AuroraClientCommandTest, FakeAuroraCommandContext
+
+from gen.apache.aurora.ttypes import (
+    GetQuotaResult,
+    Quota,
+)
+
+from mock import patch
+
+
+class TestGetQuotaCommand(AuroraClientCommandTest):
+  @classmethod
+  def setup_mock_quota_call_no_consumed(cls, mock_context):
+    api = mock_context.get_api('west')
+    response = cls.create_simple_success_response()
+    response.result.getQuotaResult = GetQuotaResult()
+    response.result.getQuotaResult.quota = Quota()
+    response.result.getQuotaResult.quota.numCpus = 5
+    response.result.getQuotaResult.quota.ramMb = 20480
+    response.result.getQuotaResult.quota.diskMb = 40960
+    response.result.getQuotaResult.consumed = None
+    api.get_quota.return_value = response
+
+  @classmethod
+  def setup_mock_quota_call_with_consumed(cls, mock_context):
+    api = mock_context.get_api('west')
+    response = cls.create_simple_success_response()
+    response.result.getQuotaResult = GetQuotaResult()
+    response.result.getQuotaResult.quota = Quota()
+    response.result.getQuotaResult.quota.numCpus = 5
+    response.result.getQuotaResult.quota.ramMb = 20480
+    response.result.getQuotaResult.quota.diskMb = 40960
+    response.result.getQuotaResult.consumed = Quota()
+    response.result.getQuotaResult.consumed.numCpus = 1
+    response.result.getQuotaResult.consumed.ramMb = 1024
+    response.result.getQuotaResult.consumed.diskMb = 2048
+    api.get_quota.return_value = response
+
+  def test_get_quota_no_consumed(self):
+    mock_context = FakeAuroraCommandContext()
+    self.setup_mock_quota_call_no_consumed(mock_context)
+    with contextlib.nested(
+        patch('apache.aurora.client.cli.quota.Quota.create_context', return_value=mock_context),
+        patch('apache.aurora.client.factory.CLUSTERS', new=self.TEST_CLUSTERS)):
+      cmd = AuroraCommandLine()
+      cmd.execute(['quota', 'get', 'west/bozo'])
+      out = '\n'.join(mock_context.get_out())
+      assert out == "Allocated:\n  CPU: 5\n  RAM: 20.000000 GB\n  Disk: 40.000000 GB"
+
+  def test_get_quota_with_consumed(self):
+    mock_context = FakeAuroraCommandContext()
+    self.setup_mock_quota_call_with_consumed(mock_context)
+    with contextlib.nested(
+        patch('apache.aurora.client.cli.quota.Quota.create_context', return_value=mock_context),
+        patch('apache.aurora.client.factory.CLUSTERS', new=self.TEST_CLUSTERS)):
+      cmd = AuroraCommandLine()
+      cmd.execute(['quota', 'get', 'west/bozo'])
+      out = '\n'.join(mock_context.get_out())
+      assert out == ("Allocated:\n  CPU: 5\n  RAM: 20.000000 GB\n  Disk: 40.000000 GB\n"
+          "Consumed:\n  CPU: 1\n  RAM: 1.000000 GB\n  Disk: 2.000000 GB")
+
+  def test_get_quota_with_consumed_json(self):
+    mock_context = FakeAuroraCommandContext()
+    self.setup_mock_quota_call_no_consumed(mock_context)
+    with contextlib.nested(
+        patch('apache.aurora.client.cli.quota.Quota.create_context', return_value=mock_context),
+        patch('apache.aurora.client.factory.CLUSTERS', new=self.TEST_CLUSTERS)):
+      cmd = AuroraCommandLine()
+      cmd.execute(['quota', 'get', '--write_json', 'west/bozo'])
+      out = '\n'.join(mock_context.get_out())
+      assert out == '{"quota":{"numCpus":5,"ramMb":20480,"diskMb":40960}}'

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/d2dc5715/src/test/python/apache/aurora/client/cli/util.py
----------------------------------------------------------------------
diff --git a/src/test/python/apache/aurora/client/cli/util.py b/src/test/python/apache/aurora/client/cli/util.py
index cbd544d..db65eee 100644
--- a/src/test/python/apache/aurora/client/cli/util.py
+++ b/src/test/python/apache/aurora/client/cli/util.py
@@ -38,6 +38,8 @@ class FakeAuroraCommandContext(AuroraCommandContext):
     self.fake_api = self.create_mock_api()
     self.task_status = []
     self.showed_urls = []
+    self.out = []
+    self.err = []
 
   def get_api(self, cluster):
     return self.fake_api
@@ -59,6 +61,18 @@ class FakeAuroraCommandContext(AuroraCommandContext):
     mock_api.scheduler_proxy = mock_scheduler_proxy
     return mock_api
 
+  def print_out(self, str):
+    self.out.append(str)
+
+  def print_err(self, str):
+    self.err.append(str)
+
+  def get_out(self):
+    return self.out
+
+  def get_err(self):
+    return self.err
+
   def open_page(self, url):
     self.showed_urls.append(url)