From 7d2d13ff4d3ceee7e48f80958a91648035f531ce Mon Sep 17 00:00:00 2001 From: Sylvain Bauza Date: Wed, 15 Jan 2014 12:26:56 +0100 Subject: [PATCH] Implement Lease creation for Physical reservations climate lease-create was not available, albeit we need it for the physical reservation usecase. Adding it, e.g. climate lease-create --physical-reservation min=1,max=5,\ hypervisor_properties='["=", "$hypervisor_type", "QEMU"]',\ resource_properties="" test_phys Partially implements blueprint python-client Change-Id: Ia836846c3d25a3dd3ccd3308d40f165253df059d --- climateclient/exception.py | 6 + climateclient/v1/shell_commands/leases.py | 179 +++++++++++++++++++++- 2 files changed, 178 insertions(+), 7 deletions(-) diff --git a/climateclient/exception.py b/climateclient/exception.py index 2b9489f..4c68fea 100644 --- a/climateclient/exception.py +++ b/climateclient/exception.py @@ -70,3 +70,9 @@ class UnsupportedVersion(ClimateClientException): """Occurs if unsupported client version was requested.""" message = _("Unsupported client version requested.") code = 406 + + +class IncorrectLease(ClimateClientException): + """Occurs if lease parameters are incorrect.""" + message = _("The lease parameters are incorrect.") + code = 409 diff --git a/climateclient/v1/shell_commands/leases.py b/climateclient/v1/shell_commands/leases.py index d061cc1..d82a246 100644 --- a/climateclient/v1/shell_commands/leases.py +++ b/climateclient/v1/shell_commands/leases.py @@ -14,9 +14,12 @@ # limitations under the License. import argparse +import datetime import logging +import re from climateclient import command +from climateclient import exception class ListLeases(command.ListCommand): @@ -32,16 +35,178 @@ class ShowLease(command.ShowCommand): class CreateLease(command.CreateCommand): - """Comprehended only for physical reservations. + resource = 'lease' + log = logging.getLogger(__name__ + '.CreateLease') + default_start = datetime.datetime.utcnow() + default_end = default_start + datetime.timedelta(days=1) - For physical reservations lease is created manually. + def get_parser(self, prog_name): + parser = super(CreateLease, self).get_parser(prog_name) + parser.add_argument( + 'name', metavar=self.resource.upper(), + help='Name for the %s' % self.resource + ) + parser.add_argument( + '--start-date', + dest='start', + help='Time (YYYY-MM-DD HH:MM) UTC TZ for starting the lease ' + '(default: now)', + default=self.default_start + ) + parser.add_argument( + '--end-date', + dest='end', + help='Time (YYYY-MM-DD HH:MM) UTC TZ for ending the lease ' + '(default: 24h later)', + default=self.default_end + ) + parser.add_argument( + '--physical-reservation', + metavar="", + 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. ', + default=[] + ) + parser.add_argument( + '--reservation', + metavar="", + action='append', + dest='reservations', + help='key/value pairs for creating a generic reservation. ' + 'Specify option multiple times to create multiple ' + 'reservations. ', + default=[] + ) + parser.add_argument( + '--event', metavar='', + action='append', + dest='events', + help='Creates an event with key/value pairs for the lease. ' + 'Specify option multiple times to create multiple events. ' + 'event_type: type of event (e.g. notification). ' + 'event_date: Time for event (YYYY-MM-DD HH:MM) UTC TZ. ', + default=[] + ) + return parser - For virtual reservations we need id of the reserved resource to create - lease. When service creates reserved resource (Nova-VM, Cinder-volume, - etc.) it comes to Climate and creates lease via Python client. + def args2body(self, parsed_args): + params = {} + if parsed_args.name: + params['name'] = parsed_args.name + if not isinstance(parsed_args.start, datetime.datetime): + try: + parsed_args.start = datetime.datetime.strptime( + parsed_args.start, '%Y-%m-%d %H:%M') + except ValueError: + raise exception.IncorrectLease + if not isinstance(parsed_args.end, datetime.datetime): + try: + parsed_args.end = datetime.datetime.strptime( + parsed_args.end, '%Y-%m-%d %H:%M') + except ValueError: + raise exception.IncorrectLease + if parsed_args.start > parsed_args.end: + raise exception.IncorrectLease + params['start'] = datetime.datetime.strftime(parsed_args.start, + '%Y-%m-%d %H:%M') + params['end'] = datetime.datetime.strftime(parsed_args.end, + '%Y-%m-%d %H:%M') - """ - pass + params['reservations'] = [] + params['events'] = [] + + physical_reservations = [] + for phys_res_str in parsed_args.physical_reservations: + err_msg = ("Invalid physical-reservation argument '%s'. " + "Reservation arguments must be of the " + "form --physical-reservation " + % phys_res_str) + phys_res_info = {"min": "", "max": "", "hypervisor_properties": "", + "resource_properties": ""} + prog = re.compile('^(\w+)=(\w+|\[[^]]+\])(?:,(.+))?$') + + def parse_params(params): + match = prog.search(params) + if match: + self.log.info("Matches: %s", match.groups()) + k, v = match.group(1, 2) + if k in phys_res_info: + phys_res_info[k] = v + else: + raise exception.IncorrectLease(err_msg) + if len(match.groups()) == 3 and match.group(3) is not None: + parse_params(match.group(3)) + + parse_params(phys_res_str) + if not phys_res_info['min'] and not phys_res_info['max']: + raise exception.IncorrectLease(err_msg) + # NOTE(sbauza): The resource type should be conf-driven mapped with + # climate.conf file but that's potentially on another + # host + 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 " + % res_str) + res_info = {} + for kv_str in res_str.split(","): + try: + k, v = kv_str.split("=", 1) + except ValueError: + raise exception.IncorrectLease(err_msg) + res_info[k] = v + 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_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 class UpdateLease(command.UpdateCommand):