Add openstackclient support
This adds support for the recommended CLI using the OpenStackClient, without modifying the existing Blazar shell CLI. The existing shell command classes are used, by introducing a check in the base comand class to use either the client passed by Blazar shell, or the client using the osc_lib client_manager. The argument --physical-reservation is also removed for the create lease command when using the OpenStack client. Implements: blueprint openstackclient-support Change-Id: I97a7b91f0d05efc887307ac167e5c368276d4f81
This commit is contained in:
parent
1cf4d73dd5
commit
f2277b5f7a
@ -81,7 +81,12 @@ class BlazarCommand(OpenStackCommand):
|
||||
# self.formatters['table'] = TableFormatter()
|
||||
|
||||
def get_client(self):
|
||||
return self.app.client
|
||||
# client_manager.reservation is used for osc_lib, and should be used
|
||||
# if it exists
|
||||
if hasattr(self.app, 'client_manager'):
|
||||
return self.app.client_manager.reservation
|
||||
else:
|
||||
return self.app.client
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(BlazarCommand, self).get_parser(prog_name)
|
||||
|
0
blazarclient/osc/__init__.py
Normal file
0
blazarclient/osc/__init__.py
Normal file
66
blazarclient/osc/plugin.py
Normal file
66
blazarclient/osc/plugin.py
Normal file
@ -0,0 +1,66 @@
|
||||
# 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 osc_lib import utils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_API_VERSION = '1'
|
||||
|
||||
# Required by the OSC plugin interface
|
||||
API_NAME = 'reservation'
|
||||
API_VERSION_OPTION = 'os_reservations_api_version'
|
||||
API_VERSIONS = {
|
||||
'1': 'blazarclient.v1.client.Client',
|
||||
}
|
||||
|
||||
|
||||
# Required by the OSC plugin interface
|
||||
def make_client(instance):
|
||||
reservation_client = utils.get_client_class(
|
||||
API_NAME,
|
||||
instance._api_version[API_NAME],
|
||||
API_VERSIONS)
|
||||
|
||||
LOG.debug("Instantiating reservation client: %s", reservation_client)
|
||||
|
||||
client = reservation_client(
|
||||
instance._api_version[API_NAME],
|
||||
session=instance.session,
|
||||
endpoint_override=instance.get_endpoint_for_service_type(
|
||||
API_NAME,
|
||||
interface=instance.interface,
|
||||
region_name=instance._region_name)
|
||||
)
|
||||
return client
|
||||
|
||||
|
||||
# Required by the OSC plugin interface
|
||||
def build_option_parser(parser):
|
||||
"""Hook to add global options.
|
||||
Called from openstackclient.shell.OpenStackShell.__init__()
|
||||
after the builtin parser has been initialized. This is
|
||||
where a plugin can add global options such as an API version setting.
|
||||
:param argparse.ArgumentParser parser: The parser object that has been
|
||||
initialized by OpenStackShell.
|
||||
"""
|
||||
parser.add_argument(
|
||||
"--os-reservation-api-version",
|
||||
metavar="<reservation-api-version>",
|
||||
help="Reservation API version, default="
|
||||
"{} (Env: OS_RESERVATION_API_VERSION)".format(
|
||||
DEFAULT_API_VERSION)
|
||||
)
|
||||
return parser
|
@ -60,9 +60,19 @@ class BlazarCommandTestCase(tests.TestCase):
|
||||
self.command = command.BlazarCommand(self.app, [])
|
||||
|
||||
def test_get_client(self):
|
||||
# Test that either client_manager.reservation or client is used,
|
||||
# whichever exists
|
||||
|
||||
client_manager = self.app.client_manager
|
||||
del self.app.client_manager
|
||||
client = self.command.get_client()
|
||||
self.assertEqual(self.app.client, client)
|
||||
|
||||
self.app.client_manager = client_manager
|
||||
del self.app.client
|
||||
client = self.command.get_client()
|
||||
self.assertEqual(self.app.client_manager.reservation, client)
|
||||
|
||||
def test_get_parser(self):
|
||||
self.command.get_parser('TestCase')
|
||||
self.parser.assert_called_once_with('TestCase')
|
||||
|
36
blazarclient/tests/test_plugin.py
Normal file
36
blazarclient/tests/test_plugin.py
Normal file
@ -0,0 +1,36 @@
|
||||
# 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 unittest import mock
|
||||
|
||||
from blazarclient.osc import plugin
|
||||
from blazarclient import tests
|
||||
|
||||
|
||||
class ReservationPluginTests(tests.TestCase):
|
||||
|
||||
@mock.patch("blazarclient.v1.client.Client")
|
||||
def test_make_client(self, mock_client):
|
||||
instance = mock.Mock()
|
||||
instance._api_version = {"reservation": "1"}
|
||||
endpoint = "blazar_endpoint"
|
||||
instance.get_endpoint_for_service_type = mock.Mock(
|
||||
return_value=endpoint
|
||||
)
|
||||
|
||||
plugin.make_client(instance)
|
||||
|
||||
mock_client.assert_called_with(
|
||||
"1",
|
||||
session=instance.session,
|
||||
endpoint_override=endpoint
|
||||
)
|
@ -91,7 +91,7 @@ class ShowLease(command.ShowCommand):
|
||||
log = logging.getLogger(__name__ + '.ShowLease')
|
||||
|
||||
|
||||
class CreateLease(command.CreateCommand):
|
||||
class CreateLeaseBase(command.CreateCommand):
|
||||
"""Create a lease."""
|
||||
resource = 'lease'
|
||||
json_indent = 4
|
||||
@ -100,7 +100,7 @@ class CreateLease(command.CreateCommand):
|
||||
default_end = _utc_now() + datetime.timedelta(days=1)
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CreateLease, self).get_parser(prog_name)
|
||||
parser = super(CreateLeaseBase, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'name', metavar=self.resource.upper(),
|
||||
help='Name for the %s' % self.resource
|
||||
@ -126,22 +126,6 @@ class CreateLease(command.CreateCommand):
|
||||
'the end of the lease (default: depends on system default)',
|
||||
default=None
|
||||
)
|
||||
parser.add_argument(
|
||||
'--physical-reservation',
|
||||
metavar="<min=int,max=int,hypervisor_properties=str,"
|
||||
"resource_properties=str,before_end=str>",
|
||||
action='append',
|
||||
dest='physical_reservations',
|
||||
help='Create a reservation for physical compute hosts. '
|
||||
'Specify option multiple times to create multiple '
|
||||
'reservations. '
|
||||
'min: minimum number of hosts to reserve. '
|
||||
'max: maximum number of hosts to reserve. '
|
||||
'hypervisor_properties: JSON string, see doc. '
|
||||
'resource_properties: JSON string, see doc. '
|
||||
'before_end: JSON string, see doc. ',
|
||||
default=[]
|
||||
)
|
||||
parser.add_argument(
|
||||
'--reservation',
|
||||
metavar="<key=value>",
|
||||
@ -165,36 +149,12 @@ class CreateLease(command.CreateCommand):
|
||||
return parser
|
||||
|
||||
def args2body(self, parsed_args):
|
||||
def parse_params(str_params, default):
|
||||
request_params = {}
|
||||
prog = re.compile('^(?:(.*),)?(%s)=(.*)$'
|
||||
% "|".join(default.keys()))
|
||||
|
||||
while str_params != "":
|
||||
match = prog.search(str_params)
|
||||
|
||||
if match is None:
|
||||
raise exception.IncorrectLease(err_msg)
|
||||
|
||||
self.log.info("Matches: %s", match.groups())
|
||||
k, v = match.group(2, 3)
|
||||
if k in request_params.keys():
|
||||
raise exception.DuplicatedLeaseParameters(err_msg)
|
||||
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
|
||||
|
||||
str_params = match.group(1) if match.group(1) else ""
|
||||
|
||||
request_params.update({k: v for k, v in default.items()
|
||||
if k not in request_params.keys() and
|
||||
v is not None})
|
||||
return request_params
|
||||
params = self._generate_params(parsed_args)
|
||||
if not params['reservations']:
|
||||
raise exception.IncorrectLease
|
||||
return params
|
||||
|
||||
def _generate_params(self, parsed_args):
|
||||
params = {}
|
||||
if parsed_args.name:
|
||||
params['name'] = parsed_args.name
|
||||
@ -243,6 +203,115 @@ class CreateLease(command.CreateCommand):
|
||||
params['reservations'] = []
|
||||
params['events'] = []
|
||||
|
||||
reservations = []
|
||||
for res_str in parsed_args.reservations:
|
||||
err_msg = ("Invalid reservation argument '%s'. "
|
||||
"Reservation arguments must be of the "
|
||||
"form --reservation <key=value>"
|
||||
% res_str)
|
||||
|
||||
if "physical:host" in res_str:
|
||||
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']
|
||||
|
||||
res_info = self._parse_params(res_str, defaults, err_msg)
|
||||
reservations.append(res_info)
|
||||
|
||||
if reservations:
|
||||
params['reservations'] += reservations
|
||||
|
||||
events = []
|
||||
for event_str in parsed_args.events:
|
||||
err_msg = ("Invalid event argument '%s'. "
|
||||
"Event arguments must be of the "
|
||||
"form --event <event_type=str,event_date=time>"
|
||||
% event_str)
|
||||
event_info = {"event_type": "", "event_date": ""}
|
||||
for kv_str in event_str.split(","):
|
||||
try:
|
||||
k, v = kv_str.split("=", 1)
|
||||
except ValueError:
|
||||
raise exception.IncorrectLease(err_msg)
|
||||
if k in event_info:
|
||||
event_info[k] = v
|
||||
else:
|
||||
raise exception.IncorrectLease(err_msg)
|
||||
if not event_info['event_type'] and not event_info['event_date']:
|
||||
raise exception.IncorrectLease(err_msg)
|
||||
event_date = event_info['event_date']
|
||||
try:
|
||||
date = datetime.datetime.strptime(event_date, '%Y-%m-%d %H:%M')
|
||||
event_date = datetime.datetime.strftime(date, '%Y-%m-%d %H:%M')
|
||||
event_info['event_date'] = event_date
|
||||
except ValueError:
|
||||
raise exception.IncorrectLease
|
||||
events.append(event_info)
|
||||
if events:
|
||||
params['events'] = events
|
||||
|
||||
return params
|
||||
|
||||
def _parse_params(self, str_params, default, err_msg):
|
||||
request_params = {}
|
||||
prog = re.compile('^(?:(.*),)?(%s)=(.*)$'
|
||||
% "|".join(default.keys()))
|
||||
|
||||
while str_params != "":
|
||||
match = prog.search(str_params)
|
||||
|
||||
if match is None:
|
||||
raise exception.IncorrectLease(err_msg)
|
||||
|
||||
self.log.info("Matches: %s", match.groups())
|
||||
k, v = match.group(2, 3)
|
||||
if k in request_params.keys():
|
||||
raise exception.DuplicatedLeaseParameters(err_msg)
|
||||
else:
|
||||
if strutils.is_int_like(v):
|
||||
request_params[k] = int(v)
|
||||
elif isinstance(default[k], list):
|
||||
request_params[k] = jsonutils.loads(v)
|
||||
else:
|
||||
request_params[k] = v
|
||||
|
||||
str_params = match.group(1) if match.group(1) else ""
|
||||
|
||||
request_params.update({k: v for k, v in default.items()
|
||||
if k not in request_params.keys() and
|
||||
v is not None})
|
||||
return request_params
|
||||
|
||||
|
||||
class CreateLease(CreateLeaseBase):
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CreateLease, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'--physical-reservation',
|
||||
metavar="<min=int,max=int,hypervisor_properties=str,"
|
||||
"resource_properties=str,before_end=str>",
|
||||
action='append',
|
||||
dest='physical_reservations',
|
||||
help='Create a reservation for physical compute hosts. '
|
||||
'Specify option multiple times to create multiple '
|
||||
'reservations. '
|
||||
'min: minimum number of hosts to reserve. '
|
||||
'max: maximum number of hosts to reserve. '
|
||||
'hypervisor_properties: JSON string, see doc. '
|
||||
'resource_properties: JSON string, see doc. '
|
||||
'before_end: JSON string, see doc. ',
|
||||
default=[]
|
||||
)
|
||||
return parser
|
||||
|
||||
def args2body(self, parsed_args):
|
||||
params = self._generate_params(parsed_args)
|
||||
|
||||
physical_reservations = []
|
||||
for phys_res_str in parsed_args.physical_reservations:
|
||||
err_msg = ("Invalid physical-reservation argument '%s'. "
|
||||
@ -252,7 +321,7 @@ class CreateLease(command.CreateCommand):
|
||||
"before_end=str>"
|
||||
% phys_res_str)
|
||||
defaults = CREATE_RESERVATION_KEYS["physical:host"]
|
||||
phys_res_info = parse_params(phys_res_str, defaults)
|
||||
phys_res_info = self._parse_params(phys_res_str, defaults, err_msg)
|
||||
|
||||
if not (phys_res_info['min'] and phys_res_info['max']):
|
||||
raise exception.IncorrectLease(err_msg)
|
||||
@ -284,61 +353,10 @@ class CreateLease(command.CreateCommand):
|
||||
phys_res_info['resource_type'] = 'physical:host'
|
||||
physical_reservations.append(phys_res_info)
|
||||
if physical_reservations:
|
||||
params['reservations'] += physical_reservations
|
||||
|
||||
reservations = []
|
||||
for res_str in parsed_args.reservations:
|
||||
err_msg = ("Invalid reservation argument '%s'. "
|
||||
"Reservation arguments must be of the "
|
||||
"form --reservation <key=value>"
|
||||
% res_str)
|
||||
|
||||
if "physical:host" in res_str:
|
||||
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']
|
||||
|
||||
res_info = parse_params(res_str, defaults)
|
||||
reservations.append(res_info)
|
||||
|
||||
if reservations:
|
||||
params['reservations'] += reservations
|
||||
|
||||
if not params['reservations']:
|
||||
raise exception.IncorrectLease
|
||||
|
||||
events = []
|
||||
for event_str in parsed_args.events:
|
||||
err_msg = ("Invalid event argument '%s'. "
|
||||
"Event arguments must be of the "
|
||||
"form --event <event_type=str,event_date=time>"
|
||||
% event_str)
|
||||
event_info = {"event_type": "", "event_date": ""}
|
||||
for kv_str in event_str.split(","):
|
||||
try:
|
||||
k, v = kv_str.split("=", 1)
|
||||
except ValueError:
|
||||
raise exception.IncorrectLease(err_msg)
|
||||
if k in event_info:
|
||||
event_info[k] = v
|
||||
else:
|
||||
raise exception.IncorrectLease(err_msg)
|
||||
if not event_info['event_type'] and not event_info['event_date']:
|
||||
raise exception.IncorrectLease(err_msg)
|
||||
event_date = event_info['event_date']
|
||||
try:
|
||||
date = datetime.datetime.strptime(event_date, '%Y-%m-%d %H:%M')
|
||||
event_date = datetime.datetime.strftime(date, '%Y-%m-%d %H:%M')
|
||||
event_info['event_date'] = event_date
|
||||
except ValueError:
|
||||
raise exception.IncorrectLease
|
||||
events.append(event_info)
|
||||
if events:
|
||||
params['events'] = events
|
||||
# We prepend the physical_reservations to preserve legacy order
|
||||
# of reservations
|
||||
params['reservations'] = physical_reservations \
|
||||
+ params['reservations']
|
||||
|
||||
return params
|
||||
|
||||
|
@ -15,6 +15,7 @@ msgpack-python==0.4.0
|
||||
netaddr==0.7.18
|
||||
netifaces==0.10.4
|
||||
os-client-config==1.28.0
|
||||
osc-lib==1.3.0
|
||||
oslo.config==5.2.0
|
||||
oslo.context==2.19.2
|
||||
oslo.i18n==3.15.3
|
||||
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Add openstackclient plugin support, enabling blazar commands to be used
|
||||
within the openstack CLI.
|
@ -9,3 +9,4 @@ oslo.i18n>=3.15.3 # Apache-2.0
|
||||
oslo.log>=3.36.0 # Apache-2.0
|
||||
oslo.utils>=3.33.0 # Apache-2.0
|
||||
keystoneauth1>=3.4.0 # Apache-2.0
|
||||
osc-lib>=1.3.0 # Apache-2.0
|
||||
|
19
setup.cfg
19
setup.cfg
@ -30,3 +30,22 @@ packages =
|
||||
[entry_points]
|
||||
console_scripts =
|
||||
blazar = blazarclient.shell:main
|
||||
|
||||
openstack.cli.extension =
|
||||
reservation = blazarclient.osc.plugin
|
||||
|
||||
openstack.reservation.v1 =
|
||||
reservation_floatingip_create = blazarclient.v1.shell_commands.floatingips:CreateFloatingIP
|
||||
reservation_floatingip_delete = blazarclient.v1.shell_commands.floatingips:DeleteFloatingIP
|
||||
reservation_floatingip_list = blazarclient.v1.shell_commands.floatingips:ListFloatingIPs
|
||||
reservation_floatingip_show = blazarclient.v1.shell_commands.floatingips:ShowFloatingIP
|
||||
reservation_host_create = blazarclient.v1.shell_commands.hosts:CreateHost
|
||||
reservation_host_delete = blazarclient.v1.shell_commands.hosts:DeleteHost
|
||||
reservation_host_list = blazarclient.v1.shell_commands.hosts:ListHosts
|
||||
reservation_host_set = blazarclient.v1.shell_commands.hosts:UpdateHost
|
||||
reservation_host_show = blazarclient.v1.shell_commands.hosts:ShowHost
|
||||
reservation_lease_create = blazarclient.v1.shell_commands.leases:CreateLeaseBase
|
||||
reservation_lease_delete = blazarclient.v1.shell_commands.leases:DeleteLease
|
||||
reservation_lease_list = blazarclient.v1.shell_commands.leases:ListLeases
|
||||
reservation_lease_set = blazarclient.v1.shell_commands.leases:UpdateLease
|
||||
reservation_lease_show = blazarclient.v1.shell_commands.leases:ShowLease
|
||||
|
Loading…
Reference in New Issue
Block a user