Add Calico component tests execution stage

Also added a method for tests results reporting to
TestRail.

Change-Id: I3cfcf46b8bc375b7ea730a456fd76cd84c9be746
diff --git a/src/com/mirantis/mcp/Calico.groovy b/src/com/mirantis/mcp/Calico.groovy
index 2ed3563..0d7428c 100644
--- a/src/com/mirantis/mcp/Calico.groovy
+++ b/src/com/mirantis/mcp/Calico.groovy
@@ -7,10 +7,11 @@
  * @param config LinkedHashMap
  *        config includes next parameters:
  *          - project_name String, Calico project to clone
- *          - projectNamespace String, gerrit namespace (optional)
- *          - commit String, Git commit to checkout
- *          - credentialsId String, gerrit credentials ID (optional)
  *          - host String, gerrit host
+ *          - projectNamespace String, gerrit namespace (optional)
+ *          - commit String, Git commit to checkout (optional)
+ *          - credentialsId String, gerrit credentials ID (optional)
+ *          - refspec String, remote refs to be retrieved (optional)
  *
  * Usage example:
  *
@@ -28,15 +29,16 @@
 
   def project_name = config.get('project_name')
   def projectNamespace = config.get('projectNamespace', 'projectcalico')
-  def commit = config.get('commit')
+  def commit = config.get('commit', '*')
   def host = config.get('host')
   def credentialsId = config.get('credentialsId', 'mcp-ci-gerrit')
+  def refspec = config.get('refspec')
 
   if (!project_name) {
     throw new RuntimeException("Parameter 'project_name' must be set for checkoutCalico() !")
   }
-  if (!commit) {
-    throw new RuntimeException("Parameter 'commit' must be set for checkoutCalico() !")
+  if (!host) {
+    throw new RuntimeException("Parameter 'host' must be set for checkoutCalico() !")
   }
 
   stage ("Checkout ${project_name}"){
@@ -46,6 +48,7 @@
       host : host,
       project : "${projectNamespace}/${project_name}",
       withWipeOut : true,
+      refspec : refspec,
     ])
   }
 }
