# Copyright (C) 2021 Nippon Telegraph and Telephone Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from datetime import datetime from oslo_log import log as logging from oslo_utils import uuidutils from tacker.sol_refactored.api import api_version from tacker.sol_refactored.api.schemas import vnflcm_v2 as schema from tacker.sol_refactored.api import validator from tacker.sol_refactored.api import wsgi as sol_wsgi from tacker.sol_refactored.common import config from tacker.sol_refactored.common import coordinate from tacker.sol_refactored.common import exceptions as sol_ex from tacker.sol_refactored.common import lcm_op_occ_utils as lcmocc_utils from tacker.sol_refactored.common import subscription_utils as subsc_utils from tacker.sol_refactored.common import vim_utils from tacker.sol_refactored.common import vnf_instance_utils as inst_utils from tacker.sol_refactored.conductor import conductor_rpc_v2 from tacker.sol_refactored.controller import vnflcm_view from tacker.sol_refactored.nfvo import nfvo_client from tacker.sol_refactored import objects from tacker.sol_refactored.objects.v2 import fields as v2fields LOG = logging.getLogger(__name__) # NOTE: unused at the moment CONF = config.CONF class VnfLcmControllerV2(sol_wsgi.SolAPIController): def __init__(self): self.nfvo_client = nfvo_client.NfvoClient() self.endpoint = CONF.v2_vnfm.endpoint self.conductor_rpc = conductor_rpc_v2.VnfLcmRpcApiV2() self._inst_view = vnflcm_view.InstanceViewBuilder(self.endpoint) self._lcmocc_view = vnflcm_view.LcmOpOccViewBuilder(self.endpoint) self._subsc_view = vnflcm_view.SubscriptionViewBuilder(self.endpoint) def api_versions(self, request): return sol_wsgi.SolResponse(200, api_version.supported_versions_v2) @validator.schema(schema.CreateVnfRequest_V200, '2.0.0') def create(self, request, body): context = request.context vnfd_id = body['vnfdId'] pkg_info = self.nfvo_client.get_vnf_package_info_vnfd( context, vnfd_id) if pkg_info.operationalState != "ENABLED": raise sol_ex.VnfdIdNotEnabled(vnfd_id=vnfd_id) vnfd = self.nfvo_client.get_vnfd(context, vnfd_id) vnfd_prop = vnfd.get_vnfd_properties() metadata = vnfd_prop['metadata'] if 'metadata' in body: metadata = inst_utils.json_merge_patch(metadata, body['metadata']) inst = objects.VnfInstanceV2( # required fields id=uuidutils.generate_uuid(), vnfdId=vnfd_id, vnfProvider=pkg_info.vnfProvider, vnfProductName=pkg_info.vnfProductName, vnfSoftwareVersion=pkg_info.vnfSoftwareVersion, vnfdVersion=pkg_info.vnfdVersion, instantiationState='NOT_INSTANTIATED', # not set at the moment. will be set when instantiate. # vimConnectionInfo # instantiatedVnfInfo ) # set optional fields if body.get('vnfInstanceName'): inst.vnfInstanceName = body['vnfInstanceName'] if body.get('vnfInstanceDescription'): inst.vnfInstanceDescription = body['vnfInstanceDescription'] if vnfd_prop.get('vnfConfigurableProperties'): inst.vnfConfigurableProperties = vnfd_prop[ 'vnfConfigurableProperties'] if metadata: inst.metadata = metadata if vnfd_prop.get('extensions'): inst.extensions = vnfd_prop['extensions'] inst.create(context) self.nfvo_client.send_inst_create_notification(context, inst, self.endpoint) resp_body = self._inst_view.detail(inst) location = inst_utils.inst_href(inst.id, self.endpoint) return sol_wsgi.SolResponse(201, resp_body, location=location) def index(self, request): filter_param = request.GET.get('filter') if filter_param is not None: filters = self._inst_view.parse_filter(filter_param) else: filters = None # validate_filter selector = self._inst_view.parse_selector(request.GET) pager = self._inst_view.parse_pager(request) insts = inst_utils.get_inst_all(request.context, marker=pager.marker) resp_body = self._inst_view.detail_list(insts, filters, selector, pager) return sol_wsgi.SolResponse(200, resp_body, link=pager.get_link()) def show(self, request, id): inst = inst_utils.get_inst(request.context, id) resp_body = self._inst_view.detail(inst) return sol_wsgi.SolResponse(200, resp_body) @coordinate.lock_vnf_instance('{id}') def delete(self, request, id): context = request.context inst = inst_utils.get_inst(context, id) if inst.instantiationState != 'NOT_INSTANTIATED': raise sol_ex.VnfInstanceIsInstantiated(inst_id=id) lcmocc_utils.check_lcmocc_in_progress(context, id) inst.delete(context) # NOTE: inst record in DB deleted but inst object still # can be accessed. self.nfvo_client.send_inst_delete_notification(context, inst, self.endpoint) return sol_wsgi.SolResponse(204, None) def _new_lcmocc(self, inst_id, operation, req_body, op_state=v2fields.LcmOperationStateType.STARTING): now = datetime.utcnow() lcmocc = objects.VnfLcmOpOccV2( id=uuidutils.generate_uuid(), operationState=op_state, stateEnteredTime=now, startTime=now, vnfInstanceId=inst_id, operation=operation, isAutomaticInvocation=False, isCancelPending=False, operationParams=req_body) return lcmocc @validator.schema(schema.VnfInfoModificationRequest_V200, '2.0.0') @coordinate.lock_vnf_instance('{id}') def update(self, request, id, body): context = request.context inst = inst_utils.get_inst(context, id) lcmocc_utils.check_lcmocc_in_progress(context, id) if (inst.instantiationState == 'NOT_INSTANTIATED' and 'vimConnectionInfo' in body): msg = 'vimConnectionInfo cannot be modified in NOT_INSTANTIATED.' raise sol_ex.SolValidationError(detail=msg) # check vnf package operational state if 'vnfdId' in body and body['vnfdId'] != inst.vnfdId: req_vnfd_id = body['vnfdId'] pkg_info = self.nfvo_client.get_vnf_package_info_vnfd( context, req_vnfd_id) if pkg_info.operationalState != "ENABLED": # NOTE: Response code 422 is not specified in SOL003, # but 422 will be returned here. This is because # instance create returns 422 for the same reason. # In addition, SOL013 defines 422 as common error situations. raise sol_ex.VnfdIdNotEnabled(vnfd_id=req_vnfd_id) if 'vnfcInfoModifications' in body: vnfc_id = [] if inst.obj_attr_is_set('instantiatedVnfInfo'): inst_info = inst.instantiatedVnfInfo if inst_info.obj_attr_is_set('vnfcInfo'): vnfc_id = [vnfc.id for vnfc in inst_info.vnfcInfo] for vnfc_mod in body['vnfcInfoModifications']: if vnfc_mod['id'] not in vnfc_id: raise sol_ex.SolValidationError( detail="vnfcInstanceId(%s) does not exist." % vnfc_mod['id']) lcmocc = self._new_lcmocc(id, v2fields.LcmOperationType.MODIFY_INFO, body, v2fields.LcmOperationStateType.PROCESSING) lcmocc.create(context) self.conductor_rpc.modify_vnfinfo(context, lcmocc.id) location = lcmocc_utils.lcmocc_href(lcmocc.id, self.endpoint) return sol_wsgi.SolResponse(202, None, location=location) @validator.schema(schema.InstantiateVnfRequest_V200, '2.0.0') @coordinate.lock_vnf_instance('{id}') def instantiate(self, request, id, body): context = request.context inst = inst_utils.get_inst(context, id) if inst.instantiationState != 'NOT_INSTANTIATED': raise sol_ex.VnfInstanceIsInstantiated(inst_id=id) lcmocc_utils.check_lcmocc_in_progress(context, id) lcmocc = self._new_lcmocc(id, v2fields.LcmOperationType.INSTANTIATE, body) req_param = lcmocc.operationParams # if there is partial vimConnectionInfo check and fulfill here. if req_param.obj_attr_is_set('vimConnectionInfo'): # if accessInfo or interfaceInfo is not specified, get from # VIM DB. vimId must be in VIM DB. req_vim_infos = req_param.vimConnectionInfo for key, req_vim_info in req_vim_infos.items(): if not (req_vim_info.obj_attr_is_set('accessInfo') and req_vim_info.obj_attr_is_set('interfaceInfo')): vim_info = vim_utils.get_vim(context, req_vim_info.vimId) req_vim_infos[key] = vim_info lcmocc.create(context) self.conductor_rpc.start_lcm_op(context, lcmocc.id) location = lcmocc_utils.lcmocc_href(lcmocc.id, self.endpoint) return sol_wsgi.SolResponse(202, None, location=location) @validator.schema(schema.TerminateVnfRequest_V200, '2.0.0') @coordinate.lock_vnf_instance('{id}') def terminate(self, request, id, body): context = request.context inst = inst_utils.get_inst(context, id) if inst.instantiationState != 'INSTANTIATED': raise sol_ex.VnfInstanceIsNotInstantiated(inst_id=id) lcmocc_utils.check_lcmocc_in_progress(context, id) lcmocc = self._new_lcmocc(id, v2fields.LcmOperationType.TERMINATE, body) lcmocc.create(context) self.conductor_rpc.start_lcm_op(context, lcmocc.id) location = lcmocc_utils.lcmocc_href(lcmocc.id, self.endpoint) return sol_wsgi.SolResponse(202, None, location=location) def _get_current_scale_level(self, inst, aspect_id): if (inst.obj_attr_is_set('instantiatedVnfInfo') and inst.instantiatedVnfInfo.obj_attr_is_set('scaleStatus')): for scale_info in inst.instantiatedVnfInfo.scaleStatus: if scale_info.aspectId == aspect_id: return scale_info.scaleLevel def _get_max_scale_level(self, inst, aspect_id): if (inst.obj_attr_is_set('instantiatedVnfInfo') and inst.instantiatedVnfInfo.obj_attr_is_set('maxScaleLevels')): for scale_info in inst.instantiatedVnfInfo.maxScaleLevels: if scale_info.aspectId == aspect_id: return scale_info.scaleLevel @validator.schema(schema.ScaleVnfRequest_V200, '2.0.0') @coordinate.lock_vnf_instance('{id}') def scale(self, request, id, body): context = request.context inst = inst_utils.get_inst(context, id) if inst.instantiationState != 'INSTANTIATED': raise sol_ex.VnfInstanceIsNotInstantiated(inst_id=id) lcmocc_utils.check_lcmocc_in_progress(context, id) # check parameters aspect_id = body['aspectId'] if 'numberOfSteps' not in body: # set default value (1) defined by SOL specification for # the convenience of the following methods. body['numberOfSteps'] = 1 scale_level = self._get_current_scale_level(inst, aspect_id) max_scale_level = self._get_max_scale_level(inst, aspect_id) if scale_level is None or max_scale_level is None: raise sol_ex.InvalidScaleAspectId(aspect_id=aspect_id) num_steps = body['numberOfSteps'] if body['type'] == 'SCALE_IN': num_steps *= -1 scale_level += num_steps if scale_level < 0 or scale_level > max_scale_level: raise sol_ex.InvalidScaleNumberOfSteps( num_steps=body['numberOfSteps']) lcmocc = self._new_lcmocc(id, v2fields.LcmOperationType.SCALE, body) lcmocc.create(context) self.conductor_rpc.start_lcm_op(context, lcmocc.id) location = lcmocc_utils.lcmocc_href(lcmocc.id, self.endpoint) return sol_wsgi.SolResponse(202, None, location=location) @validator.schema(schema.HealVnfRequest_V200, '2.0.0') @coordinate.lock_vnf_instance('{id}') def heal(self, request, id, body): context = request.context inst = inst_utils.get_inst(context, id) if inst.instantiationState != 'INSTANTIATED': raise sol_ex.VnfInstanceIsNotInstantiated(inst_id=id) lcmocc_utils.check_lcmocc_in_progress(context, id) # check parameter for later use is_all = body.get('additionalParams', {}).get('all', False) if not isinstance(is_all, bool): raise sol_ex.SolValidationError( detail="additionalParams['all'] must be bool.") if 'vnfcInstanceId' in body: inst_info = inst.instantiatedVnfInfo vnfc_id = [] if inst_info.obj_attr_is_set('vnfcInfo'): vnfc_id = [vnfc.id for vnfc in inst_info.vnfcInfo] for req_vnfc_id in body['vnfcInstanceId']: if req_vnfc_id not in vnfc_id: raise sol_ex.SolValidationError( detail="vnfcInstanceId(%s) does not exist." % req_vnfc_id) lcmocc = self._new_lcmocc(id, v2fields.LcmOperationType.HEAL, body) lcmocc.create(context) self.conductor_rpc.start_lcm_op(context, lcmocc.id) location = lcmocc_utils.lcmocc_href(lcmocc.id, self.endpoint) return sol_wsgi.SolResponse(202, None, location=location) @validator.schema(schema.ChangeExtVnfConnectivityRequest_V200, '2.0.0') @coordinate.lock_vnf_instance('{id}') def change_ext_conn(self, request, id, body): context = request.context inst = inst_utils.get_inst(context, id) if inst.instantiationState != 'INSTANTIATED': raise sol_ex.VnfInstanceIsNotInstantiated(inst_id=id) lcmocc_utils.check_lcmocc_in_progress(context, id) lcmocc = self._new_lcmocc( id, v2fields.LcmOperationType.CHANGE_EXT_CONN, body) lcmocc.create(context) self.conductor_rpc.start_lcm_op(context, lcmocc.id) location = lcmocc_utils.lcmocc_href(lcmocc.id, self.endpoint) return sol_wsgi.SolResponse(202, None, location=location) def _check_vdu_params(self, inst, vdu_params, vim_type, new_script, old_script): inst_vdu_ids = [] if inst.instantiatedVnfInfo.obj_attr_is_set('vnfcResourceInfo'): inst_vdu_ids = [inst_vnfc.vduId for inst_vnfc in inst.instantiatedVnfInfo.vnfcResourceInfo] for vdu_param in vdu_params: if 'vdu_id' not in vdu_param: raise sol_ex.SolValidationError( detail="If you set vdu_params in additionalParams, you" "must set vdu_id for each element.") if vdu_param['vdu_id'] not in inst_vdu_ids: raise sol_ex.SolValidationError( detail="vdu_id '%s' does not exist." % vdu_param['vdu_id']) def _check_vnfc_param(attr): for key in ['cp_name', 'username', 'password']: if key not in vdu_param[attr]: raise sol_ex.SolValidationError( detail=f"If you set {attr} in " f"vdu_param, you must set" f" 'cp_name', 'username' and" f" 'password'") if vim_type == 'ETSINFV.OPENSTACK_KEYSTONE.V_3' and new_script: if 'new_vnfc_param' not in vdu_param: raise sol_ex.SolValidationError( detail="'new_vnfc_param' must exist in vdu_param") _check_vnfc_param('new_vnfc_param') if vim_type == 'ETSINFV.OPENSTACK_KEYSTONE.V_3' and old_script: if 'old_vnfc_param' not in vdu_param: raise sol_ex.SolValidationError( detail="'old_vnfc_param' must exist in vdu_param") _check_vnfc_param('old_vnfc_param') @validator.schema(schema.ChangeCurrentVnfPkgRequest_V200, '2.0.0') @coordinate.lock_vnf_instance('{id}') def change_vnfpkg(self, request, id, body): context = request.context inst = inst_utils.get_inst(context, id) vnfd_id = body['vnfdId'] if inst.instantiationState != 'INSTANTIATED': raise sol_ex.VnfInstanceIsNotInstantiated(inst_id=id) lcmocc_utils.check_lcmocc_in_progress(context, id) pkg_info = self.nfvo_client.get_vnf_package_info_vnfd( context, vnfd_id) if pkg_info.operationalState != "ENABLED": raise sol_ex.VnfdIdNotEnabled(vnfd_id=vnfd_id) additional_params = body.get('additionalParams') if additional_params is None: raise sol_ex.SolValidationError( detail="Change Current VNF Package " "operation must have 'additionalParams'") upgrade_type = additional_params.get('upgrade_type', '') if upgrade_type != 'RollingUpdate': raise sol_ex.NotSupportUpgradeType(upgrade_type=upgrade_type) vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo) vdu_params = additional_params.get('vdu_params') if vdu_params is None: raise sol_ex.SolValidationError( detail="'vdu_params' must exist in additionalParams") self._check_vdu_params(inst, vdu_params, vim_info.vimType, additional_params.get('lcm-operation-coordinate-new-vnf'), additional_params.get('lcm-operation-coordinate-old-vnf')) if (vim_info.vimType == "kubernetes" and not additional_params.get('lcm-kubernetes-def-files')): raise sol_ex.SolValidationError( detail="'lcm-kubernetes-def-files' must be specified") lcmocc = self._new_lcmocc(id, v2fields.LcmOperationType.CHANGE_VNFPKG, body) lcmocc.create(context) self.conductor_rpc.start_lcm_op(context, lcmocc.id) location = lcmocc_utils.lcmocc_href(lcmocc.id, self.endpoint) return sol_wsgi.SolResponse(202, None, location=location) @validator.schema(schema.LccnSubscriptionRequest_V200, '2.0.0') def subscription_create(self, request, body): context = request.context subsc = objects.LccnSubscriptionV2( id=uuidutils.generate_uuid(), callbackUri=body['callbackUri'], verbosity=body.get('verbosity', 'FULL') # default is 'FULL' ) if body.get('filter'): subsc.filter = ( objects.LifecycleChangeNotificationsFilterV2.from_dict( body['filter']) ) auth_req = body.get('authentication') if auth_req: auth = objects.SubscriptionAuthentication( authType=auth_req['authType'] ) if 'BASIC' in auth.authType: basic_req = auth_req.get('paramsBasic') if basic_req is None: msg = "ParamsBasic must be specified." raise sol_ex.InvalidSubscription(sol_detail=msg) auth.paramsBasic = ( objects.SubscriptionAuthentication_ParamsBasic( userName=basic_req.get('userName'), password=basic_req.get('password') ) ) if 'OAUTH2_CLIENT_CREDENTIALS' in auth.authType: oauth2_req = auth_req.get('paramsOauth2ClientCredentials') if oauth2_req is None: msg = "paramsOauth2ClientCredentials must be specified." raise sol_ex.InvalidSubscription(sol_detail=msg) auth.paramsOauth2ClientCredentials = ( objects.SubscriptionAuthentication_ParamsOauth2( clientId=oauth2_req.get('clientId'), clientPassword=oauth2_req.get('clientPassword'), tokenEndpoint=oauth2_req.get('tokenEndpoint') ) ) if 'TLS_CERT' in auth.authType: msg = "'TLS_CERT' is not supported at the moment." raise sol_ex.InvalidSubscription(sol_detail=msg) subsc.authentication = auth if CONF.v2_nfvo.test_callback_uri: subsc_utils.test_notification(subsc) subsc.create(context) resp_body = self._subsc_view.detail(subsc) self_href = subsc_utils.subsc_href(subsc.id, self.endpoint) return sol_wsgi.SolResponse(201, resp_body, location=self_href) def subscription_list(self, request): filter_param = request.GET.get('filter') if filter_param is not None: filters = self._subsc_view.parse_filter(filter_param) else: filters = None pager = self._subsc_view.parse_pager(request) subscs = subsc_utils.get_subsc_all(request.context, marker=pager.marker) resp_body = self._subsc_view.detail_list(subscs, filters, pager) return sol_wsgi.SolResponse(200, resp_body, link=pager.get_link()) def subscription_show(self, request, id): subsc = subsc_utils.get_subsc(request.context, id) resp_body = self._subsc_view.detail(subsc) return sol_wsgi.SolResponse(200, resp_body) def subscription_delete(self, request, id): context = request.context subsc = subsc_utils.get_subsc(request.context, id) subsc.delete(context) return sol_wsgi.SolResponse(204, None) def lcm_op_occ_list(self, request): filter_param = request.GET.get('filter') if filter_param is not None: filters = self._lcmocc_view.parse_filter(filter_param) else: filters = None selector = self._lcmocc_view.parse_selector(request.GET) pager = self._lcmocc_view.parse_pager(request) lcmoccs = lcmocc_utils.get_lcmocc_all(request.context, marker=pager.marker) resp_body = self._lcmocc_view.detail_list(lcmoccs, filters, selector, pager) return sol_wsgi.SolResponse(200, resp_body, link=pager.get_link()) def lcm_op_occ_show(self, request, id): lcmocc = lcmocc_utils.get_lcmocc(request.context, id) resp_body = self._lcmocc_view.detail(lcmocc) return sol_wsgi.SolResponse(200, resp_body) def lcm_op_occ_retry(self, request, id): context = request.context lcmocc = lcmocc_utils.get_lcmocc(context, id) return self._lcm_op_occ_retry(context, lcmocc) @coordinate.lock_vnf_instance('{lcmocc.vnfInstanceId}') def _lcm_op_occ_retry(self, context, lcmocc): if lcmocc.operationState != v2fields.LcmOperationStateType.FAILED_TEMP: raise sol_ex.LcmOpOccNotFailedTemp(lcmocc_id=lcmocc.id) # TODO(YiFeng) support retry operation for CNF instantiate # At present, the retry operation will fail for instantiate with # kubernetes vim. if lcmocc.operation == v2fields.LcmOperationType.INSTANTIATE: if lcmocc.operationParams.obj_attr_is_set('vimConnectionInfo'): vim_infos = lcmocc.operationParams.vimConnectionInfo else: vim_info = vim_utils.get_default_vim(context) vim_infos = {"default": vim_info} else: inst = inst_utils.get_inst(context, lcmocc.vnfInstanceId) vim_infos = inst.vimConnectionInfo vim_info = inst_utils.select_vim_info(vim_infos) self.conductor_rpc.retry_lcm_op(context, lcmocc.id) return sol_wsgi.SolResponse(202, None) def lcm_op_occ_rollback(self, request, id): context = request.context lcmocc = lcmocc_utils.get_lcmocc(context, id) return self._lcm_op_occ_rollback(context, lcmocc) @coordinate.lock_vnf_instance('{lcmocc.vnfInstanceId}') def _lcm_op_occ_rollback(self, context, lcmocc): if lcmocc.operationState != v2fields.LcmOperationStateType.FAILED_TEMP: raise sol_ex.LcmOpOccNotFailedTemp(lcmocc_id=lcmocc.id) # TODO(YiFeng) support rollback operation for CNF instantiate # At present, the rollback operation will fail for instantiate with # kubernetes vim. if lcmocc.operation == v2fields.LcmOperationType.INSTANTIATE: if lcmocc.operationParams.obj_attr_is_set('vimConnectionInfo'): vim_infos = lcmocc.operationParams.vimConnectionInfo else: vim_info = vim_utils.get_default_vim(context) vim_infos = {"default": vim_info} else: inst = inst_utils.get_inst(context, lcmocc.vnfInstanceId) vim_infos = inst.vimConnectionInfo vim_info = inst_utils.select_vim_info(vim_infos) self.conductor_rpc.rollback_lcm_op(context, lcmocc.id) return sol_wsgi.SolResponse(202, None) def lcm_op_occ_fail(self, request, id): context = request.context lcmocc = lcmocc_utils.get_lcmocc(context, id) return self._lcm_op_occ_fail(context, lcmocc) @coordinate.lock_vnf_instance('{lcmocc.vnfInstanceId}') def _lcm_op_occ_fail(self, context, lcmocc): if lcmocc.operationState != v2fields.LcmOperationStateType.FAILED_TEMP: raise sol_ex.LcmOpOccNotFailedTemp(lcmocc_id=lcmocc.id) inst = inst_utils.get_inst(context, lcmocc.vnfInstanceId) grant_req, grant = lcmocc_utils.get_grant_req_and_grant(context, lcmocc) lcmocc.operationState = v2fields.LcmOperationStateType.FAILED with context.session.begin(subtransactions=True): lcmocc.update(context) if grant_req is not None: grant_req.delete(context) grant.delete(context) # send notification FAILED self.nfvo_client.send_lcmocc_notification(context, lcmocc, inst, self.endpoint) resp_body = self._lcmocc_view.detail(lcmocc) return sol_wsgi.SolResponse(200, resp_body) def lcm_op_occ_delete(self, request, id): # not allowed to delete on the specification if not CONF.v2_vnfm.test_enable_lcm_op_occ_delete: raise sol_ex.MethodNotAllowed(method='DELETE') # NOTE: This is for test use since it is inconvenient not to be # able to delete. context = request.context lcmocc = lcmocc_utils.get_lcmocc(context, id) lcmocc.delete(context) return sol_wsgi.SolResponse(204, None) def supported_api_versions(self, action): if action == 'api_versions': # support all versions and it is OK there is no Version header. return None else: return api_version.v2_versions def allowed_content_types(self, action): if action == 'update': # Content-Type of Modify request shall be # 'application/mergepatch+json' according to SOL spec. # But 'application/json' and 'text/plain' is OK for backward # compatibility. return ['application/mergepatch+json', 'application/json', 'text/plain'] else: return ['application/json', 'text/plain']