From 94b40dd2f87d4d335a61844c0def5a92525a8dec Mon Sep 17 00:00:00 2001
From: Chris Yeoh <cyeoh@au1.ibm.com>
Date: Thu, 12 Dec 2013 16:18:51 +1030
Subject: [PATCH] Adds services support for Nova V3 API

Adds support and tests for the os-services extension
for the Nova V3 API

Partially implements blueprint v3-api

Change-Id: Ic64e465e24a6e840c567677d4f2ea9d63b742ccd
---
 novaclient/tests/v1_1/test_services.py | 93 ++++++++++++++++----------
 novaclient/tests/v3/test_services.py   | 40 +++++++++++
 novaclient/v1_1/services.py            | 13 +++-
 novaclient/v3/client.py                |  2 +
 novaclient/v3/services.py              | 36 ++++++++++
 5 files changed, 144 insertions(+), 40 deletions(-)
 create mode 100644 novaclient/tests/v3/test_services.py
 create mode 100644 novaclient/v3/services.py

diff --git a/novaclient/tests/v1_1/test_services.py b/novaclient/tests/v1_1/test_services.py
index 85e4f345c..8f9b7eb33 100644
--- a/novaclient/tests/v1_1/test_services.py
+++ b/novaclient/tests/v1_1/test_services.py
@@ -20,58 +20,77 @@ from novaclient.tests.v1_1 import fakes
 from novaclient.v1_1 import services
 
 
-cs = fakes.FakeClient()
-
-
 class ServicesTest(utils.TestCase):
+    def setUp(self):
+        super(ServicesTest, self).setUp()
+        self.cs = self._get_fake_client()
+        self.service_type = self._get_service_type()
+
+    def _get_fake_client(self):
+        return fakes.FakeClient()
+
+    def _get_service_type(self):
+        return services.Service
 
     def test_list_services(self):
-        svs = cs.services.list()
-        cs.assert_called('GET', '/os-services')
-        [self.assertTrue(isinstance(s, services.Service)) for s in svs]
-        [self.assertEqual(s.binary, 'nova-compute') for s in svs]
-        [self.assertEqual(s.host, 'host1') for s in svs]
+        svs = self.cs.services.list()
+        self.cs.assert_called('GET', '/os-services')
+        for s in svs:
+            self.assertTrue(isinstance(s, self._get_service_type()))
+            self.assertEqual(s.binary, 'nova-compute')
+            self.assertEqual(s.host, 'host1')
 
     def test_list_services_with_hostname(self):
-        svs = cs.services.list(host='host2')
-        cs.assert_called('GET', '/os-services?host=host2')
-        [self.assertTrue(isinstance(s, services.Service)) for s in svs]
-        [self.assertEqual(s.binary, 'nova-compute') for s in svs]
-        [self.assertEqual(s.host, 'host2') for s in svs]
+        svs = self.cs.services.list(host='host2')
+        self.cs.assert_called('GET', '/os-services?host=host2')
+        for s in svs:
+            self.assertTrue(isinstance(s, self._get_service_type()))
+            self.assertEqual(s.binary, 'nova-compute')
+            self.assertEqual(s.host, 'host2')
 
     def test_list_services_with_binary(self):
-        svs = cs.services.list(binary='nova-cert')
-        cs.assert_called('GET', '/os-services?binary=nova-cert')
-        [self.assertTrue(isinstance(s, services.Service)) for s in svs]
-        [self.assertEqual(s.binary, 'nova-cert') for s in svs]
-        [self.assertEqual(s.host, 'host1') for s in svs]
+        svs = self.cs.services.list(binary='nova-cert')
+        self.cs.assert_called('GET', '/os-services?binary=nova-cert')
+        for s in svs:
+            self.assertTrue(isinstance(s, self._get_service_type()))
+            self.assertEqual(s.binary, 'nova-cert')
+            self.assertEqual(s.host, 'host1')
 
     def test_list_services_with_host_binary(self):
-        svs = cs.services.list(host='host2', binary='nova-cert')
-        cs.assert_called('GET', '/os-services?host=host2&binary=nova-cert')
-        [self.assertTrue(isinstance(s, services.Service)) for s in svs]
-        [self.assertEqual(s.binary, 'nova-cert') for s in svs]
-        [self.assertEqual(s.host, 'host2') for s in svs]
+        svs = self.cs.services.list(host='host2', binary='nova-cert')
+        self.cs.assert_called('GET',
+                              '/os-services?host=host2&binary=nova-cert')
+        for s in svs:
+            self.assertTrue(isinstance(s, self._get_service_type()))
+            self.assertEqual(s.binary, 'nova-cert')
+            self.assertEqual(s.host, 'host2')
+
+    def _update_body(self, host, binary, disabled_reason=None):
+        body = {"host": host,
+                "binary": binary}
+        if disabled_reason is not None:
+            body["disabled_reason"] = disabled_reason
+        return body
 
     def test_services_enable(self):
-        service = cs.services.enable('host1', 'nova-cert')
-        values = {"host": "host1", 'binary': 'nova-cert'}
-        cs.assert_called('PUT', '/os-services/enable', values)
-        self.assertTrue(isinstance(service, services.Service))
+        service = self.cs.services.enable('host1', 'nova-cert')
+        values = self._update_body("host1", "nova-cert")
+        self.cs.assert_called('PUT', '/os-services/enable', values)
+        self.assertTrue(isinstance(service, self._get_service_type()))
         self.assertEqual(service.status, 'enabled')
 
     def test_services_disable(self):
