# -*- coding: utf-8 -*- # # Copyright 2021 Google LLC. All Rights Reserved. # # 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. """Command for compute reservations update.""" from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals from googlecloudsdk.api_lib.compute import base_classes from googlecloudsdk.api_lib.compute import request_helper from googlecloudsdk.api_lib.compute import utils from googlecloudsdk.calliope import base from googlecloudsdk.calliope import exceptions from googlecloudsdk.command_lib.compute import flags as compute_flags from googlecloudsdk.command_lib.compute.reservations import flags as r_flags from googlecloudsdk.command_lib.compute.reservations import resource_args from googlecloudsdk.command_lib.compute.reservations import util def _ValidateArgs( args, support_share_with_flag, support_auto_delete=False, support_reservation_sharing_policy=False, support_emergent_maintenance=False, support_share_type=False, support_scheduling_type=False, ): """Validates that both share settings arguments are mentioned. Args: args: The arguments given to the update command. support_share_with_flag: Check if share_with is supported. support_auto_delete: Check if auto-delete settings are supported. support_reservation_sharing_policy: Check if reservation sharing policy is supported. support_emergent_maintenance: Check if emergent maintenance is supported. support_share_type: Check if share setting is supported. support_scheduling_type: Check if scheduling type is supported. """ # Check the version and share-with option. share_with = False parameter_names = [ '--share-with', '--vm-count', ] one_option_exception_message = ( 'Please provide one of these options: 1- Specify share-with or' ' add-share-with or remove-share-with to update the project list. 2-' ' Specify reservation vm-count to resize. ' ) if support_auto_delete: parameter_names.extend([ '--delete-at-time', '--delete-after-duration', '--disable-auto-delete', ]) one_option_exception_message += ( '3- Modify auto-delete' ' properties with specifing auto-delete-at-time or' ' auto-delete-after-duration or disable-auto-delete flags.' ) if support_reservation_sharing_policy: parameter_names.extend([ '--reservation-sharing-policy', ]) one_option_exception_message += ( '4- Modify reservation sharing policy with specifying' ' reservation-sharing-policy flag.' ) if support_emergent_maintenance: parameter_names.extend([ '--enable-emergent-maintenance', '--no-enable-emergent-maintenance' ]) one_option_exception_message += ( '5- Modify emergent maintenance with specifying' ' enable-emergent-maintenance flag.' ) if support_share_type: parameter_names.extend([ '--share-setting', ]) one_option_exception_message += ( '6- Modify share setting with specifying share-setting flag.' ) if support_scheduling_type: parameter_names.extend([ '--scheduling-type', ]) one_option_exception_message += ( '7- Modify scheduling type with specifying scheduling-type flag.' ) has_share_with = False if support_share_with_flag: has_share_with = args.IsSpecified('share_with') has_add_share_with = args.IsSpecified('add_share_with') has_remove_share_with = args.IsSpecified('remove_share_with') if has_share_with or has_add_share_with or has_remove_share_with: share_with = True if ( (has_share_with and has_add_share_with) or (has_share_with and has_remove_share_with) or (has_add_share_with and has_remove_share_with) ): raise exceptions.ConflictingArgumentsException( '--share-with', '--add-share-with', '--remove-share-with' ) if has_remove_share_with: for project in getattr(args, 'remove_share_with', []): if not project.isnumeric(): raise exceptions.InvalidArgumentException( '--remove-share-with', 'Please specify project number (not project id/name).', ) minimum_argument_specified = not share_with and not args.IsSpecified( 'vm_count' ) if support_auto_delete: auto_delete_settings_updated = args.IsSpecified('delete_at_time') or ( args.IsSpecified('delete_after_duration') or args.IsSpecified('disable_auto_delete') ) minimum_argument_specified = ( minimum_argument_specified and not auto_delete_settings_updated ) if support_reservation_sharing_policy: minimum_argument_specified = ( minimum_argument_specified and not args.IsSpecified('reservation_sharing_policy') ) if support_emergent_maintenance: minimum_argument_specified = ( minimum_argument_specified and not args.IsSpecified('enable_emergent_maintenance') ) if support_scheduling_type: minimum_argument_specified = ( minimum_argument_specified and not args.IsSpecified('scheduling_type') ) # Check parameters (add_share_with and remove_share_with are on GA). if minimum_argument_specified: raise exceptions.MinimumArgumentException(parameter_names, one_option_exception_message) def _GetShareSettingUpdateRequest( args, reservation_ref, holder, support_share_with_flag, support_share_type): """Create Update Request for share-with. Returns: update request. Args: args: The arguments given to the update command. reservation_ref: reservation refrence. holder: base_classes.ComputeApiHolder. support_share_with_flag: Check if share_with is supported. support_share_type: Check if share_type is supported. """ messages = holder.client.messages # Set updated properties and build update mask. share_settings = None update_mask = [] setting_configs = None if support_share_type and args.IsSpecified('share_setting'): setting_configs = getattr(args, 'share_setting', None) update_mask.append('shareSettings.shareType') if support_share_with_flag: if not setting_configs and (args.IsSpecified('share_with') or args.IsSpecified('add_share_with') or args.IsSpecified('remove_share_with')): setting_configs = 'projects' if args.IsSpecified('share_with'): share_settings = util.MakeShareSettingsWithArgs( messages, args, setting_configs, share_with='share_with') update_mask.extend([ 'shareSettings.projectMap.' + project for project in getattr(args, 'share_with', []) ]) else: setting_configs = 'projects' if args.IsSpecified('add_share_with'): share_settings = util.MakeShareSettingsWithArgs( messages, args, setting_configs, share_with='add_share_with') update_mask.extend([ 'shareSettings.projectMap.' + project for project in getattr(args, 'add_share_with', []) ]) elif args.IsSpecified('remove_share_with'): share_settings = util.MakeShareSettingsWithArgs( messages, args, setting_configs, share_with='remove_share_with') update_mask.extend([ 'shareSettings.projectMap.' + project for project in getattr(args, 'remove_share_with', []) ]) # Build reservation object using new share-settings. r_resource = util.MakeReservationMessage(messages, reservation_ref.Name(), share_settings, None, None, None, reservation_ref.zone) # Build update request. r_update_request = messages.ComputeReservationsUpdateRequest( reservation=reservation_ref.Name(), reservationResource=r_resource, paths=update_mask, project=reservation_ref.project, zone=reservation_ref.zone) return r_update_request def _GetReservationSharingPolicyUpdateRequest(args, reservation_ref, holder): """Create Update Request for the reservation sharing policy. Returns: UpdateRequest Args: args: The arguments given to the update command. reservation_ref: reservation refrence. holder: base_classes.ComputeApiHolder. """ messages = holder.client.messages r_resource = util.MakeReservationMessage( messages, reservation_ref.Name(), None, None, None, None, reservation_ref.zone, reservation_sharing_policy=getattr( args, 'reservation_sharing_policy', None ), ) # Build update request. return messages.ComputeReservationsUpdateRequest( reservation=reservation_ref.Name(), reservationResource=r_resource, paths=['reservationSharingPolicy.serviceShareType'], project=reservation_ref.project, zone=reservation_ref.zone, ) def _GetResizeRequest(args, reservation_ref, holder): """Create Update Request for vm_count. Returns: resize request. Args: args: The arguments given to the update command. reservation_ref: reservation refrence. holder: base_classes.ComputeApiHolder. """ messages = holder.client.messages vm_count = None if args.IsSpecified('vm_count'): vm_count = args.vm_count # Build resize request. r_resize_request = messages.ComputeReservationsResizeRequest( reservation=reservation_ref.Name(), reservationsResizeRequest=messages.ReservationsResizeRequest( specificSkuCount=vm_count), project=reservation_ref.project, zone=reservation_ref.zone) return r_resize_request def _AutoDeleteUpdateRequest(args, reservation_ref, holder): """Create Update Request for modifying auto-delete properties.""" messages = holder.client.messages update_mask = [] if args.IsSpecified('delete_at_time'): update_mask.append('deleteAtTime') delete_at_time = args.delete_at_time else: delete_at_time = None if args.IsSpecified('delete_after_duration'): update_mask.append('deleteAfterDuration') delete_after_duration = args.delete_after_duration else: delete_after_duration = None if args.IsSpecified('disable_auto_delete'): update_mask.append('deleteAtTime') r_resource = util.MakeReservationMessage( messages, reservation_ref.Name(), None, None, None, None, reservation_ref.zone, delete_at_time, delete_after_duration, ) # Build update request. return messages.ComputeReservationsUpdateRequest( reservation=reservation_ref.Name(), reservationResource=r_resource, paths=update_mask, project=reservation_ref.project, zone=reservation_ref.zone, ) def _EnableEmergentMaintenanceUpdateRequest(args, reservation_ref, holder): """Create Update Request for enabling emergent maintenance.""" messages = holder.client.messages update_mask = [] if args.IsSpecified('enable_emergent_maintenance'): update_mask.append('enableEmergentMaintenance') enable_emergent_maintenance = args.enable_emergent_maintenance else: enable_emergent_maintenance = None r_resource = util.MakeReservationMessage( messages, reservation_ref.Name(), None, None, None, None, reservation_ref.zone, enable_emergent_maintenance=enable_emergent_maintenance, ) # Build Update Request. return messages.ComputeReservationsUpdateRequest( reservation=reservation_ref.Name(), reservationResource=r_resource, paths=update_mask, project=reservation_ref.project, zone=reservation_ref.zone, ) def _SchedulingTypeUpdateRequest(args, reservation_ref, holder): """Create Update Request for scheduling type.""" messages = holder.client.messages update_mask = [] if args.IsSpecified('scheduling_type'): update_mask.append('schedulingType') scheduling_type = args.scheduling_type else: scheduling_type = None r_resource = util.MakeReservationMessage( messages, reservation_ref.Name(), None, None, None, None, reservation_ref.zone, scheduling_type=scheduling_type, ) # Build Update Request. return messages.ComputeReservationsUpdateRequest( reservation=reservation_ref.Name(), reservationResource=r_resource, paths=update_mask, project=reservation_ref.project, zone=reservation_ref.zone, ) @base.ReleaseTracks(base.ReleaseTrack.GA) @base.UniverseCompatible class Update(base.UpdateCommand): """Update Compute Engine reservations.""" _support_share_with_flag = False _support_auto_delete = False _support_reservation_sharing_policy = True _support_emergent_maintenance = True _support_share_type = False _support_scheduling_type = True @classmethod def Args(cls, parser): resource_args.GetReservationResourceArg().AddArgument( parser, operation_type='update') r_flags.GetAddShareWithFlag().AddToParser(parser) r_flags.GetRemoveShareWithFlag().AddToParser(parser) r_flags.GetVmCountFlag(False).AddToParser(parser) r_flags.GetReservationSharingPolicyFlag().AddToParser(parser) r_flags.GetEnableEmergentMaintenanceFlag().AddToParser(parser) r_flags.GetSchedulingTypeFlag().AddToParser(parser) def Run(self, args): """Common routine for updating reservation.""" holder = base_classes.ComputeApiHolder(self.ReleaseTrack()) resources = holder.resources service = holder.client.apitools_client.reservations # Validate the command. _ValidateArgs( args, self._support_share_with_flag, self._support_auto_delete, self._support_reservation_sharing_policy, self._support_emergent_maintenance, self._support_share_type, self._support_scheduling_type, ) reservation_ref = resource_args.GetReservationResourceArg( ).ResolveAsResource( args, resources, scope_lister=compute_flags.GetDefaultScopeLister(holder.client)) result = list() errors = [] share_with = False if args.IsSpecified('add_share_with') or args.IsSpecified( 'remove_share_with'): share_with = True if self._support_share_with_flag: if args.IsSpecified('share_with'): share_with = True if share_with: r_update_request = _GetShareSettingUpdateRequest( args, reservation_ref, holder, self._support_share_with_flag, self._support_share_type) # Invoke Reservation.update API. result.append( list( request_helper.MakeRequests( requests=[(service, 'Update', r_update_request)], http=holder.client.apitools_client.http, batch_url=holder.client.batch_url, errors=errors))) if errors: utils.RaiseToolException(errors) if self._support_reservation_sharing_policy: if args.IsSpecified('reservation_sharing_policy'): r_sharing_policy_request = _GetReservationSharingPolicyUpdateRequest( args, reservation_ref, holder ) result.append( list( request_helper.MakeRequests( requests=[(service, 'Update', r_sharing_policy_request)], http=holder.client.apitools_client.http, batch_url=holder.client.batch_url, errors=errors, ) ) ) if args.IsSpecified('vm_count'): r_resize_request = _GetResizeRequest(args, reservation_ref, holder) # Invoke Reservation.resize API. result.append( holder.client.MakeRequests(([(service, 'Resize', r_resize_request)]))) if self._support_auto_delete: if args.IsSpecified('delete_at_time') or ( args.IsSpecified('delete_after_duration') or args.IsSpecified('disable_auto_delete') ): r_update_request = _AutoDeleteUpdateRequest( args, reservation_ref, holder ) result.append( list( request_helper.MakeRequests( requests=[(service, 'Update', r_update_request)], http=holder.client.apitools_client.http, batch_url=holder.client.batch_url, errors=errors, ) ) ) if errors: utils.RaiseToolException(errors) if self._support_emergent_maintenance: if args.IsSpecified('enable_emergent_maintenance'): r_update_request = _EnableEmergentMaintenanceUpdateRequest( args, reservation_ref, holder ) result.append( list( request_helper.MakeRequests( requests=[(service, 'Update', r_update_request)], http=holder.client.apitools_client.http, batch_url=holder.client.batch_url, errors=errors, ))) if errors: utils.RaiseToolException(errors) if self._support_scheduling_type: if args.IsSpecified('scheduling_type'): r_update_request = _SchedulingTypeUpdateRequest( args, reservation_ref, holder ) result.append( list( request_helper.MakeRequests( requests=[(service, 'Update', r_update_request)], http=holder.client.apitools_client.http, batch_url=holder.client.batch_url, errors=errors, ) ) ) if errors: utils.RaiseToolException(errors) return result @base.ReleaseTracks(base.ReleaseTrack.BETA) class UpdateBeta(Update): """Update Compute Engine reservations.""" _support_share_with_flag = True _support_auto_delete = True _support_reservation_sharing_policy = True _support_emergent_maintenance = True _support_share_type = True _support_scheduling_type = True @classmethod def Args(cls, parser): resource_args.GetReservationResourceArg().AddArgument( parser, operation_type='update') r_flags.GetShareWithFlag().AddToParser(parser) r_flags.GetAddShareWithFlag().AddToParser(parser) r_flags.GetRemoveShareWithFlag().AddToParser(parser) r_flags.GetVmCountFlag(False).AddToParser(parser) r_flags.GetReservationSharingPolicyFlag().AddToParser(parser) r_flags.GetEnableEmergentMaintenanceFlag().AddToParser(parser) r_flags.GetSharedSettingFlag( support_folder_share_setting=False).AddToParser(parser) r_flags.GetSchedulingTypeFlag().AddToParser(parser) auto_delete_group = base.ArgumentGroup( 'Manage auto-delete properties for reservations.', required=False, mutex=True, ) auto_delete_group.AddArgument(r_flags.GetDeleteAtTimeFlag()) auto_delete_group.AddArgument(r_flags.GetDeleteAfterDurationFlag()) auto_delete_group.AddArgument(r_flags.GetDisableAutoDelete()) auto_delete_group.AddToParser(parser) @base.ReleaseTracks(base.ReleaseTrack.ALPHA) class UpdateAlpha(Update): """Update Compute Engine reservations.""" _support_share_with_flag = True _support_auto_delete = True _support_reservation_sharing_policy = True _support_emergent_maintenance = True _support_share_type = True _support_scheduling_type = True @classmethod def Args(cls, parser): resource_args.GetReservationResourceArg().AddArgument( parser, operation_type='update' ) r_flags.GetShareWithFlag().AddToParser(parser) r_flags.GetAddShareWithFlag().AddToParser(parser) r_flags.GetRemoveShareWithFlag().AddToParser(parser) r_flags.GetVmCountFlag(False).AddToParser(parser) r_flags.GetReservationSharingPolicyFlag().AddToParser(parser) r_flags.GetEnableEmergentMaintenanceFlag().AddToParser(parser) r_flags.GetSharedSettingFlag( support_folder_share_setting=False).AddToParser(parser) r_flags.GetSchedulingTypeFlag().AddToParser(parser) auto_delete_group = base.ArgumentGroup( 'Manage auto-delete properties for reservations.', required=False, mutex=True, ) auto_delete_group.AddArgument(r_flags.GetDeleteAtTimeFlag()) auto_delete_group.AddArgument(r_flags.GetDeleteAfterDurationFlag()) auto_delete_group.AddArgument(r_flags.GetDisableAutoDelete()) auto_delete_group.AddToParser(parser) Update.detailed_help = { 'EXAMPLES': """ To add `project-1,project-2,project-3` to the list of projects that are shared with a Compute Engine reservation, `my-reservation` in zone: `us-central1-a`, run: $ {command} my-reservation --add-share-with=project-1,project-2,project-3 --zone=us-central1-a To remove `project-1,project-2,project-3` from the list of projects that are shared with a Compute Engine reservation, `my-reservation` in zone: `us-central1-a`, run: $ {command} my-reservation --remove-share-with=project-1,project-2,project-3 --zone=us-central1-a To update the number of reserved VM instances to 500 for a Compute Engine reservation, `my-reservation` in zone: `us-central1-a`, run: $ {command} my-reservation --zone=us-central1-a --vm-count=500 """ } UpdateBeta.detailed_help = { 'EXAMPLES': """ To add `project-1,project-2,project-3` to the list of projects that are shared with a Compute Engine reservation, `my-reservation` in zone: `us-central1-a`, run: $ {command} my-reservation --add-share-with=project-1,project-2,project-3 --zone=us-central1-a To remove `project-1,project-2,project-3` from the list of projects that are shared with a Compute Engine reservation, `my-reservation` in zone: `us-central1-a`, run: $ {command} my-reservation --remove-share-with=project-1,project-2,project-3 --zone=us-central1-a To update the entire list of projects that are shared with a Compute Engine reservation, `my-reservation` in zone: `us-central1-a`, run: $ {command} my-reservation --share-with=project-1,project-2,project-3 --zone=us-central1-a To update the number of reserved VM instances to 500 for a Compute Engine reservation, `my-reservation` in zone: `us-central1-a`, run: $ {command} my-reservation --zone=us-central1-a --vm-count=500 """ } UpdateAlpha.detailed_help = { 'EXAMPLES': """ To add `project-1,project-2,project-3` to the list of projects that are shared with a Compute Engine reservation, `my-reservation` in zone: `us-central1-a`, run: $ {command} my-reservation --add-share-with=project-1,project-2,project-3 --zone=us-central1-a To remove `project-1,project-2,project-3` from the list of projects that are shared with a Compute Engine reservation, `my-reservation` in zone: `us-central1-a`, run: $ {command} my-reservation --remove-share-with=project-1,project-2,project-3 --zone=us-central1-a To update the entire list of projects that are shared with a Compute Engine reservation, `my-reservation` in zone: `us-central1-a`, run: $ {command} my-reservation --share-with=project-1,project-2,project-3 --zone=us-central1-a To update the number of reserved VM instances to 500 for a Compute Engine reservation, `my-reservation` in zone: `us-central1-a`, run: $ {command} my-reservation --zone=us-central1-a --vm-count=500 """ }