Merge "Add devices-list to support /v1/devices"
This commit is contained in:
commit
adaf64c626
@ -105,12 +105,15 @@ class CRUDClient(object):
|
||||
def list(self, skip_merge=False, **kwargs):
|
||||
"""Generate the items from this endpoint."""
|
||||
autopaginate = kwargs.pop('autopaginate', True)
|
||||
nested = kwargs.pop('nested', False)
|
||||
self.merge_request_arguments(kwargs, skip_merge)
|
||||
url = self.build_url(path_arguments=kwargs)
|
||||
|
||||
response_generator = self.session.paginate(
|
||||
url,
|
||||
autopaginate=autopaginate,
|
||||
items_key=(self.key + 's'),
|
||||
nested=nested,
|
||||
params=kwargs,
|
||||
)
|
||||
for response, items in response_generator:
|
||||
|
@ -12,6 +12,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""Craton-specific session details."""
|
||||
from itertools import chain
|
||||
import logging
|
||||
|
||||
from keystoneauth1 import session as ksa_session
|
||||
@ -233,7 +234,8 @@ class Session(object):
|
||||
|
||||
return response
|
||||
|
||||
def paginate(self, url, items_key, autopaginate=True, **kwargs):
|
||||
def paginate(self, url, items_key, autopaginate=True, nested=False,
|
||||
**kwargs):
|
||||
"""Make a GET request to a paginated resource.
|
||||
|
||||
If :param:`autopaginate` is set to ``True``, this will automatically
|
||||
@ -256,20 +258,23 @@ class Session(object):
|
||||
Determines whether or not this method continues requesting items
|
||||
automatically after the first page.
|
||||
"""
|
||||
get_items = True
|
||||
|
||||
while get_items:
|
||||
response = self.get(url, **kwargs)
|
||||
json_body = response.json()
|
||||
if nested:
|
||||
items = list(chain(*json_body[items_key].values()))
|
||||
else:
|
||||
items = json_body[items_key]
|
||||
links = json_body['links']
|
||||
|
||||
yield response, items
|
||||
while autopaginate and len(items) > 0:
|
||||
|
||||
links = json_body['links']
|
||||
url = _find_next_link(links)
|
||||
if url is None:
|
||||
break
|
||||
response = self.get(url)
|
||||
json_body = response.json()
|
||||
items = json_body[items_key]
|
||||
links = json_body['links']
|
||||
yield response, items
|
||||
|
||||
kwargs = {}
|
||||
get_items = url and autopaginate and len(items) > 0
|
||||
|
||||
|
||||
def _find_next_link(links):
|
||||
|
122
cratonclient/shell/v1/devices_shell.py
Normal file
122
cratonclient/shell/v1/devices_shell.py
Normal file
@ -0,0 +1,122 @@
|
||||
# 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.
|
||||
"""Hosts resource and resource shell wrapper."""
|
||||
from __future__ import print_function
|
||||
|
||||
from cratonclient.common import cliutils
|
||||
from cratonclient import exceptions as exc
|
||||
from cratonclient.v1 import devices
|
||||
|
||||
|
||||
@cliutils.arg('--fields',
|
||||
nargs='+',
|
||||
metavar='<fields>',
|
||||
default=[],
|
||||
help='Space-separated list of fields to display. '
|
||||
'Only these fields will be fetched from the server.')
|
||||
@cliutils.arg('--all',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Retrieve and show all devices. This will override '
|
||||
'the provided value for --limit and automatically '
|
||||
'retrieve each page of results.')
|
||||
@cliutils.arg('--sort-key',
|
||||
metavar='<field>',
|
||||
help='Device field that will be used for sorting.')
|
||||
@cliutils.arg('--sort-dir',
|
||||
metavar='<direction>',
|
||||
default='asc',
|
||||
choices=('asc', 'desc'),
|
||||
help='Sort direction: "asc" (default) or "desc".')
|
||||
@cliutils.arg('--limit',
|
||||
metavar='<limit>',
|
||||
type=int,
|
||||
help='Maximum number of devices to return.')
|
||||
@cliutils.arg('--marker',
|
||||
metavar='<marker>',
|
||||
default=None,
|
||||
help='ID of the device to use to resume listing devices.')
|
||||
@cliutils.arg('--cloud',
|
||||
metavar='<cloud>',
|
||||
type=int,
|
||||
help='ID of the cloud that the device belongs to.')
|
||||
@cliutils.arg('-r', '--region',
|
||||
metavar='<region>',
|
||||
type=int,
|
||||
help='ID of the region that the device belongs to.')
|
||||
@cliutils.arg('-c', '--cell',
|
||||
metavar='<cell>',
|
||||
type=int,
|
||||
help='Integer ID of the cell that contains '
|
||||
'the desired list of devices.')
|
||||
@cliutils.arg('--parent',
|
||||
metavar='<parent>',
|
||||
type=int,
|
||||
help='Parent ID of required devices.')
|
||||
@cliutils.arg('--descendants',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help='When parent is also specified, include all descendants.')
|
||||
@cliutils.arg('--active',
|
||||
metavar='<active>',
|
||||
choices=("true", "false"),
|
||||
help='Filter devices by their active state.')
|
||||
def do_device_list(cc, args):
|
||||
"""List all devices."""
|
||||
params = {}
|
||||
default_fields = [
|
||||
'cloud_id', 'region_id', 'cell_id', 'parent_id', 'id', 'name',
|
||||
'device_type', 'active',
|
||||
]
|
||||
if args.limit is not None:
|
||||
if args.limit < 0:
|
||||
raise exc.CommandError('Invalid limit specified. Expected '
|
||||
'non-negative limit, got {0}'
|
||||
.format(args.limit))
|
||||
params['limit'] = args.limit
|
||||
if args.all is True:
|
||||
params['limit'] = 100
|
||||
|
||||
if args.fields:
|
||||
try:
|
||||
fields = {x: devices.DEVICE_FIELDS[x] for x in args.fields}
|
||||
except KeyError as err:
|
||||
raise exc.CommandError('Invalid field "{}"'.format(err.args[0]))
|
||||
else:
|
||||
fields = default_fields
|
||||
sort_key = args.sort_key and args.sort_key.lower()
|
||||
if sort_key is not None:
|
||||
if sort_key not in devices.DEVICE_FIELDS:
|
||||
raise exc.CommandError(
|
||||
'{0} is an invalid key for sorting, valid values for '
|
||||
'--sort-key are: {1}'.format(
|
||||
args.sort_key, devices.DEVICE_FIELDS.keys()
|
||||
)
|
||||
)
|
||||
params['sort_keys'] = sort_key
|
||||
params['sort_dir'] = args.sort_dir
|
||||
params['marker'] = args.marker
|
||||
params['autopaginate'] = args.all
|
||||
if args.parent:
|
||||
params['parent_id'] = args.parent
|
||||
params['descendants'] = args.descendants
|
||||
if args.cloud:
|
||||
params['cloud_id'] = args.cloud
|
||||
if args.region:
|
||||
params['region_id'] = args.region
|
||||
if args.cell:
|
||||
params['cell_id'] = args.cell
|
||||
if args.active:
|
||||
params['active'] = args.active
|
||||
|
||||
devices_list = cc.devices.list(**params)
|
||||
args.formatter.configure(fields=list(fields)).handle(devices_list)
|
@ -12,6 +12,7 @@
|
||||
"""Command-line interface to the OpenStack Craton API V1."""
|
||||
from cratonclient.shell.v1 import cells_shell
|
||||
from cratonclient.shell.v1 import clouds_shell
|
||||
from cratonclient.shell.v1 import devices_shell
|
||||
from cratonclient.shell.v1 import hosts_shell
|
||||
from cratonclient.shell.v1 import projects_shell
|
||||
from cratonclient.shell.v1 import regions_shell
|
||||
@ -22,6 +23,7 @@ COMMAND_MODULES = [
|
||||
projects_shell,
|
||||
clouds_shell,
|
||||
regions_shell,
|
||||
devices_shell,
|
||||
hosts_shell,
|
||||
cells_shell,
|
||||
]
|
||||
|
182
cratonclient/tests/integration/shell/v1/test_devices_shell.py
Normal file
182
cratonclient/tests/integration/shell/v1/test_devices_shell.py
Normal file
@ -0,0 +1,182 @@
|
||||
# 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.
|
||||
|
||||
"""Tests for `cratonclient.shell.v1.devices_shell` module."""
|
||||
|
||||
import mock
|
||||
import re
|
||||
|
||||
from cratonclient import exceptions as exc
|
||||
from cratonclient.tests.integration.shell import base
|
||||
|
||||
|
||||
class TestDevicesShell(base.ShellTestCase):
|
||||
"""Test our craton devices shell commands."""
|
||||
|
||||
re_options = re.DOTALL | re.MULTILINE
|
||||
|
||||
@mock.patch('cratonclient.v1.devices.DeviceManager.list')
|
||||
def test_device_list_success(self, mock_list):
|
||||
"""Verify that no arguments prints out all project devices."""
|
||||
self.shell('device-list')
|
||||
self.assertTrue(mock_list.called)
|
||||
|
||||
@mock.patch('cratonclient.v1.devices.DeviceManager.list')
|
||||
def test_device_list_parse_param_success(self, mock_list):
|
||||
"""Verify that success of parsing a subcommand argument."""
|
||||
self.shell('device-list --limit 0')
|
||||
self.assertTrue(mock_list.called)
|
||||
|
||||
@mock.patch('cratonclient.v1.devices.DeviceManager.list')
|
||||
def test_device_list_limit_0_success(self, mock_list):
|
||||
"""Verify that --limit 0 prints out all project devices."""
|
||||
self.shell('device-list --limit 0')
|
||||
mock_list.assert_called_once_with(
|
||||
limit=0,
|
||||
sort_dir='asc',
|
||||
descendants=False,
|
||||
marker=None,
|
||||
autopaginate=False,
|
||||
)
|
||||
|
||||
@mock.patch('cratonclient.v1.devices.DeviceManager.list')
|
||||
def test_device_list_limit_positive_num_success(self, mock_list):
|
||||
"""Verify --limit X, where X is a positive integer, succeeds.
|
||||
|
||||
The command will print out X number of project devices.
|
||||
"""
|
||||
self.shell('device-list --limit 1')
|
||||
mock_list.assert_called_once_with(
|
||||
limit=1,
|
||||
sort_dir='asc',
|
||||
descendants=False,
|
||||
marker=None,
|
||||
autopaginate=False,
|
||||
)
|
||||
|
||||
def test_device_list_limit_negative_num_failure(self):
|
||||
"""Verify --limit X, where X is a negative integer, fails.
|
||||
|
||||
The command will cause a Command Error message response.
|
||||
"""
|
||||
self.assertRaises(exc.CommandError,
|
||||
self.shell,
|
||||
'device-list -r 1 --limit -1')
|
||||
|
||||
@mock.patch('cratonclient.v1.devices.DeviceManager.list')
|
||||
def test_device_list_cell_success(self, mock_list):
|
||||
"""Verify --cell arguments successfully pass cell to Client."""
|
||||
for cell_arg in ['-c', '--cell']:
|
||||
self.shell('device-list {0} 1'.format(cell_arg))
|
||||
mock_list.assert_called_once_with(
|
||||
cell_id=1,
|
||||
sort_dir='asc',
|
||||
descendants=False,
|
||||
marker=None,
|
||||
autopaginate=False,
|
||||
)
|
||||
mock_list.reset_mock()
|
||||
|
||||
@mock.patch('cratonclient.v1.devices.DeviceManager.list')
|
||||
def test_device_list_fields_success(self, mock_list):
|
||||
"""Verify --fields argument successfully passed to Client."""
|
||||
self.shell('device-list --fields id name')
|
||||
mock_list.assert_called_once_with(
|
||||
sort_dir='asc',
|
||||
descendants=False,
|
||||
marker=None,
|
||||
autopaginate=False,
|
||||
)
|
||||
|
||||
@mock.patch('cratonclient.v1.devices.DeviceManager.list')
|
||||
def test_device_list_sort_keys_field_key_success(self, mock_list):
|
||||
"""Verify --sort-key arguments successfully passed to Client."""
|
||||
self.shell('device-list --sort-key cell_id')
|
||||
mock_list.assert_called_once_with(
|
||||
sort_keys='cell_id',
|
||||
sort_dir='asc',
|
||||
descendants=False,
|
||||
marker=None,
|
||||
autopaginate=False,
|
||||
)
|
||||
|
||||
def test_device_list_sort_keys_invalid(self):
|
||||
"""Verify --sort-key with invalid args, fails with Command Error."""
|
||||
self.assertRaises(exc.CommandError,
|
||||
self.shell,
|
||||
'device-list --sort-key invalid')
|
||||
|
||||
@mock.patch('cratonclient.v1.devices.DeviceManager.list')
|
||||
def test_device_list_sort_dir_not_passed_without_sort_key(self, mock_list):
|
||||
"""Verify --sort-dir arg ignored without --sort-key."""
|
||||
self.shell('device-list --sort-dir desc')
|
||||
mock_list.assert_called_once_with(
|
||||
sort_dir='desc',
|
||||
descendants=False,
|
||||
marker=None,
|
||||
autopaginate=False,
|
||||
)
|
||||
|
||||
@mock.patch('cratonclient.v1.devices.DeviceManager.list')
|
||||
def test_device_list_sort_dir_asc_success(self, mock_list):
|
||||
"""Verify --sort-dir asc successfully passed to Client."""
|
||||
self.shell('device-list --sort-key name --sort-dir asc')
|
||||
mock_list.assert_called_once_with(
|
||||
sort_keys='name',
|
||||
sort_dir='asc',
|
||||
descendants=False,
|
||||
marker=None,
|
||||
autopaginate=False,
|
||||
)
|
||||
|
||||
@mock.patch('cratonclient.v1.devices.DeviceManager.list')
|
||||
def test_device_list_sort_dir_desc_success(self, mock_list):
|
||||
"""Verify --sort-dir desc successfully passed to Client."""
|
||||
self.shell('device-list --sort-key name --sort-dir desc')
|
||||
mock_list.assert_called_once_with(
|
||||
sort_keys='name',
|
||||
sort_dir='desc',
|
||||
descendants=False,
|
||||
marker=None,
|
||||
autopaginate=False,
|
||||
)
|
||||
|
||||
def test_device_list_sort_dir_invalid_value(self):
|
||||
"""Verify --sort-dir with invalid args, fails with Command Error."""
|
||||
(_, error) = self.shell(
|
||||
'device-list -r 1 --sort-key name --sort-dir invalid'
|
||||
)
|
||||
self.assertIn("invalid choice: 'invalid'", error)
|
||||
|
||||
@mock.patch('cratonclient.v1.devices.DeviceManager.list')
|
||||
def test_device_list_filter_by_parent_success(self, mock_list):
|
||||
"""Verify --parent ID successfully passed to Client."""
|
||||
self.shell('device-list --parent 12345')
|
||||
mock_list.assert_called_once_with(
|
||||
sort_dir='asc',
|
||||
descendants=False,
|
||||
parent_id=12345,
|
||||
marker=None,
|
||||
autopaginate=False,
|
||||
)
|
||||
|
||||
@mock.patch('cratonclient.v1.devices.DeviceManager.list')
|
||||
def test_device_list_filter_by_parent_descendants_success(self, mock_list):
|
||||
"""Verify --parent ID successfully passed to Client."""
|
||||
self.shell('device-list --parent 12345 --descendants')
|
||||
mock_list.assert_called_once_with(
|
||||
sort_dir='asc',
|
||||
parent_id=12345,
|
||||
descendants=True,
|
||||
marker=None,
|
||||
autopaginate=False,
|
||||
)
|
257
cratonclient/tests/unit/shell/v1/test_devices_shell.py
Normal file
257
cratonclient/tests/unit/shell/v1/test_devices_shell.py
Normal file
@ -0,0 +1,257 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# 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.
|
||||
"""Tests for the shell functions for the devices resource."""
|
||||
from cratonclient.shell.v1 import devices_shell
|
||||
from cratonclient.tests.unit.shell import base
|
||||
|
||||
|
||||
class TestDoDeviceList(base.TestShellCommandUsingPrintList):
|
||||
"""Unit tests for the device list command."""
|
||||
|
||||
def args_for(self, **kwargs):
|
||||
"""Generate a Namespace for do_device_list."""
|
||||
kwargs.setdefault('cloud', None)
|
||||
kwargs.setdefault('region', None)
|
||||
kwargs.setdefault('cell', None)
|
||||
kwargs.setdefault('parent', None)
|
||||
kwargs.setdefault('descendants', False)
|
||||
kwargs.setdefault('active', None)
|
||||
kwargs.setdefault('limit', None)
|
||||
kwargs.setdefault('sort_key', None)
|
||||
kwargs.setdefault('sort_dir', 'asc')
|
||||
kwargs.setdefault('fields', [])
|
||||
kwargs.setdefault('marker', None)
|
||||
kwargs.setdefault('all', False)
|
||||
return super(TestDoDeviceList, self).args_for(**kwargs)
|
||||
|
||||
def test_only_required_parameters(self):
|
||||
"""Verify the behaviour with the minimum number of params."""
|
||||
args = self.args_for()
|
||||
|
||||
devices_shell.do_device_list(self.craton_client, args)
|
||||
|
||||
self.craton_client.devices.list.assert_called_once_with(
|
||||
descendants=False,
|
||||
sort_dir='asc',
|
||||
autopaginate=False,
|
||||
marker=None,
|
||||
)
|
||||
self.assertSortedPrintListFieldsEqualTo([
|
||||
'active', 'cell_id', 'cloud_id', 'device_type', 'id', 'name',
|
||||
'parent_id', 'region_id',
|
||||
])
|
||||
|
||||
def test_with_parent_id(self):
|
||||
"""Verify that we include the parent_id in the params."""
|
||||
args = self.args_for(parent=789)
|
||||
|
||||
devices_shell.do_device_list(self.craton_client, args)
|
||||
|
||||
self.craton_client.devices.list.assert_called_once_with(
|
||||
descendants=False,
|
||||
parent_id=789,
|
||||
sort_dir='asc',
|
||||
autopaginate=False,
|
||||
marker=None,
|
||||
)
|
||||
self.assertSortedPrintListFieldsEqualTo([
|
||||
'active', 'cell_id', 'cloud_id', 'device_type', 'id', 'name',
|
||||
'parent_id', 'region_id',
|
||||
])
|
||||
|
||||
def test_with_parent_id_and_descendants(self):
|
||||
"""Verify that the parent_id and descendants is in the params."""
|
||||
args = self.args_for(parent=789, descendants=False)
|
||||
|
||||
devices_shell.do_device_list(self.craton_client, args)
|
||||
|
||||
self.craton_client.devices.list.assert_called_once_with(
|
||||
descendants=False,
|
||||
parent_id=789,
|
||||
sort_dir='asc',
|
||||
autopaginate=False,
|
||||
marker=None,
|
||||
)
|
||||
self.assertSortedPrintListFieldsEqualTo([
|
||||
'active', 'cell_id', 'cloud_id', 'device_type', 'id', 'name',
|
||||
'parent_id', 'region_id',
|
||||
])
|
||||
|
||||
def test_with_region_id(self):
|
||||
"""Verify that we include the region_id in the params."""
|
||||
args = self.args_for(region=789)
|
||||
|
||||
devices_shell.do_device_list(self.craton_client, args)
|
||||
|
||||
self.craton_client.devices.list.assert_called_once_with(
|
||||
descendants=False,
|
||||
region_id=789,
|
||||
sort_dir='asc',
|
||||
autopaginate=False,
|
||||
marker=None,
|
||||
)
|
||||
self.assertSortedPrintListFieldsEqualTo([
|
||||
'active', 'cell_id', 'cloud_id', 'device_type', 'id', 'name',
|
||||
'parent_id', 'region_id',
|
||||
])
|
||||
|
||||
def test_with_cell_id(self):
|
||||
"""Verify that we include the cell_id in the params."""
|
||||
args = self.args_for(cell=789)
|
||||
|
||||
devices_shell.do_device_list(self.craton_client, args)
|
||||
|
||||
self.craton_client.devices.list.assert_called_once_with(
|
||||
descendants=False,
|
||||
cell_id=789,
|
||||
sort_dir='asc',
|
||||
autopaginate=False,
|
||||
marker=None,
|
||||
)
|
||||
self.assertSortedPrintListFieldsEqualTo([
|
||||
'active', 'cell_id', 'cloud_id', 'device_type', 'id', 'name',
|
||||
'parent_id', 'region_id',
|
||||
])
|
||||
|
||||
def test_with_cloud_id(self):
|
||||
"""Verify that we include the cell_id in the params."""
|
||||
args = self.args_for(cloud=123)
|
||||
|
||||
devices_shell.do_device_list(self.craton_client, args)
|
||||
|
||||
self.craton_client.devices.list.assert_called_once_with(
|
||||
descendants=False,
|
||||
sort_dir='asc',
|
||||
cloud_id=123,
|
||||
autopaginate=False,
|
||||
marker=None,
|
||||
)
|
||||
self.assertSortedPrintListFieldsEqualTo([
|
||||
'active', 'cell_id', 'cloud_id', 'device_type', 'id', 'name',
|
||||
'parent_id', 'region_id',
|
||||
])
|
||||
|
||||
def test_with_limit(self):
|
||||
"""Verify the behaviour with --limit specified."""
|
||||
args = self.args_for(limit=20)
|
||||
|
||||
devices_shell.do_device_list(self.craton_client, args)
|
||||
|
||||
self.craton_client.devices.list.assert_called_once_with(
|
||||
descendants=False,
|
||||
limit=20,
|
||||
sort_dir='asc',
|
||||
autopaginate=False,
|
||||
marker=None,
|
||||
)
|
||||
self.assertSortedPrintListFieldsEqualTo([
|
||||
'active', 'cell_id', 'cloud_id', 'device_type', 'id', 'name',
|
||||
'parent_id', 'region_id',
|
||||
])
|
||||
|
||||
def test_negative_limit_raises_command_error(self):
|
||||
"""Verify that we forbid negative limit values."""
|
||||
args = self.args_for(limit=-10)
|
||||
|
||||
self.assertRaisesCommandErrorWith(devices_shell.do_device_list, args)
|
||||
self.assertNothingWasCalled()
|
||||
|
||||
def test_fields(self):
|
||||
"""Verify that we can specify custom fields."""
|
||||
args = self.args_for(fields=['id', 'name', 'cell_id'])
|
||||
|
||||
devices_shell.do_device_list(self.craton_client, args)
|
||||
|
||||
self.craton_client.devices.list.assert_called_once_with(
|
||||
descendants=False,
|
||||
sort_dir='asc',
|
||||
autopaginate=False,
|
||||
marker=None,
|
||||
)
|
||||
self.assertSortedPrintListFieldsEqualTo([
|
||||
'cell_id', 'id', 'name',
|
||||
])
|
||||
|
||||
def test_invalid_sort_key(self):
|
||||
"""Verify that we disallow invalid sort keys."""
|
||||
args = self.args_for(sort_key='my-fake-sort-key')
|
||||
|
||||
self.assertRaisesCommandErrorWith(
|
||||
devices_shell.do_device_list, args
|
||||
)
|
||||
self.assertNothingWasCalled()
|
||||
|
||||
def test_sort_key(self):
|
||||
"""Verify we pass sort_key to our list call."""
|
||||
args = self.args_for(sort_key='ip_address')
|
||||
|
||||
devices_shell.do_device_list(self.craton_client, args)
|
||||
|
||||
self.craton_client.devices.list.assert_called_once_with(
|
||||
descendants=False,
|
||||
sort_keys='ip_address',
|
||||
sort_dir='asc',
|
||||
autopaginate=False,
|
||||
marker=None,
|
||||
)
|
||||
|
||||
def test_invalid_fields_raise_command_error(self):
|
||||
"""Verify sending an invalid field raises a CommandError."""
|
||||
args = self.args_for(fields=['fake-field', 'id'])
|
||||
|
||||
self.assertRaisesCommandErrorWith(
|
||||
devices_shell.do_device_list, args,
|
||||
)
|
||||
self.assertNothingWasCalled()
|
||||
|
||||
def test_autopagination(self):
|
||||
"""Verify autopagination is controlled by --all."""
|
||||
args = self.args_for(all=True)
|
||||
|
||||
devices_shell.do_device_list(self.craton_client, args)
|
||||
|
||||
self.craton_client.devices.list.assert_called_once_with(
|
||||
descendants=False,
|
||||
sort_dir='asc',
|
||||
limit=100,
|
||||
marker=None,
|
||||
autopaginate=True,
|
||||
)
|
||||
|
||||
def test_autopagination_overrides_limit(self):
|
||||
"""Verify --all overrides --limit."""
|
||||
args = self.args_for(all=True, limit=30)
|
||||
|
||||
devices_shell.do_device_list(self.craton_client, args)
|
||||
|
||||
self.craton_client.devices.list.assert_called_once_with(
|
||||
descendants=False,
|
||||
sort_dir='asc',
|
||||
limit=100,
|
||||
marker=None,
|
||||
autopaginate=True,
|
||||
)
|
||||
|
||||
def test_marker_pass_through(self):
|
||||
"""Verify we pass our marker through to the client."""
|
||||
args = self.args_for(marker=42)
|
||||
|
||||
devices_shell.do_device_list(self.craton_client, args)
|
||||
|
||||
self.craton_client.devices.list.assert_called_once_with(
|
||||
descendants=False,
|
||||
sort_dir='asc',
|
||||
marker=42,
|
||||
autopaginate=False,
|
||||
)
|
@ -183,6 +183,7 @@ class TestCRUDClient(base.TestCase):
|
||||
autopaginate=True,
|
||||
items_key='test_keys',
|
||||
params={'sort': 'asc'},
|
||||
nested=False,
|
||||
)
|
||||
self.resource_spec.assert_called_once_with(
|
||||
self.client,
|
||||
|
@ -12,6 +12,9 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""Session specific unit tests."""
|
||||
from itertools import chain
|
||||
from operator import itemgetter
|
||||
|
||||
from keystoneauth1 import session as ksa_session
|
||||
import mock
|
||||
|
||||
@ -162,3 +165,57 @@ class TestSession(base.TestCase):
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
def test_paginate_nested_response(self):
|
||||
"""Verify Session#paginate can extract nested lists."""
|
||||
responses = [
|
||||
self.create_response(
|
||||
{
|
||||
"items-sub-type-1": [
|
||||
{"id": 1},
|
||||
],
|
||||
"items-sub-type-2": [
|
||||
{"id": 2},
|
||||
],
|
||||
},
|
||||
"http://example.com/v1/items?limit=30&marker=2"
|
||||
),
|
||||
self.create_response(
|
||||
{
|
||||
"items-sub-type-1": [],
|
||||
"items-sub-type-2": [],
|
||||
},
|
||||
""
|
||||
),
|
||||
]
|
||||
mock_session = mock.Mock()
|
||||
mock_session.request.side_effect = responses
|
||||
|
||||
craton_session = session.Session(session=mock_session)
|
||||
paginated_items = list(craton_session.paginate(
|
||||
url='http://example.com/v1/items',
|
||||
items_key='items',
|
||||
autopaginate=True,
|
||||
nested=True,
|
||||
))
|
||||
|
||||
self.assertEqual(2, len(paginated_items))
|
||||
resp_items = sorted(
|
||||
chain(*(resp[1] for resp in paginated_items)), key=itemgetter("id")
|
||||
)
|
||||
self.assertListEqual([{"id": 1}, {"id": 2}], resp_items)
|
||||
self.assertListEqual(
|
||||
mock_session.request.call_args_list,
|
||||
[
|
||||
mock.call(
|
||||
method='GET',
|
||||
url='http://example.com/v1/items',
|
||||
endpoint_filter={'service_type': 'fleet_management'},
|
||||
),
|
||||
mock.call(
|
||||
method='GET',
|
||||
url='http://example.com/v1/items?limit=30&marker=2',
|
||||
endpoint_filter={'service_type': 'fleet_management'},
|
||||
),
|
||||
]
|
||||
)
|
||||
|
@ -14,6 +14,7 @@
|
||||
"""Top-level client for version 1 of Craton's API."""
|
||||
from cratonclient.v1 import cells
|
||||
from cratonclient.v1 import clouds
|
||||
from cratonclient.v1 import devices
|
||||
from cratonclient.v1 import hosts
|
||||
from cratonclient.v1 import projects
|
||||
from cratonclient.v1 import regions
|
||||
@ -44,4 +45,5 @@ class Client(object):
|
||||
self.cells = cells.CellManager(**manager_kwargs)
|
||||
self.projects = projects.ProjectManager(**manager_kwargs)
|
||||
self.clouds = clouds.CloudManager(**manager_kwargs)
|
||||
self.devices = devices.DeviceManager(**manager_kwargs)
|
||||
self.regions = regions.RegionManager(**manager_kwargs)
|
||||
|
48
cratonclient/v1/devices.py
Normal file
48
cratonclient/v1/devices.py
Normal file
@ -0,0 +1,48 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# 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.
|
||||
"""Devices manager code."""
|
||||
from cratonclient import crud
|
||||
|
||||
|
||||
class Device(crud.Resource):
|
||||
"""Representation of a Device."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class DeviceManager(crud.CRUDClient):
|
||||
"""A manager for devices."""
|
||||
|
||||
key = 'device'
|
||||
base_path = '/devices'
|
||||
resource_class = Device
|
||||
|
||||
def list(self, **kwargs):
|
||||
"""Generate the items from this endpoint."""
|
||||
return super(DeviceManager, self).list(nested=True, **kwargs)
|
||||
|
||||
DEVICE_FIELDS = {
|
||||
'id': 'ID',
|
||||
'project_id': 'Project ID',
|
||||
'cloud_id': 'Cloud ID',
|
||||
'region_id': 'Region ID',
|
||||
'cell_id': 'Cell ID',
|
||||
'parent_id': 'Parent ID',
|
||||
'name': 'Name',
|
||||
'ip_address': 'IP Address',
|
||||
'device_type': 'Device Type',
|
||||
'note': 'Note',
|
||||
'created_at': 'Created At',
|
||||
'updated_at': 'Updated At'
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user