def __set_obj_attribute(self, obj, property_path, param_value, param_name): """ Recursively set object properties :param obj: The object on which to set a property value. :param property_path: A list of property names in the form of strings. :param param_value: The value to set. :return: The original object. """ while len(property_path) > 0: raw_prop_name = property_path.pop(0) prop_name = PYTHON_KEYWORD_MAPPING.get(raw_prop_name, raw_prop_name) prop_kind = obj.swagger_types[prop_name] if prop_kind in PRIMITIVES: try: setattr(obj, prop_name, param_value) except ValueError as exc: msg = str(exc) if param_value is None and 'None' in msg: pass else: raise KubernetesException( "Error setting {0} to {1}: {2}".format( prop_name, param_value, msg)) elif prop_kind.startswith('dict('): if not getattr(obj, prop_name): setattr(obj, prop_name, param_value) else: self.__compare_dict(getattr(obj, prop_name), param_value, param_name) elif prop_kind.startswith('list['): if getattr(obj, prop_name) is None: setattr(obj, prop_name, []) obj_type = prop_kind.replace('list[', '').replace(']', '') if obj_type not in PRIMITIVES and obj_type not in ('list', 'dict'): self.__compare_obj_list(getattr(obj, prop_name), param_value, obj_type, param_name) else: self.__compare_list(getattr(obj, prop_name), param_value, param_name) else: # prop_kind is an object class sub_obj = getattr(obj, prop_name) if not sub_obj: sub_obj = self.model_class_from_name(prop_kind)() setattr( obj, prop_name, self.__set_obj_attribute(sub_obj, property_path, param_value, param_name)) return obj
def __compare_list(self, src_values, request_values, param_name): """ Compare src_values list with request_values list, and append any missing request_values to src_values. """ if not request_values: return if not src_values: src_values += request_values if type(src_values[0]).__name__ in PRIMITIVES: if set(src_values) >= set(request_values): # src_value list includes request_value list return # append the missing elements from request value src_values += list(set(request_values) - set(src_values)) elif type(src_values[0]).__name__ == 'dict': missing = [] for request_dict in request_values: match = False for src_dict in src_values: if '__cmp__' in dir(src_dict): # python < 3 if src_dict >= request_dict: match = True break elif iteritems(src_dict) == iteritems(request_dict): # python >= 3 match = True break if not match: missing.append(request_dict) src_values += missing elif type(src_values[0]).__name__ == 'list': missing = [] for request_list in request_values: match = False for src_list in src_values: if set(request_list) >= set(src_list): match = True break if not match: missing.append(request_list) src_values += missing else: raise KubernetesException( "Evaluating {0}: encountered unimplemented type {1} in " "__compare_list()".format(param_name, type(src_values[0]).__name__))
def test_stream_creation(self, mock_watch): _name = 'test-vmirs' # Desired state: args = dict(name=_name, namespace='vms', replicas=2, wait=True) set_module_args(args) # Mock pre-change state: resource_args = dict( kind='VirtualMachineInstanceReplicaSet', **RESOURCE_DEFAULT_ARGS ) K8sAnsibleMixin.find_resource.return_value = Resource(**resource_args) res_inst = ResourceInstance('', dict(metadata = {'name': _name}, spec = {'replicas': 3})) Resource.get.return_value = res_inst # Actual test: mock_watch.side_effect = KubernetesException("Test", value=42) with pytest.raises(AnsibleFailJson) as result: mymodule.KubeVirtScaleVMIRS().execute_module()
def find_arg_spec(self, module_param_name): """For testing, allow the param_name value to be an alias""" if module_param_name in self.argspec: return self.argspec[module_param_name] result = None for key, value in iteritems(self.argspec): if value.get('aliases'): for alias in value['aliases']: if alias == module_param_name: result = self.argspec[key] break if result: break if not result: raise KubernetesException( "Error: received unrecognized module parameter {0}".format( module_param_name)) return result
def __compare_dict(self, src_value, request_value, param_name): """ Compare src_value dict with request_value dict, and update src_value with any differences. Does not remove items from src_value dict. """ if not request_value: return for item, value in iteritems(request_value): if type(value).__name__ in ('str', 'int', 'bool'): src_value[item] = value elif type(value).__name__ == 'list': self.__compare_list(src_value[item], value, param_name) elif type(value).__name__ == 'dict': self.__compare_dict(src_value[item], value, param_name) else: raise KubernetesException( "Evaluating {0}: encountered unimplemented type {1} in " "__compare_dict()".format(param_name, type(value).__name__))
def test_stream_creation(self, mock_watch): _kind = 'PersistentVolumeClaim' # Desired state: args = dict(state='present', kind=_kind, wait=True, name='testvmi', namespace='vms', api_version='v1') set_module_args(args) # Mock pre-change state: Resource.get.return_value = None # Resource does NOT initially exist in cluster resource_args = dict(kind=_kind, **RESOURCE_DEFAULT_ARGS) K8sAnsibleMixin.find_resource.return_value = Resource(**resource_args) # Actual test: mock_watch.side_effect = KubernetesException("Test", value=42) with pytest.raises(AnsibleFailJson) as result: mymodule.KubeVirtVM().execute_module()
def __update_object_properties(self, obj, item): """ Recursively update an object's properties. Returns a pointer to the object. """ for key, value in iteritems(item): snake_key = self.attribute_to_snake(key) try: kind = obj.swagger_types[snake_key] except (AttributeError, KeyError): possible_matches = ', '.join(list(obj.swagger_types.keys())) class_snake_name = self.get_base_model_name_snake( type(obj).__name__) raise KubernetesException( "Unable to find '{0}' in {1}. Valid property names include: {2}" .format(snake_key, class_snake_name, possible_matches)) if kind in PRIMITIVES or kind.startswith( 'list[') or kind.startswith('dict('): self.__set_obj_attribute(obj, [snake_key], value, snake_key) else: # kind is an object, hopefully if not getattr(obj, snake_key): setattr(obj, snake_key, self.model_class_from_name(kind)()) self.__update_object_properties(getattr(obj, snake_key), value) return obj
def __compare_obj_list(self, src_value, request_value, obj_class, param_name): """ Compare a src_value (list of ojects) with a request_value (list of dicts), and update src_value with differences. Assumes each object and each dict has a 'name' attributes, which can be used for matching. Elements are not removed from the src_value list. """ if not request_value: return sample_obj = self.model_class_from_name(obj_class)() # Try to determine the unique key for the array key_names = ['name', 'type'] key_name = None for key in key_names: if hasattr(sample_obj, key): key_name = key break if key_name: # If the key doesn't exist in the request values, then ignore it, rather than throwing an error for item in request_value: if not item.get(key_name): key_name = None break if key_name: # compare by key field for item in request_value: if not item.get(key_name): # Prevent user from creating something that will be impossible to patch or update later raise KubernetesException( "Evaluating {0} - expecting parameter {1} to contain a `{2}` attribute " "in __compare_obj_list().".format( param_name, self.get_base_model_name_snake(obj_class), key_name)) found = False for obj in src_value: if not obj: continue if getattr(obj, key_name) == item[key_name]: # Assuming both the src_value and the request value include a name property found = True for key, value in iteritems(item): snake_key = self.attribute_to_snake(key) item_kind = sample_obj.swagger_types.get(snake_key) if item_kind and item_kind in PRIMITIVES or type( value).__name__ in PRIMITIVES: setattr(obj, snake_key, value) elif item_kind and item_kind.startswith('list['): obj_type = item_kind.replace('list[', '').replace( ']', '') if getattr(obj, snake_key) is None: setattr(obj, snake_key, []) if obj_type not in ('str', 'int', 'bool'): self.__compare_obj_list( getattr(obj, snake_key), value, obj_type, param_name) else: # Straight list comparison self.__compare_list( getattr(obj, snake_key), value, param_name) elif item_kind and item_kind.startswith('dict('): self.__compare_dict(getattr(obj, snake_key), value, param_name) elif item_kind and type(value).__name__ == 'dict': # object param_obj = getattr(obj, snake_key) if not param_obj: setattr( obj, snake_key, self.model_class_from_name(item_kind) ()) param_obj = getattr(obj, snake_key) self.__update_object_properties( param_obj, value) else: if item_kind: raise KubernetesException( "Evaluating {0}: encountered unimplemented type {1} in " "__compare_obj_list() for model {2}". format( param_name, item_kind, self.get_base_model_name_snake( obj_class))) else: raise KubernetesException( "Evaluating {0}: unable to get swagger_type for {1} in " "__compare_obj_list() for item {2} in model {3}" .format( param_name, snake_key, str(item), self.get_base_model_name_snake( obj_class))) if not found: # Requested item not found. Adding. obj = self.__update_object_properties( self.model_class_from_name(obj_class)(), item) src_value.append(obj) else: # There isn't a key, or we don't know what it is, so check for all properties to match for item in request_value: found = False for obj in src_value: match = True for item_key, item_value in iteritems(item): # TODO: this should probably take the property type into account snake_key = self.attribute_to_snake(item_key) if getattr(obj, snake_key) != item_value: match = False break if match: found = True break if not found: obj = self.__update_object_properties( self.model_class_from_name(obj_class)(), item) src_value.append(obj)