File: //snap/google-cloud-cli/current/lib/surface/compute/networks/update.py
# -*- coding: utf-8 -*- #
# Copyright 2017 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 networks."""
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.operations import poller
from googlecloudsdk.api_lib.util import waiter
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.compute.networks import flags
from googlecloudsdk.command_lib.compute.networks import network_utils
from googlecloudsdk.core import log
from googlecloudsdk.core import resources
from googlecloudsdk.core.console import console_io
from googlecloudsdk.core.console import progress_tracker
@base.ReleaseTracks(base.ReleaseTrack.GA)
@base.UniverseCompatible
class Update(base.UpdateCommand):
r"""Update a Compute Engine Network.
*{command}* is used to update virtual networks. The updates that
cabe be performed on a network are changing the BGP routing mode
and switching from auto subnet mode to custom subnet mode. Switching
from auto subnet mode to custom subnet mode cannot be undone.
## EXAMPLES
To update regional network with the name 'network-name' to global, run:
$ {command} network-name \
--bgp-routing-mode=global
To update an auto subnet mode network with the name 'network-name' to custom
subnet mode, run:
$ {command} network-name \
--switch-to-custom-subnet-mode
"""
NETWORK_ARG = None
_support_firewall_order = True
MIGRATION_STAGES = dict(
VALIDATING_NETWORK='Validating Network',
CREATING_SUBNETWORK='Creating Subnetwork',
UPDATING_INSTANCES='Updating Instances',
UPDATING_INSTANCE_GROUPS='Updating Instance Groups',
UPDATING_FORWARDING_RULES='Updating Forwarding Rules',
CONVERTING_NETWORK_TO_SUBNET_MODE='Converting Network to Subnet Mode',
)
@classmethod
def Args(cls, parser):
cls.NETWORK_ARG = flags.NetworkArgument()
cls.NETWORK_ARG.AddArgument(parser)
base.ASYNC_FLAG.AddToParser(parser)
network_utils.AddUpdateArgs(parser)
def Run(self, args):
holder = base_classes.ComputeApiHolder(self.ReleaseTrack())
messages = holder.client.messages
service = holder.client.apitools_client.networks
cleared_fields = []
network_ref = self.NETWORK_ARG.ResolveAsResource(args, holder.resources)
if args.switch_to_custom_subnet_mode:
prompt_msg = (
'Network [{0}] will be switched to custom mode. '.format(
network_ref.Name()
)
+ 'This operation cannot be undone.'
)
console_io.PromptContinue(
message=prompt_msg, default=True, cancel_on_no=True
)
result = service.SwitchToCustomMode(
messages.ComputeNetworksSwitchToCustomModeRequest(
project=network_ref.project, network=network_ref.Name()
)
)
operation_ref = resources.REGISTRY.Parse(
result.name,
params={'project': network_ref.project},
collection='compute.globalOperations',
)
if args.async_:
log.UpdatedResource(
operation_ref,
kind='network {0}'.format(network_ref.Name()),
is_async=True,
details=(
'Run the [gcloud compute operations describe] command '
'to check the status of this operation.'
),
)
return result
operation_poller = poller.Poller(service, network_ref)
if result.operationType == 'switchLegacyToCustomModeBeta':
return self._WaitForLegacyNetworkMigration(
operation_poller, operation_ref
)
return waiter.WaitFor(
poller=operation_poller,
operation_ref=operation_ref,
message='Switching network to custom-mode',
)
network_resource = messages.Network()
should_patch = False
if getattr(args, 'mtu', None) is not None:
msg = (
'This might cause connectivity issues when '
+ 'there are running VMs attached.'
)
console_io.PromptContinue(message=msg, default=False, cancel_on_no=True)
network_resource.mtu = args.mtu
should_patch = True
if hasattr(args, 'enable_ula_internal_ipv6'):
network_resource.enableUlaInternalIpv6 = args.enable_ula_internal_ipv6
should_patch = True
if hasattr(args, 'internal_ipv6_range'):
network_resource.internalIpv6Range = args.internal_ipv6_range
should_patch = True
if args.bgp_routing_mode:
should_patch = True
network_resource.routingConfig = messages.NetworkRoutingConfig()
network_resource.routingConfig.routingMode = (
messages.NetworkRoutingConfig.RoutingModeValueValuesEnum(
args.bgp_routing_mode.upper()
)
)
if getattr(args, 'bgp_best_path_selection_mode', None) is not None:
bps_change_warning_message = (
'Updating the best path selection mode can cause routing changes for'
' egress traffic. No new routes are learned or deleted, and data'
" plane traffic isn't dropped or interrupted."
)
console_io.PromptContinue(
message=bps_change_warning_message, default=True, cancel_on_no=True
)
should_patch = True
if getattr(network_resource, 'routingConfig', None) is None:
network_resource.routingConfig = messages.NetworkRoutingConfig()
network_resource.routingConfig.bgpBestPathSelectionMode = (
messages.NetworkRoutingConfig.BgpBestPathSelectionModeValueValuesEnum(
args.bgp_best_path_selection_mode
)
)
# In case the customer set the BGP BPS mode to LEGACY, we need to clear
# any STANDARD mode-only fields.
if args.bgp_best_path_selection_mode == 'LEGACY':
cleared_fields.append('routingConfig.bgpAlwaysCompareMed')
cleared_fields.append('routingConfig.bgpInterRegionCost')
if getattr(args, 'bgp_bps_always_compare_med', None) is not None:
should_patch = True
if getattr(network_resource, 'routingConfig', None) is None:
network_resource.routingConfig = messages.NetworkRoutingConfig()
network_resource.routingConfig.bgpAlwaysCompareMed = (
args.bgp_bps_always_compare_med
)
if getattr(args, 'bgp_bps_inter_region_cost', None) is not None:
should_patch = True
if getattr(network_resource, 'routingConfig', None) is None:
network_resource.routingConfig = messages.NetworkRoutingConfig()
network_resource.routingConfig.bgpInterRegionCost = (
messages.NetworkRoutingConfig.BgpInterRegionCostValueValuesEnum(
args.bgp_bps_inter_region_cost
)
)
if (
self._support_firewall_order
and args.network_firewall_policy_enforcement_order
):
should_patch = True
network_resource.networkFirewallPolicyEnforcementOrder = (
messages.Network.NetworkFirewallPolicyEnforcementOrderValueValuesEnum(
args.network_firewall_policy_enforcement_order
)
)
if should_patch:
with holder.client.apitools_client.IncludeFields(cleared_fields):
resource = service.Patch(
messages.ComputeNetworksPatchRequest(
project=network_ref.project,
network=network_ref.Name(),
networkResource=network_resource,
)
)
return resource
def _WaitForLegacyNetworkMigration(self, operation_poller, operation_ref):
progress_stages = []
for key, label in self.MIGRATION_STAGES.items():
progress_stages.append(progress_tracker.Stage(label, key=key))
tracker = progress_tracker.StagedProgressTracker(
message='Migrating Network from Legacy to Custom Mode',
stages=progress_stages,
)
first_status_message = list(self.MIGRATION_STAGES.keys())[0]
tracker.last_status_message = first_status_message
return waiter.WaitFor(
poller=operation_poller,
operation_ref=operation_ref,
custom_tracker=tracker,
tracker_update_func=self._LegacyNetworkMigrationTrackerUpdateFunc,
)
def _LegacyNetworkMigrationTrackerUpdateFunc(
self, tracker, operation, unused_status
):
latest_status_message = operation.statusMessage
self._MarkStagesCompleted(tracker, latest_status_message)
tracker.StartStage(latest_status_message)
tracker.last_status_message = latest_status_message
# Mark all stages between last and latest status messages as completed
def _MarkStagesCompleted(self, tracker, latest_status_message):
ordered_stages = list(self.MIGRATION_STAGES.keys())
last_status_message_idx = ordered_stages.index(tracker.last_status_message)
latest_status_message_idx = ordered_stages.index(latest_status_message)
stages_to_update = list(self.MIGRATION_STAGES.keys())[
last_status_message_idx:latest_status_message_idx
]
for stage_to_update in stages_to_update:
tracker.CompleteStage(stage_to_update)
@base.ReleaseTracks(base.ReleaseTrack.BETA)
@base.UniverseCompatible
class UpdateBeta(Update):
r"""Update a Compute Engine Network.
*{command}* is used to update virtual networks. The updates that
cabe be performed on a network are changing the BGP routing mode
and switching from auto subnet mode to custom subnet mode. Switching
from auto subnet mode to custom subnet mode cannot be undone.
## EXAMPLES
To update regional network with the name 'network-name' to global, run:
$ {command} network-name \
--bgp-routing-mode=global
To update an auto subnet mode network with the name 'network-name' to custom
subnet mode, run:
$ {command} network-name \
--switch-to-custom-subnet-mode
"""
_support_firewall_order = True
@base.ReleaseTracks(base.ReleaseTrack.ALPHA)
@base.UniverseCompatible
class UpdateAlpha(UpdateBeta):
"""Update a Compute Engine Network."""
_support_firewall_order = True
@classmethod
def Args(cls, parser):
cls.NETWORK_ARG = flags.NetworkArgument()
cls.NETWORK_ARG.AddArgument(parser)
base.ASYNC_FLAG.AddToParser(parser)
network_utils.AddUpdateArgs(parser)
Update.detailed_help = {
'brief': 'Update a Compute Engine network',
'DESCRIPTION': """\
*{command}* is used to update Compute Engine networks.""",
}