Files
svc-api-x/v1/resources/instance_operation_run.cfc
T

324 lines
17 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<cfcomponent extends="taffy.core.resource" taffy:uri="/instanceOperations/{instanceOperationUid}/run">
<cfsilent>
<cfimport prefix="m" taglib="../lib"/>
<cfset this.helper=CreateObject("component","lib.rest_api_helper")/><!---*** странно, почему мы его видим?---><!---вынести в апп?--->
</cfsilent>
<cffunction name="post" hint="Запуск выполнения операции">
<cfargument name="instanceOperationUid" type="string" required=true hint="type:guid"/>
<cfset local={}/>
<!--- <cfset local.instanceUid=#createGUID()#/> --->
<!--- *** Проверить наличие обязательных CFS параметров
Создать RFS параметры
(валидируем CFS параметры при создании, а не сейчас)
Проверить, что операция уже не выполняется
--->
<cftry>
<!---NOT NULL fields--->
<cfset this.helper.keyExistsAndValid(arguments, "instanceOperationUid", "guid")/>
<cfquery name="local.qSvcOperation" result="local.result">
select io.operation, s.svc_id, so.svc_operation_id
from instance_operation io
join instance e on (io.instance_uid=e.instance_uid)
join svc s on (e.service_id=s.svc_id)
join svc_operation so on (s.svc_id=so.svc_id AND io.operation=so.operation)
where io.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#" null=#!isValid('guid',arguments.instanceOperationUid)#/>
</cfquery>
<!--- Check operation status, do not start already started (*** consider timeout and cancelling timed out operations here) --->
<cfquery name="local.qCheckStarted" result="local.result">
select io.dt_submit, io.dt_finish, io.dt_start, io.submit_result, io.status_url
from instance_operation io
where io.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#" null=#!isValid('guid',arguments.instanceOperationUid)#/>
</cfquery> <!--- <cfdump var=#local.qCheckStarted.dt_finish#/><cfabort/> --->
<!---
попытаемся проверить, не упала ли операция раньше времени
при этом стараемся исключить эффект дребезга - если мы пошлем две команды на выполнение одну за другой, вторая не должна запуститься
перезапуск операции разрешен, если она зафейлилась либо явно (оркестратор ее пометил таковой) или неявно (джоб упал, не успев отметить операцию стартовавшей)
Возможно, эту логику нужно воспроизвести на фронте
--->
<cfif local.qCheckStarted.recordCount EQ 0>
<cfreturn representationOf(this.helper.formatMessage("There is no configured instance operation with uid #arguments.instanceOperationUid#","Instance operation not found")).withStatus(404)/>
<cfelseif left(local.qCheckStarted.submit_result,1) EQ "2" AND len(local.qCheckStarted.dt_finish) EQ 0><!--- 201 etc --->
<cfif getJobStatus(local.qCheckStarted.status_url) NEQ 'FAILED'>
<cfreturn representationOf(this.helper.formatMessage("Operation already started", "Parameter creation disabled for started operation")).withStatus(422)/>
</cfif>
</cfif>
<!--- Check CFS parameters at least for existence
(*** would be better to check RFS but now CFS params are primary, RFS even mostly do not have is_required flag set) --->
<!--- *** !!! тут возможна ошибка из-за нечеткого понимания: даже в отсутствие обязательного CFS параметра RFS параметр может быть сгенерирован, например, из дефолта. То есть данный вариант проверки - неполный и ущербный, впоследствии может неожиданно перестать работать --->
<cfquery name="local.qCheckCfsParams" result="local.result">
select sop.svc_operation_cfs_param_id, sop.svc_operation_cfs_param, sop.is_required,
iop.instance_operation_cfs_param_uid::text as instance_operation_cfs_param_uid, iop.param_value
from svc_operation_cfs_param sop
left outer join instance_operation_cfs_param iop on (sop.svc_operation_cfs_param_id=iop.svc_operation_cfs_param_id AND iop.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#" null=#!isValid('guid',arguments.instanceOperationUid)#/>)
where sop.svc_operation_id=<cfqueryparam cfsqltype="cf_sql_integer" value="#local.qSvcOperation.svc_operation_id#"/>
</cfquery><!--- <cfdump var=#local.qCheckCfsParams#/><cfabort/> --->
<cfset local.cfsParamChecker = CreateObject("component", "instance_operation_cfs_param_ls")/>
<!--- we could check existence of required parameters right in the query,
but for possible value chech and verbosity do it in the loop --->
<cfloop query=#local.qCheckCfsParams#>
<cfif (local.qCheckCfsParams.is_required GT 0 AND NOT len(local.qCheckCfsParams.instance_operation_cfs_param_uid) GT 0)>
<cfreturn representationOf(this.helper.formatMessage("required CFS parameter #local.qCheckCfsParams.svc_operation_cfs_param# (#local.qCheckCfsParams.svc_operation_cfs_param_id#) is missing", "Missing required CFS parameter")).withStatus(409)/>
<cfelse>
<cfset local.cfsParamChecker.checkParam(local.qCheckCfsParams.svc_operation_cfs_param_id, qCheckCfsParams.param_value, qCheckCfsParams.instance_operation_cfs_param_uid)/>
</cfif>
</cfloop><!--- <cfdump var=#local.qCheckCfsParams#/><cfabort/> --->
<!--- ***** тут должен быть контроль доступа на уровне параметров. Желательно по максимуму параноидальный --->
<!--- ***** или при сабмите. Может быть, и на уровне RFS параметров тоже --->
<cfset generateRfsParams(arguments.instanceOperationUid, arguments.usrId, arguments.contragentId, arguments.contractId)/>
<cfset submitJob(arguments.instanceOperationUid)/>
<cfcatch type="invalidParamValue">
<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
</cfcatch>
<cfcatch type="missingDependency">
<cfreturn representationOf(this.helper.formatException(cfcatch,"Missing Dependency")).withStatus(424)/>
</cfcatch>
</cftry>
<!--- <cfreturn representationOf("Not Implemented yet").withStatus(501)/> --->
<cfset generateRfsParams(arguments.instanceOperationUid, arguments.usrId, arguments.contragentId, arguments.contractId)/><!--- *** а это зачем? прокомментировать --->
<!--- --->
<cfreturn noData().withStatus(201, "Created") />
</cffunction>
<cffunction name="generateRfsParams">
<cfargument name="instanceOperationUid" type="guid"/>
<cfargument name="usrId" type="numeric"/>
<cfargument name="contragentId" type="numeric"/>
<cfargument name="contractId" type="numeric"/>
<!---
-- полезный селект, не выбрасывать
<cfquery name="qSvcOperationCfsParam">
select sop.svc_operation_cfs_param_id
,iop.instance_operation_cfs_param_uid::text as instance_operation_cfs_param_uid /*pgjdbc issue fix*/
,iop.param_value
,sop.svc_operation_cfs_param
,sop.data_type
,sop.is_required
,sop.maxlength,sop.minlength,sop.regex,sop.unique_scope
from instance_operation io
join instance e on (io.instance_uid=e.instance_uid)
join svc_operation so on (e.service_id=so.svc_id AND io.operation=so.operation)
join svc_operation_cfs_param sop on (so.svc_operation_id=sop.svc_operation_id)
left outer join instance_operation_cfs_param iop
on (sop.svc_operation_cfs_param_id=iop.svc_operation_cfs_param_id AND io.instance_operation_uid=iop.instance_operation_uid)
where iop.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value=#arguments.instanceOperationUid# null=#!isValid("guid",arguments.instanceOperationUid)#/>)
</cfquery>
--->
<cfquery name="qSvcOperation">
select so.svc_operation_id, io.instance_uid
from instance_operation io
join instance e on (io.instance_uid=e.instance_uid)
join svc_operation so on (e.service_id=so.svc_id AND io.operation=so.operation)
where io.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value=#arguments.instanceOperationUid# null=#!isValid("guid",arguments.instanceOperationUid)#/>
</cfquery>
<!--- поступим проще, по engine/generate_rfs_12.cfm --->
<cfset setInstanceOperationRfsParam(arguments.instanceOperationUid, "instanceUid", qSvcOperation.instance_uid)/>
<cfset setInstanceOperationRfsParam(arguments.instanceOperationUid, "operationUid", arguments.instanceOperationUid)/>
<cfset setInstanceOperationRfsParam(arguments.instanceOperationUid, "modifierId", arguments.usrId)/>
<cfset setInstanceOperationRfsParam(arguments.instanceOperationUid, "billingObject", "underconstruction")/>
<cfset setInstanceOperationRfsParam(arguments.instanceOperationUid, "contragentCode", getContragentCode(arguments.contragentId))/>
<cfquery name="qWriteRfsFromCfsParams">
insert into instance_operation_param (instance_operation_uid,param,param_value)
select
iocp.instance_operation_uid
,sop.svc_operation_param
,iocp.param_value
from instance_operation_cfs_param iocp
join svc_operation_cfs_param socp on (iocp.svc_operation_cfs_param_id=socp.svc_operation_cfs_param_id)
join svc_operation_param sop on (socp.svc_operation_id=sop.svc_operation_id AND socp.svc_operation_cfs_param=sop.svc_operation_param)
where iocp.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#"/>
/*PostgreSQL specific*/
ON CONFLICT (instance_operation_uid,param)
DO UPDATE SET param_value = EXCLUDED.param_value;
</cfquery>
<!--- так яснее, но можно было все в один запрос --->
<cfquery name="qWriteRfsParamsFromDefaults">
insert into instance_operation_param (instance_operation_uid,param,param_value)
select
<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#"/>
,sop.svc_operation_param
,sop.default_value
from svc_operation_param sop
where sop.svc_operation_id=<cfqueryparam cfsqltype="cf_sql_integer" value="#qSvcOperation.svc_operation_id#"/> AND length(sop.default_value)>0 /***null will be better*/
/*PostgreSQL specific*/
ON CONFLICT (instance_operation_uid,param)
DO NOTHING;
</cfquery>
</cffunction>
<cffunction name="submitJob">
<cfargument name="instanceOperationUid" type="string" required=true hint="type:guid"/>
<cfquery name="qInstanceOperation">
select so.url
from instance_operation o
join instance i on (o.instance_uid=i.instance_uid)
left outer join svc_operation so on (i.service_id=so.svc_id AND o.operation=so.operation)
where o.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value=#arguments.instanceOperationUid#/>
</cfquery>
<cfquery name="qInstanceOperationParam">
select iop.param, iop.param_value
from instance_operation_param iop
where iop.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value=#arguments.instanceOperationUid#/>
AND length(iop.param)>0
</cfquery>
<!--- *** тут нужно бы указать, кто запустил операцию --->
<cfquery name="qMarkOperationStart">
update instance_operation
set dt_submit=<cfqueryparam cfsqltype="cf_sql_timestamp" value=#now()#/>
,dt_start=null, dt_finish=null, submit_result='0'
where instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value=#arguments.instanceOperationUid#/>
</cfquery>
<cftry>
<cfhttp method="post"
url="#qInstanceOperation.url#"
multipart=true
multipartType="form-data"
timeout="3"
charset="utf-8"
result="orchestrator"
>
<cfhttpparam type="HEADER" name="Authorization" value="#request.ORCHESTRATOR_AUTH#"/>
<cfhttpparam type="HEADER" name="Accept" value="application/json"/>
<cfhttpparam type="formfield" name="delay" value="0sec"/>
<cfloop query="qInstanceOperationParam">
<cfhttpparam type="formfield" name="#param#" value="#param_value#"/>
</cfloop>
</cfhttp>
<cfcatch type="any">
<cfthrow message="Cannot initiate connection to the orchestrator #cfcatch.message#" detail="#cfcatch.detail#"/>
<!--- <cfdump var=#qInstanceOperation#/>
<cfabort/> --->
</cfcatch>
</cftry>
<cfif len(orchestrator.errorDetail)>
<cfthrow type="missingDependency" message="Orchestrator communication error" detail="#orchestrator.errorDetail#"/>
<!--- лучше бы возвращать 503 или 424 --->
</cfif>
<cfquery name="qMarkOperationSubmit">
update instance_operation
set submit_result=<cfqueryparam cfsqltype="cf_sql_varchar" value=#orchestrator.responseHeader.status_code#/>
where instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value=#arguments.instanceOperationUid#/>
</cfquery>
<cfset sleep(0)/><!--- если придется подождать, можно добавить, но мы специально указываем delay=0sec --->
<cfif listFind("200,201",#orchestrator.responseHeader.status_code#)>
<cftry>
<cfset var buildURL="#orchestrator.responseHeader.Location#/api/json"/>
<cfhttp method="get"
url="#buildUrl#"
timeout="3"
charset="utf-8"
result="job"
>
<cfhttpparam type="HEADER" name="Authorization" value="#request.ORCHESTRATOR_AUTH#">
<cfhttpparam type="HEADER" name="Accept" value="application/json">
</cfhttp>
<cfset var jobData=#deserializeJson(job.filecontent)#/>
<!--- jobData: <cfdump var=#jobData#/> --->
<cfset operation_url=jobData.executable.url />
<cfquery name="qSetStatusUrl">
update instance_operation
set status_url=<cfqueryparam cfsqltype="cf_sql_varchar" value="#operation_url#api/json"/><!--- *** костыль --->
where instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value=#arguments.instanceOperationUid#/>
</cfquery>
<cfcatch type="any">
<cfrethrow/><!--- пока не придумал --->
</cfcatch>
</cftry>
</cfif>
</cffunction>
<!--- *** судя по всему, RFS параметр не обязан быть специфицирован на сервисе (никак не проверяется) --->
<cffunction name="setInstanceOperationRfsParam">
<cfargument name="instanceOperationUid" type="guid">
<cfargument name="param"><!--- значение не должно содержать лишних пробелов, чувствительность к регистру определяется БД --->
<cfargument name="val">
<cfquery name="qSaveRfsParam">
insert into instance_operation_param (instance_operation_uid,param,param_value)
values (
<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#"/>
,<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.param#"/>
,<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.val#"/>
)
/*PostgreSQL specific*/
ON CONFLICT (instance_operation_uid,param)
DO UPDATE SET param_value = EXCLUDED.param_value;
</cfquery>
<!--- <cfdump var=#ARGUMENTS#/> --->
</cffunction>
<cffunction name="getJobStatus">
<cfargument name="status_url">
<cftry>
<cfhttp method="get"
url="#arguments.status_url#"
timeout="3"
charset="utf-8"
result="local.jobStatus"
>
<cfhttpparam type="HEADER" name="Authorization" value="#request.ORCHESTRATOR_AUTH#">
<cfhttpparam type="HEADER" name="Accept" value="application/json">
</cfhttp>
<cftry>
<cfset var jobStatusData=#deserializeJson(local.jobStatus.filecontent)#/>
<cfreturn jobStatusData.result/>
<cfcatch type="any">
<cfreturn "cannot obtain job result"/><!--- можно было бы и поаккуратнее - проверить код ответа, потом проверить наличие поля --->
<!--- <cfthrow message="ERROR #cfcatch.message#" detail="#cfcatch.detail#"/> --->
</cfcatch>
</cftry>
<cfcatch type="any">
<cfthrow message="Cannot connect to the orchestrator. #cfcatch.message#" detail="#cfcatch.detail#"/>
</cfcatch>
</cftry>
</cffunction>
<cffunction name="getContragentCode">
<cfargument name="contragentId">
<cfquery name="qContragentCode">
select external_code
from contragent
where contragent_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.contragentId# null=#!isNumeric(arguments.contragentId)#/>
</cfquery>
<cfreturn #qContragentCode.external_code#/>
</cffunction>
</cfcomponent>