Skip to content
Merged
48 changes: 48 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Unit tests

env:
# This should match the default python_version build arg
PYTHON_VERSION: 3.8
TOX_ENV: py38

on:
push:
branches:
- "*"
pull_request:

jobs:
run_linting:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: astral-sh/ruff-action@v3

run_tests:
strategy:
matrix:
include:
- python_version: "3.10"
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python_version }}
- name: install uv for speedup
run: pip install uv
- name: install test tools
run: |
uv pip install \
--system \
-r requirements.txt \
-r test-requirements.txt \
.
- name: init stestr repo
run: stestr init
- name: run stestr tests
run: stestr run
- name: list failing tests
run: stestr failing --list
if: always() #run even if tests fail
88 changes: 73 additions & 15 deletions blazarclient/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,10 @@ def get_parser(self, prog_name):
parser = super(BlazarCommand, self).get_parser(prog_name)
return parser

def format_output_data(self, data):
def format_output_data(self, data, parsed_args):
# Do not format output data if the formatter is not table
if parsed_args.formatter != 'table':
return
for k, v in data.items():
if isinstance(v, str):
try:
Expand All @@ -106,12 +109,7 @@ def format_output_data(self, data):
# NOTE(sbauza): This is not something AST can evaluate,
# probably a string.
pass
if isinstance(v, list):
value = '\n'.join(utils.dumps(
i, indent=self.json_indent) if isinstance(i, dict)
else str(i) for i in v)
data[k] = value
elif isinstance(v, dict):
elif isinstance(v, list) or isinstance(v, dict):
value = utils.dumps(v, indent=self.json_indent)
data[k] = value
elif v is None:
Expand All @@ -137,7 +135,7 @@ def get_data(self, parsed_args):
body = self.args2body(parsed_args)
resource_manager = getattr(blazar_client, self.resource)
data = resource_manager.create(**body)
self.format_output_data(data)
self.format_output_data(data, parsed_args)

if data:
print('Created a new %s:' % self.resource, file=self.app.stdout)
Expand Down Expand Up @@ -229,20 +227,30 @@ class ListCommand(BlazarCommand, lister.Lister):
log = None
_formatters = {}
list_columns = []
long_columns = []
unknown_parts_flag = True
_filters = []

def args2body(self, parsed_args):
params = {}
if parsed_args.sort_by:
if parsed_args.sort_by in self.list_columns:
params['sort_by'] = parsed_args.sort_by
elif self.long_columns and parsed_args.sort_by in self.long_columns:
params['sort_by'] = parsed_args.sort_by
else:
msg = 'Invalid sort option %s' % parsed_args.sort_by
raise exception.BlazarClientException(msg)
return params

def get_parser(self, prog_name):
parser = super(ListCommand, self).get_parser(prog_name)
if self.long_columns:
parser.add_argument(
'--long',
action='store_true',
help='Display detailed information for each item'
)
return parser

def retrieve_list(self, parsed_args):
Expand All @@ -251,6 +259,8 @@ def retrieve_list(self, parsed_args):
body = self.args2body(parsed_args)
resource_manager = getattr(blazar_client, self.resource)
data = resource_manager.list(**body)
for f in self._filters:
data = list(filter(f, data))
return data

def setup_columns(self, info, parsed_args):
Expand All @@ -269,10 +279,19 @@ def setup_columns(self, info, parsed_args):
valid_parsed_columns = {col for col in parsed_args.columns if col in columns}
else:
valid_parsed_columns = set()
if self.list_columns:

default_columns = self.list_columns
if self.long_columns and parsed_args.long:
default_columns += self.long_columns
if default_columns:
columns = {
col for col in self.list_columns if col in columns
} | valid_parsed_columns
col for col in default_columns if col in columns
} | valid_parsed_columns
# sort the columns based on list_columns
sorting_map = {item: i for i, item in enumerate(default_columns)}
# sort key is either index of item in list_columns, or placed at end of list
columns = sorted(columns, key=lambda x: sorting_map.get(x, len(default_columns)))

