marketplace/vm-solution/common/common.py (135 lines of code) (raw):

# Copyright 2015 Google Inc. 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. """Generic simple functions used for python based templage generation.""" import re import sys import traceback import default import yaml RFC1035_RE = re.compile(r'^[a-z][-a-z0-9]{1,61}[a-z0-9]{1}$') class Error(Exception): """Common exception wrapper for template exceptions.""" pass def AddDiskResourcesIfNeeded(context): """Checks context if disk resources need to be added.""" if default.DISK_RESOURCES in context.properties: return context.properties[default.DISK_RESOURCES] else: return [] def AutoName(base, resource, *args): """Helper method to generate names automatically based on default.""" auto_name = '%s-%s' % (base, '-'.join(list(args) + [default.AKA[resource]])) if not RFC1035_RE.match(auto_name): raise Error('"%s" name for type %s does not match RFC1035 regex (%s)' % (auto_name, resource, RFC1035_RE.pattern)) return auto_name def AutoRef(base, resource, *args): """Helper method that builds a reference for an auto-named resource.""" return Ref(AutoName(base, resource, *args)) def OrderedItems(dict_obj): """Convenient method to yield sorted iteritems of a dictionary.""" keys = dict_obj.keys() keys.sort() for k in keys: yield (k, dict_obj[k]) def ShortenZoneName(zone): """Given a string that looks like a zone name, creates a shorter version.""" geo, coord, number, letter = re.findall(r'(\w+)-(\w+)(\d)-(\w)', zone)[0] geo = geo.lower() if len(geo) == 2 else default.LOC[geo.lower()] coord = default.LOC[coord.lower()] number = str(number) letter = letter.lower() return geo + '-' + coord + number + letter def ZoneToRegion(zone): """Derives the region from a zone name.""" parts = zone.split('-') if len(parts) != 3: raise Error('Cannot derive region from zone "%s"' % zone) return '-'.join(parts[:2]) def FormatException(message): """Adds more information to the exception.""" message = ('Exception Type: %s\n' 'Details: %s\n' 'Message: %s\n') % (sys.exc_type, traceback.format_exc(), message) return message def Ref(name): return '$(ref.%s.selfLink)' % name def RefGroup(name): return '$(ref.%s.instanceGroup)' % name def GlobalComputeLink(project, collection, value): if IsComputeLink(value): return value return ''.join([default.COMPUTE_URL_BASE, 'projects/', project, '/global/', collection, '/', value]) def LocalComputeLink(project, zone, key, value): if IsComputeLink(value): return value return ''.join([default.COMPUTE_URL_BASE, 'projects/', project, '/zones/', zone, '/', key, '/', value]) def MakeLocalComputeLink(context, key): return LocalComputeLink(context.env['project'], context.properties.get('zone', None), key + 's', context.properties[key]) def MakeNetworkComputeLink(context, value): return GlobalComputeLink(context.env['project'], 'networks', value) def MakeSubnetworkComputeLink(context, value): region = ZoneToRegion(context.properties.get('zone', None)) if IsComputeLink(value): return value return ''.join([ default.COMPUTE_URL_BASE, 'projects/', context.env['project'], '/regions/', region, '/subnetworks/', value ]) def MakeAcceleratorTypeLink(context, accelerator_type): project = context.env['project'] zone = context.properties.get('zone', None) return ''.join([default.COMPUTE_URL_BASE, 'projects/', project, '/zones/', zone, '/acceleratorTypes/', accelerator_type]) def MakeFQHN(context, name): return '%s.c.%s.internal' % (name, context.env['project']) # TODO(victorg): Consider moving this method to a different file def MakeC2DImageLink(name, dev_mode=False): if IsGlobalProjectShortcut(name) or name.startswith('http'): return name else: if dev_mode: return 'global/images/%s' % name else: return GlobalComputeLink(default.C2D_IMAGES, 'images', name) def IsGlobalProjectShortcut(name): return name.startswith('projects/') or name.startswith('global/') def IsComputeLink(name): return (name.startswith(default.COMPUTE_URL_BASE) or name.startswith(default.REFERENCE_PREFIX)) def GetNamesAndTypes(resources_dict): return [(d['name'], d['type']) for d in resources_dict] def SummarizeResources(res_dict): """Summarizes the name of resources per resource type.""" result = {} for res in res_dict: result.setdefault(res['type'], []).append(res['name']) return result def ListPropertyValuesOfType(res_dict, prop, res_type): """Lists all the values for a property of a certain type.""" return [r['properties'][prop] for r in res_dict if r['type'] == res_type] def MakeResource(resource_list, output_list=None): """Wrapper for a DM template basic spec.""" content = {'resources': resource_list} if output_list: content['outputs'] = output_list return yaml.dump(content) def TakeZoneOut(properties): """Given a properties dictionary, removes the zone specific information.""" def _CleanZoneUrl(value): value = value.split('/')[-1] if IsComputeLink(value) else value return value for name in default.VM_ZONE_PROPERTIES: if name in properties: properties[name] = _CleanZoneUrl(properties[name]) if default.ZONE in properties: properties.pop(default.ZONE) if default.BOOTDISK in properties: properties[default.BOOTDISK] = _CleanZoneUrl(properties[default.BOOTDISK]) if default.DISKS in properties: for disk in properties[default.DISKS]: # Don't touch references to other disks if default.DISK_SOURCE in disk: continue if default.INITIALIZEP in disk: disk_init = disk[default.INITIALIZEP] if default.DISKTYPE in disk_init: disk_init[default.DISKTYPE] = _CleanZoneUrl(disk_init[default.DISKTYPE]) def GenerateEmbeddableYaml(yaml_string): # Because YAML is a space delimited format, we need to be careful about # embedding one YAML document in another. This function takes in a string in # YAML format and produces an equivalent YAML representation that can be # inserted into arbitrary points of another YAML document. It does so by # printing the YAML string in a single line format. Consistent ordering of # the string is also guaranteed by using yaml.dump. yaml_object = yaml.load(yaml_string) dumped_yaml = yaml.dump(yaml_object, default_flow_style=True) return dumped_yaml def FormatErrorsDec(func): """Decorator to format exceptions if they get raised.""" def FormatErrorsWrap(context): try: return func(context) except Exception as e: raise Error(FormatException(e.message)) return FormatErrorsWrap