2015-02-13

Java To Trust GoDaddy Class2 G2 Certs

Sadly, GoDaddy's "Class 2" and "Class 2 - G2" root cacerts are not included in the Java7 packages, so they are not trusted by default.

So we'll have to add the CA certificates to the Java TrustStore.

I use Apache Tomcat, so I'll need another step: configuring where to look for the truststore file, which I'll add to the CATALINA_OPTS, but you should use setenv.sh or whatever your deployment process forces you to. For some reason I haven't got the urge to look up, Tomcat does NOT pick up the truststore by default. Maybe it's just some PEBKAC in the setup somewhere else.

Align the cacerts file location to your needs.

-Djavax.net.ssl.trustStore=/opt/apps/jira/jdk/jre/lib/security/cacerts -Djavax.net.ssl.trustStorePassword=changeit

Firstly, fetch the certificates we want to trust:
  1. https://certs.godaddy.com/repository/gd_bundle.crt
  2. https://certs.godaddy.com/repository/gdroot-g2.crt
  3. https://certs.godaddy.com/repository/gd-class2-root.crt
  4. https://certs.godaddy.com/repository/gdig2.crt

Sometimes they interleave (eg. gdig2 is an intermediate cert, connecting Class2 and G2), if it throws an error about already being imported, ignore the error.

Secondly, import the certs to trust them (do it on all four):
keytool -import -trustcacerts -alias gd_bundle -file gd_bundle.crt -keystore /opt/apps/jira/jdk/jre/lib/security/cacerts
Here, I use the same alias as the filename. Again, align the cacerts's file location to your needs.
The keytool binary will ask for the password, which is changeit by default. If you change it, remember to change the tomcat parameter trustStorePassword also.

Lastly, restart Tomcat.

2015-02-06

JIRA: Check JIRA issues with null status

So you see errors like: "java.lang.RuntimeException: Could not find workflow status for issue SOMEPRJ-123456.".
It suggests, the jiraissue.issuestatus column on that issue is null.
How to check:
SELECT ID FROM JIRAISSUE WHERE ISSUESTATUS IS NULL;
How to fix it in Oracle:
Prerequisites: For the TEMP_JIRAWORKFLOWS, see here: http://kozelljozsef.blogspot.hu/2014/11/jira-check-workflow-current-step-entries.html
MERGE INTO jiraissue USING
(
  WITH W_JIRAISSUE AS
  ( SELECT
      I.ID AS ISSUEID,
      I.WORKFLOW_ID,
      I.ISSUESTATUS,
      W.NAME AS WFNAME,
      C.STEP_ID AS CURRENT_STEP_ID
    FROM JIRAISSUE I
    INNER JOIN OS_WFENTRY W
    ON I.WORKFLOW_ID = W.ID
    INNER JOIN OS_CURRENTSTEP C
    ON I.WORKFLOW_ID = C.ENTRY_ID
    WHERE I.ISSUESTATUS IS NULL
  )
SELECT
  I.ISSUEID,
  I.ISSUESTATUS,
  WF.LINKEDSTATUSID AS LINKED_STATUS_ID
FROM W_JIRAISSUE I
INNER JOIN TEMP_JIRAWORKFLOWS WF
ON WF.WORKFLOWNAME = I.WFNAME AND WF.STEPID = I.CURRENT_STEP_ID
) GOODSTATUS ON (GOODSTATUS.issueid = JIRAISSUE.ID)
WHEN MATCHED THEN UPDATE
    SET JIRAISSUE.ISSUESTATUS = GOODSTATUS.LINKED_STATUS_ID;
This essentially looks up the workflow step associated with the failing issue, reads out the linked status id that should be written back to the issuestatus column.

JIRA: Check workflow current step entries

An issue can have an invalid status (one that does not exist for the workflow) if a Workflow Migration or Bulk Move died half way through.

An issue must have exactly 1 (one) currentstep. It tells, how did we got here.
If it has zero, we should insert one for the current wfentry based on the workflow.
If it has more than one, we should only keep the latest. (Not covered by this blog entry.)

How to check:
SELECT COUNT(*) AS PROBLEM_CNT
FROM JIRAISSUE I    
LEFT JOIN OS_CURRENTSTEP C ON I.WORKFLOW_ID = C.ENTRY_ID
INNER JOIN TEMP_JIRAWORKFLOWS WF ON I.ISSUESTATUS = WF.LINKEDSTATUSID 
INNER JOIN OS_WFENTRY W ON I.WORKFLOW_ID = W.ID AND WF.WORKFLOWNAME = W.NAME
WHERE C.STEP_ID != WF.STEPID OR C.ENTRY_ID IS NULL;
Essentially, this check is an antijoin, looking for issues without a corresponding currentstep (however the condition also gives us the multiple ones too) OR with an incorrect step.