return (
columns,
(utils.get_item_properties(s, columns, formatters=self._formatters)
Expand All @@ -288,12 +307,43 @@ def get_data(self, parsed_args):
class ListAllocationCommand(ListCommand, lister.Lister):
"""List allocations that belong to a given tenant."""

def get_parser(self, prog_name):
parser = super(ListAllocationCommand, self).get_parser(prog_name)
parser.add_argument(
'--lease-id', metavar="<lease_id>",
help='Lease to filter allocations by',
)
parser.add_argument(
'--username', metavar="<username>",
help='Username to filter allocations by',
)
return parser

def retrieve_list(self, parsed_args):
"""Retrieve a list of resources from Blazar server."""
blazar_client = self.get_client()
body = self.args2body(parsed_args)
resource_manager = getattr(blazar_client, self.resource)
data = resource_manager.list_allocations(**body)
filters = []
if parsed_args.lease_id:
filters.append(lambda x: x['lease_id'] == parsed_args.lease_id)
if parsed_args.username:
filters.append(lambda x: x.get("extras", {}).get('user_name') == parsed_args.username)

if filters:
new_data = []
for allocation in data:
new_reservations = []
for reservation in allocation["reservations"]:
if all(f(reservation) for f in filters):
new_reservations.append(reservation)
if new_reservations:
new_data.append({
"resource_id": allocation["resource_id"],
"reservations": new_reservations,
})
data = new_data
return data


Expand All @@ -310,6 +360,7 @@ def get_parser(self, prog_name):

def get_data(self, parsed_args):
self.log.debug('get_data(%s)' % parsed_args)
body = self.args2body(parsed_args)
blazar_client = self.get_client()

if self.allow_names:
Expand All @@ -323,7 +374,9 @@ def get_data(self, parsed_args):

resource_manager = getattr(blazar_client, self.resource)
data = resource_manager.get(res_id)
self.format_output_data(data)
if body.get('detail'):
data.update(resource_manager.additional_details(res_id))
self.format_output_data(data, parsed_args)
return list(zip(*sorted(data.items())))


Expand All @@ -346,7 +399,7 @@ def get_data(self, parsed_args):
blazar_client = self.get_client()
resource_manager = getattr(blazar_client, self.resource)
data = resource_manager.get_allocation(parsed_args.id)
self.format_output_data(data)
self.format_output_data(data, parsed_args)
return list(zip(*sorted(data.items())))


Expand Down Expand Up @@ -407,8 +460,6 @@ def get_data(self, parsed_args):
blazar_client = self.get_client()
resource_manager = getattr(blazar_client, self.resource)
data = resource_manager.get_property(parsed_args.property_name)
if parsed_args.formatter == 'table':
self.format_output_data(data)
return list(zip(*sorted(data.items())))


Expand Down Expand Up @@ -447,9 +498,16 @@ def get_parser(self, prog_name):
default=False,
help='Set property to public.'
)
parser.add_argument(
'--unique',
action='store_true',
default=False,
help='Set capability as unique.'
)
return parser

def args2body(self, parsed_args):
return dict(
property_name=parsed_args.property_name,
is_unique=(parsed_args.unique is True),
private=(parsed_args.private is True))
6 changes: 4 additions & 2 deletions blazarclient/tests/test_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import argparse
from unittest import mock

import testtools
Expand Down Expand Up @@ -84,10 +85,11 @@ def test_format_output_data(self):
'key_none': None}
data_after = {'key_string': 'string_value',
'key_dict': '{"key": "value"}',
'key_list': '1\n2\n3',
'key_list': '["1", "2", "3"]',
'key_none': ''}

self.command.format_output_data(data_before)
args = argparse.Namespace(formatter="table")
self.command.format_output_data(data_before, args)

self.assertEqual(data_after, data_before)

Expand Down
2 changes: 1 addition & 1 deletion blazarclient/tests/v1/shell_commands/test_floatingips.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def test_show_floatingip(self):
show_floatingip, floatingip_manager = self.create_show_command(
list_value, get_value)

args = argparse.Namespace(id='84c4d37e-1f8b-45ce-897b-16ad7f49b0e9')
args = argparse.Namespace(id='84c4d37e-1f8b-45ce-897b-16ad7f49b0e9', formatter="table")
expected = [('id',), ('84c4d37e-1f8b-45ce-897b-16ad7f49b0e9',)]

ret = show_floatingip.get_data(args)
Expand Down
6 changes: 3 additions & 3 deletions blazarclient/tests/v1/shell_commands/test_hosts.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ def test_show_host(self):
show_host, host_manager = self.create_show_command(list_value,
get_value)

args = argparse.Namespace(id='101')
args = argparse.Namespace(id='101', formatter="table")
expected = [('hypervisor_hostname', 'id'), ('host-1', '101')]

ret = show_host.get_data(args)
Expand All @@ -197,7 +197,7 @@ def test_show_host_with_name(self):
show_host, host_manager = self.create_show_command(list_value,
get_value)

args = argparse.Namespace(id='host-1')
args = argparse.Namespace(id='host-1', formatter="table")
expected = [('hypervisor_hostname', 'id'), ('host-1', '101')]

