Commit 0f5d7134 authored by Thomas Goirand's avatar Thomas Goirand

Merge tag '2.2.1' into debian/train

python-blazarclient 2.2.1 release

meta:version: 2.2.1
meta:diff-start: -
meta:series: train
meta:release-type: release
meta:pypi: yes
meta:first: no
meta:release:Author: Pierre Riteau <pierre@stackhpc.com>
meta:release:Commit: Pierre Riteau <pierre@stackhpc.com>
meta:release:Change-Id: I429bc5aebbc549bd917d000bd563b461e0d831ed
meta:release:Code-Review+2: Thierry Carrez <thierry@openstack.org>
meta:release:Code-Review+2: Sean McGinnis <sean.mcginnis@gmail.com>
meta:release:Workflow+1: Sean McGinnis <sean.mcginnis@gmail.com>
parents fd856803 e98421c9
[gerrit]
host=review.openstack.org
host=review.opendev.org
port=29418
project=openstack/python-blazarclient.git
- project:
templates:
- check-requirements
- openstack-lower-constraints-jobs
- openstack-python-jobs
- openstack-python35-jobs
- openstack-python36-jobs
- openstack-python3-train-jobs
- release-notes-jobs-python3
check:
jobs:
- openstack-tox-lower-constraints
gate:
jobs:
- openstack-tox-lower-constraints
......@@ -19,10 +19,10 @@ Other Resources
* Source code:
* `Blazar <https://git.openstack.org/cgit/openstack/blazar>`__
* `Nova scheduler filter <https://git.openstack.org/cgit/openstack/blazar-nova>`__
* `Client tools <https://git.openstack.org/cgit/openstack/python-blazarclient>`__
* `Dashboard (Horizon plugin) <https://git.openstack.org/cgit/openstack/blazar-dashboard>`__
* `Blazar <https://opendev.org/openstack/blazar>`__
* `Nova scheduler filter <https://opendev.org/openstack/blazar-nova>`__
* `Client tools <https://opendev.org/openstack/python-blazarclient>`__
* `Dashboard (Horizon plugin) <https://opendev.org/openstack/blazar-dashboard>`__
* Blueprints/Bugs: https://launchpad.net/blazar
* Documentation: https://docs.openstack.org/blazar/latest/
......
......@@ -150,4 +150,4 @@ class BaseClientManager(object):
auth_token=self.auth_token,
user_agent=self.user_agent)
else:
raise exception.InsufficientAuthInfomation
raise exception.InsufficientAuthInformation
......@@ -84,7 +84,7 @@ class DuplicatedLeaseParameters(BlazarClientException):
code = 400
class InsufficientAuthInfomation(BlazarClientException):
class InsufficientAuthInformation(BlazarClientException):
"""Occurs if the auth info passed to blazar client is insufficient."""
message = _("The passed arguments are insufficient "
"for the authentication. The instance of "
......
......@@ -14,7 +14,7 @@
"""oslo.i18n integration module.
See http://docs.openstack.org/developer/oslo.i18n/usage.html .
See https://docs.openstack.org/oslo.i18n/latest/user/usage.html .
"""
......
......@@ -33,6 +33,7 @@ import six
from blazarclient import client as blazar_client
from blazarclient import exception
from blazarclient import utils
from blazarclient.v1.shell_commands import floatingips
from blazarclient.v1.shell_commands import hosts
from blazarclient.v1.shell_commands import leases
from blazarclient import version as base_version
......@@ -47,7 +48,11 @@ COMMANDS_V1 = {
'host-show': hosts.ShowHost,
'host-create': hosts.CreateHost,
'host-update': hosts.UpdateHost,
'host-delete': hosts.DeleteHost
'host-delete': hosts.DeleteHost,
'floatingip-list': floatingips.ListFloatingIPs,
'floatingip-show': floatingips.ShowFloatingIP,
'floatingip-create': floatingips.CreateFloatingIP,
'floatingip-delete': floatingips.DeleteFloatingIP,
}
VERSION = 1
......
......@@ -183,7 +183,7 @@ class BaseClientManagerTestCase(tests.TestCase):
base.RequestManager)
def test_init_with_insufficient_info(self):
self.assertRaises(exception.InsufficientAuthInfomation,
self.assertRaises(exception.InsufficientAuthInformation,
base.BaseClientManager,
blazar_url=None,
auth_token=self.auth_token,
......
# Copyright (c) 2019 StackHPC Ltd.
#
# 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 argparse
import mock
from blazarclient import shell
from blazarclient import tests
from blazarclient.v1.shell_commands import floatingips
class CreateFloatingIPTest(tests.TestCase):
def setUp(self):
super(CreateFloatingIPTest, self).setUp()
self.create_floatingip = floatingips.CreateFloatingIP(
shell.BlazarShell(), mock.Mock())
def test_args2body(self):
args = argparse.Namespace(
network_id='1e17587e-a7ed-4b82-a17b-4beb32523e28',
floating_ip_address='172.24.4.101',
)
expected = {
'network_id': '1e17587e-a7ed-4b82-a17b-4beb32523e28',
'floating_ip_address': '172.24.4.101',
}
ret = self.create_floatingip.args2body(args)
self.assertDictEqual(ret, expected)
class ListFloatingIPsTest(tests.TestCase):
def create_list_command(self, list_value):
mock_floatingip_manager = mock.Mock()
mock_floatingip_manager.list.return_value = list_value
mock_client = mock.Mock()
mock_client.floatingip = mock_floatingip_manager
blazar_shell = shell.BlazarShell()
blazar_shell.client = mock_client
return (floatingips.ListFloatingIPs(blazar_shell, mock.Mock()),
mock_floatingip_manager)
def test_list_floatingips(self):
list_value = [
{'id': '84c4d37e-1f8b-45ce-897b-16ad7f49b0e9'},
{'id': 'f180cf4c-f886-4dd1-8c36-854d17fbefb5'},
]
list_floatingips, floatingip_manager = self.create_list_command(
list_value)
args = argparse.Namespace(sort_by='id', columns=['id'])
expected = [['id'], [('84c4d37e-1f8b-45ce-897b-16ad7f49b0e9',),
('f180cf4c-f886-4dd1-8c36-854d17fbefb5',)]]
ret = list_floatingips.get_data(args)
self.assertEqual(expected[0], ret[0])
self.assertEqual(expected[1], [x for x in ret[1]])
floatingip_manager.list.assert_called_once_with(sort_by='id')
class ShowFloatingIPTest(tests.TestCase):
def create_show_command(self, list_value, get_value):
mock_floatingip_manager = mock.Mock()
mock_floatingip_manager.list.return_value = list_value
mock_floatingip_manager.get.return_value = get_value
mock_client = mock.Mock()
mock_client.floatingip = mock_floatingip_manager
blazar_shell = shell.BlazarShell()
blazar_shell.client = mock_client
return (floatingips.ShowFloatingIP(blazar_shell, mock.Mock()),
mock_floatingip_manager)
def test_show_floatingip(self):
list_value = [
{'id': '84c4d37e-1f8b-45ce-897b-16ad7f49b0e9'},
{'id': 'f180cf4c-f886-4dd1-8c36-854d17fbefb5'},
]
get_value = {
'id': '84c4d37e-1f8b-45ce-897b-16ad7f49b0e9'}
show_floatingip, floatingip_manager = self.create_show_command(
list_value, get_value)
args = argparse.Namespace(id='84c4d37e-1f8b-45ce-897b-16ad7f49b0e9')
expected = [('id',), ('84c4d37e-1f8b-45ce-897b-16ad7f49b0e9',)]
ret = show_floatingip.get_data(args)
self.assertEqual(ret, expected)
floatingip_manager.get.assert_called_once_with(
'84c4d37e-1f8b-45ce-897b-16ad7f49b0e9')
class DeleteFloatingIPTest(tests.TestCase):
def create_delete_command(self, list_value):
mock_floatingip_manager = mock.Mock()
mock_floatingip_manager.list.return_value = list_value
mock_client = mock.Mock()
mock_client.floatingip = mock_floatingip_manager
blazar_shell = shell.BlazarShell()
blazar_shell.client = mock_client
return (floatingips.DeleteFloatingIP(blazar_shell, mock.Mock()),
mock_floatingip_manager)
def test_delete_floatingip(self):
list_value = [
{'id': '84c4d37e-1f8b-45ce-897b-16ad7f49b0e9'},
{'id': 'f180cf4c-f886-4dd1-8c36-854d17fbefb5'},
]
delete_floatingip, floatingip_manager = self.create_delete_command(
list_value)
args = argparse.Namespace(id='84c4d37e-1f8b-45ce-897b-16ad7f49b0e9')
delete_floatingip.run(args)
floatingip_manager.delete.assert_called_once_with(
'84c4d37e-1f8b-45ce-897b-16ad7f49b0e9')
......@@ -15,6 +15,7 @@
import logging
from blazarclient.v1 import floatingips
from blazarclient.v1 import hosts
from blazarclient.v1 import leases
......@@ -55,3 +56,9 @@ class Client(object):
session=self.session,
version=self.version,
**kwargs)
self.floatingip = floatingips.FloatingIPClientManager(
blazar_url=self.blazar_url,
auth_token=self.auth_token,
session=self.session,
version=self.version,
**kwargs)
# Copyright (c) 2019 StackHPC Ltd.
#
# 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 blazarclient import base
class FloatingIPClientManager(base.BaseClientManager):
"""Manager for floating IP requests."""
def create(self, network_id, floating_ip_address, **kwargs):
"""Creates a floating IP from values passed."""
values = {'floating_network_id': network_id,
'floating_ip_address': floating_ip_address}
values.update(**kwargs)
resp, body = self.request_manager.post('/floatingips', body=values)
return body['floatingip']
def get(self, floatingip_id):
"""Show floating IP details."""
resp, body = self.request_manager.get(
'/floatingips/%s' % floatingip_id)
return body['floatingip']
def delete(self, floatingip_id):
"""Deletes floating IP with specified ID."""
resp, body = self.request_manager.delete(
'/floatingips/%s' % floatingip_id)
def list(self, sort_by=None):
"""List all floating IPs."""
resp, body = self.request_manager.get('/floatingips')
floatingips = body['floatingips']
if sort_by:
floatingips = sorted(floatingips, key=lambda l: l[sort_by])
return floatingips
# Copyright (c) 2019 StackHPC Ltd.
#
# 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 logging
from blazarclient import command
class ListFloatingIPs(command.ListCommand):
"""Print a list of floating IPs."""
resource = 'floatingip'
log = logging.getLogger(__name__ + '.ListFloatingIPs')
list_columns = ['id', 'floating_ip_address', 'floating_network_id']
def get_parser(self, prog_name):
parser = super(ListFloatingIPs, self).get_parser(prog_name)
parser.add_argument(
'--sort-by', metavar="<floatingip_column>",
help='column name used to sort result',
default='id'
)
return parser
class ShowFloatingIP(command.ShowCommand):
"""Show floating IP details."""
resource = 'floatingip'
allow_names = False
json_indent = 4
log = logging.getLogger(__name__ + '.ShowFloatingIP')
class CreateFloatingIP(command.CreateCommand):
"""Create a floating IP."""
resource = 'floatingip'
json_indent = 4
log = logging.getLogger(__name__ + '.CreateFloatingIP')
def get_parser(self, prog_name):
parser = super(CreateFloatingIP, self).get_parser(prog_name)
parser.add_argument(
'network_id', metavar='NETWORK_ID',
help='External network ID to which the floating IP belongs'
)
parser.add_argument(
'floating_ip_address', metavar='FLOATING_IP_ADDRESS',
help='Floating IP address to add to Blazar'
)
return parser
def args2body(self, parsed_args):
params = {}
if parsed_args.network_id:
params['network_id'] = parsed_args.network_id
if parsed_args.floating_ip_address:
params['floating_ip_address'] = parsed_args.floating_ip_address
return params
class DeleteFloatingIP(command.DeleteCommand):
"""Delete a floating IP."""
resource = 'floatingip'
allow_names = False
log = logging.getLogger(__name__ + '.DeleteFloatingIP')
......@@ -18,12 +18,16 @@ import datetime
import logging
import re
from oslo_serialization import jsonutils
from oslo_utils import strutils
from blazarclient import command
from blazarclient import exception
# All valid reservation parameters must be added to CREATE_RESERVATION_KEYS to
# make them parsable. Note that setting the default value to None ensures that
# the parameter is not included in the POST request if absent.
CREATE_RESERVATION_KEYS = {
"physical:host": {
"min": "",
......@@ -33,6 +37,12 @@ CREATE_RESERVATION_KEYS = {
"before_end": None,
"resource_type": 'physical:host'
},
"virtual:floatingip": {
"amount": 1,
"network_id": None,
"required_floatingips": [],
"resource_type": 'virtual:floatingip'
},
"virtual:instance": {
"vcpus": "",
"memory_mb": "",
......@@ -163,6 +173,8 @@ class CreateLease(command.CreateCommand):
else:
if strutils.is_int_like(v):
request_params[k] = int(v)
elif isinstance(defaults[k], list):
request_params[k] = jsonutils.loads(v)
else:
request_params[k] = v
......@@ -204,8 +216,8 @@ class CreateLease(command.CreateCommand):
parsed_args.before_end, '%Y-%m-%d %H:%M')
except ValueError:
raise exception.IncorrectLease
if (parsed_args.before_end < start
or parsed_args.end < parsed_args.before_end):
if (parsed_args.before_end < start or
parsed_args.end < parsed_args.before_end):
raise exception.IncorrectLease
params['before_end'] = datetime.datetime.strftime(
parsed_args.before_end, '%Y-%m-%d %H:%M')
......@@ -275,6 +287,8 @@ class CreateLease(command.CreateCommand):
defaults = CREATE_RESERVATION_KEYS['physical:host']
elif "virtual:instance" in res_str:
defaults = CREATE_RESERVATION_KEYS['virtual:instance']
elif "virtual:floatingip" in res_str:
defaults = CREATE_RESERVATION_KEYS['virtual:floatingip']
else:
defaults = CREATE_RESERVATION_KEYS['others']
......@@ -398,14 +412,17 @@ class UpdateLease(command.UpdateCommand):
if parsed_args.start_date:
params['start_date'] = parsed_args.start_date
if parsed_args.reservation:
keys = [
keys = set([
# General keys
'id',
# Keys for host reservation
'min', 'max', 'hypervisor_properties', 'resource_properties',
# Keys for instance reservation
'vcpus', 'memory_mb', 'disk_gb', 'amount', 'affinity'
]
'vcpus', 'memory_mb', 'disk_gb', 'amount', 'affinity',
# Keys for floating IP reservation
'amount', 'network_id', 'required_floatingips',
])
list_keys = ['required_floatingips']
params['reservations'] = []
reservations = []
for res_str in parsed_args.reservation:
......@@ -419,7 +436,9 @@ class UpdateLease(command.UpdateCommand):
match = prog.search(params)
if match:
k, v = match.group(2, 3)
if strutils.is_int_like(v):
if k in list_keys:
v = jsonutils.loads(v)
elif strutils.is_int_like(v):
v = int(v)
res_info[k] = v
if match.group(1) is not None:
......
......@@ -3,4 +3,5 @@
# process, which may cause wedges in the gate later.
openstackdocstheme>=1.18.1 # Apache-2.0
reno>=2.5.0 # Apache-2.0
sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD
sphinx!=1.6.6,!=1.6.7,>=1.6.2,<2.0.0;python_version=='2.7' # BSD
sphinx!=1.6.6,!=1.6.7,>=1.6.2;python_version>='3.4' # BSD
---
fixes:
- |
The command-line client now parses floating IP reservation values when
using the ``lease-update`` command. Note that while accepted by the client,
the Blazar service may prevent the update of some floating IP reservation
values.
---
features:
- |
Added support for operators to manage reservable floating IPs using the
following new commands:
* ``floatingip-create``
* ``floatingip-delete``
* ``floatingip-list``
* ``floatingip-show``
- |
Added support for users to create floating IP reservations using the
``virtual:floatingip`` resource type.
---
fixes:
- |
Parse the ``required_floatingips`` command-line parameter as a list instead
of a string, to pass it to the API in the expected format. For example,
this parameter can be used in the following fashion:
``blazar lease-create --reservation 'resource_type=virtual:floatingip,network_id=81fabec7-00ae-497a-b485-72f4bf187d3e,amount=2,required_floatingips=["172.24.4.2","172.24.4.3"]' fip-lease``
For more details, see `bug 1843258 <https://launchpad.net/bugs/1843258>`_.
......@@ -6,4 +6,5 @@
:maxdepth: 1
unreleased
stein
rocky
===================================
Stein Series Release Notes
===================================
.. release-notes::
:branch: stable/stein
......@@ -8,7 +8,8 @@ classifiers =
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3
Programming Language :: Python :: 3.5
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Environment :: OpenStack
Development Status :: 3 - Alpha
Framework :: Setuptools Plugin
......@@ -17,7 +18,7 @@ classifiers =
License :: OSI Approved :: Apache Software License
Operating System :: POSIX :: Linux
author = OpenStack
author_email = openstack-dev@lists.openstack.org
author_email = openstack-discuss@lists.openstack.org
home-page = https://launchpad.net/blazar
[global]
......
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
hacking<0.11,>=0.10.2
hacking>=1.1.0,<1.2.0 # Apache-2.0
mock>=2.0.0 # BSD
oslotest>=3.2.0 # Apache-2.0
fixtures>=3.0.0 # Apache-2.0/BSD
......
[tox]
minversion = 2.0
envlist = py35,py34,py27,pep8
envlist = py27,py36,py37,pep8
[testenv]
install_command = pip install {opts} {packages}
deps =
-c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt}
-c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/branch/master/upper-constraints.txt}
-r{toxinidir}/test-requirements.txt
-r{toxinidir}/requirements.txt
setenv = VIRTUAL_ENV={envdir}
......@@ -30,7 +30,7 @@ import_exceptions = blazarclient.i18n
[testenv:venv]
basepython = python3
deps =
-c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt}
-c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/branch/master/upper-constraints.txt}
-r{toxinidir}/requirements.txt
-r{toxinidir}/doc/requirements.txt
commands = {posargs}
......@@ -50,7 +50,7 @@ deps =
[testenv:releasenotes]
basepython = python3
deps =
-c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt}
-c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/branch/master/upper-constraints.txt}
-r{toxinidir}/requirements.txt
-r{toxinidir}/doc/requirements.txt
commands = sphinx-build -a -W -E -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment