# $SnapHashLicense:
#
# SnapLogic - Open source data services
#
# Copyright (C) 2009, SnapLogic, Inc. All rights reserved.
#
# See http://www.snaplogic.org for more information about
# the SnapLogic project.
#
# This program is free software, distributed under the terms of
# the GNU General Public License Version 2. See the LEGAL file
# at the top of the source tree.
#
# "SnapLogic" is a trademark of SnapLogic, Inc.
#
#
# $
import types
# $Id:resdef.py 1764 2008-03-21 04:27:56Z dhiraj $
import simplejson
from copy import deepcopy
from decimal import Decimal
from datetime import datetime
import urlparse
import re
from snaplogic.snapi_base.exceptions import SnapiHttpException,SnapiException
from snaplogic import snapi_base
from snaplogic.snapi_base import keys,exec_interface
from snaplogic.common.snap_exceptions import *
from snaplogic.common import snap_http_lib,snap_crypt
from snaplogic.server.http_request import HttpRequest
# Pick the number used by lists for largest possible index (http://docs.python.org/ref/sequence-methods.html)
# Use 2**31-1 to fit within limit imposed by xmlrpclib.py
UNLIMITED = 2**31-1
UNLIMITED_ENTRIES = UNLIMITED
LIST_TYPE_PROPERTY = "list_type"
DICT_TYPE_PROPERTY = "dict_type"
list_of_simple_types = ("datetime", "number", "string", "boolean")
list_of_field_types = ("datetime", "number", "string")
# Keys contain all allowed types and values are the list of allowed constraints for that type.
type_and_constraints = {"datetime" : ["min_value", "max_value"],
"number" : ["min_value", "max_value", "lov"],
"string" : ["min_length", "max_length", "lov", keys.CONSTRAINT_OBFUSCATE],
"boolean" : [] }
param_pattern = re.compile('\$\?{\w+}')
def is_int(val):
"""Return True, if the value is int, else False."""
try:
if int(val) != val:
return False
except (TypeError, ValueError):
return False
return True
def is_numeric(val):
"""Return True, if the value is int, else False."""
try:
val + 0
except TypeError:
return False
return True
def is_str(val):
"""Return True, if the value is str/unicode, else False."""
v_uni = unicode(val)
if val != v_uni:
return False
return True
def has_param(value):
if hasattr(value, "append") and callable(value.append):
for v in value:
if has_param(v):
return True
elif hasattr(value, "keys") and callable(value.keys):
for k in value:
if has_param(value[k]):
return True
elif is_str(value):
ret = param_pattern.search(value)
if ret is not None:
return True
return False
def validate_constraint(simple_type, prop_constraint):
"""
Validate the contents of the property constraint dictionary.
This method ensures that the property constraints in the dictionary are valid. It may edit the constraint
dictionary to make sure that the constraint values are in the stadard type like datetime or decimal.
@param simple_type: The type of the simple property.
@type simple_type: str
@param prop_constraint: The dictionary with the property constraints. This dict may be edited by this method.
@type prop_constraint: dict
@raise SnapResDefError: If the constraint values are not valid..
"""
if prop_constraint is None or not len(prop_constraint):
return
if simple_type not in type_and_constraints:
raise SnapResDefError("'%s' is not a simple type" % simple_type)
for k in prop_constraint:
if k not in type_and_constraints[simple_type]:
raise SnapResDefError("Simple type '%s' does not have constraint '%s'" % (simple_type, k))
if simple_type == "string":
both_limits_present = True
for cons in ("min_length", "max_length"):
if cons in prop_constraint:
# Cannot specify both lov and max/min length
if "lov" in prop_constraint:
raise SnapResDefError("Property constraint '%s' cannot be specified with constraint 'lov'" % cons)
# Check if the limit is an integer
if not is_int(prop_constraint[cons]):
raise SnapResDefError("Property constraint '%s' is not an integer (%s)"
% (cons, prop_constraint[cons]))
if prop_constraint[cons] < 0:
raise SnapResDefError("Property constraint '%s' cannot be negative (%s)"
% (cons, prop_constraint[cons]))
else:
both_limits_present = False
if both_limits_present and prop_constraint["min_length"] > prop_constraint["max_length"]:
raise SnapResDefError("Property constraint min_length cannot be greater than max_length")
if "lov" in prop_constraint:
new_lov = []
for v in prop_constraint["lov"]:
# All lov entries must strings.
if not is_str(v):
raise SnapResDefError("Value \'%s\' in 'lov' constaint is not a string/unicode value" % v)
new_lov.append(unicode(v))
prop_constraint["lov"] = new_lov
if "obfuscate" in prop_constraint:
# The constraint obfuscate takes a integer value. If the value is 0, then the whole string
# is obfuscated. If the value is some positive integer 'n', then all but the first n characters
# in the string will be obfucated. If the value is some negative integer -n, then all but the
# last n characters in the string will be obfuscated.
if not is_int(prop_constraint["obfuscate"]):
raise SnapResDefError("Property constraint 'obfuscate' does not have an integer value (%s)"
% prop_constraint["obfuscate"])
elif simple_type == "number":
both_limits_present = True
for cons in ("min_value", "max_value"):
if cons in prop_constraint:
# Cannot specify both lov and max/min value
if "lov" in prop_constraint:
raise SnapResDefError("Property constraint '%s' cannot be specified with constraint 'lov'" %
cons)
# limits must be numeric
if not is_numeric(prop_constraint[cons]):
raise SnapResDefError("Property constraint '%s' is not a number (%s)"
% (cons, prop_constraint[cons]))
# Need to make str and then Decimal, thats how Decinmal works for float values.
prop_constraint[cons] = Decimal(str(prop_constraint[cons]))
else:
both_limits_present = False
if both_limits_present and prop_constraint["min_value"] > prop_constraint["max_value"]:
raise SnapResDefError("Property constraint min_value cannot be greater than max_value")
if "lov" in prop_constraint:
new_lov = []
for v in prop_constraint["lov"]:
# All lov entries must be integers, since floating point is inaccurate.
if not is_int(v):
raise SnapResDefError("LOV for a number type must only be integers, found '%s'" % v)
new_lov.append(Decimal(v))
prop_constraint["lov"] = new_lov
elif simple_type == "datetime":
both_limits_present = True
for cons in ("min_value", "max_value"):
if cons in prop_constraint:
if not isinstance(prop_constraint[cons], datetime):
raise SnapResDefError("The constraint '%s' is not datetime" % cons)
else:
both_limits_present = False
if both_limits_present and prop_constraint["min_value"] > prop_constraint["max_value"]:
raise SnapResDefError("Property constraint min_value '%s' cannot be greater than max_value '%s'" %
(prop_constraint["min_value"], prop_constraint["max_value"]))
def validate_simple_prop_value(simple_type, value, constraint, custom_lov_error_message = None):
"""
Validate that the value meets the constraints and is a valid value for the simple property.
@param simple_type: The type of the simple property.
@type simple_type: str
@param value: The value of the simple property.
@type value: Varies, depending on the simple property type.
@param prop_constraint: The dictionary with the property constraints. This dict may be edited by this method
@type prop_constraint: dict
@param custom_lov_error_message: Error message to use if LOV constraint doesn't validate.
If not specified, default error message is used simply stating
that 'Value not in the list' (including the actual list).
However for validating dynamic LOV constraints (e.g. keys.CONSTRAINT_LOV_INPUT_FIELD)
it's more helpful to display a more specific error message like:
'Expecting an input field name'
@type custom_lov_error_message: str
@return: None if the value is valid, else, a string error message.
@rtype: str
"""
if value is None:
return
if constraint is None:
constraint = {}
if simple_type == "number":
# We have to do this test, because Decimal converts strings to numeric.
if has_param(value):
return None
if not is_numeric(value):
return "The value '%s' is not numeric" % value
try:
value = Decimal(str(value))
except Exception, e:
return ("Value '%s' cannot be converted to decimal. " % value) + str(e)
if "lov" in constraint and len(constraint["lov"]) > 0:
if value not in constraint["lov"]:
return custom_lov_error_message if custom_lov_error_message else "Allowed values are %s" % ", ".join([str(n) for n in constraint["lov"]])
if "min_value" in constraint and value < constraint["min_value"]:
return "Numeric value '%s' is less than the minimum allowed value '%s'" % (value, constraint["min_value"])
if "max_value" in constraint and value > constraint["max_value"]:
return "Numeric value '%s' is greater than the maximum allowed value '%s'" % (value,
constraint["max_value"])
elif simple_type == "string":
if has_param(value):
return None
if not is_str(value):
return ("The value '%s' is not a string" % value)
if "min_length" in constraint and len(value) < constraint["min_length"]:
return "String value '%s' is less than minimum length allowed - %s" % (value, constraint["min_length"])
if "max_length" in constraint and len(value) > constraint["max_length"]:
return "String value '%s' is more than maximum length allowed - %s" % (value, constraint["max_length"])
if "lov" in constraint and value not in constraint["lov"]:
if custom_lov_error_message:
return custom_lov_error_message
else:
return "Allowed values are %s" % ", ".join(constraint["lov"])
elif simple_type == "boolean":
if has_param(value):
return None
if value not in (True, False):
return ("The value '%s' is not a boolean value (True/False)" % value)
elif simple_type == "datetime":
if has_param(value):
return None
if not isinstance(value, datetime):
return ("The value '%s' is not of type datetime" % value)
if "min_value" in constraint and value < constraint["min_value"]:
return "Datetime value '%s' is less than the minimum allowed value '%s'" % (value, constraint["min_value"])
if "max_value" in constraint and value > constraint["max_value"]:
return "Datetime value '%s' is greater than the maximum allowed value '%s'" % (value,
constraint["max_value"])
return None
def validate_limits(min_size, max_size):
"""
Validates that the minimum and maximum limit values are appropriate.
@param min_size: The minimum limit
@type min_size: numeric
@param max_size: The maximum limit
@type max_size: numeric
@raise SnapResDefError: If the limit value is invalid.
"""
try:
min_size + 0
except TypeError:
raise SnapResDefError("The minimum size is not a numeric value (%s)" % min_size)
if min_size < 0:
raise SnapResDefError("Minimum size of list cannot be below 0 (%s)" % min_size)
try:
max_size + 0
except TypeError:
raise SnapResDefError("The maximum size is not a numeric value (%s)" % max_size)
if max_size < 0:
raise SnapResDefError("Maximum size of list cannot be below 0 (%s)" % max_size)
if min_size > max_size:
raise SnapResDefError("Minimum size cannot exceed maximum size")
def create_from_dict(dict):
cname = dict[keys.COMPONENT_NAME]
if cname == 'snaplogic.components.Pipeline':
return PipelineResDef(dict)
else:
return ResDef(dict)
def read_resdef(uri, credentials=None, custom_hdr=None, local_server_only=False):
"""
Fetch resdef from the specified server.
We have this method here, so that there is common code that is used by both
methods in client snapi and methods in component API.
@param uri: Resource URI.
@type uri: str
@param credentials: Credentials to make the request (username, password)
@type credentials: 2-tuple
@param custom_hdr: Any custom header entries that need to be sent with the request.
@type custom_hdr: dict
@param local_server_only: If set to True, the resdef can only be accessed from and saved into the local server.
@type local_server_only: bool
@return: ResDef object retrieved from repository.
@rtype: L{ResDef} or L{PipelineResDef}
"""
ret = snapi_base.read_resource(uri, credentials, custom_hdr)
if uri not in ret:
raise SnapResDefError("Could not find resource %s" % uri)
resource_dict = ret[uri]
resdef_dict = resource_dict[keys.RESDEF]
if resdef_dict[keys.COMPONENT_NAME] == keys.PIPELINE_COMPONENT_NAME:
resdef = PipelineResDef(resdef_dict)
else:
resdef = ResDef(resdef_dict)
resdef.guid = resource_dict[keys.GUID]
resdef.gen_id = resource_dict[keys.GEN_ID]
resdef._update_uri(uri)
resdef.credentials = credentials
resdef.custom_hdr = custom_hdr
resdef._local_server_only = local_server_only
if resdef_dict[keys.COMPONENT_NAME] != keys.PIPELINE_COMPONENT_NAME:
comp_list = snapi_base.list_components(resdef._server_uri, credentials, custom_hdr)
info = comp_list[resdef_dict[keys.COMPONENT_NAME]]
resdef.capabilities = info['capabilities']
return resdef
def get_resdef_template(server_uri, name, credentials=None, custom_hdr=None, local_server_only=False):
"""
Fetch resdef template from the specified server.
We have this method here, so that there is common code that is used by both
methods in client snapi and methods in component API.
@param server_uri: Server URI.
@type server_uri: str
@param name: Component name.
@type name: str
@param credentials: Credentials to make the request (username, password)
@type credentials: 2-tuple
@param custom_hdr: Any custom header entries that need to be sent with the request.
@type custom_hdr: dict
@param local_server_only: If set to True, the resdef can only be accessed from and saved into the local server.
@type local_server_only: bool
@return: ResDef object created from the template returned.
@rtype: L{ResDef} or L{PipelineResDef}
"""
if name != keys.PIPELINE_COMPONENT_NAME:
comp_list = snapi_base.list_components(server_uri, credentials, custom_hdr)
info = comp_list[name]
capabilities = info[keys.CAPABILITIES]
template_uri = capabilities[keys.CAPABILITY_CREATE_RESOURCE_TEMPLATE]
if template_uri is not None:
if not template_uri.lower().startswith("http://") and not template_uri.lower().startswith("https://"):
template_uri = snap_http_lib.concat_paths(server_uri, template_uri)
template = snapi_base.get_component_template(template_uri, credentials, custom_hdr)
else:
return None
resdef = ResDef(template)
else:
capabilities = None
#template = snapi_base.get_pipeline_template(SERVER_URI)
#resdef = resdef.PipelineResDef(template)
resdef = PipelineResDef()
resdef.capabilities = capabilities
resdef._server_uri = server_uri
resdef._local_server_only = local_server_only
resdef._uri = None
resdef.credentials = credentials
resdef.guid = None
resdef.gen_id = None
resdef.custom_hdr = custom_hdr
return resdef
def get_resdef_format_simple_prop(label_str, simple_type, description_str = "", prop_constraint = None,
required = False,):
r = {}
r[keys.LABEL] = label_str
r[keys.PROPERTY_TYPE] = simple_type
r[keys.DESCRIPTION] = description_str
r[keys.SIMPLE_PROPERTY_CONSTRAINT] = prop_constraint
r[keys.REQUIRED] = required
return r
def get_resdef_format_list_prop(label_str, default_entry_type_doc, description_str = "", min_size = 0,
max_size = UNLIMITED_ENTRIES, required = False):
"""
Return a template of what dictionary property should look like in a resdef document.
"""
r = {}
r[keys.LABEL] = label_str
r[keys.COMPOSITE_PROPERTY_DEFAULT_ENTRY_TYPE] = default_entry_type_doc
r[keys.DESCRIPTION] = description_str
r[keys.COMPOSITE_PROPERTY_MIN_SIZE] = min_size
r[keys.COMPOSITE_PROPERTY_MAX_SIZE] = max_size
r[keys.PROPERTY_TYPE] = LIST_TYPE_PROPERTY
r[keys.COMPOSITE_PROPERTY_STRUCT] = []
r[keys.REQUIRED] = required
return r
def get_resdef_format_dict_prop(label_str, default_entry_type_doc, description_str = "", min_size = 0,
max_size = UNLIMITED_ENTRIES, fixed_keys = False, required = False):
r = {}
r[keys.LABEL] = label_str
r[keys.COMPOSITE_PROPERTY_DEFAULT_ENTRY_TYPE] = default_entry_type_doc
r[keys.DESCRIPTION] = description_str
r[keys.COMPOSITE_PROPERTY_MIN_SIZE] = min_size
r[keys.COMPOSITE_PROPERTY_MAX_SIZE] = max_size
r[keys.PROPERTY_TYPE] = DICT_TYPE_PROPERTY
r[keys.COMPOSITE_PROPERTY_STRUCT] = {}
r[keys.FIXED_KEYS] = fixed_keys
r[keys.REQUIRED] = required
return r
class ResDefBase:
def __init__(self, d = None, member_resource = False):
"""
Initialize ResDefBase.
@param d: Resource dictionary to be used in the initialization.
@type d: dict
@param member_resource: If set to True, the dictionary will be referred to by this object,
else, a copy is kept.
@type member_resource: bool
"""
if d is None:
d = {}
if member_resource:
# We don't clone the dictionary when object is created in this mode.
# We do this to allow the caller to directly edit the original member resource
# dictionary which sits inside the pipeline.
self.dict = d
return
self.dict = deepcopy(d)
if not self.dict.has_key(keys.CAPABILITIES):
self.dict[keys.CAPABILITIES] = {}
if keys.DESCRIPTION not in self.dict:
self.dict[keys.DESCRIPTION] = ''
if keys.COMPONENT_DOC_URI not in self.dict:
self.dict[keys.COMPONENT_DOC_URI] = None
default_capabilities = {
keys.CAPABILITY_INPUT_VIEW_LOWER_LIMIT : 0,
keys.CAPABILITY_INPUT_VIEW_UPPER_LIMIT : UNLIMITED_ENTRIES,
keys.CAPABILITY_OUTPUT_VIEW_LOWER_LIMIT : 0,
keys.CAPABILITY_OUTPUT_VIEW_UPPER_LIMIT : UNLIMITED_ENTRIES,
keys.CAPABILITY_INPUT_VIEW_ALLOW_BINARY : False,
keys.CAPABILITY_OUTPUT_VIEW_ALLOW_BINARY : False,
keys.CAPABILITY_ALLOW_PASS_THROUGH : False,
keys.CAPABILITY_VALIDATE : None,
keys.CAPABILITY_SUGGEST_RESOURCE_VALUES : None,
keys.CAPABILITY_CREATE_RESOURCE_TEMPLATE : None
}
for key in default_capabilities:
if key not in self.dict[keys.CAPABILITIES]:
self.dict[keys.CAPABILITIES][key] = default_capabilities[key]
if keys.COMPONENT_NAME not in self.dict:
self.dict[keys.COMPONENT_NAME] = None
for key in [keys.INPUT_VIEWS, keys.OUTPUT_VIEWS, keys.PARAMS, keys.OUTPUT_VIEW_PASSTHROUGH,
keys.PROPERTY_DEFS, keys.PROPERTY_VALS]:
if key not in self.dict:
self.dict[key] = {}
if keys.RESOURCE_CATEGORY not in self.dict:
self.dict[keys.RESOURCE_CATEGORY] = {keys.MODIFIABLE : True,
keys.VALUE : []}
if keys.SNAP_GENERAL_INFO not in self.list_property_names():
default_type_doc = get_resdef_format_simple_prop("default type", "string")
snap_prop = get_resdef_format_dict_prop("Snaplogic Properties", default_type_doc,
"Standard resource definition properties", 2, 2)
snap_prop[keys.COMPOSITE_PROPERTY_STRUCT][keys.AUTHOR] = \
get_resdef_format_simple_prop("Author", "string", "Author of the resource")
snap_prop[keys.COMPOSITE_PROPERTY_STRUCT][keys.DESCRIPTION] = \
get_resdef_format_simple_prop("Description", "string",
"Description of the resource")
self._set_property_def(keys.SNAP_GENERAL_INFO, snap_prop)
pval = self.get_property_value(keys.SNAP_GENERAL_INFO)
if pval is None:
pval = {}
if keys.AUTHOR not in pval:
pval[keys.AUTHOR] = None
if keys.DESCRIPTION not in pval:
pval[keys.DESCRIPTION] = None
self.set_property_value(keys.SNAP_GENERAL_INFO, pval)
if keys.SNAP_INTERNAL not in self.list_property_names():
default_type_doc = get_resdef_format_simple_prop("default type", "string")
snap_prop = get_resdef_format_dict_prop("Snaplogic Internal Properties", default_type_doc,
"Snaplogic properties that should not be modified by client")
self._set_property_def(keys.SNAP_INTERNAL, snap_prop)
self.set_property_value(keys.SNAP_INTERNAL, {})
self._uri = None
self._server_uri = None
self._local_server_only = False
self.custom_hdr = None
self.credentials = None
def _update_uri(self, uri):
"""
Update the resdef with the uri specified.
If the uri is same as the existing one, then False is returned (since no
real update happened, else True is returned.
@param uri: The uri with which to do the update.
@type uri: str
@return: True if uri needed updating, False is no updating was needed.
@rtype: bool
"""
old_uri = self._uri
if uri is not None:
parsed_uri = urlparse.urlparse(uri)
if parsed_uri.scheme:
# The supplied URI has absolute path. Get the host information from it.
if self._local_server_only:
raise SnapResDefError(
"The URI '%s' must be a relative URI as the resource can only be saved into the local server"
% uri)
self._server_uri = urlparse.urlunparse((parsed_uri[0], parsed_uri[1], "", "", "", ""))
self._uri = uri
else:
# Its a relative URI. Set the absolute URI
if not self._server_uri:
raise SnapResDefError("The URI %s needs host information" % uri)
self._uri = snap_http_lib.concat_paths(self._server_uri, uri)
if old_uri and self._uri:
return not old_uri.lower() == self._uri.lower()
else:
return True
def get_full_uri(self):
"""Return the absolute URI of this resource definition object (can be None)"""
print "This method has been deprecated as of 2.0.3. Please use get_absolute_uri()"
return self._uri
def get_absolute_uri(self):
"""Return the absolute URI of this resource definition object (can be None)"""
return self._uri
def get_relative_uri(self):
"""Return the relative URI of this resource definition object (can be None)"""
if self._uri is not None:
parsed_uri = urlparse.urlparse(self._uri)
u = urlparse.urlunparse(("", "", parsed_uri[2], parsed_uri[3], parsed_uri[4], parsed_uri[5]))
return u
return None
def _get_server_loc(self):
"""Return the network location (http://hostname:port) of the resource."""
return self._server_uri
def _get_obfuscate_descriptor(self, name):
"""
Return the obfuscation constraint for a property.
The value of the constraint is returned (a signed integer).
If no obfuscation constraint was set, None is returned.
@param name: Name of the property.
@type name: str
@return: The obfuscation constraint.
@rtype: int or None
"""
try:
pdef = self.dict[keys.PROPERTY_DEFS][name][keys.PROPERTY_DEFINITION]
except KeyError:
raise SnapResDefError("Failed to find property '%s'" % name)
constraints = pdef.get(keys.SIMPLE_PROPERTY_CONSTRAINT)
if constraints:
if 'obfuscate' in constraints:
return constraints['obfuscate']
return None
def _fetch_resdef_if_uri(self, ref_value):
"""Return a resdef if the value provided is a URI, else, return None."""
inp_uri = urlparse.urlparse(ref_value)
if inp_uri.scheme:
# This is absolute URI
abs_res_uri = ref_value
elif ref_value.startswith('/'):
# This is a relative URI. Let us compute the full URI, in order to retrieve the resef.
server_uri = urlparse.urlparse(self._get_server_loc())
abs_res_uri = urlparse.urlunparse((server_uri[0], server_uri[1], inp_uri[2], inp_uri[3],
inp_uri[4], inp_uri[5]))
else:
# Not a URI.
return None
return read_resdef(abs_res_uri, self.credentials)
def _set_property_def(self, name, prop_dict):
"""
Set the property definition in the resource.
This method is meant for internal use in SnapLogic code.
@param name: Name of the property.
@type name: str
@param prop_dict: Property definition dictionary.
@type prop_dict: dict
"""
self.dict[keys.PROPERTY_DEFS][name] = { keys.PROPERTY_DEFINITION: deepcopy(prop_dict)}
def _del_property_def(self, property_name):
"""
Deletes the specified property definition and its value from the resdef
This method is meant for internal use in SnapLogic code.
@param property_name: Name of the property to be removed
@type property_name: str
"""
if keys.PROPERTY_DEFS in self.dict:
del self.dict[keys.PROPERTY_DEFS][property_name]
if keys.PROPERTY_VALS in self.dict:
del self.dict[keys.PROPERTY_VALS][property_name]
def get_property_def(self, name):
"""Return property definition dictionary."""
try:
return deepcopy(self.dict[keys.PROPERTY_DEFS][name])
except KeyError:
raise SnapResDefError("Failed to find property '%s'" % name)
def list_resource_categories(self):
"""
Return categories set for this resource.
@return: Categories found.
@rtype : list
"""
ret = self.dict.get(keys.RESOURCE_CATEGORY)
if ret is None:
return []
ret = ret.get(keys.VALUE)
if ret is None:
return []
return deepcopy(ret)
def is_categories_modifiable(self):
"""Return whether categories value of this resdef is modifiable or not."""
ret = self.dict.get(keys.RESOURCE_CATEGORY)
if ret is None:
return True
ret = ret.get(keys.MODIFIABLE)
if ret is None:
return True
return ret
@classmethod
def validate_category(cls, category):
for s in category.split("."):
if not s.isalnum():
return "Category '%s' has to be alpha numeric values separated by '.'" % category
return None
def _set_categories_modifiable(self, modifiable):
"""
Indicate oif a client can modify the resource or not.
@param modifiable: If true, then a client can modify the categories and if False, then
not.
@type modifiable: bool
"""
d = self.dict.setdefault(keys.RESOURCE_CATEGORY, {})
d[keys.MODIFIABLE] = modifiable
def set_resource_categories(self, l):
"""
Set categories list (thereby overwriting all existing entries).
@param l: List of categories.
@type l: list
"""
d = self.dict.setdefault(keys.RESOURCE_CATEGORY, {keys.MODIFIABLE : True})
new_list = []
if l is None:
# No categories.
d[keys.VALUE] = new_list
return
for category in l:
ret = self.validate_category(category)
if ret is not None:
raise Exception(ret)
new_list.append(category)
d[keys.VALUE] = new_list
def list_property_names(self):
"""List property names for the resource."""
return self.dict[keys.PROPERTY_DEFS].keys()
def _process_property_value(self, prop_def, value, obfuscate=True):
if value is None:
return value
if prop_def[keys.PROPERTY_TYPE] == DICT_TYPE_PROPERTY:
struct = prop_def[keys.COMPOSITE_PROPERTY_STRUCT]
for key in struct.keys():
if value.has_key(key):
value[key] = self._process_property_value(struct[key], value[key], obfuscate)
elif prop_def[keys.PROPERTY_TYPE] == LIST_TYPE_PROPERTY:
def_entry_type = prop_def[keys.COMPOSITE_PROPERTY_DEFAULT_ENTRY_TYPE]
value = [self._process_property_value(def_entry_type, elt, obfuscate) for elt in value]
elif prop_def[keys.PROPERTY_TYPE] == 'string':
constraints = prop_def.get(keys.SIMPLE_PROPERTY_CONSTRAINT)
if constraints:
if 'obfuscate' in constraints:
obf_desc = constraints['obfuscate']
if obfuscate:
value = snap_crypt.obfuscate(value)
else:
value = snap_crypt.deobfuscate(value)
return value
def set_property_value(self, name, value):
"""Set property values."""
if name not in self.dict[keys.PROPERTY_DEFS]:
raise SnapResDefError("No such property '%s' defined" % name)
prop_def = self.get_property_def(name)
prop_def = prop_def[keys.PROPERTY_DEFINITION]
value = self._process_property_value(prop_def, value)
self.dict[keys.PROPERTY_VALS][name] = value
def get_property_value(self, name):
"""Get property value."""
if name not in self.dict[keys.PROPERTY_DEFS]:
raise SnapResDefError("No such property '%s' defined" % name)
props = self.dict[keys.PROPERTY_VALS]
try:
retval = deepcopy(props[name])
prop_def = self.get_property_def(name)
prop_def = prop_def[keys.PROPERTY_DEFINITION]
retval = self._process_property_value(prop_def, retval, False)
return retval
except KeyError:
props[name] = None
return props[name]
def set_general_info(self, name, value):
"""
Set general information in the resource (like author and description).
@param name: Name of the general information.
@type name: str
@param value: Value
"""
if name not in [keys.AUTHOR, keys.DESCRIPTION]:
raise SnapResDefError("%s is not a valid entry in general information" % name)
pval = self.get_property_value(keys.SNAP_GENERAL_INFO)
if pval is None:
pval = {}
pval[name] = value
self.set_property_value(keys.SNAP_GENERAL_INFO, pval)
def get_general_info(self, name):
"""
Get general information from the resource (like author and description).
@param name: Name of the general information.
@type name: str
@return: Value correspondin to the name.
"""
if name not in [keys.AUTHOR, keys.DESCRIPTION]:
raise SnapResDefError("%s is not a valid entry in general information" % name)
pval = self.get_property_value(keys.SNAP_GENERAL_INFO)
if pval is None:
return None
if name in pval:
return pval[name]
else:
return None
def define_param(self, param_name, default_val=None, modifiable=True):
"""
Define parameter in the resource object.
@param param_name: Name of the parameter.
@type param_name: str
@param default_val: The default value (if any) for the param. This must be a string. We only
support string values for params. If no default value is specified, then a value *must*
be specified for the param at execution time.
@param default_val: str
@param modifiable: Set to True if the param definition is modifiable.
@type modifiable: bool
"""
if default_val is not None:
if (not isinstance(default_val, str)) and (not isinstance(default_val, unicode)):
raise SnapResDefError("The parameter default value must be string or unicode. Received type '%s'"
% type(default_val))
params = self.dict[keys.PARAMS]
try:
param = params[param_name]
param[keys.DEFAULT_VALUE] = default_val
param[keys.MODIFIABLE] = modifiable
except KeyError:
params[param_name] = {
keys.DEFAULT_VALUE : default_val,
keys.MODIFIABLE : modifiable
}
def get_component_name(self):
"""Return the name of the component."""
return self.dict[keys.COMPONENT_NAME]
def get_param_def(self, param_name):
"""
Return parameter definition for the specified param.
@param param_name: Name of the parameter.
@type param_name: str
@return: A dictionary containing the definition of the param-
{ "default_value" : "some default value"/None,
"modifiable" : True/False
}
@rtype: dict
"""
params = self.dict[keys.PARAMS]
try:
# Make a copy, since the dict may be modified by the caller.
param = deepcopy(params[param_name])
except KeyError:
raise SnapResDefError("Parameter '%s' does not exist" % param_name)
return param
def remove_param_def(self, param_name):
"""Remove specified parameter definition from the resource object."""
try:
self.dict[keys.PARAMS].__delitem__(param_name)
except KeyError:
raise SnapResDefError("No such param '%s'" % param_name)
def list_param_names(self):
"""Return a list of parameter names in this resource object."""
return self.dict[keys.PARAMS].keys()
def get_param_default_value(self, param_name):
"""Return the default value of the specified parameter in the resource object."""
try:
param = self.dict[keys.PARAMS][param_name]
except KeyError:
raise SnapResDefError("Parameter '%s' does not exist" % param_name)
# Should be safe to return default value directly, string is immutable.
return param[keys.DEFAULT_VALUE]
def _check_param_values(self, param_vals):
"""Returns error message if the parameter values are not valid."""
par_names = self.list_param_names()
for pname in par_names:
if (self.get_param_default_value(pname) is None) and (pname not in param_vals):
# This is a parameter which has no default value and none has been specified
# in the parameter values passed. Raise an error.
return "Required parameter \"%s\" not specified" % pname
return None
def _update_param_values_with_defaults(self, param_vals):
"""
Update the params dictionary with the default value for missing params.
@return: A copy of the params dict with the default value added for missing params.
@rtype: dict
"""
# Populate (default) parameters to the argument dictionary. If a required parameter, or its value,
# is missing, raise an exception.
p_ret = dict(param_vals)
par_names = self.list_param_names()
for pname in par_names:
if (self.get_param_default_value(pname) is not None) and (p_ret.get(pname) is None):
# This is a parameter which has a default value
p_ret[pname] = self.get_param_default_value(pname)
return p_ret
def get_input_view(self, view_name):
"""
Retrieves the specified input view.
@param view_name: Name of the input view to be retrieved
@type view_name: str
@return: a dictionary describing the view. The dictionary has the
following entries:
is_record - If this is True, then the view is in record and the
dictionary has the following other entries
fields - The list of fields that was specified for
add_record_input_view()
description_str - Description string for the view.
If is_record is set to False, then the view is in binary mode and
the dictionary has the following entries:
content_types - The list of content_types as specified for the
method add_binary_input_view().
description_str - Description string for the view.
@rtype: dict
"""
try:
return deepcopy(self.dict[keys.INPUT_VIEWS][view_name])
except KeyError:
raise SnapResDefError("Input view %s not found" % view_name)
def describe(self):
"""
Provide only the information needed to use this resource.
@return: a dictionary containing only the entries needed to use
this resource.
@rtype: dict
"""
desc_dict = deepcopy(self.dict)
desc_dict.__delitem__(keys.PROPERTY_DEFS)
desc_dict.__delitem__(keys.PROPERTY_VALS)
desc_dict[keys.CAPABILITIES] = {}
desc_dict[keys.CAPABILITIES][keys.CAPABILITY_ALLOW_PASS_THROUGH] = \
self.dict[keys.CAPABILITIES][keys.CAPABILITY_ALLOW_PASS_THROUGH]
return desc_dict
def input_is_pass_through(self, view_name):
"""Returns true if the inout view is pass-through, false otherwise."""
for outview in self.dict[keys.OUTPUT_VIEW_PASSTHROUGH]:
if view_name in self.dict[keys.OUTPUT_VIEW_PASSTHROUGH][outview][keys.INPUT_VIEWS]:
return True
return False
def input_is_record_mode(self, view_name):
"""Returns true if the input view is record mode, false otherwise."""
return self.get_input_view(view_name)[keys.VIEW_IS_RECORD]
def list_input_view_fields(self, view_name):
"""
Return the fields definition of the specified input view.
@param view_name: Name of the input view.
@type view_name: str
@return: Sequence of the field definitions along these lines -
((fname1, type, description), (fname2, type, description),...).
@return: list/tuple
"""
return deepcopy(self.get_input_view(view_name)[keys.VIEW_FIELDS])
def list_input_view_names(self):
"""Return list of input view names in the resource object."""
return self.dict[keys.INPUT_VIEWS].keys()
def list_input_field_names(self, view_name):
"""Return list of field names in the specified input view."""
view = self.get_input_view(view_name)
if not view[keys.VIEW_IS_RECORD]:
raise SnapResDefError("The input view '%s' is not in record mode" % view_name)
l = []
for f in view[keys.VIEW_FIELDS]:
l.append(f[0])
return l
def get_input_field_type(self, view_name, field_name):
"""
Get field type for the specified field name in the input view.
@param view_name: Name of the input view.
@type view_name: str
@param field_name: Name of the field that is being looked up.
@type field_name: str
@return: Type of the field.
@rtype: str
"""
view = self.get_input_view(view_name)
if not view[keys.VIEW_IS_RECORD]:
raise SnapResDefError("The input view '%s' is not in record mode" % view_name)
for f in view[keys.VIEW_FIELDS]:
if f[0] == field_name:
return f[1]
raise SnapResDefError("Field '%s' was not found in input view '%s'" % (field_name, view_name))
def get_input_view_content_types(self, view_name):
"""
Return content types for the specified binary input view.
@param view_name: Name of the input view.
@type view_name: str
@return: List of content-types supported. Along these lines -
('image/jpeg; q=0.6', 'image/png; q=0.6', 'image/gif; q=0.5', ...)
@rtype: list/tuple
"""
view = self.get_input_view(view_name)
return deepcopy(view[keys.VIEW_CONTENT_TYPES])
def list_output_view_fields(self, view_name):
"""
Return list of field definitions for the specified output view.
@param view_name: Name of the output view.
@type view_name: str
@return: Sequence of the field definitions along these lines -
((fname1, type, description), (fname2, type, description),...).
@return: list/tuple
"""
return deepcopy(self.get_output_view(view_name)[keys.VIEW_FIELDS])
def get_output_view(self, view_name):
"""
Return definition of the specified view name.
@param view_name: Name of the output view
@type view_name: str
@return: A dictionary describing the view. The dictionary has the
following entries:
is_record - If this is True, then the view is in record mode
and the dictionary has the following other entries:
fields - The list of fields that was specified for
add_record_output_view_def()
description_str - Description string for the view.
If is_record is set to False, then the view is in binary mode
and the dictionary has the following set of other entries:
view_content_types - Basically the list of view_content_types
as specified for the method
add_binary_output_view_def().
description_str - Description string for the view.
@rtype: dict
"""
try:
return deepcopy(self.dict[keys.OUTPUT_VIEWS][view_name])
except KeyError:
raise SnapResDefError("Output view '%s' not found" % view_name)
def _set_output_view(self, view_name, view_def):
self.dict[keys.OUTPUT_VIEWS][view_name] = view_def
def output_is_record_mode(self, view_name):
"""Returns true if the output view is record mode, false otherwise."""
return self.get_output_view(view_name)[keys.VIEW_IS_RECORD]
def list_output_field_names(self, view_name):
"""Return list of field names in the specified output view."""
view = self.get_output_view(view_name)
if not view[keys.VIEW_IS_RECORD]:
raise SnapResDefError("The output view '%s' is not in record mode" % view_name)
l = []
for f in view[keys.VIEW_FIELDS]:
l.append(f[0])
return l
def get_output_field_type(self, view_name, field_name):
"""
Get field type for the specified field name in the output view.
@param view_name: Name of the output view.
@type view_name: str
@param field_name: Name of the field that is being looked up.
@type field_name: str
@return: Type of the field.
@rtype: str
"""
view = self.get_output_view(view_name)
if not view[keys.VIEW_IS_RECORD]:
raise SnapResDefError("The output view '%s' is not in record mode" % view_name)
for f in view[keys.VIEW_FIELDS]:
if f[0] == field_name:
return f[1]
raise SnapResDefError("Field '%s' was not found in output view '%s'" % (field_name, view_name))
def list_output_view_names(self):
"""
Returns list of output view names.
@return: list of output view names.
@rtype: list
"""
return self.dict[keys.OUTPUT_VIEWS].keys()
def unset_output_view_pass_through(self, output_view):
"""
Remove any pass-through definition on the output view.
@param output_view: Name of the output view.
@type output_view: str
"""
del self.dict[keys.OUTPUT_VIEW_PASSTHROUGH][output_view]
def list_pass_through_output_views(self):
"""Return list of output view names, that has a pass-through setting."""
return self.dict[keys.OUTPUT_VIEW_PASSTHROUGH].keys()
def get_output_view_pass_through(self, output_view):
"""
Return the pass-through setting for specified output view name.
@param output_view: Name of output view.
@type output_view: str
@return: dictionary containing pass-through setting for that output view-
{'input_views': (List of input view names that are passing through fields to the output view),
'modifable': True/False modifable flag }
@rtype: dict
"""
try:
ret = self.dict[keys.OUTPUT_VIEW_PASSTHROUGH][output_view]
except KeyError:
raise SnapResDefError("Output view '%s' has not pass through setting" % output_view)
return deepcopy(ret)
def get_output_view_content_types(self, view_name):
"""
Return content types for the specified binary output view.
@param view_name: Name of the output view.
@type view_name: str
@return: List of content-types supported. Along these lines -
('image/jpeg; q=0.6', 'image/png; q=0.6', 'image/gif; q=0.5', ...)
@rtype: list/tuple
"""
view = self.get_output_view(view_name)
return deepcopy(view[keys.VIEW_CONTENT_TYPES])
def save(self, uri=None, force_flag=False):
"""
Save the resource object.
@param uri: If uri is specified, then the object will be saved under that
specified URI and the object will also be updated with this new URI.
@type uri: str
@param force_flag: Flag to indicate a resource be overwritten if it exists.
@type force_flag: bool
@return: The URI saved under.
@rtype: str
"""
did_update = self._update_uri(uri)
if did_update or force_flag:
# If the URI is different from the old URI or if a force flag has been specified, then
# get rid of the guids and gen_ids
self.guid = None
self.gen_id = None
d = snapi_base.write_resource(self.dict, self.guid, self.gen_id, self._uri,
force_flag=force_flag, cred=self.credentials, custom_hdr=self.custom_hdr)
self.guid = d[keys.GUID]
self.gen_id = d[keys.GEN_ID]
if self._uri is None:
# Update with autogen URI.
self._update_uri(d[keys.URI])
return self._uri
def execute(self, inputs=None, outputs=None, params=None, name=None):
"""
Execute the resource object.
The caller must ensure that a validated copy of this resource has already been saved,
before this call is made.
@param inputs: Dictionary with names of the input views as keys. The value can be
requested content type or can be None, if default content_type is fine.
@type inputs: dict
@param outputs: Dictionary with names of the output views as keys. The value can be
requested content type or can be None, if default content_type is fine.
@type outputs: dict
@param params: Dictionary with param name as key and param value as value.
@type params: dict
@param name: Optional, user defined name for the executing resource. Could be any
string the caller of this method would like to use to identify the pipeline
being invoked. This name does not have to be unique.
@type name: str
@return: Handle to the executing resource.
@rtype: L{exec_interface.Handle}
"""
return exec_interface.exec_resource(name, self._uri, self, inputs, outputs, params, self.credentials,
self.custom_hdr)
class ResDef(ResDefBase):
"""
Represents a definition of a resource.
"""
def __init__(self, d = None, member_resource = False):
"""
Initialize ResDef object.
@param d: Resource dictionary to be used in the initialization.
@type d: dict
@param member_resource: If set to True, the dictionary will be referred to by this object,
else, a copy is kept.
@type member_resource: bool
"""
ResDefBase.__init__(self, d, member_resource)
if not member_resource:
for key in [keys.PROPERTY_DEFS, keys.PROPERTY_VALS]:
if key not in self.dict:
self.dict[key] = {}
def __repr__(self):
"""Returns the resource dictionary's repr()."""
return repr(self.dict)
def _get_capabilities_uri(self, key):
"""
Get the correct absolute URI for the capability identified by key.
@param key: key in the L{keys.CAPABILITIES} portion of the L{self.dict}.
@type key: str
@return: The absolute URI to use for this capability. If resdef has a relative
URI, then L{self._server_uri} is prepended
@rtype: str
"""
if not self.capabilities.has_key(key):
return None
uri = self.capabilities[key]
if uri is None:
return None
if uri.lower().startswith('http://') or uri.lower().startswith('https://'):
return uri
uri = snap_http_lib.concat_paths(self._server_uri, uri)
return uri
@classmethod
def match_categories(cls, candidate, target):
"""
Check if candidate categories match the constraints of the target categories.
If the target is is None or empty list or contains empty string, then anything can match that.
The target must equal or be a prefix of the candidate, such that the prefix ends at a '.'.
Here are some examples:
If candidate is "connection.db.mysql" and target is "connection.db", then there is a match.
If candidate is "connection.dbmysql" and the target is "connect.db", then there is no match. This
is because, even thought target is a prefix of candidate, the prefix does not end in a '.'.
@param candidate: List of candidate categories.
@type candidate: list
@param target: List of target categories.
@type target: list
@return: True, if there was a match, else, False.
@rtype: bool
"""
if not target or "" in target:
# Target is None or empty list or contains empty string, then anything can match that.
return True
for t in target:
for c in candidate:
# Either equals or is a prefix.
if c == t or (c.startswith(t) and c[len(t)] == '.'):
return True
return False
def _define_resource_ref(self, name, label, categories_allowed=None, required=True):
"""
Define a resource reference.
This method is called only by component api and not by clients.
@param name: Name of the resource reference. The reference value can be set and
fetched using this name.
@type name: str
@param label: This is a UI friendly label to display resource reference.
@type label: str
@param categories_allowed: List of allowed categories.
@type categories_allowed: list
@param required: Indicates whether a value must be specified or not.
@type required: bool
"""
# Set resource ref key, if it doesn't already exist.
d = self.dict.setdefault(keys.RESOURCE_REF, {})
if categories_allowed is not None:
for category in categories_allowed:
ret = self.validate_category(category)
if ret is not None:
raise SnapResDefError(ret)
d[name] = {
keys.LABEL : label,
keys.CATEGORIES_ALLOWED : categories_allowed,
keys.VALUE : None,
keys.REQUIRED : required
}
def list_resource_ref_names(self):
ret = self.dict.get(keys.RESOURCE_REF)
if ret is None:
return []
else:
return ret.keys()
def get_resource_ref(self, name):
ret = self.dict.get(keys.RESOURCE_REF)
if ret is None:
raise SnapResDefError("No resource reference named '%s' found." % name)
ret = ret.get(name)
if ret is None:
raise SnapResDefError("No resource reference named '%s' found." % name)
return ret[keys.VALUE]
def get_resource_ref_def(self, name):
"""
Get resource reference definition and value.
@param name: Name of resource ref.
@type name: str
@return: Definition and value of resource ref.
{ keys.LABEL : label,
keys.CATEGORIES_ALLOWED : categories_allowed,
keys.VALUE : None,
keys.OVERRIDDEN_VALUE : None, # This key is only found in member of a pipeline resource.
keys.REQUIRED : required
}
@rtype: dict
"""
ret = self.dict.get(keys.RESOURCE_REF)
if ret is None:
raise SnapResDefError("No resource reference named '%s' found." % name)
ret = ret.get(name)
if ret is None:
raise SnapResDefError("No resource reference named '%s' found." % name)
return ret
def set_resource_ref(self, ref_name, ref_value):
"""
Set resource ref to a particular absolute URI or relative URI of a resdef.
In order to reset the current value, just set ref_value to None.
@param ref_name: Resource reference name
@type ref_name: str
@param ref_value: The resource reference being set.
@type ref_value: str
"""
if ref_value is None:
# This is only a request to reset the reference value.
self._set_resource_ref(ref_name, None, None)
return
resdef = self._fetch_resdef_if_uri(ref_value)
if resdef is None:
raise SnapResDefError("Resource reference '%s' is not a valid URI" % ref_value)
value_categories = resdef.list_resource_categories()
self._set_resource_ref(ref_name, ref_value, value_categories)
def _set_resource_ref(self, ref_name, ref_value, value_categories, in_pipeline=False):
"""
Set resource ref value and use the valueCategories to validate if the value is compatible with ref definition.
The compatibility is based on whether any one of the reference's allowed categories is equal to or the
prefix of any one of value's categories. The prefix must end at the '.' boundary. For example:
If value category is "connection.db.mysql" and allowed category is "connection.db", then there is a match.
If value category is "connection.dbmysql" and the allowed category is "connect.db", then there is no match.
This is because, even thought target is a prefix of candidate, the prefix does not end in a '.'.
@param ref_name: Name of the reference.
@type ref_name: str
@param ref_value: Value of the reference.
@type ref_value: str
@param value_categories: List of categories to which the ref value belongs.
@type value_categories: list
@param in_pipeline: Set to True, if the set resource ref is being made inside a pipeline.
@type in_pipeline: bool
"""
d = self.dict.get(keys.RESOURCE_REF)
if d is None or ref_name not in d.keys():
raise SnapResDefError("No resource reference named '%s' defined for this resource." % ref_name)
if ref_value is None:
# This is only a request to reset the reference value.
if in_pipeline:
d[ref_name][keys.OVERRIDDEN_VALUE] = None
else:
d[ref_name][keys.VALUE] = None
return
if (not self.match_categories(value_categories, d[ref_name][keys.CATEGORIES_ALLOWED])):
val_cat = ", ".join(value_categories) if value_categories else "none"
tgt_cat = ", ".join(d[ref_name][keys.CATEGORIES_ALLOWED]) if d[ref_name][keys.CATEGORIES_ALLOWED] else "none"
raise SnapResDefError("Resource ref '%s' categories '%s' not compatible with categories '%s' of %s" %
(ref_name, tgt_cat, val_cat, ref_value))
if in_pipeline:
d[ref_name][keys.OVERRIDDEN_VALUE] = ref_value
else:
d[ref_name][keys.VALUE] = ref_value
def list_resource_ref_categories(self, ref_name):
"""
List all the acceptable categories defined for the specified resource reference.
@param ref_name: Name of the reference.
@type ref_name: str
@return: List of all categories.
@rtype: list
"""
ret = self.dict.get(keys.RESOURCE_REF)
if ret is None:
raise SnapResDefError("No resource reference named '%s' found." % ref_name)
ret = ret.get(ref_name)
if ret is None:
raise SnapResDefError("No resource reference named '%s' found." % ref_name)
return deepcopy(ret[keys.CATEGORIES_ALLOWED])
def add_record_input_view(self, view_name, fields, description_str, modifiable = True):
"""
Adds or overwrites an input view in record mode.
@param view_name: Name of the input view
@type view_name: str
@param fields: The fields of the view. Specified using the format:
((fname1, type, description), (fname2, type, description),...).
@type fields: tuple/list
@param description_str: Description of the view as a whole
@type description_str: str
@param modifiable: If this is set to False, then only the RH and component code can modify its values.
The client (acting via SnAPI) cannot. The default value of this param is True.
@type modifiable: bool
"""
if self.dict[keys.CAPABILITIES][keys.CAPABILITY_INPUT_VIEW_UPPER_LIMIT] <= len(self.dict[keys.INPUT_VIEWS]) and\
view_name not in self.dict[keys.INPUT_VIEWS]:
raise SnapResDefError("The upper limit on the number of views (%s) has been reached"
% self.dict[keys.CAPABILITIES][keys.CAPABILITY_INPUT_VIEW_UPPER_LIMIT])
for f in fields:
if len(f) != 3:
raise SnapResDefError("Each field in the fields param should have 3 entries. Received '%s'" % str(f))
if (not isinstance(f[0], str)) and (not isinstance(f[0], unicode)):
raise SnapResDefError("The field name must be string or unicode. Received type '%s'"
% type(f[0]))
if f[1] not in type_and_constraints:
raise SnapResDefError("The field type (%s) for field '%s' is not a valid type" % (f[1], f[0]))
view = self.dict[keys.INPUT_VIEWS][view_name] = {}
# Don't want to risk using the same list the user provided.
view[keys.VIEW_FIELDS] = deepcopy(fields)
view[keys.DESCRIPTION] = description_str
view[keys.MODIFIABLE] = modifiable
view[keys.VIEW_IS_RECORD] = True
def add_binary_input_view(self, view_name, content_types, description_str, modifiable = True):
"""
Add binary mode output view.
The output view is defined in "binary mode". In binary mode, the output view has no concept of fields,
the component code writes a binary strings to the output view. As with binary input view, the output
view must be created with the list of possible content types specified.
@param view_name: Input view name.
@type view_name: str
@param view_content_types: Specifies a list of content types the output view can create.
For example:
('image/jpeg',)
Or, a more extensive list can be specified as follows:
('image/jpeg; q=0.6', 'image/png; q=0.6', 'image/gif; q=0.5', 'image/*; q=0.1')
The q represents the quality param of the content type as defined for Accept header in RFC 2616.
It basically indicates the preference for a particular content type and takes the value from
0.0 to 1.0.
@type view_content_types: sequence
@param description_str: Description string for the entire view.
@type description_str: str
@param modifiable: If set to False, then only the server and component can modify this view and not
the client. The default value is True (meaning it is modifiable by client).
@type modifiable: bool
"""
if self.dict[keys.CAPABILITIES][keys.CAPABILITY_INPUT_VIEW_ALLOW_BINARY] == False:
raise SnapResDefError("The capabilities do not allow for binary input view")
if self.dict[keys.CAPABILITIES][keys.CAPABILITY_INPUT_VIEW_UPPER_LIMIT] <= len(self.dict[keys.INPUT_VIEWS]) and\
view_name not in self.dict[keys.INPUT_VIEWS]:
raise SnapResDefError("The upper limit on the number of views (%s) has been reached"
% self.dict[keys.CAPABILITIES][keys.CAPABILITY_INPUT_VIEW_UPPER_LIMIT])
# Don't want to risk using the same list the user provided.
content_types = tuple(content_types)
view = self.dict[keys.INPUT_VIEWS][view_name] = {}
view[keys.DESCRIPTION] = description_str
view[keys.VIEW_CONTENT_TYPES] = content_types
view[keys.VIEW_IS_RECORD] = False
view[keys.MODIFIABLE] = modifiable
def remove_input_view(self, view_name):
"""
Removes the specified input view.
@param view_name: view to delete
@type view_name: str
"""
self.dict[keys.INPUT_VIEWS].__delitem__(view_name)
# Cleanup pass-through settings after the delete of input view.
for out_name in self.dict[keys.OUTPUT_VIEW_PASSTHROUGH]:
input_views = self.dict[keys.OUTPUT_VIEW_PASSTHROUGH][out_name][keys.INPUT_VIEWS]
if view_name in input_views:
self.dict[keys.OUTPUT_VIEW_PASSTHROUGH][out_name][keys.INPUT_VIEWS] = \
tuple([v for v in input_views if v != view_name])
if len(input_views) == 0:
# The input view list has become empty. Delete the pass-though setting.
del res_dict[keys.OUTPUT_VIEW_PASSTHROUGH][out_name]
def add_record_output_view(self, view_name, fields, description_str, modifiable=True):
"""
Creates a record mode output view. This type of output view has data written to it in the
record format. The view defines the list of typed fields that the records must have.
@param view_name: Output view name.
@type view_name: str
@param fields: A sequence of fields, where each field is defined by the sequence
(field name, field type, field description string). The field name must be a
unique string name in the context of that view. The field type is also a string
which specifies one of the supported data types for that field. The field
description is a string which describes the field.
@type fields: sequence
@param description_str: Description string for the entire view.
@type description_str: str
@param modifiable: If set to False, then only the server and component can modify this view and not
the client. The default value is True (meaning it is modifiable by client).
@type modifiable: bool
"""
if self.dict[keys.CAPABILITIES][keys.CAPABILITY_OUTPUT_VIEW_UPPER_LIMIT] <= len(self.dict[keys.OUTPUT_VIEWS]) \
and (view_name not in self.dict[keys.OUTPUT_VIEWS]):
raise SnapResDefError("The upper limit on the number of views (%s) has been reached"
% self.dict[keys.CAPABILITIES][keys.CAPABILITY_OUTPUT_VIEW_UPPER_LIMIT])
for f in fields:
if len(f) != 3:
raise SnapResDefError("Each field in the fields param should have 3 entries. Received '%s'" % str(f))
if (not isinstance(f[0], str)) and (not isinstance(f[0], unicode)):
raise SnapResDefError("The field name must be string or unicode. Received type '%s'"
% type(f[0]))
if f[1] not in type_and_constraints:
raise SnapResDefError("The field type (%s) for field '%s' is not a valid type" % (f[1], f[0]))
view = self.dict[keys.OUTPUT_VIEWS][view_name] = {}
# Don't want to risk using the same list the user provided.
view[keys.VIEW_FIELDS] = deepcopy(fields)
view[keys.DESCRIPTION] = description_str
view[keys.MODIFIABLE] = modifiable
view[keys.VIEW_IS_RECORD] = True
def add_binary_output_view(self, view_name, content_types, description_str, modifiable = True):
"""
Add binary mode output view.
The output view is defined in "binary mode". In binary mode, the output view has no concept of fields,
the component code writes a binary strings to the output view. As with binary input view, the output
view must be created with the list of possible content types specified.
@param view_name: Input view name.
@type view_name: str
@param view_content_types: Specifies a list of content types the output view can create.
For example:
('image/jpeg',)
Or, a more extensive list can be specified as follows:
('image/jpeg; q=0.6', 'image/png; q=0.6', 'image/gif; q=0.5', 'image/*; q=0.1')
The q represents the quality param of the content type as defined for Accept header in RFC 2616.
It basically indicates the preference for a particular content type and takes the value from
0.0 to 1.0.
@type view_content_types: sequence
@param description_str: Description string for the entire view.
@type description_str: str
@param modifiable: If set to False, then only the server and component can modify this view and not
the client. The default value is True (meaning it is modifiable by client).
@type modifiable: bool
"""
if self.dict[keys.CAPABILITIES][keys.CAPABILITY_OUTPUT_VIEW_ALLOW_BINARY] == False:
raise SnapResDefError("The capabilities do not allow for output binary view")
if self.dict[keys.CAPABILITIES][keys.CAPABILITY_OUTPUT_VIEW_UPPER_LIMIT] <= len(self.dict[keys.OUTPUT_VIEWS]) \
and (view_name not in self.dict[keys.OUTPUT_VIEWS]):
raise SnapResDefError("The upper limit on the number of views (%s) has been reached"
% self.dict[keys.CAPABILITIES][keys.CAPABILITY_OUTPUT_VIEW_UPPER_LIMIT])
# Don't want to risk using the same list the user provided.
content_types = tuple(content_types)
view = self.dict[keys.OUTPUT_VIEWS][view_name] = {}
view[keys.DESCRIPTION] = description_str
view[keys.VIEW_CONTENT_TYPES] = content_types
view[keys.VIEW_IS_RECORD] = False
view[keys.MODIFIABLE] = modifiable
def remove_output_view(self, view_name):
"""Remove specified output view from the resource object."""
self.dict[keys.OUTPUT_VIEWS].__delitem__(view_name)
# Cleanup pass-through settings after the delete of input view.
if view_name in self.dict[keys.OUTPUT_VIEW_PASSTHROUGH]:
del self.dict[keys.OUTPUT_VIEW_PASSTHROUGH][view_name]
def set_output_view_pass_through(self, output_view, input_views, modifiable = True):
"""
Specify a pass-through setting for the output view.
The setting should specify the list of input view names that pass-through fields to
the output view. The order of the input view names is important. It dictates the
order in which the fields are passed through to the output view.
@param output_view: Name of the output view.
@type output_view: str
@param input_views: Ordered list of input view names that pass-through fields to this
output view.
@type input_views: list
@param modifiable: Flag set to True if clients can modify this setting.
@type modifiable: bool
"""
# TODO : modifiable?
if not self.dict[keys.CAPABILITIES][keys.CAPABILITY_ALLOW_PASS_THROUGH]:
raise SnapResDefError("The capabilities do not allow for pass through")
try:
view = self.get_output_view(output_view)
except KeyError:
raise SnapResDefError("Output view '%s' does not exist" % output_view)
if not view[keys.VIEW_IS_RECORD]:
raise SnapResDefError("Output view '%s' is not a record mode view" % output_view)
if len(input_views) == 0:
raise SnapResDefError("The list of input view names cannot be empty")
for inp_name in input_views:
# make sure that the input view exists.
inp_def = self.get_input_view(inp_name)
if not inp_def[keys.VIEW_IS_RECORD]:
raise SnapResDefError("Input view '%s' is not a record mode view" % inp_name)
self.dict[keys.OUTPUT_VIEW_PASSTHROUGH][output_view] = {}
self.dict[keys.OUTPUT_VIEW_PASSTHROUGH][output_view][keys.INPUT_VIEWS] = tuple(input_views)
self.dict[keys.OUTPUT_VIEW_PASSTHROUGH][output_view][keys.MODIFIABLE] = modifiable
def validate(self):
"""
Validate the resource.
@return: None if there is no error and a dict if there is an error.
@rtype: dict
"""
validate_uri = self._get_capabilities_uri(keys.CAPABILITY_VALIDATE)
try:
snapi_base.validate(self.dict, validate_uri, self.credentials, self.custom_hdr)
except SnapiHttpException, e:
if e.status == HttpRequest.BAD_REQUEST:
return snapi_base.ValidationDict(e.body)
else:
raise
return None
def suggest_values(self, params=None):
"""
Suggest values for the resource.
@param params: Parameters needed to request resource values from the component.
@type params: dict
@return: A new resource definition with suggested resource values.
@rtype: L{ResDef}
"""
suggest_uri = self._get_capabilities_uri(keys.CAPABILITY_SUGGEST_RESOURCE_VALUES)
try:
d = snapi_base.suggest_resource_values(self.dict, suggest_uri, params, self.credentials, self.custom_hdr)
except SnapiHttpException, e:
if e.status == HttpRequest.BAD_REQUEST:
# Provide a more readable exception.
e.body = str(snapi_base.ValidationDict(e.body))
raise
res = deepcopy(self)
res.dict = d
return res
class PipelineResDef(ResDef):
"""
Represents definition of a pipeline.
"""
def __init__(self, d = None):
"""Initialize PipelineResDef object."""
ResDef.__init__(self, d)
for key in [keys.PIPELINE_RESOURCES, keys.INPUT_VIEW_ASSIGNMENT, keys.OUTPUT_VIEW_ASSIGNMENT]:
if key not in self.dict:
self.dict[key] = {}
self.dict[keys.RELATED_PIPELINES] = {}
if keys.COMPONENT_NAME not in self.dict or self.dict[keys.COMPONENT_NAME] is None:
self.dict[keys.COMPONENT_NAME] = keys.PIPELINE_COMPONENT_NAME
if keys.PIPELINE_PARAM_MAP not in self.dict:
self.dict[keys.PIPELINE_PARAM_MAP] = []
if keys.VIEW_LINKS not in self.dict:
self.dict[keys.VIEW_LINKS] = []
def describe(self):
"""
See L{ResDef.describe}.
"""
desc_dict = ResDefBase.describe(self)
desc_dict.__delitem__(keys.PIPELINE_RESOURCES)
desc_dict.__delitem__(keys.INPUT_VIEW_ASSIGNMENT)
desc_dict.__delitem__(keys.OUTPUT_VIEW_ASSIGNMENT)
desc_dict.__delitem__(keys.VIEW_LINKS)
desc_dict.__delitem__(keys.PIPELINE_PARAM_MAP)
return desc_dict
def __make_resource_instance(self, resdef):
inst = {}
for key in [keys.INPUT_VIEWS, keys.OUTPUT_VIEWS, keys.PARAMS, keys.OUTPUT_VIEW_PASSTHROUGH,
keys.RESOURCE_CATEGORY, keys.RESOURCE_REF]:
d = resdef.dict.get(key)
if d is not None:
inst[key] = deepcopy(d)
for out in resdef.dict[keys.OUTPUT_VIEW_PASSTHROUGH]:
# Keep a copy of the original user defined output view, for pass-through views, since they
# get modified inside a pipeline.
inst[keys.OUTPUT_VIEWS][out][keys.ORIGINAL_OUTPUT_FIELDS] = \
deepcopy(inst[keys.OUTPUT_VIEWS][out][keys.VIEW_FIELDS])
return inst
def copy_param_map_to_params(self):
"""Defines parameters mentioned in the param map as the pipeline's parameter."""
# First reset the old values.
self.dict[keys.PARAMS] = {}
pmap = self.get_param_map()
for (pipe_param, res_name, res_param, def_value) in pmap:
if pipe_param is not None:
self.define_param(pipe_param, def_value)
def _copy_input_view(self, res_name, res_viewname, pipe_viewname):
"""
Copy the resource's input view defintion to the pipeline input view definition.
Pipelines create input views by assigning input view of a member resource
as its own input view. This assignment needs to be followed up with actual
copying of the resource view definition to the pipeline view definition,
so to the outside world, the pipeline looks like any component based resource
with proper view definitions. This method does that copying.
@param res_name: Name of the resource
@type res_name: str
@param res_viewname: Name of the resource view.
@type res_viewname: str
@param pipe_viewname: Name given to the assigned view in the pipeline.
@type pipe_viewname: str
"""
view_def = self.get_resource(res_name).get_input_view(res_viewname)
self.dict[keys.INPUT_VIEWS][pipe_viewname] = deepcopy(view_def)
def _copy_output_view(self, res_name, res_viewname, pipe_viewname):
"""
Copy the resource's output view defintion to the pipeline output view definition.
Pipelines create output views by assigning output view of a member resource
as its own output view. This assignment needs to be followed up with actual
copying of the resource view definition to the pipeline view definition,
so to the outside world, the pipeline looks like any component based resource
with proper view definitions. This method does that copying.
@param res_name: Name of the resource
@type res_name: str
@param res_viewname: Name of the resource view.
@type res_viewname: str
@param pipe_viewname: Name given to the assigned view in the pipeline.
@type pipe_viewname: str
"""
view_def = self.get_resource(res_name).get_output_view(res_viewname)
self.dict[keys.OUTPUT_VIEWS][pipe_viewname] = {}
for k in view_def:
if k in [keys.ORIGINAL_OUTPUT_FIELDS, keys.RENAMED_FIELDS_MAP]:
# Don't copy these values, as they are only used for modifying output view of a resource
# inside a pipeline
continue
self.dict[keys.OUTPUT_VIEWS][pipe_viewname][k] = deepcopy(view_def[k])
def copy_assigned_views(self):
"""Defines views mentioned in the assignments as the pipeline's views."""
# First reset the old values.
self.dict[keys.INPUT_VIEWS] = {}
self.dict[keys.OUTPUT_VIEWS] = {}
for pipe_viewname in self.list_input_assignments():
(res_name, res_viewname) = self.get_input_assignment(pipe_viewname)
self._copy_input_view(res_name, res_viewname, pipe_viewname)
for pipe_viewname in self.list_output_assignments():
(res_name, res_viewname) = self.get_output_assignment(pipe_viewname)
self._copy_output_view(res_name, res_viewname, pipe_viewname)
def add_resource(self, name, resdef, uri, guid, gen_id, cred=None):
"""
Add a member resource to a pipeline.
@param name: name of the member resource.
@type name: str
@param uri: URI of the member resource
@type uri: str
"""
self.dict[keys.PIPELINE_RESOURCES][name] = {}
member_dict = self.__make_resource_instance(resdef)
self.dict[keys.PIPELINE_RESOURCES][name][keys.URI] = uri
self.dict[keys.PIPELINE_RESOURCES][name][keys.GUID] = guid
self.dict[keys.PIPELINE_RESOURCES][name][keys.GEN_ID] = gen_id
self.dict[keys.PIPELINE_RESOURCES][name][keys.RESDEF] = member_dict
def get_resource(self, name):
"""
Get resource definition of the resource inside the pipeline.
@param name: Resource name
@type name: str
@return: Resource definition of the resource.
@rtype: L{ResDef}
"""
if name not in self.dict[keys.PIPELINE_RESOURCES]:
raise SnapResDefError("Pipeline does not have resource '%s'" % name)
# Create and return as a "member resdef" object and not as a normal resdef.
# This includes, not trying to return a copy of the dictionary, but just a
# reference to the one inside the pipeline resef, not creating unnecesarry
# keys like capabilities and user defined properties.
return ResDef(self.dict[keys.PIPELINE_RESOURCES][name][keys.RESDEF], True)
def get_resource_uri(self, name):
"""
Get URI for the resource in the pipeline.
@param name: Name of the resource inside the pipeline.
@type name: str
@return: URI of that resource.
@rtype: str
"""
return self.dict[keys.PIPELINE_RESOURCES][name][keys.URI]
def remove_resource(self, name):
"""Remove specified resource from the pipeline."""
self._remove_all_resource_references(name)
self.dict[keys.PIPELINE_RESOURCES].__delitem__(name)
# Remove parameter mappings
pm = self.dict[keys.PIPELINE_PARAM_MAP]
to_remove = []
for m in pm:
if m[1] == name:
to_remove.append(m)
for m in to_remove:
pm.remove(m)
# Remove view mappings
for view_type in [keys.INPUT_VIEW_ASSIGNMENT, keys.OUTPUT_VIEW_ASSIGNMENT]:
views = deepcopy(self.dict[view_type].keys())
for view in views:
view_ass = self.dict[view_type][view]
if view_ass['resource_name'] == name:
self.dict[view_type].__delitem__(view)
if view_type == keys.INPUT_VIEW_ASSIGNMENT:
self.dict[keys.INPUT_VIEWS].__delitem__(view)
else:
self.dict[keys.OUTPUT_VIEWS].__delitem__(view)
def make_resources_relative(self, local_server_url=None):
"""
Convert local resource URIs to relative paths if absolute.
For all resources whose URI is absolute and has local_server_url as its base URL, convert
the URI to a relative path from local_server_url. The new URI is saved back to the resource
definition internally.
If local_server_url is None, all resources will be converted to relative.
@param local_server_url: URL to local server.
@type local_server_url: string
"""
if local_server_url is not None:
local_parsed = urlparse.urlparse(local_server_url)
local_hostname = local_parsed.hostname.lower()
local_port = local_parsed.port
for resource_info in self.dict[keys.PIPELINE_RESOURCES].values():
if resource_info[keys.URI] and resource_info[keys.URI][0] != '/':
res_parsed = urlparse.urlparse(resource_info[keys.URI])
if local_server_url is None or \
(res_parsed.hostname.lower() == local_hostname and local_port == res_parsed.port):
resource_info[keys.URI] = res_parsed.path
def list_resource_names(self):
"""List of names of resources inside the pipeline."""
return self.dict[keys.PIPELINE_RESOURCES].keys()
def assign_output_view(self, res_name, res_viewname, pipe_viewname):
"""
Assigns/exposes the output view of a resource instance as that of the pipeline.
@param res_name: Name of resource instance whose view is being exposed.
@type res_name: string
@param res_viewname: Name of the resource instance view that is being exposed.
@type res_viewname: string
@param pipe_viewname: Name for the exposed view in the pipeline.
@type pipe_viewname: string
"""
# TODO add checks
assignment = {}
self.dict[keys.OUTPUT_VIEW_ASSIGNMENT][pipe_viewname] = assignment
assignment[keys.RESOURCE_NAME] = res_name
assignment[keys.RESOURCE_VIEW_NAME] = res_viewname
self._copy_output_view(res_name, res_viewname, pipe_viewname)
def assign_input_view(self, res_name, res_viewname, pipe_viewname):
"""
Assigns/exposes the input view of a resource instance as that of the pipeline.
@param res_name: Name of resource instance whose view is being exposed.
@type res_name: string
@param res_viewname: Name of the resource instance view that is being exposed.
@type res_viewname: string
@param pipe_viewname: Name for the exposed view in the pipeline.
@type pipe_viewname: string
"""
# TODO add checks
assignment = {}
self.dict[keys.INPUT_VIEW_ASSIGNMENT][pipe_viewname] = assignment
assignment[keys.RESOURCE_NAME] = res_name
assignment[keys.RESOURCE_VIEW_NAME] = res_viewname
self._copy_input_view(res_name, res_viewname, pipe_viewname)
def get_input_assignment(self, pipe_viewname):
"""
Return the resouce name and the resource view name that were assigned to the specific pipeline view.
@param pipe_viewname: Name of the pipeline input view.
@type pipe_viewname: str
@return: Returns a 2-tuple containing (resource name, resource view name)
@rtype: tuple
"""
d = self.dict[keys.INPUT_VIEW_ASSIGNMENT][pipe_viewname]
return (d[keys.RESOURCE_NAME], d[keys.RESOURCE_VIEW_NAME])
def list_input_assignments(self):
"""Return list of pipeline input view names that have been assigned."""
return self.dict[keys.INPUT_VIEW_ASSIGNMENT].keys()
def list_output_assignments(self):
"""Return list of pipeline output view names that have been assigned."""
return self.dict[keys.OUTPUT_VIEW_ASSIGNMENT].keys()
def get_output_assignment(self, pipe_viewname):
"""
Return the resouce name and the resource view name that were assigned to the specific pipeline view.
@param pipe_viewname: Name of the pipeline output view.
@type pipe_viewname: str
@return: Returns a 2-tuple containing (resource name, resource view name)
@rtype: tuple
"""
d = self.dict[keys.OUTPUT_VIEW_ASSIGNMENT][pipe_viewname]
return (d[keys.RESOURCE_NAME], d[keys.RESOURCE_VIEW_NAME])
def check_input_view_assignment(self, res_name, res_viewname):
"""
Return input pipeline view name that was assigned the specified view name (of a resource inside the pipeline).
If the resource's input view was not assigned as pipeline view, then None is returned.
@param res_name: The name of the resource that has the input view.
@type res_name: str
@param res_viewname: The name of the resource input view being looked up.
@type res_viewname: str
@return: Name of the pipeline input view that has the view assigned to it, or None.
@rtype: str
"""
for k in self.dict[keys.INPUT_VIEW_ASSIGNMENT]:
if (self.dict[keys.INPUT_VIEW_ASSIGNMENT][k][keys.RESOURCE_NAME] == res_name and
self.dict[keys.INPUT_VIEW_ASSIGNMENT][k][keys.RESOURCE_VIEW_NAME] == res_viewname):
return k
return None
def map_param(self, pipe_param, res_name, res_param, default_value):
"""
Map parameters of the resource to pipeline parameters.
A mapping can do two things:
1) Map param of a resource to a pipeline param. This action automatically creates the
pipeline parameter. A default value for that pipeline param can also be specified in this method
2) Map param of a resource to a constant default_value. This is done by setting pipe_param
to None and the default_value is expected to hold the constant value
@param pipe_param: Name of new pipeline parameter. Should be None if resource param is being mapped
to a constant value.
@type pipe_param: str
@param res_name: Name of the resource in the pipeline that is having its parameter mapped.
@type res_name: str
@param res_param: Name of the resource param that is being mapped to a pipeline param or constant.
@type res_param: str
@param default_value: If we are mapping to a pipeline param, then this will be the default value
of the pipeline param. If we are mapping to a constant, then this will be the actual constant
value.
@type res_param: str
NOTE 1: We only support string values to parameters.
NOTE 2: In a pipeline, all resource parameters that are required paramter, must be mapped, else
there will be validation error (a required parameter is one that does not have a default value).
"""
if pipe_param is None and default_value is None:
raise SnapResDefError("If pipeline param is not specified, then a constant value must be specified")
param_map = self.dict[keys.PIPELINE_PARAM_MAP]
param_map.append((pipe_param, res_name, res_param, default_value))
# Define a corresponding pipeline level parameter, if necessary
if pipe_param is not None:
self.define_param(pipe_param, default_value)
def get_param_map(self):
"""
Get the parameter map for the resource.
@return: The parameter map.
@rtype: list/tuple
"""
return deepcopy(self.dict[keys.PIPELINE_PARAM_MAP])
def link_views(self, src_name, src_viewname, dest_name, dest_viewname, field_links=None, over_write = False):
"""
Link views of source and destination member resources.
Links views output view of one component (called the source view and source
component here) and input view of another component (called the destination
view and destination component).
@param src_name: Name of the source resource instance.
@type src_name: string
@param src_viewname: Name of the output view in the source resource instance.
@type src_viewname: string
@param dest_name: Name the destination resource instance.
@type dest_name: string
@param dest_viewname: Name of the input view in the destination resource.
instance.
@type dest_viewname: string
@param fieldLinks: List of 2-tuples, where each 2-tuple contains
(source field, destination field).
@type fieldLinks: List of 2-tuples
@param over_write: Flag, if true, then any existing link to the input view
of the destination resource will be over written by this new link.
@type over_write: bool
@return: The link object representing this link.
@rtype: L{ViewLink}
"""
if field_links is not None:
field_links = deepcopy(field_links)
view_links = self.dict[keys.VIEW_LINKS]
# TODO: add validation
for (i, val) in enumerate(view_links):
if (val[0], val[1], val[2], val[3]) == (src_name, src_viewname, dest_name, dest_viewname):
view_links.pop(i)
break
view_links.append((src_name, src_viewname, dest_name, dest_viewname, field_links))
# Next, call suggest in order to modify output view fields based on the link.
try:
self.dict = snapi_base.suggest_pipeline_values(self.dict, self._server_uri, {}, self.credentials)
except SnapiHttpException, e:
if e.status == HttpRequest.BAD_REQUEST:
raise SnapiException(str(snapi_base.ValidationDict(e.body)))
else:
raise
def get_links(self):
"""Return list of links specified in the pipeline."""
return deepcopy(self.dict[keys.VIEW_LINKS])
def set_output_view_pass_through(self, output_view, input_views, modifiable = True):
"""
Specify a pass-through setting for the output view.
The setting should specify the list of input view names that pass-through fields to
the output view. The order of the input view names is important. It dictates the
order in which the fields are passed through to the output view.
@param output_view: Name of the output view.
@type output_view: str
@param input_views: Ordered list of input view names that pass-through fields to this
output view.
@type input_views: list
@param modifiable: Flag set to True if clients can modify this setting.
@type modifiable: bool
"""
# TODO : modifiable?
(res_name, res_viewname) = self.get_output_assignment(output_view)
view = self.get_resource(res_name).get_output_view(res_viewname)
if not view[keys.VIEW_IS_RECORD]:
raise SnapResDefError("Output view '%s' is not a record mode view" % output_view)
if len(input_views) == 0:
raise SnapResDefError("The list of input view names cannot be empty")
for inp_name in input_views:
# Make sure input view exists.
(res_name, res_viewname) = self.get_input_assignment(inp_name)
inp_def = self.get_resource(res_name).get_input_view(res_viewname)
if not inp_def[keys.VIEW_IS_RECORD]:
raise SnapResDefError("Input view '%s' is not a record mode view" % inp_name)
self.dict[keys.OUTPUT_VIEW_PASSTHROUGH][output_view] = {}
self.dict[keys.OUTPUT_VIEW_PASSTHROUGH][output_view][keys.INPUT_VIEWS] = tuple(input_views)
self.dict[keys.OUTPUT_VIEW_PASSTHROUGH][output_view][keys.MODIFIABLE] = modifiable
def validate(self):
"""
Validate the pipeline.
@return: None if there is no error and a dict if there is an error.
@rtype: dict
"""
try:
snapi_base.validate_pipeline(self.dict, self._server_uri, self.credentials, self.custom_hdr)
except SnapiHttpException, e:
if e.status == HttpRequest.BAD_REQUEST:
return snapi_base.ValidationDict(e.body)
else:
raise
return None
def suggest_values(self, params=None):
"""Suggest values is not supported for pipelines resource objects. """
raise SnapResDefError("Suggest values is not supported for pipeline resources")
def _autogen_name(self, comp_name):
"""
Automatically create a name, based on the component name.
@param comp_name: Component name
@type comp_name: str
@return: Generated name.
@rtype: str
"""
name = comp_name.split(".").pop()
curr_names = self.list_resource_names()
c = 1
while ("%s%s" % (name, c) in curr_names):
c += 1
return "%s%s" % (name, c)
def add(self, resdef, name=None):
"""
Add resource to pipeline.
@param resdef: The resource object being added.
@type resdef: {ResDef}
@param name: Name to be assigned to the resource inside the pipeline. If not
specified, then an auto-generated name will be used.
@type name: str
@return: Name of the resource added.
@rtype: str
"""
if name is None:
name = self._autogen_name(resdef.get_component_name())
if self._get_server_loc() == resdef._get_server_loc():
uri = resdef.get_relative_uri()
else:
uri = resdef.get_absolute_uri()
self.add_resource(name, resdef, uri, resdef.guid, resdef.gen_id, self.credentials)
return name
def add_uri(self, uri, name=None):
"""
Add resource to pipeline.
@param uri: The uri of resource being added.
@type uri: str
@param name: Name to be assigned to the resource inside the pipeline. If not
specified, then an auto-generated name will be used.
@type name: str
@return: Name of the resource added.
@rtype: str
"""
inp_uri = urlparse.urlparse(uri)
if not inp_uri.scheme:
# This is a relative URI. Let us compute the full URI, in order to retrieve the resef.
server_uri = urlparse.urlparse(self._get_server_loc())
abs_res_uri = urlparse.urlunparse((server_uri[0], server_uri[1], inp_uri[2], inp_uri[3],
inp_uri[4], inp_uri[5]))
else:
abs_res_uri = uri
resdef = read_resdef(abs_res_uri, self.credentials)
if name is None:
name = self._autogen_name(resdef.get_component_name())
# The resource URI we save in the pipeline is exactly the one that was provided by the user.
self.add_resource(name, resdef, uri, resdef.guid, resdef.gen_id, self.credentials)
return name
def set_member_resource_ref(self, member_name, ref_name, ref_value):
"""
Set the resource reference of a member resource.
The reference value can be URI of a resource in the repository or instance name of a
resource in the same pipeline.
@param member_name: Name of resource whose resource reference is being set.
@type member_name: str
@param ref_name: Name of the reference being set.
@type ref_name: str
@param ref_value: New value of the reference. Can be None (in order to reset the value), or
absolute URI of a resource, or relative URI of the resource or instance name of a resource
in the same pipeline.
@type ref_value: str
"""
target_member = self.get_resource(member_name)
if ref_value is None:
# This is only a request to reset the reference value.
target_member._set_resource_ref(ref_name, None, None, True)
ref_resdef = self._fetch_resdef_if_uri(ref_value)
if ref_resdef is None:
# Must be a member of the pipeline.
ref_resdef = self.get_resource(ref_value)
target_member._set_resource_ref(ref_name, ref_value, ref_resdef.list_resource_categories(), True)
def get_member_resource_ref(self, member_name, ref_name):
target_member = self.get_resource(member_name)
entry = target_member.get_resource_ref_def(ref_name)
if keys.OVERRIDDEN_VALUE in entry:
return entry[keys.OVERRIDDEN_VALUE]
else:
return entry[keys.VALUE]
def _remove_all_resource_references(self, removed_name):
for res_name in self.list_resource_names():
member_res = self.get_resource(removed_name)
for ref_name in member_res.list_resource_ref_names():
if member_res.get_resource_ref(ref_name) == removed_name:
# Reset all references to this removed resource instance.
member_res.set_resource_ref(ref_name, None)
def diff_resource(self, member_name):
"""
Diff the specified member resource against the latest version of it in the repository.
@param member_name: Name of the member resource in the pipeline.
@type member_name: str
@return: Returns a dictionary containing the latest 'describe' level resdef of the specified
member resource and the diff dictionary.
@rtype: dict
"""
if member_name not in self.dict[keys.PIPELINE_RESOURCES]:
raise SnapResDefError("Pipeline does not have resource '%s'" % member_name)
member_uri = self.dict[keys.PIPELINE_RESOURCES][member_name][keys.URI]
parsed_uri = urlparse.urlparse(member_uri)
if not parsed_uri.scheme:
# Its a relative URI
server_uri = self._server_uri
else:
server_uri = urlparse.urlunparse((parsed_uri[0], parsed_uri[1], "", "", "", ""))
resp = snapi_base.diff_member_resource(server_uri, member_uri,
self.dict[keys.PIPELINE_RESOURCES][member_name][keys.RESDEF],
self.credentials)
if len(resp) != 2:
SnapResDefError("Unexpected response: %s" % resp)
return resp
|