ret = show_host.get_data(args)
Expand All @@ -215,7 +215,7 @@ def test_show_host_with_name_startwith_number(self):

show_host, host_manager = self.create_show_command(list_value,
get_value)
args = argparse.Namespace(id='1-host')
args = argparse.Namespace(id='1-host', formatter="table")
expected = [('hypervisor_hostname', 'id'), ('1-host', '101')]

ret = show_host.get_data(args)
Expand Down
49 changes: 47 additions & 2 deletions blazarclient/tests/v1/shell_commands/test_leases.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,12 +334,33 @@ def test_show_lease(self):
]
mock.seal(lease_manager)

args = argparse.Namespace(id=FIRST_LEASE)
args = argparse.Namespace(id=FIRST_LEASE, detail=False, formatter="table")
expected = [('id',), (FIRST_LEASE,)]

self.assertEqual(show_lease.get_data(args), expected)
lease_manager.get.assert_called_once_with(FIRST_LEASE)

def test_show_lease_with_allocations(self):
show_lease, lease_manager = self.create_show_command()
lease_manager.get.return_value = {'id': FIRST_LEASE}
lease_manager.additional_details.return_value = {
'hosts': [],
'networks': [],
'devices': []
}
lease_manager.list.return_value = [
{'id': FIRST_LEASE, 'name': 'first-lease'},
{'id': SECOND_LEASE, 'name': 'second-lease'},
]
mock.seal(lease_manager)

args = argparse.Namespace(id=FIRST_LEASE, detail=True, formatter="table")
expected = [('devices', 'hosts', 'id', 'networks',), ('[]', '[]', FIRST_LEASE, '[]',)]

self.assertEqual(show_lease.get_data(args), expected)
lease_manager.get.assert_called_once_with(FIRST_LEASE)
lease_manager.additional_details.assert_called_once_with(FIRST_LEASE)

def test_show_lease_by_name(self):
show_lease, lease_manager = self.create_show_command()
lease_manager.list.return_value = [
Expand All @@ -349,13 +370,37 @@ def test_show_lease_by_name(self):
lease_manager.get.return_value = {'id': SECOND_LEASE}
mock.seal(lease_manager)

args = argparse.Namespace(id='second-lease')
args = argparse.Namespace(id='second-lease', detail=False, formatter="table")
expected = [('id',), (SECOND_LEASE,)]

self.assertEqual(show_lease.get_data(args), expected)
lease_manager.list.assert_called_once_with()
lease_manager.get.assert_called_once_with(SECOND_LEASE)

def test_show_lease_by_name_with_allocations(self):
show_lease, lease_manager = self.create_show_command()
lease_manager.list.return_value = [
{'id': FIRST_LEASE, 'name': 'first-lease'},
{'id': SECOND_LEASE, 'name': 'second-lease'},
]
lease_manager.get.return_value = {'id': SECOND_LEASE}
host1 = {'id': '101', 'hypervisor_hostname': 'host-1'}
lease_manager.additional_details.return_value = {
'hosts': [host1],
'networks': [],
'devices': []
}
mock.seal(lease_manager)
args = argparse.Namespace(id='second-lease', detail=True, formatter="table")
expected = [
('devices', 'hosts', 'id', 'networks',),
('[]', '[\n {\n "id": "101",\n "hypervisor_hostname": "host-1"\n }\n]', '424d21c3-45a2-448a-81ad-32eddc888375', '[]')
]
self.assertEqual(show_lease.get_data(args), expected)
lease_manager.list.assert_called_once_with()
lease_manager.get.assert_called_once_with(SECOND_LEASE)
lease_manager.additional_details.assert_called_once_with(SECOND_LEASE)


class DeleteLeaseTestCase(tests.TestCase):

Expand Down
6 changes: 4 additions & 2 deletions blazarclient/tests/v1/shell_commands/test_networks.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ def test_show_network(self):
show_network, network_manager = self.create_show_command(list_value,
get_value)

args = argparse.Namespace(id='072c58c0-64ac-467b-b040-9138771e146a')
args = argparse.Namespace(id='072c58c0-64ac-467b-b040-9138771e146a', formatter="table")
expected = [('id',), ('072c58c0-64ac-467b-b040-9138771e146a',)]

ret = show_network.get_data(args)
Expand Down Expand Up @@ -232,7 +232,9 @@ def test_list_network_sort_by(self):
list_network, network_manager = self.create_list_command()
list_networks_args = argparse.Namespace(
sort_by='segment_id',
columns=[]
columns=[],
formatter="table",
long=False,
)
return_networks = list_network.get_data(list_networks_args)
segment_id_index = list(return_networks[0]).index('segment_id')
Expand Down
Loading