@@ -462,6 +465,60 @@
 
 
 /**
+ * Run Calico system tests stage
+ *
+ * @param nodeImage String, docker image for calico/node container
+ * @param ctlImage String, docker image with calicoctl binary
+ * @param failOnErrors Boolean, raise exception if some tests fail (default true)
+ *
+ * Usage example:
+ *
+ * def calico = new com.mirantis.mcp.Calico()
+ * calico.systestCalico('calico/node:latest', 'calico/ctl:latest')
+ *
+ */
+def systestCalico(nodeImage, ctlImage, failOnErrors = true) {
+  stage ('Run Calico system tests'){
+    try {
+      // create fake targets to avoid execution of unneeded operations
+      sh """
+        mkdir -p vendor
+      """
+      // pull calico/ctl image and extract calicoctl binary from it
+      sh """
+        mkdir -p dist
+        docker run --rm -u \$(id -u):\$(id -g) --entrypoint /bin/cp -v \$(pwd)/dist:/dist ${ctlImage} /calicoctl /dist/calicoctl
+        touch dist/calicoctl dist/calicoctl-linux-amd64
+      """
+      // pull calico/node image and extract required binaries
+      sh """
+        mkdir -p calico_node/filesystem/bin
+        for calico_binary in startup allocate-ipip-addr calico-felix bird calico-bgp-daemon confd libnetwork-plugin; do
+          docker run --rm -u \$(id -u):\$(id -g) --entrypoint /bin/cp -v \$(pwd)/calico_node/filesystem/bin:/calicobin ${nodeImage} /bin/\${calico_binary} /calicobin/
+        done
+        cp calico_node/filesystem/bin/startup dist/
+        cp calico_node/filesystem/bin/allocate-ipip-addr dist/
+        touch calico_node/filesystem/bin/*
+        touch calico_node/.calico_node.created
+      """
+      sh "NODE_CONTAINER_NAME=${nodeImage} make st"
+    } catch (Exception e) {
+      sh "make stop-etcd"
+      // FIXME: cleaning has to be done by make stop/clean targets
+      sh """
+        for dc in calico-felix cali-st-ext-nginx cali-st-host cali-st-gw host1 host2 host3; do
+          docker rm -f "\${dc}" || :
+        done
+      """
+      if (failOnErrors) {
+        throw e
+      }
+    }
+  }
+}
+
+
+/**
  * Build Calico containers stages
  *
  * @param config LinkedHashMap
diff --git a/src/com/mirantis/mcp_qa/Common.groovy b/src/com/mirantis/mcp_qa/Common.groovy
index 7d9fd89..22dcf81 100644
--- a/src/com/mirantis/mcp_qa/Common.groovy
+++ b/src/com/mirantis/mcp_qa/Common.groovy
@@ -97,3 +97,87 @@
     }
     return jobSetParameters
 }
+
+/**
+ * Upload tests results to TestRail
+ *
+ * @param config LinkedHashMap
+ *        config includes next parameters:
+ *          - junitXml String, path to XML file with tests results
+ *          - testPlanName String, name of test plan in TestRail
+ *          - testSuiteName String, name of test suite in TestRail
+ *          - testrailMilestone String, milestone name in TestRail
+ *          - tesPlanDesc String, description of test plan in TestRail (optional)
+ *          - jobURL String, URL of job build with tests (optional)
+ *          - testrailURL String, TestRail URL (optional)
+ *          - testrailProject String, project name in TestRail (optional)
+ *
+ *
+ * Usage example:
+ *
+ * uploadResultsTestRail([
+ *   junitXml: './nosetests.xml',
+ *   testPlanName: 'MCP test plan #1',
+ *   testSuiteName: 'Calico component tests',
+ *   jobURL: 'jenkins.example.com/job/tests.mcp/1',
+ * ])
+ *
+ */
+def uploadResultsTestRail(config) {
+  def venvPath = 'testrail-venv'
+  // TODO: install 'testrail_reporter' pypi when new version with eee508d commit is released
+  def testrailReporterPackage = 'git+git://github.com/gdyuldin/testrail_reporter.git'
+  def testrailReporterVersion = 'eee508d'
+
+  def requiredArgs = ['junitXml', 'testPlanName', 'testSuiteName', 'testrailMilestone']
+  def missingArgs = []
+  for (i in requiredArgs) { if (!config.containsKey(i)) { missingArgs << i }}
+  if (missingArgs) { println "Required arguments are missing for '${funcName}': ${missingArgs.join(', ')}" }
+
+  def junitXml = config.get('junitXml')
+  def testPlanName = config.get('testPlanName')
+  def testSuiteName = config.get('testSuiteName')
+  def testrailMilestone = config.get('testrailMilestone')
+  def testrailURL = config.get('testrailURL', 'https://mirantis.testrail.com')
+  def testrailProject = config.get('testrailProject', 'Mirantis Cloud Platform')
+  def tesPlanDesc = config.get('tesPlanDesc')
+  def jobURL = config.get('jobURL')
+
+  def reporterOptions = [
+    "--verbose",
+    "--testrail-run-update",
+    "--testrail-url '${testrailURL}'",
+    "--testrail-user \"\${TESTRAIL_USER}\"",
+    "--testrail-password \"\${TESTRAIL_PASSWORD}\"",
+    "--testrail-project '${testrailProject}'",
+    "--testrail-plan-name '${testPlanName}'",
+    "--testrail-milestone '${testrailMilestone}'",
+    "--testrail-suite '${testSuiteName}'",
+    "--xunit-name-template '{methodname}'",
+    "--testrail-name-template '{custom_test_group}'",
+  ]
+
+  if (tesPlanDesc) { reporterOptions << "--env-description '${tesPlanDesc}'" }
+  if (jobURL) { reporterOptions << "--test-results-link '${jobURL}'" }
+
+  // Install testrail reporter
+  sh """
+    virtualenv ${venvPath}
+    . ${venvPath}/bin/activate
+    pip install --upgrade ${testrailReporterPackage}@${testrailReporterVersion}
+  """
+
+  def script = """
+    . ${venvPath}/bin/activate
+    report ${reporterOptions.join(' ')} ${junitXml}
+  """
+
+  withCredentials([
+             [$class          : 'UsernamePasswordMultiBinding',
+             credentialsId   : 'testrail',
+             passwordVariable: 'TESTRAIL_PASSWORD',
+             usernameVariable: 'TESTRAIL_USER']
+  ]) {
+    return sh(script: script, returnStdout: true).trim().split().last()
+  }
+}