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