How to fix:

UPDATE: Now, as converting the XML-s to a table is pretty expensive, we'll create a temporary table, and fill it in with the workflow-step-status tuples.

-- Create me once.
CREATE GLOBAL TEMPORARY TABLE TEMP_JIRAWORKFLOWS
(WORKFLOWNAME VARCHAR2(255 CHAR),
STEPID NUMBER,
LINKEDSTATUSID VARCHAR2(16))
ON COMMIT PRESERVE ROWS;

Every time we connect, we'll have to fill it out.
Note: you can create a real table with triggers to reflect the changes on the original, or have a materialized view. I choose this cumbersome way, because our DBA-s are afraid of triggers (wtf?!), and also they does not let me create materialized views.

INSERT INTO TEMP_JIRAWORKFLOWS
WITH W_JIRAWORKFLOWS AS
  ( SELECT
        WORKFLOWNAME,
        XMLTYPE.CREATEXML(REPLACE(DESCRIPTOR,
        '<!DOCTYPE workflow PUBLIC "-//OpenSymphony Group//DTD OSWorkflow 2.8//EN" "http://www.opensymphony.com/osworkflow/workflow_2_8.dtd">'
        )) AS DESCRIPTOR
     FROM JIRAWORKFLOWS)
SELECT
  W_JIRAWORKFLOWS.WORKFLOWNAME,
  WF.STEPID                   ,
  WF.LINKEDSTATUSID
FROM
  W_JIRAWORKFLOWS,
  XMLTABLE('/workflow/steps/step[@id]' PASSING W_JIRAWORKFLOWS.DESCRIPTOR
     COLUMNS
        "STEPID" NUMBER PATH '@id',
        "LINKEDSTATUSID" VARCHAR2(16) PATH 'meta[@name="jira.status.id"]/text()'
     ) WF;

COMMIT;
 
SET TRANSACTION ISOLATION LEVEL READ COMMITTED NAME 'Check workflow current step entries';
 
MERGE INTO OS_CURRENTSTEP USING
( SELECT
       ROWNUM+(SELECT SEQ_ID FROM SEQUENCE_VALUE_ITEM WHERE SEQ_NAME = 'OSCurrentStep') AS N,
       I.WORKFLOW_ID     AS ENTRY_ID,
       WF.STEPID         AS STEP_ID ,
       WF.LINKEDSTATUSID AS STATUS
  FROM JIRAISSUE I
  LEFT JOIN OS_CURRENTSTEP C ON I.WORKFLOW_ID = C.ENTRY_ID
  INNER JOIN TEMP_JIRAWORKFLOWS WF ON I.ISSUESTATUS = WF.LINKEDSTATUSID
  INNER JOIN OS_WFENTRY W ON I.WORKFLOW_ID = W.ID AND WF.WORKFLOWNAME = W.NAME
  WHERE C.STEP_ID != WF.STEPID OR C.ENTRY_ID IS NULL
) GOODSTEP ON (GOODSTEP.ENTRY_ID = OS_CURRENTSTEP.ENTRY_ID)
WHEN MATCHED THEN UPDATE
    SET OS_CURRENTSTEP.STEP_ID = GOODSTEP.STEP_ID
WHEN NOT MATCHED THEN INSERT
VALUES (GOODSTEP.N, GOODSTEP.ENTRY_ID, GOODSTEP.STEP_ID, 0, NULL, SYSDATE, NULL, NULL, GOODSTEP.STATUS, NULL);
 
UPDATE SEQUENCE_VALUE_ITEM 
   SET SEQ_ID = (1 + (SELECT MAX(ID) FROM OS_CURRENTSTEP)) 
   WHERE SEQ_NAME = 'OSCurrentStep';
 
COMMIT;
The fix is more complex than the others. We need an ID range taken from the SEQUENCE_VALUE_ITEM table, so later nothing tries to use our id-s. I wonder, why Atlassian does not utilize things like autoincrement fields or sequences...
So we can fetch the step_id and status_id that took us to the current step, essentially everything we need in the currentstep. Then, we increment the sequence.