Add cluster related commands
This patch updates client to support cluster related changes on the API done on microversion 3.7. Service listing will include "cluster_name" field and we have 4 new commands, "cluster-list", "cluster-show", "cluster-enable" and "cluster-disable". Specs: https://review.openstack.org/327283 Implements: blueprint cinder-volume-active-active-support Depends-On: If1ef3a80900ca6d117bf854ad3de142d93694adf Change-Id: I824f46b876e21e552d9f0c5cd3e836f35ea31837
This commit is contained in:
parent
c95539753d
commit
25bc7e7402
@ -32,7 +32,7 @@ if not LOG.handlers:
|
|||||||
|
|
||||||
# key is a deprecated version and value is an alternative version.
|
# key is a deprecated version and value is an alternative version.
|
||||||
DEPRECATED_VERSIONS = {"1": "2"}
|
DEPRECATED_VERSIONS = {"1": "2"}
|
||||||
MAX_VERSION = "3.1"
|
MAX_VERSION = "3.7"
|
||||||
|
|
||||||
_SUBSTITUTIONS = {}
|
_SUBSTITUTIONS = {}
|
||||||
|
|
||||||
|
@ -27,7 +27,10 @@ class ServicesTest(utils.TestCase):
|
|||||||
svs = cs.services.list()
|
svs = cs.services.list()
|
||||||
cs.assert_called('GET', '/os-services')
|
cs.assert_called('GET', '/os-services')
|
||||||
self.assertEqual(3, len(svs))
|
self.assertEqual(3, len(svs))
|
||||||
[self.assertIsInstance(s, services.Service) for s in svs]
|
for service in svs:
|
||||||
|
self.assertIsInstance(service, services.Service)
|
||||||
|
# Make sure cluster fields from v3.7 are not there
|
||||||
|
self.assertFalse(hasattr(service, 'cluster'))
|
||||||
self._assert_request_id(svs)
|
self._assert_request_id(svs)
|
||||||
|
|
||||||
def test_list_services_with_hostname(self):
|
def test_list_services_with_hostname(self):
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from cinderclient.tests.unit import fakes
|
from cinderclient.tests.unit import fakes
|
||||||
from cinderclient.v3 import client
|
from cinderclient.v3 import client
|
||||||
from cinderclient.tests.unit.v2 import fakes as fake_v2
|
from cinderclient.tests.unit.v2 import fakes as fake_v2
|
||||||
@ -34,3 +36,138 @@ class FakeHTTPClient(fake_v2.FakeHTTPClient):
|
|||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super(FakeHTTPClient, self).__init__()
|
super(FakeHTTPClient, self).__init__()
|
||||||
self.management_url = 'http://10.0.2.15:8776/v3/fake'
|
self.management_url = 'http://10.0.2.15:8776/v3/fake'
|
||||||
|
vars(self).update(kwargs)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Services
|
||||||
|
#
|
||||||
|
def get_os_services(self, **kw):
|
||||||
|
host = kw.get('host', None)
|
||||||
|
binary = kw.get('binary', None)
|
||||||
|
services = [
|
||||||
|
{
|
||||||
|
'id': 1,
|
||||||
|
'binary': 'cinder-volume',
|
||||||
|
'host': 'host1',
|
||||||
|
'zone': 'cinder',
|
||||||
|
'status': 'enabled',
|
||||||
|
'state': 'up',
|
||||||
|
'updated_at': datetime(2012, 10, 29, 13, 42, 2),
|
||||||
|
'cluster': 'cluster1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 2,
|
||||||
|
'binary': 'cinder-volume',
|
||||||
|
'host': 'host2',
|
||||||
|
'zone': 'cinder',
|
||||||
|
'status': 'disabled',
|
||||||
|
'state': 'down',
|
||||||
|
'updated_at': datetime(2012, 9, 18, 8, 3, 38),
|
||||||
|
'cluster': 'cluster1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 3,
|
||||||
|
'binary': 'cinder-scheduler',
|
||||||
|
'host': 'host2',
|
||||||
|
'zone': 'cinder',
|
||||||
|
'status': 'disabled',
|
||||||
|
'state': 'down',
|
||||||
|
'updated_at': datetime(2012, 9, 18, 8, 3, 38),
|
||||||
|
'cluster': 'cluster2',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
if host:
|
||||||
|
services = list(filter(lambda i: i['host'] == host, services))
|
||||||
|
if binary:
|
||||||
|
services = list(filter(lambda i: i['binary'] == binary, services))
|
||||||
|
if not self.api_version.matches('3.7'):
|
||||||
|
for svc in services:
|
||||||
|
del svc['cluster']
|
||||||
|
return (200, {}, {'services': services})
|
||||||
|
|
||||||
|
#
|
||||||
|
# Clusters
|
||||||
|
#
|
||||||
|
def _filter_clusters(self, return_keys, **kw):
|
||||||
|
date = datetime(2012, 10, 29, 13, 42, 2),
|
||||||
|
clusters = [
|
||||||
|
{
|
||||||
|
'id': '1',
|
||||||
|
'name': 'cluster1@lvmdriver-1',
|
||||||
|
'state': 'up',
|
||||||
|
'status': 'enabled',
|
||||||
|
'binary': 'cinder-volume',
|
||||||
|
'is_up': 'True',
|
||||||
|
'disabled': 'False',
|
||||||
|
'disabled_reason': None,
|
||||||
|
'num_hosts': '3',
|
||||||
|
'num_down_hosts': '2',
|
||||||
|
'updated_at': date,
|
||||||
|
'created_at': date,
|
||||||
|
'last_heartbeat': date,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': '2',
|
||||||
|
'name': 'cluster1@lvmdriver-2',
|
||||||
|
'state': 'down',
|
||||||
|
'status': 'enabled',
|
||||||
|
'binary': 'cinder-volume',
|
||||||
|
'is_up': 'False',
|
||||||
|
'disabled': 'False',
|
||||||
|
'disabled_reason': None,
|
||||||
|
'num_hosts': '2',
|
||||||
|
'num_down_hosts': '2',
|
||||||
|
'updated_at': date,
|
||||||
|
'created_at': date,
|
||||||
|
'last_heartbeat': date,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': '3',
|
||||||
|
'name': 'cluster2',
|
||||||
|
'state': 'up',
|
||||||
|
'status': 'disabled',
|
||||||
|
'binary': 'cinder-backup',
|
||||||
|
'is_up': 'True',
|
||||||
|
'disabled': 'True',
|
||||||
|
'disabled_reason': 'Reason',
|
||||||
|
'num_hosts': '1',
|
||||||
|
'num_down_hosts': '0',
|
||||||
|
'updated_at': date,
|
||||||
|
'created_at': date,
|
||||||
|
'last_heartbeat': date,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
for key, value in kw.items():
|
||||||
|
clusters = [cluster for cluster in clusters
|
||||||
|
if cluster[key] == str(value)]
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for cluster in clusters:
|
||||||
|
result.append({key: cluster[key] for key in return_keys})
|
||||||
|
return result
|
||||||
|
|
||||||
|
CLUSTER_SUMMARY_KEYS = ('name', 'binary', 'state', 'status')
|
||||||
|
CLUSTER_DETAIL_KEYS = (CLUSTER_SUMMARY_KEYS +
|
||||||
|
('num_hosts', 'num_down_hosts', 'last_heartbeat',
|
||||||
|
'disabled_reason', 'created_at', 'updated_at'))
|
||||||
|
|
||||||
|
def get_clusters(self, **kw):
|
||||||
|
clusters = self._filter_clusters(self.CLUSTER_SUMMARY_KEYS, **kw)
|
||||||
|
return (200, {}, {'clusters': clusters})
|
||||||
|
|
||||||
|
def get_clusters_detail(self, **kw):
|
||||||
|
clusters = self._filter_clusters(self.CLUSTER_DETAIL_KEYS, **kw)
|
||||||
|
return (200, {}, {'clusters': clusters})
|
||||||
|
|
||||||
|
def get_clusters_1(self):
|
||||||
|
res = self.get_clusters_detail(id=1)
|
||||||
|
return (200, {}, {'cluster': res[2]['clusters'][0]})
|
||||||
|
|
||||||
|
def put_clusters_enable(self, body):
|
||||||
|
res = self.get_clusters(id=1)
|
||||||
|
return (200, {}, {'cluster': res[2]['clusters'][0]})
|
||||||
|
|
||||||
|
def put_clusters_disable(self, body):
|
||||||
|
res = self.get_clusters(id=3)
|
||||||
|
return (200, {}, {'cluster': res[2]['clusters'][0]})
|
||||||
|
128
cinderclient/tests/unit/v3/test_clusters.py
Normal file
128
cinderclient/tests/unit/v3/test_clusters.py
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
# Copyright (c) 2016 Red Hat 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.
|
||||||
|
|
||||||
|
from cinderclient.tests.unit import utils
|
||||||
|
from cinderclient.tests.unit.v3 import fakes
|
||||||
|
import ddt
|
||||||
|
|
||||||
|
|
||||||
|
cs = fakes.FakeClient()
|
||||||
|
|
||||||
|
|
||||||
|
@ddt.ddt
|
||||||
|
class ClusterTest(utils.TestCase):
|
||||||
|
def _check_fields_present(self, clusters, detailed=False):
|
||||||
|
expected_keys = {'name', 'binary', 'state', 'status'}
|
||||||
|
|
||||||
|
if detailed:
|
||||||
|
expected_keys.update(('num_hosts', 'num_down_hosts',
|
||||||
|
'last_heartbeat', 'disabled_reason',
|
||||||
|
'created_at', 'updated_at'))
|
||||||
|
|
||||||
|
for cluster in clusters:
|
||||||
|
self.assertEqual(expected_keys, set(cluster.to_dict()))
|
||||||
|
|
||||||
|
def _assert_call(self, base_url, detailed, params=None, method='GET',
|
||||||
|
body=None):
|
||||||
|
url = base_url
|
||||||
|
if detailed:
|
||||||
|
url += '/detail'
|
||||||
|
if params:
|
||||||
|
url += '?' + params
|
||||||
|
if body:
|
||||||
|
cs.assert_called(method, url, body)
|
||||||
|
else:
|
||||||
|
cs.assert_called(method, url)
|
||||||
|
|
||||||
|
@ddt.data(True, False)
|
||||||
|
def test_clusters_list(self, detailed):
|
||||||
|
lst = cs.clusters.list(detailed=detailed)
|
||||||
|
self._assert_call('/clusters', detailed)
|
||||||
|
self.assertEqual(3, len(lst))
|
||||||
|
self._assert_request_id(lst)
|
||||||
|
self._check_fields_present(lst, detailed)
|
||||||
|
|
||||||
|
@ddt.data(True, False)
|
||||||
|
def test_cluster_list_name(self, detailed):
|
||||||
|
lst = cs.clusters.list(name='cluster1@lvmdriver-1',
|
||||||
|
detailed=detailed)
|
||||||
|
self._assert_call('/clusters', detailed,
|
||||||
|
'name=cluster1@lvmdriver-1')
|
||||||
|
self.assertEqual(1, len(lst))
|
||||||
|
self._assert_request_id(lst)
|
||||||
|
self._check_fields_present(lst, detailed)
|
||||||
|
|
||||||
|
@ddt.data(True, False)
|
||||||
|
def test_clusters_list_binary(self, detailed):
|
||||||
|
lst = cs.clusters.list(binary='cinder-volume', detailed=detailed)
|
||||||
|
self._assert_call('/clusters', detailed, 'binary=cinder-volume')
|
||||||
|
self.assertEqual(2, len(lst))
|
||||||
|
self._assert_request_id(lst)
|
||||||
|
self._check_fields_present(lst, detailed)
|
||||||
|
|
||||||
|
@ddt.data(True, False)
|
||||||
|
def test_clusters_list_is_up(self, detailed):
|
||||||
|
lst = cs.clusters.list(is_up=True, detailed=detailed)
|
||||||
|
self._assert_call('/clusters', detailed, 'is_up=True')
|
||||||
|
self.assertEqual(2, len(lst))
|
||||||
|
self._assert_request_id(lst)
|
||||||
|
self._check_fields_present(lst, detailed)
|
||||||
|
|
||||||
|
@ddt.data(True, False)
|
||||||
|
def test_clusters_list_disabled(self, detailed):
|
||||||
|
lst = cs.clusters.list(disabled=True, detailed=detailed)
|
||||||
|
self._assert_call('/clusters', detailed, 'disabled=True')
|
||||||
|
self.assertEqual(1, len(lst))
|
||||||
|
self._assert_request_id(lst)
|
||||||
|
self._check_fields_present(lst, detailed)
|
||||||
|
|
||||||
|
@ddt.data(True, False)
|
||||||
|
def test_clusters_list_num_hosts(self, detailed):
|
||||||
|
lst = cs.clusters.list(num_hosts=1, detailed=detailed)
|
||||||
|
self._assert_call('/clusters', detailed, 'num_hosts=1')
|
||||||
|
self.assertEqual(1, len(lst))
|
||||||
|
self._assert_request_id(lst)
|
||||||
|
self._check_fields_present(lst, detailed)
|
||||||
|
|
||||||
|
@ddt.data(True, False)
|
||||||
|
def test_clusters_list_num_down_hosts(self, detailed):
|
||||||
|
lst = cs.clusters.list(num_down_hosts=2, detailed=detailed)
|
||||||
|
self._assert_call('/clusters', detailed, 'num_down_hosts=2')
|
||||||
|
self.assertEqual(2, len(lst))
|
||||||
|
self._assert_request_id(lst)
|
||||||
|
self._check_fields_present(lst, detailed)
|
||||||
|
|
||||||
|
def test_cluster_show(self):
|
||||||
|
result = cs.clusters.show('1')
|
||||||
|
self._assert_call('/clusters/1', False)
|
||||||
|
self._assert_request_id(result)
|
||||||
|
self._check_fields_present([result], True)
|
||||||
|
|
||||||
|
def test_cluster_enable(self):
|
||||||
|
body = {'binary': 'cinder-volume', 'name': 'cluster@lvmdriver-1'}
|
||||||
|
result = cs.clusters.update(body['name'], body['binary'], False,
|
||||||
|
disabled_reason='is ignored')
|
||||||
|
self._assert_call('/clusters/enable', False, method='PUT', body=body)
|
||||||
|
self._assert_request_id(result)
|
||||||
|
self._check_fields_present([result], False)
|
||||||
|
|
||||||
|
def test_cluster_disable(self):
|
||||||
|
body = {'binary': 'cinder-volume', 'name': 'cluster@lvmdriver-1',
|
||||||
|
'disabled_reason': 'is passed'}
|
||||||
|
result = cs.clusters.update(body['name'], body['binary'], True,
|
||||||
|
body['disabled_reason'])
|
||||||
|
self._assert_call('/clusters/disable', False, method='PUT', body=body)
|
||||||
|
self._assert_request_id(result)
|
||||||
|
self._check_fields_present([result], False)
|
33
cinderclient/tests/unit/v3/test_services.py
Normal file
33
cinderclient/tests/unit/v3/test_services.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# Copyright (c) 2016 Red Hat 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.
|
||||||
|
|
||||||
|
from cinderclient.tests.unit import utils
|
||||||
|
from cinderclient.tests.unit.v3 import fakes
|
||||||
|
from cinderclient.v3 import services
|
||||||
|
from cinderclient import api_versions
|
||||||
|
|
||||||
|
|
||||||
|
class ServicesTest(utils.TestCase):
|
||||||
|
|
||||||
|
def test_list_services_with_cluster_info(self):
|
||||||
|
cs = fakes.FakeClient(api_version=api_versions.APIVersion('3.7'))
|
||||||
|
services_list = cs.services.list()
|
||||||
|
cs.assert_called('GET', '/os-services')
|
||||||
|
self.assertEqual(3, len(services_list))
|
||||||
|
for service in services_list:
|
||||||
|
self.assertIsInstance(service, services.Service)
|
||||||
|
# Make sure cluster fields from v3.7 is present and not None
|
||||||
|
self.assertIsNotNone(getattr(service, 'cluster'))
|
||||||
|
self._assert_request_id(services_list)
|
@ -17,6 +17,7 @@ from cinderclient import client
|
|||||||
from cinderclient import api_versions
|
from cinderclient import api_versions
|
||||||
from cinderclient.v3 import availability_zones
|
from cinderclient.v3 import availability_zones
|
||||||
from cinderclient.v3 import cgsnapshots
|
from cinderclient.v3 import cgsnapshots
|
||||||
|
from cinderclient.v3 import clusters
|
||||||
from cinderclient.v3 import consistencygroups
|
from cinderclient.v3 import consistencygroups
|
||||||
from cinderclient.v3 import capabilities
|
from cinderclient.v3 import capabilities
|
||||||
from cinderclient.v3 import limits
|
from cinderclient.v3 import limits
|
||||||
@ -77,6 +78,7 @@ class Client(object):
|
|||||||
self.restores = volume_backups_restore.VolumeBackupRestoreManager(self)
|
self.restores = volume_backups_restore.VolumeBackupRestoreManager(self)
|
||||||
self.transfers = volume_transfers.VolumeTransferManager(self)
|
self.transfers = volume_transfers.VolumeTransferManager(self)
|
||||||
self.services = services.ServiceManager(self)
|
self.services = services.ServiceManager(self)
|
||||||
|
self.clusters = clusters.ClusterManager(self)
|
||||||
self.consistencygroups = consistencygroups.\
|
self.consistencygroups = consistencygroups.\
|
||||||
ConsistencygroupManager(self)
|
ConsistencygroupManager(self)
|
||||||
self.cgsnapshots = cgsnapshots.CgsnapshotManager(self)
|
self.cgsnapshots = cgsnapshots.CgsnapshotManager(self)
|
||||||
|
83
cinderclient/v3/clusters.py
Normal file
83
cinderclient/v3/clusters.py
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
# Copyright (c) 2016 Red Hat, 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Interface to clusters API
|
||||||
|
"""
|
||||||
|
from cinderclient import base
|
||||||
|
|
||||||
|
|
||||||
|
class Cluster(base.Resource):
|
||||||
|
def __repr__(self):
|
||||||
|
return "<Cluster: %s (id: %s)>" % (self.name, self.id)
|
||||||
|
|
||||||
|
|
||||||
|
class ClusterManager(base.ManagerWithFind):
|
||||||
|
resource_class = Cluster
|
||||||
|
base_url = '/clusters'
|
||||||
|
|
||||||
|
def _build_url(self, url_path=None, **kwargs):
|
||||||
|
url = self.base_url + ('/' + url_path if url_path else '')
|
||||||
|
filters = {'%s=%s' % (k, v) for k, v in kwargs.items() if v}
|
||||||
|
if filters:
|
||||||
|
url = "%s?%s" % (url, "&".join(filters))
|
||||||
|
return url
|
||||||
|
|
||||||
|
def list(self, name=None, binary=None, is_up=None, disabled=None,
|
||||||
|
num_hosts=None, num_down_hosts=None, detailed=False):
|
||||||
|
"""Clustered Service list.
|
||||||
|
|
||||||
|
:param name: filter by cluster name.
|
||||||
|
:param binary: filter by cluster binary.
|
||||||
|
:param is_up: filtering by up/down status.
|
||||||
|
:param disabled: filtering by disabled status.
|
||||||
|
:param num_hosts: filtering by number of hosts.
|
||||||
|
:param num_down_hosts: filtering by number of hosts that are down.
|
||||||
|
:param detailed: retrieve simple or detailed list.
|
||||||
|
"""
|
||||||
|
url_path = 'detail' if detailed else None
|
||||||
|
url = self._build_url(url_path, name=name, binary=binary, is_up=is_up,
|
||||||
|
disabled=disabled, num_hosts=num_hosts,
|
||||||
|
num_down_hosts=num_down_hosts)
|
||||||
|
return self._list(url, 'clusters')
|
||||||
|
|
||||||
|
def show(self, name, binary=None):
|
||||||
|
"""Clustered Service show.
|
||||||
|
|
||||||
|
:param name: Cluster name.
|
||||||
|
:param binary: Clustered service binary.
|
||||||
|
"""
|
||||||
|
url = self._build_url(name, binary=binary)
|
||||||
|
resp, body = self.api.client.get(url)
|
||||||
|
return self.resource_class(self, body['cluster'], loaded=True,
|
||||||
|
resp=resp)
|
||||||
|
|
||||||
|
def update(self, name, binary, disabled, disabled_reason=None):
|
||||||
|
"""Enable or disable a clustered service.
|
||||||
|
|
||||||
|
:param name: Cluster name.
|
||||||
|
:param binary: Clustered service binary.
|
||||||
|
:param disabled: Boolean determining desired disabled status.
|
||||||
|
:param disabled_reason: Value to pass as disabled reason.
|
||||||
|
"""
|
||||||
|
url_path = 'disable' if disabled else 'enable'
|
||||||
|
url = self._build_url(url_path)
|
||||||
|
|
||||||
|
body = {'name': name, 'binary': binary}
|
||||||
|
if disabled and disabled_reason:
|
||||||
|
body['disabled_reason'] = disabled_reason
|
||||||
|
result = self._update(url, body)
|
||||||
|
return self.resource_class(self, result['cluster'], loaded=True,
|
||||||
|
resp=result.request_ids)
|
@ -24,6 +24,7 @@ import time
|
|||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
from cinderclient import api_versions
|
||||||
from cinderclient import base
|
from cinderclient import base
|
||||||
from cinderclient import exceptions
|
from cinderclient import exceptions
|
||||||
from cinderclient import utils
|
from cinderclient import utils
|
||||||
@ -1604,6 +1605,80 @@ def do_transfer_create(cs, args):
|
|||||||
utils.print_dict(info)
|
utils.print_dict(info)
|
||||||
|
|
||||||
|
|
||||||
|
@utils.service_type('volumev3')
|
||||||
|
@api_versions.wraps('3.7')
|
||||||
|
@utils.arg('--name', metavar='<name>', default=None,
|
||||||
|
help='Filter by cluster name, without backend will list all '
|
||||||
|
'clustered services from the same cluster. Default=None.')
|
||||||
|
@utils.arg('--binary', metavar='<binary>', default=None,
|
||||||
|
help='Cluster binary. Default=None.')
|
||||||
|
@utils.arg('--is-up', metavar='<True|true|False|false>', default=None,
|
||||||
|
choices=('True', 'true', 'False', 'false'),
|
||||||
|
help='Filter by up/dow status. Default=None.')
|
||||||
|
@utils.arg('--disabled', metavar='<True|true|False|false>', default=None,
|
||||||
|
choices=('True', 'true', 'False', 'false'),
|
||||||
|
help='Filter by disabled status. Default=None.')
|
||||||
|
@utils.arg('--num-hosts', metavar='<num-hosts>', default=None,
|
||||||
|
help='Filter by number of hosts in the cluster.')
|
||||||
|
@utils.arg('--num-down-hosts', metavar='<num-down-hosts>', default=None,
|
||||||
|
help='Filter by number of hosts that are down.')
|
||||||
|
@utils.arg('--detailed', dest='detailed', default=False,
|
||||||
|
help='Get detailed clustered service information (Default=False).',
|
||||||
|
action='store_true')
|
||||||
|
def do_cluster_list(cs, args):
|
||||||
|
"""Lists clustered services with optional filtering."""
|
||||||
|
clusters = cs.clusters.list(name=args.name, binary=args.binary,
|
||||||
|
is_up=args.is_up, disabled=args.disabled,
|
||||||
|
num_hosts=args.num_hosts,
|
||||||
|
num_down_hosts=args.num_down_hosts,
|
||||||
|
detailed=args.detailed)
|
||||||
|
|
||||||
|
columns = ['Name', 'Binary', 'State', 'Status']
|
||||||
|
if args.detailed:
|
||||||
|
columns.extend(('Num Hosts', 'Num Down Hosts', 'Last Heartbeat',
|
||||||
|
'Disabled Reason', 'Created At', 'Updated at'))
|
||||||
|
utils.print_list(clusters, columns)
|
||||||
|
|
||||||
|
|
||||||
|
@utils.service_type('volumev3')
|
||||||
|
@api_versions.wraps('3.7')
|
||||||
|
@utils.arg('binary', metavar='<binary>', nargs='?', default='cinder-volume',
|
||||||
|
help='Binary to filter by. Default: cinder-volume.')
|
||||||
|
@utils.arg('name', metavar='<cluster-name>',
|
||||||
|
help='Name of the clustered service to show.')
|
||||||
|
def do_cluster_show(cs, args):
|
||||||
|
"""Show detailed information on a clustered service."""
|
||||||
|
cluster = cs.clusters.show(args.name, args.binary)
|
||||||
|
utils.print_dict(cluster.to_dict())
|
||||||
|
|
||||||
|
|
||||||
|
@utils.service_type('volumev3')
|
||||||
|
@api_versions.wraps('3.7')
|
||||||
|
@utils.arg('binary', metavar='<binary>', nargs='?', default='cinder-volume',
|
||||||
|
help='Binary to filter by. Default: cinder-volume.')
|
||||||
|
@utils.arg('name', metavar='<cluster-name>',
|
||||||
|
help='Name of the clustered services to update.')
|
||||||
|
def do_cluster_enable(cs, args):
|
||||||
|
"""Enables clustered services."""
|
||||||
|
cluster = cs.clusters.update(args.name, args.binary, disabled=False)
|
||||||
|
utils.print_dict(cluster.to_dict())
|
||||||
|
|
||||||
|
|
||||||
|
@utils.service_type('volumev3')
|
||||||
|
@api_versions.wraps('3.7')
|
||||||
|
@utils.arg('binary', metavar='<binary>', nargs='?', default='cinder-volume',
|
||||||
|
help='Binary to filter by. Default: cinder-volume.')
|
||||||
|
@utils.arg('name', metavar='<cluster-name>',
|
||||||
|
help='Name of the clustered services to update.')
|
||||||
|
@utils.arg('--reason', metavar='<reason>', default=None,
|
||||||
|
help='Reason for disabling clustered service.')
|
||||||
|
def do_cluster_disable(cs, args):
|
||||||
|
"""Disables clustered services."""
|
||||||
|
cluster = cs.clusters.update(args.name, args.binary, disabled=True,
|
||||||
|
disabled_reason=args.reason)
|
||||||
|
utils.print_dict(cluster.to_dict())
|
||||||
|
|
||||||
|
|
||||||
@utils.arg('transfer', metavar='<transfer>',
|
@utils.arg('transfer', metavar='<transfer>',
|
||||||
help='Name or ID of transfer to delete.')
|
help='Name or ID of transfer to delete.')
|
||||||
@utils.service_type('volumev3')
|
@utils.service_type('volumev3')
|
||||||
@ -1696,6 +1771,8 @@ def do_service_list(cs, args):
|
|||||||
replication = strutils.bool_from_string(args.withreplication)
|
replication = strutils.bool_from_string(args.withreplication)
|
||||||
result = cs.services.list(host=args.host, binary=args.binary)
|
result = cs.services.list(host=args.host, binary=args.binary)
|
||||||
columns = ["Binary", "Host", "Zone", "Status", "State", "Updated_at"]
|
columns = ["Binary", "Host", "Zone", "Status", "State", "Updated_at"]
|
||||||
|
if cs.api_version.matches('3.7'):
|
||||||
|
columns.append('Cluster')
|
||||||
if replication:
|
if replication:
|
||||||
columns.extend(["Replication Status", "Active Backend ID", "Frozen"])
|
columns.extend(["Replication Status", "Active Backend ID", "Frozen"])
|
||||||
# NOTE(jay-lau-513): we check if the response has disabled_reason
|
# NOTE(jay-lau-513): we check if the response has disabled_reason
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Service listings will display additional "cluster" field when working with
|
||||||
|
microversion 3.7 or higher.
|
||||||
|
- Add clustered services commands to list -summary and detailed-
|
||||||
|
(`cluster-list`), show (`cluster-show`), and update (`cluster-enable`,
|
||||||
|
`cluster-disable`). Listing supports filtering by name, binary,
|
||||||
|
disabled status, number of hosts, number of hosts that are down, and
|
||||||
|
up/down status. These commands require API version 3.7 or higher.
|
Loading…
Reference in New Issue
Block a user