# -*- coding: utf-8 -*- # # Copyright 2014 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 updating a backend in a backend service.""" from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals from apitools.base.py import encoding from googlecloudsdk.api_lib.compute import base_classes from googlecloudsdk.calliope import base from googlecloudsdk.command_lib.compute import exceptions from googlecloudsdk.command_lib.compute import flags as compute_flags from googlecloudsdk.command_lib.compute.backend_services import backend_flags from googlecloudsdk.command_lib.compute.backend_services import backend_services_utils from googlecloudsdk.command_lib.compute.backend_services import flags @base.UniverseCompatible @base.ReleaseTracks(base.ReleaseTrack.GA) class UpdateBackend(base.UpdateCommand): """Update an existing backend of a load balancer or Traffic Director. *{command}* updates attributes of a backend that is already associated with a backend service. Configurable attributes depend on the load balancing scheme and the type of backend (instance group, zonal NEG, serverless NEG, or internet NEG). For more information, see [traffic distribution](https://cloud.google.com/load-balancing/docs/backend-service#traffic_distribution). and the [Failover for Internal TCP/UDP Load Balancing overview](https://cloud.google.com/load-balancing/docs/internal/failover-overview). To add, remove, or swap backends, use the `gcloud compute backend-services remove-backend` and `gcloud compute backend-services add-backend` commands. """ @staticmethod def Args(parser): flags.GLOBAL_REGIONAL_BACKEND_SERVICE_ARG.AddArgument(parser) backend_flags.AddDescription(parser) flags.AddInstanceGroupAndNetworkEndpointGroupArgs(parser, 'update in') backend_flags.AddBalancingMode(parser) backend_flags.AddCapacityLimits(parser) backend_flags.AddCapacityScalar(parser) backend_flags.AddFailover(parser, default=None) backend_flags.AddPreference(parser) backend_flags.AddCustomMetrics(parser, add_clear_argument=True) def _GetGetRequest(self, client, backend_service_ref): if backend_service_ref.Collection() == 'compute.regionBackendServices': return (client.apitools_client.regionBackendServices, 'Get', client.messages.ComputeRegionBackendServicesGetRequest( backendService=backend_service_ref.Name(), region=backend_service_ref.region, project=backend_service_ref.project)) return (client.apitools_client.backendServices, 'Get', client.messages.ComputeBackendServicesGetRequest( backendService=backend_service_ref.Name(), project=backend_service_ref.project)) def _GetSetRequest(self, client, backend_service_ref, replacement): if backend_service_ref.Collection() == 'compute.regionBackendServices': return (client.apitools_client.regionBackendServices, 'Update', client.messages.ComputeRegionBackendServicesUpdateRequest( backendService=backend_service_ref.Name(), backendServiceResource=replacement, region=backend_service_ref.region, project=backend_service_ref.project)) return (client.apitools_client.backendServices, 'Update', client.messages.ComputeBackendServicesUpdateRequest( backendService=backend_service_ref.Name(), backendServiceResource=replacement, project=backend_service_ref.project)) def _GetGroupRef(self, args, resources, client): if args.instance_group: return flags.MULTISCOPE_INSTANCE_GROUP_ARG.ResolveAsResource( args, resources, scope_lister=compute_flags.GetDefaultScopeLister(client)) if args.network_endpoint_group: return flags.GetNetworkEndpointGroupArg().ResolveAsResource( args, resources, scope_lister=compute_flags.GetDefaultScopeLister(client)) def _Modify(self, client, resources, backend_service_ref, args, existing): replacement = encoding.CopyProtoMessage(existing) group_ref = self._GetGroupRef(args, resources, client) backend_to_update = None for backend in replacement.backends: # At most one backend will match if group_ref.RelativeName() == resources.ParseURL( backend.group).RelativeName(): backend_to_update = backend break if not backend_to_update: scope_type = None scope_name = None if hasattr(group_ref, 'zone'): scope_type = 'zone' scope_name = group_ref.zone if hasattr(group_ref, 'region'): scope_type = 'region' scope_name = group_ref.region raise exceptions.ArgumentError( 'No backend with name [{0}] in {1} [{2}] is part of the backend ' 'service [{3}].'.format(group_ref.Name(), scope_type, scope_name, backend_service_ref.Name())) if args.description: backend_to_update.description = args.description elif args.description is not None: backend_to_update.description = None self._ModifyBalancingModeArgs( client, args, backend_to_update, self.ReleaseTrack() ) if backend_to_update is not None and args.failover is not None: backend_to_update.failover = args.failover if backend_to_update is not None and args.preference is not None: backend_to_update.preference = ( client.messages.Backend.PreferenceValueValuesEnum(args.preference) ) if ( ( self.ReleaseTrack() == base.ReleaseTrack.ALPHA or self.ReleaseTrack() == base.ReleaseTrack.BETA ) and backend_to_update is not None and args.traffic_duration is not None ): backend_to_update.trafficDuration = ( client.messages.Backend.TrafficDurationValueValuesEnum( args.traffic_duration ) ) if args.custom_metrics: backend_to_update.customMetrics = args.custom_metrics if args.custom_metrics_file: backend_to_update.customMetrics = args.custom_metrics_file if args.clear_custom_metrics: backend_to_update.customMetrics = [] return replacement def _ModifyBalancingModeArgs( self, client, args, backend_to_update, release_track=None ): """Update balancing mode fields in backend_to_update according to args. Args: client: The compute client. args: The arguments given to the update-backend command. backend_to_update: The backend message to modify. release_track: The release track of the command. """ _ModifyBalancingModeArgs( client.messages, args, backend_to_update, release_track ) def _ValidateArgs(self, args): """Validatest that at least one field to update is specified. Args: args: The arguments given to the update-backend command. """ if not any([ args.description is not None, args.balancing_mode, args.max_utilization is not None, args.max_rate is not None, args.max_rate_per_instance is not None, args.max_rate_per_endpoint is not None, args.max_connections is not None, args.max_connections_per_instance is not None, args.max_connections_per_endpoint is not None, args.capacity_scaler is not None, args.failover is not None, args.preference is not None, ]): raise exceptions.UpdatePropertyError( 'At least one property must be modified.') def Run(self, args): """Issues requests necessary to update backend of the Backend Service.""" self._ValidateArgs(args) holder = base_classes.ComputeApiHolder(self.ReleaseTrack()) client = holder.client backend_service_ref = ( flags.GLOBAL_REGIONAL_BACKEND_SERVICE_ARG.ResolveAsResource( args, holder.resources, scope_lister=compute_flags.GetDefaultScopeLister(client))) get_request = self._GetGetRequest(client, backend_service_ref) backend_service = client.MakeRequests([get_request])[0] modified_backend_service = self._Modify( client, holder.resources, backend_service_ref, args, backend_service) return client.MakeRequests([ self._GetSetRequest(client, backend_service_ref, modified_backend_service) ]) @base.ReleaseTracks(base.ReleaseTrack.BETA) class UpdateBackendBeta(UpdateBackend): """Update an existing backend in a backend service. *{command}* updates a backend that is part of a backend service. This is useful for changing the way a backend behaves. Example changes that can be made include changing the load balancing policy and draining a backend by setting its capacity scaler to zero. Backends are instance groups or network endpoint groups. One of the `--network-endpoint-group` or `--instance-group` flags is required to identify the backend that you are modifying. You cannot change the instance group or network endpoint group associated with a backend, but you can remove a backend and add a new one with `backend-services remove-backend` and `backend-services add-backend`. The `gcloud compute backend-services edit` command can also update a backend if the use of a text editor is desired. For more information about the available settings, see https://cloud.google.com/load-balancing/docs/backend-service. """ @classmethod def Args(cls, parser): flags.GLOBAL_REGIONAL_BACKEND_SERVICE_ARG.AddArgument(parser) flags.AddInstanceGroupAndNetworkEndpointGroupArgs(parser, 'update in') backend_flags.AddDescription(parser) backend_flags.AddBalancingMode(parser, release_track=cls.ReleaseTrack()) backend_flags.AddCapacityLimits(parser, release_track=cls.ReleaseTrack()) backend_flags.AddCapacityScalar(parser) backend_flags.AddFailover(parser, default=None) backend_flags.AddPreference(parser) backend_flags.AddCustomMetrics(parser, add_clear_argument=True) backend_flags.AddTrafficDuration(parser) def _ValidateArgs(self, args): """Overrides.""" if not any([ args.description is not None, args.balancing_mode, args.max_utilization is not None, args.max_rate is not None, args.max_rate_per_instance is not None, args.max_rate_per_endpoint is not None, args.max_connections is not None, args.max_connections_per_instance is not None, args.max_connections_per_endpoint is not None, args.max_in_flight_requests is not None, args.max_in_flight_requests_per_instance is not None, args.max_in_flight_requests_per_endpoint is not None, args.traffic_duration is not None, args.capacity_scaler is not None, args.failover is not None, args.preference is not None, ]): raise exceptions.UpdatePropertyError( 'At least one property must be modified.') @base.ReleaseTracks(base.ReleaseTrack.ALPHA) class UpdateBackendAlpha(UpdateBackendBeta): """Update an existing backend in a backend service. *{command}* updates a backend that is part of a backend service. This is useful for changing the way a backend behaves. Example changes that can be made include changing the load balancing policy and draining a backend by setting its capacity scaler to zero. Backends are instance groups or network endpoint groups. One of the `--network-endpoint-group` or `--instance-group` flags is required to identify the backend that you are modifying. You cannot change the instance group or network endpoint group associated with a backend, but you can remove a backend and add a new one with `backend-services remove-backend` and `backend-services add-backend`. The `gcloud compute backend-services edit` command can also update a backend if the use of a text editor is desired. For more information about the available settings, see https://cloud.google.com/load-balancing/docs/backend-service. """ @classmethod def Args(cls, parser): flags.GLOBAL_REGIONAL_BACKEND_SERVICE_ARG.AddArgument(parser) flags.AddInstanceGroupAndNetworkEndpointGroupArgs(parser, 'update in') backend_flags.AddDescription(parser) backend_flags.AddBalancingMode(parser, release_track=cls.ReleaseTrack()) backend_flags.AddCapacityLimits(parser, release_track=cls.ReleaseTrack()) backend_flags.AddCapacityScalar(parser) backend_flags.AddFailover(parser, default=None) backend_flags.AddPreference(parser) backend_flags.AddTrafficDuration(parser) backend_flags.AddCustomMetrics(parser, add_clear_argument=True) def _ValidateArgs(self, args): """Overrides.""" if not any([ args.description is not None, args.balancing_mode, args.max_utilization is not None, args.max_rate is not None, args.max_rate_per_instance is not None, args.max_rate_per_endpoint is not None, args.max_connections is not None, args.max_connections_per_instance is not None, args.max_connections_per_endpoint is not None, args.max_in_flight_requests is not None, args.max_in_flight_requests_per_instance is not None, args.max_in_flight_requests_per_endpoint is not None, args.traffic_duration is not None, args.capacity_scaler is not None, args.failover is not None, args.preference is not None, args.custom_metrics is not None, args.custom_metrics_file is not None, args.clear_custom_metrics is not None, ]): raise exceptions.UpdatePropertyError( 'At least one property must be modified.') def _ClearMutualExclusiveBackendCapacityThresholds(backend, release_track=None): """Initialize the backend's mutually exclusive capacity thresholds.""" backend.maxRate = None backend.maxRatePerInstance = None backend.maxConnections = None backend.maxConnectionsPerInstance = None backend.maxRatePerEndpoint = None backend.maxConnectionsPerEndpoint = None if ( release_track == base.ReleaseTrack.ALPHA or release_track == base.ReleaseTrack.BETA ): backend.maxInFlightRequests = None backend.maxInFlightRequestsPerInstance = None backend.maxInFlightRequestsPerEndpoint = None def _ModifyBalancingModeArgs( messages, args, backend_to_update, release_track=None ): """Update balancing mode fields in backend_to_update according to args. Args: messages: API messages class, determined by the release track. args: The arguments given to the update-backend command. backend_to_update: The backend message to modify. release_track: The release track of the command. """ backend_services_utils.ValidateBalancingModeArgs( messages, args, backend_to_update.balancingMode, release_track) if args.balancing_mode: backend_to_update.balancingMode = ( messages.Backend.BalancingModeValueValuesEnum( args.balancing_mode)) # If the balancing mode is being changed to RATE (CONNECTION), we must # clear the max utilization, max inflight requests and max connections # (rate) fields. If the balancing mode is being chagned to IN_FLIGHT, # we must clear the max rate and max connections fields otherwise the # server will reject the request. if (backend_to_update.balancingMode == messages.Backend.BalancingModeValueValuesEnum.RATE): backend_to_update.maxUtilization = None backend_to_update.maxConnections = None backend_to_update.maxConnectionsPerInstance = None backend_to_update.maxConnectionsPerEndpoint = None if ( release_track == base.ReleaseTrack.ALPHA or release_track == base.ReleaseTrack.BETA ): backend_to_update.maxInFlightRequests = None backend_to_update.maxInFlightRequestsPerInstance = None backend_to_update.maxInFlightRequestsPerEndpoint = None elif (backend_to_update.balancingMode == messages.Backend.BalancingModeValueValuesEnum.CONNECTION): backend_to_update.maxUtilization = None backend_to_update.maxRate = None backend_to_update.maxRatePerInstance = None backend_to_update.maxRatePerEndpoint = None if ( release_track == base.ReleaseTrack.ALPHA or release_track == base.ReleaseTrack.BETA ): backend_to_update.maxInFlightRequests = None backend_to_update.maxInFlightRequestsPerInstance = None backend_to_update.maxInFlightRequestsPerEndpoint = None elif ( ( release_track == base.ReleaseTrack.ALPHA or release_track == base.ReleaseTrack.BETA ) and backend_to_update.balancingMode == messages.Backend.BalancingModeValueValuesEnum.IN_FLIGHT ): backend_to_update.maxRate = None backend_to_update.maxRatePerInstance = None backend_to_update.maxRatePerEndpoint = None backend_to_update.maxConnections = None backend_to_update.maxConnectionsPerInstance = None backend_to_update.maxConnectionsPerEndpoint = None # Now, we set the parameters that control load balancing. # ValidateBalancingModeArgs takes care that the control parameters # are compatible with the balancing mode. if args.max_utilization is not None: backend_to_update.maxUtilization = args.max_utilization # max_rate, max_rate_per_instance, max_connections and # max_connections_per_instance, max_in_flight_requests # and max_in_flight_requests_per_instance are mutually exclusive arguments. if args.max_rate is not None: _ClearMutualExclusiveBackendCapacityThresholds(backend_to_update) backend_to_update.maxRate = args.max_rate elif args.max_rate_per_instance is not None: _ClearMutualExclusiveBackendCapacityThresholds(backend_to_update) backend_to_update.maxRatePerInstance = args.max_rate_per_instance elif args.max_connections is not None: _ClearMutualExclusiveBackendCapacityThresholds(backend_to_update) backend_to_update.maxConnections = args.max_connections elif args.max_connections_per_instance is not None: _ClearMutualExclusiveBackendCapacityThresholds(backend_to_update) backend_to_update.maxConnectionsPerInstance = ( args.max_connections_per_instance) elif args.max_rate_per_endpoint is not None: _ClearMutualExclusiveBackendCapacityThresholds(backend_to_update) backend_to_update.maxRatePerEndpoint = args.max_rate_per_endpoint elif args.max_connections_per_endpoint is not None: _ClearMutualExclusiveBackendCapacityThresholds(backend_to_update) backend_to_update.maxConnectionsPerEndpoint = ( args.max_connections_per_endpoint) elif ( release_track == base.ReleaseTrack.ALPHA or release_track == base.ReleaseTrack.BETA ): if args.max_in_flight_requests is not None: _ClearMutualExclusiveBackendCapacityThresholds( backend_to_update, release_track ) backend_to_update.maxInFlightRequests = args.max_in_flight_requests elif args.max_in_flight_requests_per_instance is not None: _ClearMutualExclusiveBackendCapacityThresholds( backend_to_update, release_track ) backend_to_update.maxInFlightRequestsPerInstance = ( args.max_in_flight_requests_per_instance ) elif args.max_in_flight_requests_per_endpoint is not None: _ClearMutualExclusiveBackendCapacityThresholds( backend_to_update, release_track ) backend_to_update.maxInFlightRequestsPerEndpoint = ( args.max_in_flight_requests_per_endpoint ) if args.capacity_scaler is not None: backend_to_update.capacityScaler = args.capacity_scaler