-        service = cs.services.disable('host1', 'nova-cert')
-        values = {"host": "host1", 'binary': 'nova-cert'}
-        cs.assert_called('PUT', '/os-services/disable', values)
-        self.assertTrue(isinstance(service, services.Service))
+        service = self.cs.services.disable('host1', 'nova-cert')
+        values = self._update_body("host1", "nova-cert")
+        self.cs.assert_called('PUT', '/os-services/disable', values)
+        self.assertTrue(isinstance(service, self._get_service_type()))
         self.assertEqual(service.status, 'disabled')
 
     def test_services_disable_log_reason(self):
-        service = cs.services.disable_log_reason('compute1', 'nova-compute',
-                                                 'disable bad host')
-        values = {'host': 'compute1', 'binary': 'nova-compute',
-                  'disabled_reason': 'disable bad host'}
-        cs.assert_called('PUT', '/os-services/disable-log-reason', values)
-        self.assertTrue(isinstance(service, services.Service))
+        service = self.cs.services.disable_log_reason(
+            'compute1', 'nova-compute', 'disable bad host')
+        values = self._update_body("compute1", "nova-compute",
+                                   "disable bad host")
+        self.cs.assert_called('PUT', '/os-services/disable-log-reason', values)
+        self.assertTrue(isinstance(service, self._get_service_type()))
         self.assertEqual(service.status, 'disabled')
diff --git a/novaclient/tests/v3/test_services.py b/novaclient/tests/v3/test_services.py
new file mode 100644
index 000000000..2ad385489
--- /dev/null
+++ b/novaclient/tests/v3/test_services.py
@@ -0,0 +1,40 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 IBM Corp.
+# 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.
+
+from novaclient.tests import utils
+from novaclient.tests.v3 import fakes
+from novaclient.v3 import services
+
+
+class ServicesTest(utils.TestCase):
+    def setUp(self):
+        super(ServicesTest, self).setUp()
+        self.cs = self._get_fake_client()
+        self.service_type = self._get_service_type()
+
+    def _get_fake_client(self):
+        return fakes.FakeClient()
+
+    def _get_service_type(self):
+        return services.Service
+
+    def _update_body(self, host, binary, disabled_reason=None):
+        body = {"host": host,
+                "binary": binary}
+        if disabled_reason is not None:
+            body["disabled_reason"] = disabled_reason
+        return body
diff --git a/novaclient/v1_1/services.py b/novaclient/v1_1/services.py
index 03424026e..e37123601 100644
--- a/novaclient/v1_1/services.py
+++ b/novaclient/v1_1/services.py
@@ -50,17 +50,24 @@ class ServiceManager(base.ManagerWithFind):
             url = "%s?%s" % (url, "&".join(filters))
         return self._list(url, "services")
 
+    def _update_body(self, host, binary, disabled_reason=None):
+        body = {"host": host,
+                "binary": binary}
+        if disabled_reason is not None:
+            body["disabled_reason"] = disabled_reason
+        return body
+
     def enable(self, host, binary):
         """Enable the service specified by hostname and binary."""
-        body = {"host": host, "binary": binary}
+        body = self._update_body(host, binary)
         return self._update("/os-services/enable", body, "service")
 
     def disable(self, host, binary):
         """Disable the service specified by hostname and binary."""
-        body = {"host": host, "binary": binary}
+        body = self._update_body(host, binary)
         return self._update("/os-services/disable", body, "service")
 
     def disable_log_reason(self, host, binary, reason):
         """Disable the service with reason."""
-        body = {"host": host, "binary": binary, "disabled_reason": reason}
+        body = self._update_body(host, binary, reason)
         return self._update("/os-services/disable-log-reason", body, "service")
diff --git a/novaclient/v3/client.py b/novaclient/v3/client.py
index 20d67bd96..e4b51866b 100644
--- a/novaclient/v3/client.py
+++ b/novaclient/v3/client.py
@@ -24,6 +24,7 @@ from novaclient.v3 import images
 from novaclient.v3 import quota_classes
 from novaclient.v3 import quotas
 from novaclient.v3 import servers
+from novaclient.v3 import services
 
 
 class Client(object):
@@ -68,6 +69,7 @@ class Client(object):
         self.quotas = quotas.QuotaSetManager(self)
         self.quota_classes = quota_classes.QuotaClassSetManager(self)
         self.servers = servers.ServerManager(self)
+        self.services = services.ServiceManager(self)
 
         # Add in any extensions...
         if extensions:
diff --git a/novaclient/v3/services.py b/novaclient/v3/services.py
new file mode 100644
index 000000000..9415755b2
--- /dev/null
+++ b/novaclient/v3/services.py
@@ -0,0 +1,36 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corp.
+#
+#    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.
+
+"""
+service interface
+"""
+from novaclient.v1_1 import services
+
+
+class Service(services.Service):
+    pass
+
+
+class ServiceManager(services.ServiceManager):
+    resource_class = Service
+
+    def _update_body(self, host, binary, disabled_reason=None):
+        body = {"service":
+                {"host": host,
+                 "binary": binary}}
+        if disabled_reason is not None:
+            body["service"]["disabled_reason"] = disabled_reason
+        return body