def validate_cluster_update_request_and_check_cluster_upgrade( input_spec: rde_2_0_0.ClusterSpec, # noqa: E501 reference_spec: rde_2_0_0.ClusterSpec) -> bool: # noqa: E501 """Validate the desired spec with curr spec and check if upgrade operation. :param dict input_spec: input spec :param dict reference_spec: reference spec to validate the desired spec :return: true if cluster operation is upgrade and false if operation is resize :rtype: bool :raises: BadRequestError for invalid payload. """ exclude_fields = [] if reference_spec.topology.workers.count == 0: # Exclude worker nodes' sizing class and storage profile from # validation if worker count is 0 exclude_fields.append( FlattenedClusterSpecKey2X.WORKERS_SIZING_CLASS.value) # noqa: E501 exclude_fields.append(FlattenedClusterSpecKey2X. WORKERS_STORAGE_PROFILE.value) # noqa: E501 if reference_spec.topology.nfs.count == 0: # Exclude nfs nodes' sizing class and storage profile from validation # if nfs count is 0 exclude_fields.append(FlattenedClusterSpecKey2X.NFS_SIZING_CLASS.value) exclude_fields.append( FlattenedClusterSpecKey2X.NFS_STORAGE_PROFILE.value) # noqa: E501 input_spec_dict = input_spec.to_dict() reference_spec_dict = reference_spec.to_dict() diff_fields = \ rde_utils.find_diff_fields(input_spec_dict, reference_spec_dict, exclude_fields=exclude_fields) # noqa: E501 # Raise exception if empty diff if not diff_fields: raise BadRequestError( "No change in cluster specification") # noqa: E501 # Raise exception if fields which cannot be changed are updated keys_with_invalid_value = \ [k for k in diff_fields if k not in VALID_UPDATE_FIELDS_2X] if len(keys_with_invalid_value) > 0: err_msg = f"Invalid input values found in {sorted(keys_with_invalid_value)}" # noqa: E501 raise BadRequestError(err_msg) is_resize_operation = False if FlattenedClusterSpecKey2X.WORKERS_COUNT.value in diff_fields or \ FlattenedClusterSpecKey2X.NFS_COUNT.value in diff_fields: is_resize_operation = True is_upgrade_operation = False if FlattenedClusterSpecKey2X.TEMPLATE_NAME.value in diff_fields or \ FlattenedClusterSpecKey2X.TEMPLATE_REVISION.value in diff_fields: is_upgrade_operation = True # Raise exception if resize and upgrade are performed at the same time if is_resize_operation and is_upgrade_operation: err_msg = "Cannot resize and upgrade the cluster at the same time" raise BadRequestError(err_msg) return is_upgrade_operation
def validate_cluster_update_request_and_check_cluster_upgrade( input_spec: dict, reference_spec: dict) -> bool: # noqa: E501 """Validate the desired spec with curr spec and check if upgrade operation. :param dict input_spec: input spec :param dict reference_spec: reference spec to validate the desired spec :return: true if cluster operation is upgrade and false if operation is resize :rtype: bool :raises: BadRequestError for invalid payload. """ diff_fields = \ rde_utils.find_diff_fields(input_spec, reference_spec, exclude_fields=[]) # noqa: E501 # Raise exception if empty diff if not diff_fields: raise BadRequestError( "No change in cluster specification") # noqa: E501 # Raise exception if fields which cannot be changed are updated keys_with_invalid_value = [ k for k in diff_fields if k not in VALID_UPDATE_FIELDS ] # noqa: E501 if len(keys_with_invalid_value) > 0: err_msg = f"Invalid input values found in {sorted(keys_with_invalid_value)}" # noqa: E501 raise BadRequestError(err_msg) is_resize_operation = False if FlattenedClusterSpecKey.WORKERS_COUNT.value in diff_fields or \ FlattenedClusterSpecKey.NFS_COUNT.value in diff_fields: is_resize_operation = True is_upgrade_operation = False if FlattenedClusterSpecKey.TEMPLATE_NAME.value in diff_fields or \ FlattenedClusterSpecKey.TEMPLATE_REVISION.value in diff_fields: is_upgrade_operation = True # Raise exception if resize and upgrade are performed at the same time if is_resize_operation and is_upgrade_operation: err_msg = "Cannot resize and upgrade the cluster at the same time" raise BadRequestError(err_msg) return is_upgrade_operation
def validate_request_payload(input_spec: dict, reference_spec: dict, exclude_fields=None): """Validate the desired spec with the current spec. :param dict input_spec: input spec :param dict reference_spec: reference spec to validate the desired spec :param list exclude_fields: exclude the list of given flattened-keys from validation # noqa: E501 :return: true on successful validation :rtype: bool :raises: BadRequestError on encountering invalid payload value """ keys_with_invalid_value = rde_utils.find_diff_fields( input_spec, reference_spec, exclude_fields=exclude_fields) if len(keys_with_invalid_value) > 0: error_msg = f"Invalid input values found in {sorted(keys_with_invalid_value)}" # noqa: E501 raise BadRequestError(error_msg)
def validate_payload(payload, required_keys): """Validate a given payload is good for a particular request. Raise appropriate error if keys are missing or if the corresponding value in the payload is None. Otherwise return True. :param dict payload: :param list required_keys: :return: True, if payload is valid :rtype: bool """ valid = True minor_error_code = None error_message = "" required = set(required_keys) if not required.issubset(payload.keys()): missing_keys = list(required.difference(payload.keys())) error_message = \ f"Missing required keys in request payload: {missing_keys}" valid = False key = RequestKey(missing_keys[0]) if key in MISSING_KEY_TO_MINOR_ERROR_CODE_MAPPING: minor_error_code = MISSING_KEY_TO_MINOR_ERROR_CODE_MAPPING[key] else: keys_with_none_value = [ k for k, v in payload.items() if k in required and v is None ] # noqa: E501 if len(keys_with_none_value) > 0: error_message = f"Following keys in request payloads have None as value: {keys_with_none_value}" # noqa: E501 valid = False key = RequestKey(keys_with_none_value[0]) if key in INVALID_VALUE_TO_MINOR_ERROR_CODE_MAPPING: minor_error_code = INVALID_VALUE_TO_MINOR_ERROR_CODE_MAPPING[ key] # noqa: E501 if not valid: raise BadRequestError(error_message, minor_error_code) return valid
def validate_request_payload(input_spec: dict, reference_spec: dict, exclude_fields=None): """Validate the desired spec with the current spec. :param dict input_spec: input spec :param dict reference_spec: reference spec to validate the desired spec :param list exclude_fields: exclude the list of given flattened-keys from validation # noqa: E501 :raises: BadRequestError on encountering invalid payload value """ keys_with_invalid_value = rde_utils.find_diff_fields( input_spec, reference_spec, exclude_fields=exclude_fields) if len(keys_with_invalid_value) > 0: err_msg = "Change detected in immutable field(s) [" for k in sorted(keys_with_invalid_value): err_msg += \ f"`{k}` found : {keys_with_invalid_value[k]['actual']} " \ f"expected : {keys_with_invalid_value[k]['expected']}, " err_msg += "]." raise BadRequestError(err_msg)
def validate(self, cloudapi_client: CloudApiClient, entity_id: str = None, entity: dict = None, operation: BehaviorOperation = None) -> bool: # noqa: E501 """Validate the input request. This method performs 1. Basic validation of the entity by simply casting the input entity dict to the model class dictated by the api_version specified in the request. This is usually performed for the "create" operation. 2. Operation (create, update, delete) specific validation. - create: "entity" is the only required parameter. - update: both "entity" and "entity_id" are required parameters. - delete: "entity_id" is the only required parameter. - kubeconfig: "entity_id" is the only required parameter. :param cloudapi_client: cloud api client :param dict entity: dict form of the native entity to be validated :param entity_id: entity id to be validated :param BehaviorOperation operation: CSE operation key :return: is validation successful or failure :rtype: bool """ if not entity_id and not entity: raise ValueError( 'Either entity_id or entity is required to validate.' ) # noqa: E501 entity_svc = DefEntityService(cloudapi_client=cloudapi_client) api_version: str = cloudapi_client.get_api_version() rde_version_introduced_at_api_version: str = rde_utils.get_rde_version_introduced_at_api_version( api_version) # noqa: E501 # TODO Reject the request if payload_version does not match with # either rde_in_use (or) rde_version_introduced_at_api_version # Cast the entity to the model class based on the user-specified # api_version. This can be considered as a basic request validation. # Any operation specific validation is handled further down NativeEntityClass: AbstractNativeEntity = rde_factory. \ get_rde_model(rde_version_introduced_at_api_version) input_entity = None if entity: try: input_entity: AbstractNativeEntity = NativeEntityClass.from_dict( entity) # noqa: E501 except Exception as err: msg = f"Failed to parse request body: {err}" raise BadRequestError(msg) # Return True if the operation is not specified. if not operation: return True # TODO: validators for rest of the CSE operations in V36 will be # implemented as and when v36/def_cluster_handler.py get other handler # functions if operation == BehaviorOperation.UPDATE_CLUSTER: if not entity_id or not entity: raise ValueError( 'Both entity_id and entity are required to validate the Update operation.' ) # noqa: E501 current_entity: AbstractNativeEntity = entity_svc.get_entity( entity_id).entity # noqa: E501 input_entity_spec: rde_2_0_0.ClusterSpec = input_entity.spec current_entity_status: rde_2_0_0.Status = current_entity.status current_entity_spec = \ rde_utils.construct_cluster_spec_from_entity_status( current_entity_status, rde_constants.RDEVersion.RDE_2_0_0.value) # noqa: E501 return validate_cluster_update_request_and_check_cluster_upgrade( input_entity_spec, current_entity_spec) # TODO check the reason why there was an unreachable raise statement raise NotImplementedError(f"Validator for {operation.name} not found")
def validate( self, cloudapi_client: CloudApiClient, sysadmin_client: Client, entity_id: str = None, entity: dict = None, operation: BehaviorOperation = BehaviorOperation.CREATE_CLUSTER, **kwargs) -> bool: """Validate the input request. This method performs 1. Basic validation of the entity by simply casting the input entity dict to the model class dictated by the api_version specified in the request. This is usually performed for the "create" operation. 2. Operation (create, update, delete) specific validation. - create: "entity" is the only required parameter. - update: both "entity" and "entity_id" are required parameters. - delete: "entity_id" is the only required parameter. - kubeconfig: "entity_id" is the only required parameter. :param cloudapi_client: cloud api client :param sysadmin_client: :param dict entity: dict form of the native entity to be validated :param entity_id: entity id to be validated :param BehaviorOperation operation: CSE operation key :return: is validation successful or failure :rtype: bool """ is_tkgm_cluster = kwargs.get('is_tkgm_cluster', False) if not entity_id and not entity: raise ValueError( 'Either entity_id or entity is required to validate.' ) # noqa: E501 entity_svc = DefEntityService(cloudapi_client=cloudapi_client) api_version: str = cloudapi_client.get_api_version() rde_version_introduced_at_api_version: str = rde_utils.get_rde_version_introduced_at_api_version( api_version) # noqa: E501 # TODO Reject the request if payload_version does not match with # either rde_in_use (or) rde_version_introduced_at_api_version # Cast the entity to the model class based on the user-specified # api_version. This can be considered as a basic request validation. # Any operation specific validation is handled further down native_entity_class: AbstractNativeEntity = rde_factory. \ get_rde_model(rde_version_introduced_at_api_version) input_entity = None if entity: try: input_entity: AbstractNativeEntity = native_entity_class.from_dict( entity) # noqa: E501 except Exception as err: msg = f"Failed to parse request body: {err}" raise BadRequestError(msg) # Need to ensure that sizing class along with cpu/memory is not # present in the request if isinstance(input_entity, rde_2_1_0.NativeEntity): # cpu and mem are properties of only rde 2.0.0 bad_request_msg = "" if input_entity.spec.topology.workers.sizing_class and \ (input_entity.spec.topology.workers.cpu or input_entity.spec.topology.workers.memory): # noqa: E501 bad_request_msg = "Cannot specify both sizing class and cpu/memory for Workers nodes." # noqa: E501 if input_entity.spec.topology.control_plane.sizing_class and ( input_entity.spec.topology.control_plane.cpu or input_entity.spec.topology.control_plane.memory ): # noqa: E501 bad_request_msg = "Cannot specify both sizing class and cpu/memory for Control Plane nodes." # noqa: E501 if bad_request_msg: raise BadRequestError(bad_request_msg) # Return True if the operation is not specified. if operation == BehaviorOperation.CREATE_CLUSTER: return True # TODO: validators for rest of the CSE operations in V36 will be # implemented as and when v36/def_cluster_handler.py get other handler # functions if operation == BehaviorOperation.UPDATE_CLUSTER: if not entity_id or not entity: raise ValueError( 'Both entity_id and entity are required to validate the Update operation.' ) # noqa: E501 current_entity: AbstractNativeEntity = entity_svc.get_entity( entity_id).entity # noqa: E501 input_entity_spec: rde_2_1_0.ClusterSpec = input_entity.spec current_entity_status: rde_2_1_0.Status = current_entity.status is_tkgm_with_default_sizing_in_control_plane = False is_tkgm_with_default_sizing_in_workers = False if is_tkgm_cluster: # NOTE: Since for TKGm cluster, if cluster is created without # a sizing class, default sizing class is assigned by VCD, # If we find the default sizing policy in the status section, # validate cpu/memory and sizing policy. # Also note that at this point in code, we are sure that only # one of sizing class or cpu/mem will be associated with # control plane and workers. vdc: VDC = get_vdc( sysadmin_client, vdc_name=current_entity_status.cloud_properties. virtual_data_center_name, # noqa: E501 org_name=current_entity_status.cloud_properties.org_name) vdc_resource = vdc.get_resource_admin() default_cp_name = vdc_resource.DefaultComputePolicy.get('name') control_plane_sizing_class = current_entity_status.nodes.control_plane.sizing_class # noqa: E501 is_tkgm_with_default_sizing_in_control_plane = \ (control_plane_sizing_class == default_cp_name) is_tkgm_with_default_sizing_in_workers = \ (len(current_entity_status.nodes.workers) > 0 and current_entity_status.nodes.workers[0].sizing_class == default_cp_name) # noqa: E501 current_entity_spec = \ rde_utils.construct_cluster_spec_from_entity_status( current_entity_status, rde_constants.RDEVersion.RDE_2_1_0.value, is_tkgm_with_default_sizing_in_control_plane=is_tkgm_with_default_sizing_in_control_plane, # noqa: E501 is_tkgm_with_default_sizing_in_workers=is_tkgm_with_default_sizing_in_workers) # noqa: E501 return validate_cluster_update_request_and_check_cluster_upgrade( input_entity_spec, current_entity_spec, is_tkgm_cluster) # TODO check the reason why there was an unreachable raise statement raise NotImplementedError(f"Validator for {operation.name} not found")
def validate_cluster_update_request_and_check_cluster_upgrade( input_spec: rde_2_1_0.ClusterSpec, reference_spec: rde_2_1_0.ClusterSpec, is_tkgm_cluster: bool, ) -> bool: """ Validate the desired spec with curr spec and check if upgrade operation. :param dict input_spec: input spec :param dict reference_spec: reference spec to validate the desired spec :param bool is_tkgm_cluster: True implies that this is a TKGm cluster :return: true if cluster operation is upgrade and false if operation is resize :rtype: bool :raises: BadRequestError for invalid payload. """ # Since these fields are only in the spec section, and since we create # the reference_spec section from a status section, we will always have # null for the POD and SVC CIDRs. Hence we cannot validate it until there # is a better way to get the spec details. exclude_fields = [ FlattenedClusterSpecKey2X.POD_CIDR.value, FlattenedClusterSpecKey2X.SVC_CIDR.value, ] if reference_spec.topology.workers.count == 0: # Exclude worker nodes' sizing class and storage profile from # validation if worker count is 0 exclude_fields.append( FlattenedClusterSpecKey2X.WORKERS_SIZING_CLASS.value) # noqa: E501 exclude_fields.append(FlattenedClusterSpecKey2X. WORKERS_STORAGE_PROFILE.value) # noqa: E501 exclude_fields.append( FlattenedClusterSpecKey2X.WORKERS_CPU_COUNT.value) # noqa: E501 exclude_fields.append( FlattenedClusterSpecKey2X.WORKERS_MEMORY_MB.value) # noqa: E501 if reference_spec.topology.nfs.count == 0: # Exclude nfs nodes' sizing class and storage profile from validation # if nfs count is 0 exclude_fields.append(FlattenedClusterSpecKey2X.NFS_SIZING_CLASS.value) exclude_fields.append( FlattenedClusterSpecKey2X.NFS_STORAGE_PROFILE.value) # noqa: E501 # Allow empty template revisions (== 0) if the value is the default # for TKGm clusters only if is_tkgm_cluster: if (reference_spec.distribution.template_revision == 1 and input_spec.distribution.template_revision == 0 # default value ): exclude_fields.append( FlattenedClusterSpecKey2X.TEMPLATE_REVISION.value) input_spec_dict = input_spec.to_dict() reference_spec_dict = reference_spec.to_dict() diff_fields = rde_utils.find_diff_fields(input_spec_dict, reference_spec_dict, exclude_fields=exclude_fields) is_upgrade_operation = False if not diff_fields: return is_upgrade_operation keys_with_invalid_value = {} for k, v in diff_fields.items(): if k not in VALID_UPDATE_FIELDS_2X: keys_with_invalid_value[k] = v # Raise exception if fields which cannot be changed are updated if len(keys_with_invalid_value) > 0: err_msg = "Change detected in immutable field(s) [" for k in sorted(keys_with_invalid_value): err_msg += \ f"{k} found : {keys_with_invalid_value[k]['actual']} " \ f"expected : {keys_with_invalid_value[k]['expected']}, " err_msg += "]." raise BadRequestError(err_msg) is_resize_operation = False if FlattenedClusterSpecKey2X.WORKERS_COUNT.value in diff_fields or \ FlattenedClusterSpecKey2X.NFS_COUNT.value in diff_fields: is_resize_operation = True if FlattenedClusterSpecKey2X.TEMPLATE_NAME.value in diff_fields or \ FlattenedClusterSpecKey2X.TEMPLATE_REVISION.value in diff_fields: is_upgrade_operation = True # Raise exception if resize and upgrade are performed at the same time if is_resize_operation and is_upgrade_operation: err_msg = "Cannot resize and upgrade the cluster at the same time" raise BadRequestError(err_msg) return is_upgrade_operation