diff --git a/functional/tests/common/test_availability_zone.py b/functional/tests/common/test_availability_zone.py new file mode 100644 index 0000000000..9296db23d7 --- /dev/null +++ b/functional/tests/common/test_availability_zone.py @@ -0,0 +1,25 @@ +# 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 functional.common import test + + +class AvailabilityZoneTests(test.TestCase): + """Functional tests for availability zone. """ + HEADERS = ["'Zone Name'"] + # So far, all components have the same default availability zone name. + DEFAULT_AZ_NAME = 'nova' + + def test_availability_zone_list(self): + opts = self.get_list_opts(self.HEADERS) + raw_output = self.openstack('availability zone list' + opts) + self.assertIn(self.DEFAULT_AZ_NAME, raw_output) diff --git a/openstackclient/compute/v2/availability_zone.py b/openstackclient/common/availability_zone.py similarity index 100% rename from openstackclient/compute/v2/availability_zone.py rename to openstackclient/common/availability_zone.py diff --git a/openstackclient/tests/common/test_availability_zone.py b/openstackclient/tests/common/test_availability_zone.py new file mode 100644 index 0000000000..35089d0623 --- /dev/null +++ b/openstackclient/tests/common/test_availability_zone.py @@ -0,0 +1,113 @@ +# 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 six + +from openstackclient.common import availability_zone +from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests import fakes +from openstackclient.tests import utils + + +def _build_compute_az_datalist(compute_az, long_datalist=False): + datalist = () + if not long_datalist: + datalist = ( + compute_az.zoneName, + 'available', + ) + else: + for host, services in six.iteritems(compute_az.hosts): + for service, state in six.iteritems(services): + datalist += ( + compute_az.zoneName, + 'available', + host, + service, + 'enabled :-) ' + state['updated_at'], + ) + return (datalist,) + + +class TestAvailabilityZone(utils.TestCommand): + + def setUp(self): + super(TestAvailabilityZone, self).setUp() + + compute_client = compute_fakes.FakeComputev2Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) + self.app.client_manager.compute = compute_client + + self.compute_azs_mock = compute_client.availability_zones + self.compute_azs_mock.reset_mock() + + +class TestAvailabilityZoneList(TestAvailabilityZone): + + compute_azs = \ + compute_fakes.FakeAvailabilityZone.create_availability_zones() + + def setUp(self): + super(TestAvailabilityZoneList, self).setUp() + + self.compute_azs_mock.list.return_value = self.compute_azs + + # Get the command object to test + self.cmd = availability_zone.ListAvailabilityZone(self.app, None) + + def test_availability_zone_list_no_options(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.compute_azs_mock.list.assert_called_with() + + columnslist = ('Zone Name', 'Zone Status') + self.assertEqual(columnslist, columns) + datalist = () + for compute_az in self.compute_azs: + datalist += _build_compute_az_datalist(compute_az) + self.assertEqual(datalist, tuple(data)) + + def test_availability_zone_list_long(self): + arglist = [ + '--long', + ] + verifylist = [ + ('long', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.compute_azs_mock.list.assert_called_with() + + columnslist = ( + 'Zone Name', + 'Zone Status', + 'Host Name', + 'Service Name', + 'Service Status', + ) + self.assertEqual(columnslist, columns) + datalist = () + for compute_az in self.compute_azs: + datalist += _build_compute_az_datalist(compute_az, + long_datalist=True) + self.assertEqual(datalist, tuple(data)) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index ecf7f599ba..a90c9ee71a 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -88,6 +88,8 @@ SERVICE = { class FakeComputev2Client(object): def __init__(self, **kwargs): + self.availability_zones = mock.Mock() + self.availability_zones.resource_class = fakes.FakeResource(None, {}) self.images = mock.Mock() self.images.resource_class = fakes.FakeResource(None, {}) self.servers = mock.Mock() @@ -289,3 +291,63 @@ class FakeFlavor(object): if flavors is None: flavors = FakeServer.create_flavors(count) return mock.MagicMock(side_effect=flavors) + + +class FakeAvailabilityZone(object): + """Fake one or more compute availability zones (AZs).""" + + @staticmethod + def create_one_availability_zone(attrs={}, methods={}): + """Create a fake AZ. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :return: + A FakeResource object with zoneName, zoneState, etc. + """ + # Set default attributes. + host_name = uuid.uuid4().hex + service_name = uuid.uuid4().hex + service_updated_at = uuid.uuid4().hex + availability_zone = { + 'zoneName': uuid.uuid4().hex, + 'zoneState': {'available': True}, + 'hosts': {host_name: {service_name: { + 'available': True, + 'active': True, + 'updated_at': service_updated_at, + }}}, + } + + # Overwrite default attributes. + availability_zone.update(attrs) + + availability_zone = fakes.FakeResource( + info=copy.deepcopy(availability_zone), + methods=methods, + loaded=True) + return availability_zone + + @staticmethod + def create_availability_zones(attrs={}, methods={}, count=2): + """Create multiple fake AZs. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :param int count: + The number of AZs to fake + :return: + A list of FakeResource objects faking the AZs + """ + availability_zones = [] + for i in range(0, count): + availability_zone = \ + FakeAvailabilityZone.create_one_availability_zone( + attrs, methods) + availability_zones.append(availability_zone) + + return availability_zones diff --git a/setup.cfg b/setup.cfg index 986a077151..4a0c302781 100644 --- a/setup.cfg +++ b/setup.cfg @@ -43,6 +43,7 @@ openstack.cli.base = volume = openstackclient.volume.client openstack.common = + availability_zone_list = openstackclient.common.availability_zone:ListAvailabilityZone configuration_show = openstackclient.common.configuration:ShowConfiguration extension_list = openstackclient.common.extension:ListExtension limits_show = openstackclient.common.limits:ShowLimits @@ -50,8 +51,6 @@ openstack.common = quota_show = openstackclient.common.quota:ShowQuota openstack.compute.v2 = - availability_zone_list = openstackclient.compute.v2.availability_zone:ListAvailabilityZone - compute_agent_create = openstackclient.compute.v2.agent:CreateAgent compute_agent_delete = openstackclient.compute.v2.agent:DeleteAgent compute_agent_list = openstackclient.compute.v2.agent:ListAgent