1# -*- coding: utf-8 -*- # 2# Copyright 2016 Google LLC. All Rights Reserved. 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15"""Convenience functions for dealing with instance templates.""" 16 17from __future__ import absolute_import 18from __future__ import division 19from __future__ import unicode_literals 20 21from googlecloudsdk.api_lib.compute import alias_ip_range_utils 22from googlecloudsdk.api_lib.compute import constants 23from googlecloudsdk.api_lib.compute import image_utils 24from googlecloudsdk.api_lib.compute import instance_utils 25from googlecloudsdk.api_lib.compute import kms_utils 26from googlecloudsdk.api_lib.compute import utils 27from googlecloudsdk.command_lib.compute import scope as compute_scope 28from googlecloudsdk.command_lib.compute.networks.subnets import flags as subnet_flags 29from googlecloudsdk.core import properties 30 31EPHEMERAL_ADDRESS = object() 32 33 34def CreateNetworkInterfaceMessage(resources, 35 scope_lister, 36 messages, 37 network, 38 private_ip, 39 region, 40 subnet, 41 address, 42 alias_ip_ranges_string=None, 43 network_tier=None, 44 stack_type=None, 45 ipv6_network_tier=None): 46 """Creates and returns a new NetworkInterface message. 47 48 Args: 49 resources: generates resource references, 50 scope_lister: function, provides scopes for prompting subnet region, 51 messages: GCE API messages, 52 network: network, 53 private_ip: IPv4 internal IP address to assign to the instance. 54 region: region for subnetwork, 55 subnet: regional subnetwork, 56 address: specify static address for instance template 57 * None - no address, 58 * EPHEMERAL_ADDRESS - ephemeral address, 59 * string - address name to be fetched from GCE API. 60 alias_ip_ranges_string: command line string specifying a list of alias 61 IP ranges. 62 network_tier: specify network tier for instance template 63 * None - no network tier 64 * PREMIUM - network tier being PREMIUM 65 * SELECT - network tier being SELECT 66 * STANDARD - network tier being STANDARD 67 stack_type: identify whether IPv6 features are enabled 68 * IPV4_ONLY - can only have IPv4 address 69 * IPV4_IPV6 - can have both IPv4 and IPv6 address 70 ipv6_network_tier: specify network tier for IPv6 access config 71 * PREMIUM - network tier being PREMIUM 72 * STANDARD - network tier being STANDARD 73 Returns: 74 network_interface: a NetworkInterface message object 75 """ 76 # By default interface is attached to default network. If network or subnet 77 # are specified they're used instead. 78 network_interface = messages.NetworkInterface() 79 if subnet is not None: 80 subnet_ref = subnet_flags.SubnetworkResolver().ResolveResources( 81 [subnet], compute_scope.ScopeEnum.REGION, region, resources, 82 scope_lister=scope_lister)[0] 83 network_interface.subnetwork = subnet_ref.SelfLink() 84 if network is not None: 85 network_ref = resources.Parse( 86 network, 87 params={'project': properties.VALUES.core.project.GetOrFail}, 88 collection='compute.networks') 89 network_interface.network = network_ref.SelfLink() 90 elif subnet is None: 91 network_ref = resources.Parse( 92 constants.DEFAULT_NETWORK, 93 params={'project': properties.VALUES.core.project.GetOrFail}, 94 collection='compute.networks') 95 network_interface.network = network_ref.SelfLink() 96 97 if private_ip is not None: 98 network_interface.networkIP = private_ip 99 100 if stack_type is not None: 101 network_interface.stackType = ( 102 messages.NetworkInterface.StackTypeValueValuesEnum(stack_type)) 103 104 if address: 105 access_config = messages.AccessConfig( 106 name=constants.DEFAULT_ACCESS_CONFIG_NAME, 107 type=messages.AccessConfig.TypeValueValuesEnum.ONE_TO_ONE_NAT) 108 109 # If the user provided an external IP, populate the access 110 # config with it. 111 if address != EPHEMERAL_ADDRESS: 112 access_config.natIP = address 113 114 if network_tier is not None: 115 access_config.networkTier = (messages.AccessConfig. 116 NetworkTierValueValuesEnum(network_tier)) 117 118 network_interface.accessConfigs = [access_config] 119 120 if ipv6_network_tier is not None: 121 ipv6_access_config = messages.AccessConfig( 122 name=constants.DEFAULT_IPV6_ACCESS_CONFIG_NAME, 123 type=messages.AccessConfig.TypeValueValuesEnum.DIRECT_IPV6) 124 ipv6_access_config.networkTier = ( 125 messages.AccessConfig.NetworkTierValueValuesEnum(ipv6_network_tier)) 126 127 network_interface.ipv6AccessConfigs = [ipv6_access_config] 128 129 if alias_ip_ranges_string: 130 network_interface.aliasIpRanges = ( 131 alias_ip_range_utils.CreateAliasIpRangeMessagesFromString( 132 messages, False, alias_ip_ranges_string)) 133 134 return network_interface 135 136 137def CreateNetworkInterfaceMessages(resources, scope_lister, messages, 138 network_interface_arg, region): 139 """Create network interface messages. 140 141 Args: 142 resources: generates resource references, 143 scope_lister: function, provides scopes for prompting subnet region, 144 messages: creates resources. 145 network_interface_arg: CLI argument specifying network interfaces. 146 region: region of the subnetwork. 147 Returns: 148 list, items are NetworkInterfaceMessages. 149 """ 150 result = [] 151 if network_interface_arg: 152 for interface in network_interface_arg: 153 address = interface.get('address', None) 154 # pylint: disable=g-explicit-bool-comparison 155 if address == '': 156 address = EPHEMERAL_ADDRESS 157 158 network_tier = interface.get('network-tier', None) 159 160 result.append( 161 CreateNetworkInterfaceMessage( 162 resources, scope_lister, messages, interface.get('network', None), 163 interface.get('private-network-ip', None), region, 164 interface.get('subnet', None), address, 165 interface.get('aliases', None), network_tier)) 166 return result 167 168 169def CreatePersistentAttachedDiskMessages( 170 messages, disks, container_mount_disk=None): 171 """Returns a list of AttachedDisk messages and the boot disk's reference. 172 173 Args: 174 messages: GCE API messages, 175 disks: disk objects - contains following properties 176 * name - the name of disk, 177 * mode - 'rw' (R/W), 'ro' (R/O) access mode, 178 * boot - whether it is a boot disk ('yes' if True), 179 * autodelete - whether disks is deleted when VM is deleted ('yes' 180 if True), 181 * device-name - device name on VM. 182 container_mount_disk: list of disks to be mounted to container, if any. 183 184 Returns: 185 list of API messages for attached disks 186 """ 187 188 disks_messages = [] 189 for disk in disks: 190 name = disk['name'] 191 # Resolves the mode. 192 mode_value = disk.get('mode', 'rw') 193 if mode_value == 'rw': 194 mode = messages.AttachedDisk.ModeValueValuesEnum.READ_WRITE 195 else: 196 mode = messages.AttachedDisk.ModeValueValuesEnum.READ_ONLY 197 198 boot = disk.get('boot') == 'yes' 199 auto_delete = disk.get('auto-delete') == 'yes' 200 device_name = instance_utils.GetDiskDeviceName(disk, name, 201 container_mount_disk) 202 203 attached_disk = messages.AttachedDisk( 204 autoDelete=auto_delete, 205 boot=boot, 206 deviceName=device_name, 207 mode=mode, 208 source=name, 209 type=messages.AttachedDisk.TypeValueValuesEnum.PERSISTENT) 210 211 # The boot disk must end up at index 0. 212 if boot: 213 disks_messages = [attached_disk] + disks_messages 214 else: 215 disks_messages.append(attached_disk) 216 217 return disks_messages 218 219 220def CreatePersistentCreateDiskMessages(client, 221 resources, 222 user_project, 223 create_disks, 224 support_kms=False, 225 container_mount_disk=None, 226 support_multi_writer=False): 227 """Returns a list of AttachedDisk messages. 228 229 Args: 230 client: Compute client adapter 231 resources: Compute resources registry 232 user_project: name of user project 233 create_disks: disk objects - contains following properties 234 * name - the name of disk, 235 * description - an optional description for the disk, 236 * mode - 'rw' (R/W), 'ro' (R/O) access mode, 237 * size - the size of the disk, 238 * type - the type of the disk (HDD or SSD), 239 * image - the name of the image to initialize from, 240 * image-family - the image family name, 241 * image-project - the project name that has the image, 242 * auto-delete - whether disks is deleted when VM is deleted ('yes' 243 if True), 244 * device-name - device name on VM, 245 * disk-resource-policy - resource policies applied to disk. 246 247 support_kms: if KMS is supported 248 container_mount_disk: list of disks to be mounted to container, if any. 249 support_multi_writer: if multi writer disks are supported. 250 251 Returns: 252 list of API messages for attached disks 253 """ 254 255 disks_messages = [] 256 for disk in create_disks or []: 257 name = disk.get('name') 258 # Resolves the mode. 259 mode_value = disk.get('mode', 'rw') 260 if mode_value == 'rw': 261 mode = client.messages.AttachedDisk.ModeValueValuesEnum.READ_WRITE 262 else: 263 mode = client.messages.AttachedDisk.ModeValueValuesEnum.READ_ONLY 264 265 auto_delete = disk.get('auto-delete') == 'yes' 266 boot = disk.get('boot') == 'yes' 267 disk_size_gb = utils.BytesToGb(disk.get('size')) 268 img = disk.get('image') 269 img_family = disk.get('image-family') 270 img_project = disk.get('image-project') 271 272 image_uri = None 273 if img or img_family: 274 image_expander = image_utils.ImageExpander(client, resources) 275 image_uri, _ = image_expander.ExpandImageFlag( 276 user_project=user_project, 277 image=img, 278 image_family=img_family, 279 image_project=img_project, 280 return_image_resource=False) 281 282 disk_key = None 283 if support_kms: 284 disk_key = kms_utils.MaybeGetKmsKeyFromDict( 285 disk, client.messages, disk_key) 286 287 device_name = instance_utils.GetDiskDeviceName(disk, name, 288 container_mount_disk) 289 290 init_params = client.messages.AttachedDiskInitializeParams( 291 diskName=name, 292 description=disk.get('description'), 293 sourceImage=image_uri, 294 diskSizeGb=disk_size_gb, 295 diskType=disk.get('type')) 296 297 policies = disk.get('disk-resource-policy') 298 if policies: 299 init_params.resourcePolicies = policies 300 301 multi_writer = disk.get('multi-writer') 302 if support_multi_writer and multi_writer: 303 init_params.multiWriter = True 304 305 create_disk = client.messages.AttachedDisk( 306 autoDelete=auto_delete, 307 boot=boot, 308 deviceName=device_name, 309 initializeParams=init_params, 310 mode=mode, 311 type=client.messages.AttachedDisk.TypeValueValuesEnum.PERSISTENT, 312 diskEncryptionKey=disk_key) 313 314 disks_messages.append(create_disk) 315 316 return disks_messages 317 318 319def CreateDefaultBootAttachedDiskMessage( 320 messages, disk_type, disk_device_name, disk_auto_delete, disk_size_gb, 321 image_uri, kms_args=None, support_kms=False): 322 """Returns an AttachedDisk message for creating a new boot disk.""" 323 disk_key = None 324 325 if support_kms: 326 disk_key = kms_utils.MaybeGetKmsKey( 327 kms_args, messages, disk_key, boot_disk_prefix=True) 328 329 return messages.AttachedDisk( 330 autoDelete=disk_auto_delete, 331 boot=True, 332 deviceName=disk_device_name, 333 initializeParams=messages.AttachedDiskInitializeParams( 334 sourceImage=image_uri, 335 diskSizeGb=disk_size_gb, 336 diskType=disk_type), 337 mode=messages.AttachedDisk.ModeValueValuesEnum.READ_WRITE, 338 type=messages.AttachedDisk.TypeValueValuesEnum.PERSISTENT, 339 diskEncryptionKey=disk_key) 340 341 342def CreateAcceleratorConfigMessages(messages, accelerator): 343 """Returns a list of accelerator config messages for Instance Templates. 344 345 Args: 346 messages: tracked GCE API messages. 347 accelerator: accelerator object with the following properties: 348 * type: the accelerator's type. 349 * count: the number of accelerators to attach. Optional, defaults to 1. 350 351 Returns: 352 a list of accelerator config messages that specify the type and number of 353 accelerators to attach to an instance. 354 """ 355 if accelerator is None: 356 return [] 357 358 accelerator_type = accelerator['type'] 359 accelerator_count = int(accelerator.get('count', 1)) 360 accelerator_config = messages.AcceleratorConfig( 361 acceleratorType=accelerator_type, acceleratorCount=accelerator_count) 362 return [accelerator_config] 363