Hello community,

here is the log from the commit of package openSUSE-release-tools for 
openSUSE:Factory checked in at 2018-04-19 15:30:01
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/openSUSE-release-tools (Old)
 and      /work/SRC/openSUSE:Factory/.openSUSE-release-tools.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "openSUSE-release-tools"

Thu Apr 19 15:30:01 2018 rev:82 rq:597429 version:20180417.b8337f4

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/openSUSE-release-tools/openSUSE-release-tools.changes
    2018-04-17 11:19:14.653554880 +0200
+++ 
/work/SRC/openSUSE:Factory/.openSUSE-release-tools.new/openSUSE-release-tools.changes
       2018-04-19 15:30:13.757234226 +0200
@@ -1,0 +2,24 @@
+Tue Apr 17 11:07:01 UTC 2018 - opensuse-releaset...@opensuse.org
+
+- Update to version 20180417.b8337f4:
+  * [openqabot-maintenance] Catch HTTPError when try get REPOHASH
+  * [openqa-maintenance] SLE 12 SP2 LTSS
+  * [openqa-maintenance] start HA/SES jobs only wheen are required
+  * [openqa-mainteance] Add data for SES5
+  * [openqa-maintenance] catch removed incident project
+  * [openqa-maintenance] add missing arch for Live Patching apimap
+  * [openqa-maintenance] Allow OS_TEST_ISSUES different than product prefix
+  * [openqa-maintenance] Fix WSM and TCM issues data
+  * [openqa-maintenance] Add correct issues for SLE-HA to apimap
+  * [openqa-maintenance] Survive malformed repomd.xml
+  * [openqa-maintenance] Add SLE-HA incidents
+  * [openqa-maintenance] Allow and correctly use other distri from apimap.json
+  * [openqa-maintenance] Add data for CAASP
+  * [openqa-maintenance] Add data for KGraft on ppc64le
+  * [openqa-maintenance] Start Incidents-Kernel flavour also for kernel 
packages
+  * [openqa-maintenance] Add support for kGraft jobs
+  * [openqa-maintenance] Rewrite of openqa-maintenace bot pt.1
+  * [openqa-maintenance] Add apimap and incidents data for kGraft
+  * [openqa-naintenance] Remove uneeded kGraft data
+
+-------------------------------------------------------------------

Old:
----
  openSUSE-release-tools-20180416.dc30723.obscpio

New:
----
  openSUSE-release-tools-20180417.b8337f4.obscpio

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ openSUSE-release-tools.spec ++++++
--- /var/tmp/diff_new_pack.1WWQxR/_old  2018-04-19 15:30:14.621198797 +0200
+++ /var/tmp/diff_new_pack.1WWQxR/_new  2018-04-19 15:30:14.625198633 +0200
@@ -20,7 +20,7 @@
 %define source_dir openSUSE-release-tools
 %define announcer_filename factory-package-news
 Name:           openSUSE-release-tools
-Version:        20180416.dc30723
+Version:        20180417.b8337f4
 Release:        0
 Summary:        Tools to aid in staging and release work for openSUSE/SUSE
 License:        GPL-2.0-or-later AND MIT

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.1WWQxR/_old  2018-04-19 15:30:14.705195352 +0200
+++ /var/tmp/diff_new_pack.1WWQxR/_new  2018-04-19 15:30:14.709195188 +0200
@@ -1,6 +1,6 @@
 <servicedata>
   <service name="tar_scm">
     <param 
name="url">https://github.com/openSUSE/openSUSE-release-tools.git</param>
-    <param 
name="changesrevision">dc30723b4a7f678dd9cd2195904ef472f49e93a1</param>
+    <param 
name="changesrevision">b8337f43a7d11b75bf5b43b52daacb806f84d6e9</param>
   </service>
 </servicedata>

++++++ openSUSE-release-tools-20180416.dc30723.obscpio -> 
openSUSE-release-tools-20180417.b8337f4.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openSUSE-release-tools-20180416.dc30723/.travis.yml 
new/openSUSE-release-tools-20180417.b8337f4/.travis.yml
--- old/openSUSE-release-tools-20180416.dc30723/.travis.yml     2018-04-16 
22:53:30.000000000 +0200
+++ new/openSUSE-release-tools-20180417.b8337f4/.travis.yml     2018-04-17 
13:01:25.000000000 +0200
@@ -62,6 +62,7 @@
         - pip install pycurl urlgrabber m2crypto
         - pip install -r requirements.txt
         - pip install python-coveralls
+        - pip install nose-exclude
       before_script:
         # travis-ci/travis-ci#7008: stop services to make room for OBS setup
         - sudo service mysql stop
@@ -70,7 +71,7 @@
         # Needs python prefix to use the correct interpretor.
         - python ./obs_clone.py --cache --debug --apiurl-target local
       script:
-        - nosetests --with-coverage --cover-package=. --cover-inclusive
+        - nosetests --with-coverage --cover-package=. --cover-inclusive 
--exclude-dir=./oqamaint
       after_success:
         - coveralls
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openSUSE-release-tools-20180416.dc30723/data/apimap.json 
new/openSUSE-release-tools-20180417.b8337f4/data/apimap.json
--- old/openSUSE-release-tools-20180416.dc30723/data/apimap.json        
2018-04-16 22:53:30.000000000 +0200
+++ new/openSUSE-release-tools-20180417.b8337f4/data/apimap.json        
2018-04-17 13:01:25.000000000 +0200
@@ -8,16 +8,6 @@
          "ppc64le"
       ]
    },
-   "SUSE:Updates:SLE-DESKTOP:12-SP2:" : {
-      "issues" : {
-         "SDK_TEST_ISSUES" : "SUSE:Updates:SLE-SDK:12-SP2:"
-      },
-      "version" : "12-SP2",
-      "flavor" : "Desktop-DVD-Incidents",
-      "archs" : [
-         "x86_64"
-      ]
-   },
    "SUSE:Updates:SLE-SERVER:12-LTSS:" : {
       "version" : "12",
       "flavor" : "Server-DVD-Incidents",
@@ -30,10 +20,10 @@
    "SUSE:Updates:SLE-SERVER:12-SP3:" : {
       "issues" : {
          "WE_TEST_ISSUES" : "SUSE:Updates:SLE-WE:12-SP3:",
-         "TCM_TEST_ISSUES" : "SUSE:Maintenance:Test:SLE-Module-Toolchain:12:",
+         "TCM_TEST_ISSUES" : "SUSE:Updates:SLE-Module-Toolchain:12:",
          "HPCM_TEST_ISSUES" : "SUSE:Updates:SLE-Module-HPC:12:",
          "SDK_TEST_ISSUES" : "SUSE:Updates:SLE-SDK:12-SP3:",
-         "WSM_TEST_ISSUES" : 
"SUSE:Maintenance:Test:SLE-Module-Web-Scripting:12:"
+         "WSM_TEST_ISSUES" : "SUSE:Updates:SLE-Module-Web-Scripting:12:"
       },
       "flavor" : "Server-DVD-Incidents",
       "version" : "12-SP3",
@@ -54,21 +44,75 @@
          "x86_64"
       ]
    },
-   "SUSE:Updates:SLE-SERVER:12-SP2:" : {
+   "SUSE:Updates:SLE-SERVER:12-SP2-LTSS:" : {
       "version" : "12-SP2",
-      "issues" : {
-         "WE_TEST_ISSUES" : "SUSE:Updates:SLE-WE:12-SP2:",
-         "TCM_TEST_ISSUES" : "SUSE:Maintenance:Test:SLE-Module-Toolchain:12:",
-         "SDK_TEST_ISSUES" : "SUSE:Updates:SLE-SDK:12-SP2:",
-         "HPCM_TEST_ISSUES" : "SUSE:Updates:SLE-Module-HPC:12:",
-         "WSM_TEST_ISSUES" : 
"SUSE:Maintenance:Test:SLE-Module-Web-Scripting:12:"
-      },
       "flavor" : "Server-DVD-Incidents",
       "archs" : [
          "x86_64",
          "s390x",
-         "ppc64le",
-         "aarch64"
+         "ppc64le"
+      ]
+   },
+   "SUSE:Updates:SLE-Live-Patching:12:" : {
+      "version" : "12",
+      "flavor" : "Server-DVD-Incidents-Kernel",
+      "archs" : [
+         "x86_64"
+      ]
+   },
+   "SUSE:Updates:SLE-Live-Patching:12-SP3:" : {
+      "version" : "12-SP3",
+      "flavor" : "Server-DVD-Incidents-Kernel",
+      "archs" : [
+         "x86_64",
+         "ppc64le"
+      ]
+      },
+   "SUSE:Updates:SUSE-CAASP:ALL:" : {
+      "version" : "2.0",
+      "flavor" : "CaaSP-DVD-Incidents",
+      "distri" : "caasp",
+      "archs" : [
+         "x86_64"
+      ]
+      },
+   "SUSE:Updates:SLE-HA:12-SP3:" : {
+      "version" : "12-SP3",
+      "issues" : {
+        "OS_TEST_ISSUES" : "SUSE:Updates:SLE-SERVER:12-SP3:",
+        "HA_TEST_ISSUES" : "SUSE:Updates:SLE-HA:12-SP3:"
+      },
+      "required_issue" : "HA_TEST_ISSUES",
+      "flavor" : "Server-DVD-Incidents-HA",
+      "distri" : "sle",
+      "archs" : [
+         "x86_64"
       ]
-   }
+      },
+   "SUSE:Updates:SLE-HA:12-SP2:" : {
+      "version" : "12-SP2",
+      "issues" : {
+        "OS_TEST_ISSUES" : "SUSE:Updates:SLE-SERVER:12-SP2:",
+        "HA_TEST_ISSUES" : "SUSE:Updates:SLE-HA:12-SP2:"
+      },
+      "flavor" : "Server-DVD-Incidents-HA",
+      "required_issue" : "HA_TEST_ISSUES",
+      "distri" : "sle",
+      "archs" : [
+         "x86_64"
+      ]
+      },
+  "SUSE:Updates:Storage:5:" : {
+     "version" : "12-SP3",
+     "issues" : {
+        "OS_TEST_ISSUES" : "SUSE:Updates:SLE-SERVER:12-SP3:",
+        "SES_TEST_ISSUES" : "SUSE:Updates:Storage:5:"
+     },
+     "required_issue" : "SES_TEST_ISSUES",
+     "flavor" : "Server-DVD-SES-Updates",
+     "distri" : "sle",
+     "archs" : [
+        "x86_64"
+    ]
+    }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openSUSE-release-tools-20180416.dc30723/data/incidents.json 
new/openSUSE-release-tools-20180417.b8337f4/data/incidents.json
--- old/openSUSE-release-tools-20180416.dc30723/data/incidents.json     
2018-04-16 22:53:30.000000000 +0200
+++ new/openSUSE-release-tools-20180417.b8337f4/data/incidents.json     
2018-04-17 13:01:25.000000000 +0200
@@ -24,19 +24,13 @@
       "ARCH" : "x86_64",
       "ISO" : "openSUSE-Leap-42.3-DVD-x86_64.iso"
    },
-   "SUSE:Updates:SLE-DESKTOP:12-SP2:x86_64" : {
-      "FLAVOR" : "Desktop-DVD-Incidents",
-      "VERSION" : "12-SP2",
-      "DISTRI" : "sle",
-      "ARCH" : "x86_64"
-   },
    "SUSE:Updates:SLE-DESKTOP:12-SP3:x86_64" : {
       "FLAVOR" : "Desktop-DVD-Incidents",
       "VERSION" : "12-SP3",
       "DISTRI" : "sle",
       "ARCH" : "x86_64"
    },
-   "SUSE:Updates:SLE-SERVER:12-SP2:s390x" : {
+   "SUSE:Updates:SLE-SERVER:12-SP2-LTSS:s390x" : {
       "DISTRI" : "sle",
       "ARCH" : "s390x",
       "VERSION" : "12-SP2",
@@ -55,11 +49,12 @@
       "FLAVOR" : "Server-DVD-Incidents-Kernel",
       "KGRAFT" : "1"
    },
-   "SUSE:Updates:SLE-SERVER:12-SP2:aarch64" : {
+   "SUSE:Updates:SLE-Live-Patching:12-SP3:ppc64le" : {
       "DISTRI" : "sle",
-      "ARCH" : "aarch64",
-      "FLAVOR" : "Server-DVD-Incidents",
-      "VERSION" : "12-SP2"
+      "ARCH" : "ppc64le",
+      "VERSION" : "12-SP3",
+      "FLAVOR" : "Server-DVD-Incidents-Kernel",
+      "KGRAFT" : "1"
    },
    "SUSE:Updates:SLE-SERVER:12-SP3:x86_64" : {
       "VERSION" : "12-SP3",
@@ -73,8 +68,14 @@
       "ARCH" : "ppc64le",
       "DISTRI" : "sle"
    },
+   "SUSE:Updates:SLE-Live-Patching:12-SP3:x86_64" : {
+      "FLAVOR" : "Server-DVD-Incidents-Kernel",
+      "VERSION" : "12-SP3",
+      "ARCH" : "x86_64",
+      "DISTRI" : "sle"
+   },
    "SUSE:Updates:SLE-Live-Patching:12:x86_64" : {
-      "FLAVOR" : "KGraft",
+      "FLAVOR" : "Server-DVD-Incidents-Kernel",
       "VERSION" : "12",
       "ARCH" : "x86_64",
       "DISTRI" : "sle"
@@ -91,7 +92,7 @@
       "DISTRI" : "sle",
       "ARCH" : "x86_64"
    },
-   "SUSE:Updates:SLE-SERVER:12-SP2:ppc64le" : {
+   "SUSE:Updates:SLE-SERVER:12-SP2-LTSS:ppc64le" : {
       "VERSION" : "12-SP2",
       "FLAVOR" : "Server-DVD-Incidents",
       "ARCH" : "ppc64le",
@@ -109,10 +110,34 @@
       "DISTRI" : "sle",
       "ARCH" : "x86_64"
    },
-   "SUSE:Updates:SLE-SERVER:12-SP2:x86_64" : {
+   "SUSE:Updates:SLE-SERVER:12-SP2-LTSS:x86_64" : {
       "VERSION" : "12-SP2",
       "FLAVOR" : "Server-DVD-Incidents",
       "ARCH" : "x86_64",
       "DISTRI" : "sle"
-   }
+   },
+   "SUSE:Updates:SUSE-CAASP:ALL:x86_64": {
+      "DISTRI" : "caasp",
+      "FLAVOR" : "CaaSP-DVD-Incidents",
+      "VERSION" : "2.0",
+      "ARCH": "x86_64"
+  },
+  "SUSE:Updates:Storage:5:x86_64": {
+     "DISTRI" : "sle",
+     "FLAVOR" : "Server-DVD-SES-Updates",
+     "VERSION" : "12-SP3",
+     "ARCH" : "x86_64"
+  },
+  "SUSE:Updates:SLE-HA:12-SP3:x86_64": {
+      "DISTRI" : "sle",
+      "FLAVOR" : "Server-DVD-Incidents-HA",
+      "VERSION" : "12-SP3",
+      "ARCH" : "x86_64"
+  },
+  "SUSE:Updates:SLE-HA:12-SP2:x86_64": {
+      "DISTRI" : "sle",
+      "FLAVOR" : "Server-DVD-Incidents-HA",
+      "VERSION" : "12-SP2",
+      "ARCH" : "x86_64"
+  }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openSUSE-release-tools-20180416.dc30723/data/kgraft.json 
new/openSUSE-release-tools-20180417.b8337f4/data/kgraft.json
--- old/openSUSE-release-tools-20180416.dc30723/data/kgraft.json        
2018-04-16 22:53:30.000000000 +0200
+++ new/openSUSE-release-tools-20180417.b8337f4/data/kgraft.json        
1970-01-01 01:00:00.000000000 +0100
@@ -1,127 +0,0 @@
-{
-  "SLE12-SP1_Update_10": {
-    "VIRSH_GUESTNAME": "kGraft1b", 
-    "VIRSH_INSTANCE": 6211, 
-    "WORKER_CLASS": "svirt-perseus"
-  }, 
-  "SLE12-SP1_Update_11": {
-    "VIRSH_GUESTNAME": "kGraft1c", 
-    "VIRSH_INSTANCE": 6212, 
-    "WORKER_CLASS": "svirt-perseus"
-  }, 
-  "SLE12-SP1_Update_12": {
-    "VIRSH_GUESTNAME": "kGraft1d", 
-    "VIRSH_INSTANCE": 6213, 
-    "WORKER_CLASS": "svirt-perseus"
-  }, 
-  "SLE12-SP1_Update_13": {
-    "VIRSH_GUESTNAME": "kGraft1e", 
-    "VIRSH_INSTANCE": 6214, 
-    "WORKER_CLASS": "svirt-pegasus"
-  }, 
-  "SLE12-SP1_Update_14": {
-    "VIRSH_GUESTNAME": "kGraft1f", 
-    "VIRSH_INSTANCE": 6215, 
-    "WORKER_CLASS": "svirt-pegasus"
-  }, 
-  "SLE12-SP1_Update_5": {
-    "VIRSH_GUESTNAME": "kGraft16", 
-    "VIRSH_INSTANCE": 6206, 
-    "WORKER_CLASS": "svirt-perseus"
-  }, 
-  "SLE12-SP1_Update_6": {
-    "VIRSH_GUESTNAME": "kGraft17", 
-    "VIRSH_INSTANCE": 6207, 
-    "WORKER_CLASS": "svirt-perseus"
-  }, 
-  "SLE12-SP1_Update_7": {
-    "VIRSH_GUESTNAME": "kGraft18", 
-    "VIRSH_INSTANCE": 6208, 
-    "WORKER_CLASS": "svirt-pegasus"
-  }, 
-  "SLE12-SP1_Update_8": {
-    "VIRSH_GUESTNAME": "kGraft19", 
-    "VIRSH_INSTANCE": 6209, 
-    "WORKER_CLASS": "svirt-pegasus"
-  }, 
-  "SLE12-SP1_Update_9": {
-    "VIRSH_GUESTNAME": "kGraft1a", 
-    "VIRSH_INSTANCE": 6210, 
-    "WORKER_CLASS": "svirt-pegasus"
-  }, 
-  "SLE12-SP2_Update_0": {
-    "VIRSH_GUESTNAME": "kGraft20", 
-    "VIRSH_INSTANCE": 6300, 
-    "WORKER_CLASS": "svirt-perseus"
-  }, 
-  "SLE12-SP2_Update_1": {
-    "VIRSH_GUESTNAME": "kGraft21", 
-    "VIRSH_INSTANCE": 6301, 
-    "WORKER_CLASS": "svirt-perseus"
-  }, 
-  "SLE12-SP2_Update_2": {
-    "VIRSH_GUESTNAME": "kGraft22", 
-    "VIRSH_INSTANCE": 6302, 
-    "WORKER_CLASS": "svirt-perseus"
-  }, 
-  "SLE12-SP2_Update_3": {
-    "VIRSH_GUESTNAME": "kGraft23", 
-    "VIRSH_INSTANCE": 6303, 
-    "WORKER_CLASS": "svirt-pegasus"
-  }, 
-  "SLE12-SP2_Update_4": {
-    "VIRSH_GUESTNAME": "kGraft24", 
-    "VIRSH_INSTANCE": 6304, 
-    "WORKER_CLASS": "svirt-pegasus"
-  }, 
-  "SLE12-SP2_Update_5": {
-    "VIRSH_GUESTNAME": "kGraft25", 
-    "VIRSH_INSTANCE": 6305, 
-    "WORKER_CLASS": "svirt-pegasus"
-  }, 
-  "SLE12-SP2_Update_6": {
-    "VIRSH_GUESTNAME": "kGraft26", 
-    "VIRSH_INSTANCE": 6306, 
-    "WORKER_CLASS": "svirt-perseus"
-  }, 
-  "SLE12_Update_13": {
-    "VIRSH_GUESTNAME": "kGraft0d", 
-    "VIRSH_INSTANCE": 6113, 
-    "WORKER_CLASS": "svirt-pegasus"
-  }, 
-  "SLE12_Update_14": {
-    "VIRSH_GUESTNAME": "kGraft0e", 
-    "VIRSH_INSTANCE": 6114, 
-    "WORKER_CLASS": "svirt-pegasus"
-  }, 
-  "SLE12_Update_15": {
-    "VIRSH_GUESTNAME": "kGraft0f", 
-    "VIRSH_INSTANCE": 6115, 
-    "WORKER_CLASS": "svirt-pegasus"
-  }, 
-  "SLE12_Update_16": {
-    "VIRSH_GUESTNAME": "kGraft0g", 
-    "VIRSH_INSTANCE": 6116, 
-    "WORKER_CLASS": "svirt-pegasus"
-  }, 
-  "SLE12_Update_17": {
-    "VIRSH_GUESTNAME": "kGraft0h", 
-    "VIRSH_INSTANCE": 6117, 
-    "WORKER_CLASS": "svirt-pegasus"
-  }, 
-  "SLE12_Update_18": {
-    "VIRSH_GUESTNAME": "kGraft0i", 
-    "VIRSH_INSTANCE": 6118, 
-    "WORKER_CLASS": "svirt-perseus"
-  }, 
-  "SLE12_Update_19": {
-    "VIRSH_GUESTNAME": "kGraft0j", 
-    "VIRSH_INSTANCE": 6119, 
-    "WORKER_CLASS": "svirt-perseus"
-  }, 
-  "SLE12_Update_20": {
-    "VIRSH_GUESTNAME": "kGraft0k", 
-    "VIRSH_INSTANCE": 6120, 
-    "WORKER_CLASS": "svirt-pegasus"
-  }
-}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openSUSE-release-tools-20180416.dc30723/data/repos.json 
new/openSUSE-release-tools-20180417.b8337f4/data/repos.json
--- old/openSUSE-release-tools-20180416.dc30723/data/repos.json 2018-04-16 
22:53:30.000000000 +0200
+++ new/openSUSE-release-tools-20180417.b8337f4/data/repos.json 2018-04-17 
13:01:25.000000000 +0200
@@ -24,27 +24,6 @@
     }
   }, 
   "https://openqa.suse.de": {
-    "SUSE:Updates:SLE-DESKTOP:12-SP2:x86_64": {
-      "incidents": {
-        "OS": "SUSE:Maintenance:Test:SLE-DESKTOP:12-SP2:x86_64", 
-        "SDK": "SUSE:Maintenance:Test:SLE-SDK:12-SP2:x86_64" 
-      }, 
-      "repos": [
-        
"http://download.suse.de/ibs/SUSE/Updates/SLE-DESKTOP/12-SP2/x86_64/update/";, 
-        
"http://download.suse.de/ibs/SUSE:/Maintenance:/Test:/SLE-DESKTOP:/12-SP2:/x86_64/update/";,
 
-        
"http://download.suse.de/ibs/SUSE:/Maintenance:/Test:/SLE-SDK:/12-SP2:/x86_64/update/";,
 
-        
"http://download.suse.de/ibs/SUSE/Updates/SLE-SDK/12-SP2/x86_64/update/";
-      ], 
-      "settings": [
-        {
-          "ARCH": "x86_64", 
-          "DISTRI": "sle", 
-          "FLAVOR": "Desktop-DVD-Updates", 
-          "VERSION": "12-SP2"
-        }
-      ], 
-      "test": "qam-gnome"
-    }, 
     "SUSE:Updates:SLE-DESKTOP:12-SP3:x86_64": {
       "incidents": {
         "OS": "SUSE:Maintenance:Test:SLE-DESKTOP:12-SP3:x86_64", 
@@ -102,28 +81,13 @@
       ], 
       "test": "qam-gnome"
     }, 
-    "SUSE:Updates:SLE-SERVER:12-SP2:x86_64": {
+    "SUSE:Updates:SLE-SERVER:12-SP2-LTSS:x86_64": {
       "incidents": {
-        "HPCM": "SUSE:Maintenance:Test:SLE-Module-HPC:12:x86_64", 
-        "OS": "SUSE:Maintenance:Test:SLE-SERVER:12-SP2:x86_64", 
-        "SDK": "SUSE:Maintenance:Test:SLE-SDK:12-SP2:x86_64", 
-        "TCM": "SUSE:Maintenance:Test:SLE-Module-Toolchain:12:x86_64", 
-        "WE": "SUSE:Maintenance:Test:SLE-WE:12-SP2:x86_64", 
-        "WSM": "SUSE:Maintenance:Test:SLE-Module-Web-Scripting:12:x86_64"
+        "OS": "SUSE:Maintenance:Test:SLE-SERVER:12-SP2-LTSS:x86_64" 
       }, 
       "repos": [
-        
"http://download.suse.de/ibs/SUSE/Updates/SLE-SERVER/12-SP2/x86_64/update/";, 
-        
"http://download.suse.de/ibs/SUSE:/Maintenance:/Test:/SLE-SERVER:/12-SP2:/x86_64/update/";,
 
-        
"http://download.suse.de/ibs/SUSE/Updates/SLE-SDK/12-SP2/x86_64/update/";, 
-        
"http://download.suse.de/ibs/SUSE:/Maintenance:/Test:/SLE-SDK:/12-SP2:/x86_64/update/";,
 
-        
"http://download.suse.de/ibs/SUSE/Updates/SLE-WE/12-SP2/x86_64/update/";, 
-        
"http://download.suse.de/ibs/SUSE:/Maintenance:/Test:/SLE-WE:/12-SP2:/x86_64/update/";,
 
-        
"http://download.suse.de/ibs/SUSE/Updates/SLE-Module-Web-Scripting/12/x86_64/update/";,
 
-        
"http://download.suse.de/ibs/SUSE:/Maintenance:/Test:/SLE-Module-Web-Scripting:/12:/x86_64/update/";,
 
-        
"http://download.suse.de/ibs/SUSE/Updates/SLE-Module-Toolchain/12/x86_64/update/";,
 
-        
"http://download.suse.de/ibs/SUSE:/Maintenance:/Test:/SLE-Module-Toolchain:/12:/x86_64/update/";,
 
-        
"http://download.suse.de/ibs/SUSE/Updates/SLE-Module-HPC/12/x86_64/update/";, 
-        
"http://download.suse.de/ibs/SUSE:/Maintenance:/Test:/SLE-Module-HPC:/12:/x86_64/update/";
+        
"http://download.suse.de/ibs/SUSE/Updates/SLE-SERVER/12-SP2-LTSS/x86_64/update/";,
 
+        
"http://download.suse.de/ibs/SUSE:/Maintenance:/Test:/SLE-SERVER:/12-SP2-LTSS:/x86_64/update/";
 
       ], 
       "settings": [
         {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openSUSE-release-tools-20180416.dc30723/openqa-maintenance2.py 
new/openSUSE-release-tools-20180417.b8337f4/openqa-maintenance2.py
--- old/openSUSE-release-tools-20180416.dc30723/openqa-maintenance2.py  
1970-01-01 01:00:00.000000000 +0100
+++ new/openSUSE-release-tools-20180417.b8337f4/openqa-maintenance2.py  
2018-04-17 13:01:25.000000000 +0200
@@ -0,0 +1,30 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2015-2017 SUSE LLC
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from oqamaint.cli import CommandLineInterface
+
+import sys
+
+if __name__ == "__main__":
+    app = CommandLineInterface()
+    sys.exit(app.main())
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openSUSE-release-tools-20180416.dc30723/oqamaint/ReviewBot.py 
new/openSUSE-release-tools-20180417.b8337f4/oqamaint/ReviewBot.py
--- old/openSUSE-release-tools-20180416.dc30723/oqamaint/ReviewBot.py   
1970-01-01 01:00:00.000000000 +0100
+++ new/openSUSE-release-tools-20180417.b8337f4/oqamaint/ReviewBot.py   
2018-04-19 15:30:15.653156478 +0200
@@ -0,0 +1 @@
+symbolic link to ../ReviewBot.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openSUSE-release-tools-20180416.dc30723/oqamaint/cli.py 
new/openSUSE-release-tools-20180417.b8337f4/oqamaint/cli.py
--- old/openSUSE-release-tools-20180416.dc30723/oqamaint/cli.py 1970-01-01 
01:00:00.000000000 +0100
+++ new/openSUSE-release-tools-20180417.b8337f4/oqamaint/cli.py 2018-04-17 
13:01:25.000000000 +0200
@@ -0,0 +1,112 @@
+# -*- coding: utf-8 -*-
+
+# standard library
+import logging
+import os.path as opa
+import simplejson as json
+import sys
+
+# external dependency
+from openqa_client.client import OpenQA_Client
+
+# from package itself
+import osc
+from openqabot import OpenQABot
+from opensuse import openSUSEUpdate
+import ReviewBot
+from suse import SUSEUpdate
+
+
+class CommandLineInterface(ReviewBot.CommandLineInterface):
+
+    def __init__(self, *args, **kwargs):
+        ReviewBot.CommandLineInterface.__init__(self, *args, **kwargs)
+        self.clazz = OpenQABot
+
+    def get_optparser(self):
+        parser = ReviewBot.CommandLineInterface.get_optparser(self)
+        parser.add_option("--force", action="store_true", help="recheck 
requests that are already considered done")
+        parser.add_option("--no-comment", dest='comment', action="store_false",
+                          default=True, help="don't actually post comments to 
obs")
+        parser.add_option("--openqa", metavar='HOST', help="openqa api host")
+        parser.add_option(
+            "--data",
+            default=opa.abspath(
+                opa.dirname(
+                    sys.argv[0])),
+            help="Path to metadata dir (data/*.json)")
+        return parser
+
+    def _load_metadata(self):
+        path = self.options.data
+        project = {}
+
+        with open(opa.join(path, "data/repos.json"), 'r') as f:
+            target = json.load(f)
+
+        with open(opa.join(path, "data/apimap.json"), 'r') as f:
+            api = json.load(f)
+
+        with open(opa.join(path, "data/incidents.json"), 'r') as f:
+            for i, j in json.load(f).items():
+                if i.startswith('SUSE'):
+                    project[i] = SUSEUpdate(j)
+                elif i.startswith('openSUSE'):
+                    project[i] = openSUSEUpdate(j)
+                else:
+                    raise "Unknown openQA", i
+        return project, target, api
+
+    def postoptparse(self):
+        # practically quiet
+        level = logging.WARNING
+        if (self.options.debug):
+            level = logging.DEBUG
+        elif (self.options.verbose):
+            # recomended variant
+            level = logging.INFO
+
+        self.logger = logging.getLogger(self.optparser.prog)
+        self.logger.setLevel(level)
+        handler = logging.StreamHandler()
+        formatter = logging.Formatter('%(levelname)-2s: %(message)s')
+        handler.setFormatter(formatter)
+        self.logger.addHandler(handler)
+
+        osc.conf.get_config(override_apiurl=self.options.apiurl)
+
+        if (self.options.osc_debug):
+            osc.conf.config['debug'] = 1
+
+        self.checker = self.setup_checker()
+
+        if self.options.config:
+            self.checker.load_config(self.options.config)
+
+        if self.options.review_mode:
+            self.checker.review_mode = self.options.review_mode
+
+        if self.options.fallback_user:
+            self.checker.fallback_user = self.options.fallback_user
+
+        if self.options.fallback_group:
+            self.checker.fallback_group = self.options.fallback_group
+
+    def setup_checker(self):
+        bot = ReviewBot.CommandLineInterface.setup_checker(self)
+
+        if self.options.force:
+            bot.force = True
+
+        bot.do_comments = self.options.comment
+
+        if not self.options.openqa:
+            raise osc.oscerr.WrongArgs("missing openqa url")
+
+        bot.openqa = OpenQA_Client(server=self.options.openqa)
+        project, target, api = self._load_metadata()
+        bot.api_map = api
+        bot.tgt_repo = target
+        bot.project_settings = project
+
+        return bot
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openSUSE-release-tools-20180416.dc30723/oqamaint/openqabot.py 
new/openSUSE-release-tools-20180417.b8337f4/oqamaint/openqabot.py
--- old/openSUSE-release-tools-20180416.dc30723/oqamaint/openqabot.py   
1970-01-01 01:00:00.000000000 +0100
+++ new/openSUSE-release-tools-20180417.b8337f4/oqamaint/openqabot.py   
2018-04-17 13:01:25.000000000 +0200
@@ -0,0 +1,605 @@
+# -*- coding: utf-8 -*-
+
+from collections import namedtuple
+from datetime import date
+import md5
+from pprint import pformat
+import re
+from urllib2 import HTTPError
+
+import requests
+import osc.core
+
+import ReviewBot
+from osclib.comments import CommentAPI
+
+from suse import SUSEUpdate
+
+try:
+    from xml.etree import cElementTree as ET
+except ImportError:
+    from xml.etree import ElementTree as ET
+
+
+try:
+    import simplejson as json
+except ImportError:
+    import json
+
+QA_UNKNOWN = 0
+QA_INPROGRESS = 1
+QA_FAILED = 2
+QA_PASSED = 3
+
+Package = namedtuple('Package', ('name', 'version', 'release'))
+pkgname_re = 
re.compile(r'(?P<name>.+)-(?P<version>[^-]+)-(?P<release>[^-]+)\.(?P<arch>[^.]+)\.rpm')
+comment_marker_re = re.compile(
+    r'<!-- openqa state=(?P<state>done|seen)(?: 
result=(?P<result>accepted|declined|none))?(?: revision=(?P<revision>\d+))? 
-->')
+
+
+class OpenQABot(ReviewBot.ReviewBot):
+
+    """ check ABI of library packages
+    """
+
+    def __init__(self, *args, **kwargs):
+        super(OpenQABot, self).__init__(*args, **kwargs)
+        self.tgt_repo = {}
+        self.project_settings = {}
+        self.api_map = {}
+
+        self.force = False
+        self.openqa = None
+        self.commentapi = CommentAPI(self.apiurl)
+        self.update_test_builds = {}
+        self.pending_target_repos = set()
+        self.openqa_jobs = {}
+
+    def gather_test_builds(self):
+        for prj, u in self.tgt_repo[self.openqa.baseurl].items():
+            buildnr = 0
+            cjob = 0
+            for j in self.jobs_for_target(u):
+                # avoid going backwards in job ID
+                if cjob > int(j['id']):
+                    continue
+                buildnr = j['settings']['BUILD']
+                cjob = int(j['id'])
+            self.update_test_builds[prj] = buildnr
+            jobs = self.jobs_for_target(u, build=buildnr)
+            self.openqa_jobs[prj] = jobs
+            if self.calculate_qa_status(jobs) == QA_INPROGRESS:
+                self.pending_target_repos.add(prj)
+
+    # reimplemention from baseclass
+    def check_requests(self):
+
+        if self.ibs:
+            self.check_suse_incidents()
+
+        # first calculate the latest build number for current jobs
+        self.gather_test_builds()
+
+        started = []
+        # then check progress on running incidents
+        for req in self.requests:
+            jobs = self.request_get_openqa_jobs(req, incident=True, 
test_repo=True)
+            ret = self.calculate_qa_status(jobs)
+            if ret != QA_UNKNOWN:
+                started.append(req)
+
+        all_requests = self.requests
+        self.requests = started
+        self.logger.debug("check started requests")
+        super(OpenQABot, self).check_requests()
+
+        self.requests = all_requests
+
+        skipped_one = False
+        # now make sure the jobs are for current repo
+        for prj, u in self.tgt_repo[self.openqa.baseurl].items():
+            if prj in self.pending_target_repos:
+                skipped_one = True
+                continue
+            self.trigger_build_for_target(prj, u)
+
+        # do not schedule new incidents unless we finished
+        # last wave
+        if skipped_one:
+            return
+        self.logger.debug("Check all requests")
+        super(OpenQABot, self).check_requests()
+
+    # check a set of repos for their primary checksums
+    @staticmethod
+    def calculate_repo_hash(repos):
+        m = md5.new()
+        # if you want to force it, increase this number
+        m.update('b')
+        for url in repos:
+            url += '/repodata/repomd.xml'
+            try:
+                root = ET.parse(osc.core.http_GET(url)).getroot()
+            except HTTPError:
+                raise
+            cs = root.find(
+                
'.//{http://linux.duke.edu/metadata/repo}data[@type="primary"]/{http://linux.duke.edu/metadata/repo}checksum')
+            m.update(cs.text)
+        return m.hexdigest()
+
+    def is_incident_in_testing(self, incident):
+        # hard coded for now as we only run this code for SUSE Maintenance 
workflow
+        project = 'SUSE:Maintenance:{}'.format(incident)
+
+        xpath = "(state/@name='review') and (action/source/@project='{}' and 
action/@type='maintenance_release')".format(project)
+        res = osc.core.search(self.apiurl, request=xpath)['request']
+        # return the one and only (or None)
+        return res.find('request')
+
+    def calculate_incidents(self, incidents):
+        """
+        get incident numbers from SUSE:Maintenance:Test project
+        returns dict with openQA var name : string with numbers
+        """
+        self.logger.debug("calculate_incidents: {}".format(pformat(incidents)))
+        l_incidents = []
+        for kind, prj in incidents.items():
+            packages = osc.core.meta_get_packagelist(self.apiurl, prj)
+            incidents = []
+            # filter out incidents in staging
+            for incident in packages:
+                # remove patchinfo. prefix
+                incident = incident.replace('_', '.').split('.')[1]
+                req = self.is_incident_in_testing(incident)
+                # without release request it's in staging
+                if not req:
+                    continue
+
+                # skip kgraft patches from aggregation
+                req_ = osc.core.Request()
+                req_.read(req)
+                src_prjs = {a.src_project for a in req_.actions}
+                if SUSEUpdate.kgraft_target(self.apiurl, src_prjs.pop()):
+                    self.logger.debug("calculate_incidents: Incident is kgraft 
- {} ".format(incident))
+                    continue
+
+                incidents.append(incident)
+
+            l_incidents.append((kind + '_TEST_ISSUES', ','.join(incidents)))
+        self.logger.debug("Calculate 
incidents:{}".format(pformat(l_incidents)))
+        return l_incidents
+
+    def jobs_for_target(self, data, build=None):
+        settings = data['settings'][0]
+        values = {
+            'distri': settings['DISTRI'],
+            'version': settings['VERSION'],
+            'arch': settings['ARCH'],
+            'flavor': settings['FLAVOR'],
+            'scope': 'relevant',
+            'latest': '1',
+        }
+        if build:
+            values['build'] = build
+        else:
+            values['test'] = data['test']
+        self.logger.debug("Get jobs: {}".format(pformat(values)))
+        return self.openqa.openqa_request('GET', 'jobs', values)['jobs']
+
+    # we don't know the current BUILD and querying all jobs is too expensive
+    # so we need to check for one known TEST first
+    # if that job doesn't contain the proper hash, we trigger a new one
+    # and then we know the build
+    def trigger_build_for_target(self, prj, data):
+        today = date.today().strftime("%Y%m%d")
+
+        try:
+            repohash = self.calculate_repo_hash(data['repos'])
+        except HTTPError as e:
+            self.logger.debug("REPOHAS not calculated with response 
{}".format(e))
+            return
+
+        buildnr = None
+        jobs = self.jobs_for_target(data)
+        for job in jobs:
+            if job['settings'].get('REPOHASH', '') == repohash:
+                # take the last in the row
+                buildnr = job['settings']['BUILD']
+        self.update_test_builds[prj] = buildnr
+        # ignore old build numbers, we want a fresh run every day
+        # to find regressions in the tests and to get data about
+        # randomly failing tests
+        if buildnr and buildnr.startswith(today):
+            return
+
+        buildnr = 0
+
+        # not found, then check for the next free build nr
+        for job in jobs:
+            build = job['settings']['BUILD']
+            if build and build.startswith(today):
+                try:
+                    nr = int(build.split('-')[1])
+                    if nr > buildnr:
+                        buildnr = nr
+                except ValueError:
+                    continue
+
+        buildnr = "{!s}-{:d}".format(today, buildnr + 1)
+
+        for s in data['settings']:
+            # now schedule it for real
+            if 'incidents' in data.keys():
+                for x, y in self.calculate_incidents(data['incidents']):
+                    s[x] = y
+            s['BUILD'] = buildnr
+            s['REPOHASH'] = repohash
+            self.logger.debug("Prepared: {}".format(pformat(s)))
+            if not self.dryrun:
+                try:
+                    self.logger.info("Openqa isos POST {}".format(pformat(s)))
+                    self.openqa.openqa_request('POST', 'isos', data=s, 
retries=1)
+                except Exception as e:
+                    self.logger.error(e)
+        self.update_test_builds[prj] = buildnr
+
+    def request_get_openqa_jobs(self, req, incident=True, test_repo=False):
+        ret = None
+        types = {a.type for a in req.actions}
+        if 'maintenance_release' in types:
+            src_prjs = {a.src_project for a in req.actions}
+            if len(src_prjs) != 1:
+                raise Exception("can't handle maintenance_release from 
different incidents")
+            build = src_prjs.pop()
+            tgt_prjs = {a.tgt_project for a in req.actions}
+            ret = []
+            if incident:
+                ret += self.openqa_jobs[build]
+            for prj in sorted(tgt_prjs):
+                repo_settings = self.tgt_repo.get(self.openqa.baseurl, {})
+                if test_repo and prj in repo_settings:
+                    repo_jobs = self.openqa_jobs[prj]
+                    ret += repo_jobs
+
+        return ret
+
+    def calculate_qa_status(self, jobs=None):
+        if not jobs:
+            return QA_UNKNOWN
+
+        j = {}
+        has_failed = False
+        in_progress = False
+
+        for job in jobs:
+            if job['clone_id']:
+                continue
+            name = job['name']
+
+            if name in j and int(job['id']) < int(j[name]['id']):
+                continue
+            j[name] = job
+
+            if job['state'] not in ('cancelled', 'done'):
+                in_progress = True
+            else:
+                if job['result'] != 'passed' and job['result'] != 'softfailed':
+                    has_failed = True
+
+        if not j:
+            return QA_UNKNOWN
+        if in_progress:
+            return QA_INPROGRESS
+        if has_failed:
+            return QA_FAILED
+
+        return QA_PASSED
+
+    def add_comment(self, msg, state, request_id=None, result=None):
+        if not self.do_comments:
+            return
+
+        comment = "<!-- openqa state={!s}{!s} -->\n".format(state, ' 
result={!s}'.format(result) if result else '')
+        comment += "\n" + msg
+
+        info = self.find_obs_request_comment(request_id=request_id)
+        comment_id = info.get('id', None)
+
+        if state == info.get('state', 'missing'):
+            lines_before = len(info['comment'].split('\n'))
+            lines_after = len(comment.split('\n'))
+            if lines_before == lines_after:
+                self.logger.info("not worth the update, previous comment %s is 
state %s", comment_id, info['state'])
+                return
+
+        self.logger.info("adding comment to %s, state %s result %s", 
request_id, state, result)
+        self.logger.info("message: %s", msg)
+        if not self.dryrun:
+            if comment_id:
+                self.commentapi.delete(comment_id)
+            self.commentapi.add_comment(request_id=request_id, 
comment=str(comment))
+
+    # escape markdown
+    @staticmethod
+    def emd(str):
+        return str.replace('_', r'\_')
+
+    @staticmethod
+    def get_step_url(testurl, modulename):
+        failurl = testurl + '/modules/{!s}/fails'.format(modulename)
+        fails = requests.get(failurl).json()
+        failed_step = fails.get('first_failed_step', 1)
+        return "[{!s}]({!s}#step/{!s}/{:d})".format(OpenQABot.emd(modulename), 
testurl, modulename, failed_step)
+
+    @staticmethod
+    def job_test_name(job):
+        return "{!s}@{!s}".format(OpenQABot.emd(job['settings']['TEST']), 
OpenQABot.emd(job['settings']['MACHINE']))
+
+    def summarize_one_openqa_job(self, job):
+        testurl = osc.core.makeurl(self.openqa.baseurl, ['tests', 
str(job['id'])])
+        if not job['result'] in ['passed', 'failed', 'softfailed']:
+            rstring = job['result']
+            if rstring == 'none':
+                return None
+            return '\n- [{!s}]({!s}) is {!s}'.format(self.job_test_name(job), 
testurl, rstring)
+
+        modstrings = []
+        for module in job['modules']:
+            if module['result'] != 'failed':
+                continue
+            modstrings.append(self.get_step_url(testurl, module['name']))
+
+        if modstrings:
+            return '\n- [{!s}]({!s}) failed in 
{!s}'.format(self.job_test_name(job), testurl, ','.join(modstrings))
+        elif job['result'] == 'failed':  # rare case: fail without module fails
+            return '\n- [{!s}]({!s}) failed'.format(self.job_test_name(job), 
testurl)
+        return ''
+
+    def summarize_openqa_jobs(self, jobs):
+        groups = {}
+        for job in jobs:
+            gl = "{!s}@{!s}".format(self.emd(job['group']), 
self.emd(job['settings']['FLAVOR']))
+            if gl not in groups:
+                groupurl = osc.core.makeurl(self.openqa.baseurl, ['tests', 
'overview'],
+                                            {'version': 
job['settings']['VERSION'],
+                                             'groupid': job['group_id'],
+                                             'flavor': 
job['settings']['FLAVOR'],
+                                             'distri': 
job['settings']['DISTRI'],
+                                             'build': job['settings']['BUILD'],
+                                             })
+                groups[gl] = {'title': "__Group [{!s}]({!s})__\n".format(gl, 
groupurl),
+                              'passed': 0, 'unfinished': 0, 'failed': []}
+
+            job_summary = self.summarize_one_openqa_job(job)
+            if job_summary is None:
+                groups[gl]['unfinished'] = groups[gl]['unfinished'] + 1
+                continue
+            # None vs ''
+            if not len(job_summary):
+                groups[gl]['passed'] = groups[gl]['passed'] + 1
+                continue
+            # if there is something to report, hold the request
+            # TODO: what is this ?
+            # qa_state = QA_FAILED
+            # gmsg = groups[gl]
+
+            groups[gl]['failed'].append(job_summary)
+
+        msg = ''
+        for group in sorted(groups.keys()):
+            msg += "\n\n" + groups[group]['title']
+            infos = []
+            if groups[group]['passed']:
+                infos.append("{:d} tests 
passed".format(groups[group]['passed']))
+            if len(groups[group]['failed']):
+                infos.append("{:d} tests 
failed".format(len(groups[group]['failed'])))
+            if groups[group]['unfinished']:
+                infos.append("{:d} unfinished 
tests".format(groups[group]['unfinished']))
+            msg += "(" + ', '.join(infos) + ")\n"
+            for fail in groups[group]['failed']:
+                msg += fail
+        return msg.rstrip('\n')
+
+    def check_one_request(self, req):
+        ret = None
+
+        try:
+            jobs = self.request_get_openqa_jobs(req)
+            qa_state = self.calculate_qa_status(jobs)
+            self.logger.debug("request %s state %s", req.reqid, qa_state)
+            msg = None
+            if self.force or qa_state == QA_UNKNOWN:
+                ret = super(OpenQABot, self).check_one_request(req)
+                jobs = self.request_get_openqa_jobs(req)
+
+                if self.force:
+                    # make sure to delete previous comments if we're forcing
+                    info = self.find_obs_request_comment(request_id=req.reqid)
+                    if 'id' in info:
+                        self.logger.debug("deleting old comment %s", 
info['id'])
+                        if not self.dryrun:
+                            self.commentapi.delete(info['id'])
+
+                if jobs:
+                    # no notification until the result is done
+                    osc.core.change_review_state(self.apiurl, req.reqid, 
newstate='new',
+                                                 by_group=self.review_group, 
by_user=self.review_user,
+                                                 message='now testing in 
openQA')
+                else:
+                    msg = "no openQA tests defined"
+                    self.add_comment(msg, 'done', request_id=req.reqid, 
result='accepted')
+                    ret = True
+            elif qa_state == QA_FAILED or qa_state == QA_PASSED:
+                # don't take test repo results into the calculation of total
+                # this is for humans to decide which incident broke the test 
repo
+                jobs += self.request_get_openqa_jobs(req, incident=False, 
test_repo=True)
+                if self.calculate_qa_status(jobs) == QA_INPROGRESS:
+                    self.logger.info(
+                        "incident tests for request %s are done, but need to 
wait for test repo", req.reqid)
+                    return
+                if qa_state == QA_PASSED:
+                    msg = "openQA tests passed\n"
+                    result = 'accepted'
+                    ret = True
+                else:
+                    msg = "openQA tests problematic\n"
+                    result = 'declined'
+                    ret = False
+
+                msg += self.summarize_openqa_jobs(jobs)
+                self.add_comment(msg, 'done', result=result, 
request_id=req.reqid)
+            elif qa_state == QA_INPROGRESS:
+                self.logger.info("request %s still in progress", req.reqid)
+            else:
+                raise Exception("unknown QA state %d", qa_state)
+
+        except Exception:
+            import traceback
+            self.logger.error("unhandled exception in openQA Bot")
+            self.logger.error(traceback.format_exc())
+            ret = None
+
+        return ret
+
+    def find_obs_request_comment(self, request_id=None, project_name=None):
+        """Return previous comments (should be one)."""
+        if self.do_comments:
+            comments = self.commentapi.get_comments(request_id=request_id, 
project_name=project_name)
+            for c in comments.values():
+                m = comment_marker_re.match(c['comment'])
+                if m:
+                    return {
+                        'id': c['id'],
+                        'state': m.group('state'),
+                        'result': m.group('result'),
+                        'comment': c['comment'],
+                        'revision': m.group('revision')}
+        return {}
+
+    def check_product(self, job, product_prefix):
+        pmap = self.api_map[product_prefix]
+        posts = []
+        for arch in pmap['archs']:
+            need = False
+            settings = {'VERSION': pmap['version'], 'ARCH': arch}
+            settings['DISTRI'] = 'sle' if 'distri' not in pmap else 
pmap['distri']
+            issues = pmap.get('issues', {})
+            issues['OS_TEST_ISSUES'] = issues.get('OS_TEST_ISSUES', 
product_prefix)
+            required_issue = pmap.get('required_issue', False)
+            for key, prefix in issues.items():
+                self.logger.debug("{} {}".format(key, prefix))
+                if prefix + arch in job['channels']:
+                    settings[key] = str(job['id'])
+                    need = True
+            if required_issue:
+                if required_issue not in settings:
+                    need = False
+
+            if need:
+                update = self.project_settings[product_prefix + arch]
+                update.apiurl = self.apiurl
+                update.logger = self.logger
+                for j in update.settings(
+                        update.maintenance_project + ':' + str(job['id']),
+                        product_prefix + arch, []):
+                    if not job.get('openqa_build'):
+                        job['openqa_build'] = update.get_max_revision(job)
+                    if not job.get('openqa_build'):
+                        return []
+                    j['BUILD'] += '.' + str(job['openqa_build'])
+                    j.update(settings)
+                    # kGraft jobs can have different version
+                    if 'real_version' in j:
+                        j['VERSION'] = j['real_version']
+                        del j['real_version']
+                    posts.append(j)
+        self.logger.debug("Pmap: {} Posts: {}".format(pmap, posts))
+        return posts
+
+    def incident_openqa_jobs(self, s):
+        return self.openqa.openqa_request(
+            'GET', 'jobs',
+            {
+                'distri': s['DISTRI'],
+                'version': s['VERSION'],
+                'arch': s['ARCH'],
+                'flavor': s['FLAVOR'],
+                'build': s['BUILD'],
+                'scope': 'relevant',
+                'latest': '1'
+            })['jobs']
+
+    def check_suse_incidents(self):
+        for inc in 
requests.get('https://maintenance.suse.de/api/incident/active/').json():
+            self.logger.info("Incident number: {}".format(inc))
+
+            job = requests.get('https://maintenance.suse.de/api/incident/' + 
inc).json()
+
+            if job['meta']['state'] in ['final', 'gone']:
+                continue
+            # required in job: project, id, channels
+            self.test_job(job['base'])
+
+    def test_job(self, job):
+        self.logger.debug("Called test_job with: {}".format(job))
+        incident_project = str(job['project'])
+        try:
+            comment_info = 
self.find_obs_request_comment(project_name=incident_project)
+        except HTTPError as e:
+            self.logger.debug("Couldn't loaadd comments - {}".format(e))
+            return
+        comment_id = comment_info.get('id', None)
+        comment_build = str(comment_info.get('revision', ''))
+
+        openqa_posts = []
+        for prod in self.api_map.keys():
+            self.logger.debug("{} -- product in apimap".format(prod))
+            openqa_posts += self.check_product(job, prod)
+        openqa_jobs = []
+        for s in openqa_posts:
+            jobs = self.incident_openqa_jobs(s)
+            # take the project comment as marker for not posting jobs
+            if not len(jobs) and comment_build != str(job['openqa_build']):
+                if self.dryrun:
+                    self.logger.info('WOULD 
POST:{}'.format(pformat(json.dumps(s, sort_keys=True))))
+                else:
+                    self.logger.info("Posted: {}".format(pformat(json.dumps(s, 
sort_keys=True))))
+                    self.openqa.openqa_request('POST', 'isos', data=s, 
retries=1)
+                    openqa_jobs += self.incident_openqa_jobs(s)
+            else:
+                self.logger.info("{} got {}".format(pformat(s), len(jobs)))
+                openqa_jobs += jobs
+
+        self.openqa_jobs[incident_project] = openqa_jobs
+
+        if len(openqa_jobs) == 0:
+            self.logger.debug("No openqa jobs defined")
+            return
+        # print openqa_jobs
+        msg = self.summarize_openqa_jobs(openqa_jobs)
+        state = 'seen'
+        result = 'none'
+        qa_status = self.calculate_qa_status(openqa_jobs)
+        if qa_status == QA_PASSED:
+            result = 'accepted'
+            state = 'done'
+        if qa_status == QA_FAILED:
+            result = 'declined'
+            state = 'done'
+        comment = "<!-- openqa state={!s} result={!s} revision={!s} 
-->\n".format(
+            state, result, job.get('openqa_build'))
+        comment += msg
+
+        if comment_id and state != 'done':
+            self.logger.info("%s is already commented, wait until done", 
incident_project)
+            return
+        if comment_info.get('comment', '').rstrip('\n') == 
comment.rstrip('\n'):
+            self.logger.info("%s comment did not change", incident_project)
+            return
+
+        self.logger.info("adding comment to %s, state %s", incident_project, 
state)
+        if not self.dryrun:
+            if comment_id:
+                self.logger.debug("delete comment: {}".format(comment_id))
+                self.commentapi.delete(comment_id)
+            self.commentapi.add_comment(project_name=str(incident_project), 
comment=str(comment))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openSUSE-release-tools-20180416.dc30723/oqamaint/opensuse.py 
new/openSUSE-release-tools-20180417.b8337f4/oqamaint/opensuse.py
--- old/openSUSE-release-tools-20180416.dc30723/oqamaint/opensuse.py    
1970-01-01 01:00:00.000000000 +0100
+++ new/openSUSE-release-tools-20180417.b8337f4/oqamaint/opensuse.py    
2018-04-17 13:01:25.000000000 +0200
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+
+from update import Update
+
+
+class openSUSEUpdate(Update):
+
+    repo_prefix = 'http://download.opensuse.org/repositories'
+    maintenance_project = 'openSUSE:Maintenance'
+
+    def settings(self, src_prj, dst_prj, packages):
+        settings = super(openSUSEUpdate, self).settings(src_prj, dst_prj, 
packages)
+        settings = settings[0]
+
+        # openSUSE:Maintenance key
+        settings['IMPORT_GPG_KEYS'] = 'gpg-pubkey-b3fd7e48-5549fd0f'
+        settings['ZYPPER_ADD_REPO_PREFIX'] = 'incident'
+
+        if packages:
+            # XXX: this may fail in various ways
+            # - conflicts between subpackages
+            # - added packages
+            # - conflicts with installed packages (e.g sendmail vs postfix)
+            settings['INSTALL_PACKAGES'] = ' '.join(set([p.name for p in 
packages]))
+            settings['VERIFY_PACKAGE_VERSIONS'] = ' '.join(
+                ['{} {}-{}'.format(p.name, p.version, p.release) for p in 
packages])
+
+        settings['ZYPPER_ADD_REPOS'] = settings['INCIDENT_REPO']
+        settings['ADDONURL'] = settings['INCIDENT_REPO']
+
+        settings['WITH_MAIN_REPO'] = 1
+        settings['WITH_UPDATE_REPO'] = 1
+
+        return [settings]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openSUSE-release-tools-20180416.dc30723/oqamaint/osclib 
new/openSUSE-release-tools-20180417.b8337f4/oqamaint/osclib
--- old/openSUSE-release-tools-20180416.dc30723/oqamaint/osclib 1970-01-01 
01:00:00.000000000 +0100
+++ new/openSUSE-release-tools-20180417.b8337f4/oqamaint/osclib 2018-04-19 
15:30:15.661156150 +0200
@@ -0,0 +1 @@
+symbolic link to ../osclib
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openSUSE-release-tools-20180416.dc30723/oqamaint/suse.py 
new/openSUSE-release-tools-20180417.b8337f4/oqamaint/suse.py
--- old/openSUSE-release-tools-20180416.dc30723/oqamaint/suse.py        
1970-01-01 01:00:00.000000000 +0100
+++ new/openSUSE-release-tools-20180417.b8337f4/oqamaint/suse.py        
2018-04-17 13:01:25.000000000 +0200
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+
+import re
+import requests
+
+import osc.core
+
+from update import Update
+
+
+MINIMALS = {
+    x.rstrip()
+    for x in requests.get(
+        
'https://gitlab.suse.de/qa-maintenance/metadata/raw/master/packages-to-be-tested-on-minimal-systems').iter_lines()
+    if len(x) > 0 and not(x.startswith("#") or x.startswith(' '))}
+
+
+class SUSEUpdate(Update):
+
+    repo_prefix = 'http://download.suse.de/ibs'
+    maintenance_project = 'SUSE:Maintenance'
+
+    def __init__(self, settings):
+        super(SUSEUpdate, self).__init__(settings)
+        self.opensuse = False
+
+    # we take requests that have a kgraft-patch package as kgraft patch 
(suprise!)
+    @staticmethod
+    def kgraft_target(apiurl, prj):
+        target = None
+        skip = False
+        pattern = re.compile(r"kgraft-patch-([^.]+)\.")
+
+        for package in osc.core.meta_get_packagelist(apiurl, prj):
+            if package.startswith("kernel-"):
+                skip = True
+                break
+            match = re.match(pattern, package)
+            if match:
+                target = match.group(1)
+        if skip:
+            return None
+
+        return target
+
+    @staticmethod
+    def parse_kgraft_version(kgraft_target):
+        return kgraft_target.lstrip('SLE').split('_')[0]
+
+    def add_minimal_settings(self, prj, settings):
+        minimal = False
+        for pkg in self.incident_packages(prj):
+            if pkg in MINIMALS:
+                minimal = True
+        if not minimal:
+            return []
+
+        settings = settings.copy()
+        settings['FLAVOR'] += '-Minimal'
+        return [settings]
+
+    @staticmethod
+    def add_kernel_settings(settings):
+        settings = settings.copy()
+        if settings['BUILD'].split(":")[-1].startswith('kernel-') and 
settings['FLAVOR'] == 'Server-DVD-Incidents':
+            settings['FLAVOR'] += '-Kernel'
+            return [settings]
+        return []
+
+    def settings(self, src_prj, dst_prj, packages):
+        settings = super(SUSEUpdate, self).settings(src_prj, dst_prj, packages)
+
+        # kGraft Handling - Fully supported kGraft lives in own space, but 
LTSS in standard LTSS channel
+        for x in range(len(settings)):
+            if settings[x]['FLAVOR'] == 'Server-DVD-Incidents' and 
settings[x]['BUILD'].split(
+                    ':')[-1].startswith('kgraft-patch'):
+                settings[x]['FLAVOR'] = 'Server-DVD-Incidents-Kernel'
+                self.logger.warning("kGraft started from INCIDENTS !!")
+            if settings[x]['FLAVOR'] == 'Server-DVD-Incidents-Kernel' and not 
settings[x]['BUILD'].split(
+                    ':')[-1].startswith('kgraft-patch'):
+                del settings[x]
+                continue
+            if settings[x]['FLAVOR'] == 'Server-DVD-Incidents-Kernel':
+                settings[x]['KGRAFT'] = "1"
+                if settings[x]['VERSION'] == '12':
+                    settings[x]['real_version'] = 
self.parse_kgraft_version(self.kgraft_target(self.apiurl, src_prj))
+
+        if not len(settings):
+            return []
+        settings += self.add_minimal_settings(src_prj, settings[0])
+        settings += self.add_kernel_settings(settings[0])
+        self.logger.debug("settings are: {}".format(settings))
+        return settings
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openSUSE-release-tools-20180416.dc30723/oqamaint/update.py 
new/openSUSE-release-tools-20180417.b8337f4/oqamaint/update.py
--- old/openSUSE-release-tools-20180416.dc30723/oqamaint/update.py      
1970-01-01 01:00:00.000000000 +0100
+++ new/openSUSE-release-tools-20180417.b8337f4/oqamaint/update.py      
2018-04-17 13:01:25.000000000 +0200
@@ -0,0 +1,128 @@
+# -*- coding: utf-8 -*-
+
+import re
+import requests
+
+
+# python 3 has gzip decompress function
+try:
+    from gzip import decompress
+except ImportError:
+    from gzip import GzipFile
+    import io
+
+    def decompress(data):
+        with GzipFile(fileobj=io.BytesIO(data)) as f:
+            return f.read()
+
+# use cElementTree by default, fallback to pure python
+try:
+    from xml.etree import cElementTree as ET
+except ImportError:
+    from xml.etree import ElementTree as ET
+
+import osc.core
+
+from osclib.memoize import memoize
+
+
+class Update(object):
+    incident_name_cache = {}
+
+    def __init__(self, settings):
+        self._settings = settings
+        self._settings['_NOOBSOLETEBUILD'] = '1'
+        self.opensuse = True
+
+    def get_max_revision(self, job):
+        repo = self.repo_prefix + '/'
+        repo += self.maintenance_project.replace(':', ':/')
+        repo += ':/{!s}'.format(job['id'])
+        max_revision = 0
+        for channel in job['channels']:
+            crepo = repo + '/' + channel.replace(':', '_')
+            xml = requests.get(crepo + '/repodata/repomd.xml')
+            if not xml.ok:
+                self.logger.info("{} skipped .. need wait".format(crepo))
+                # if one fails, we skip it and wait
+                return None
+            root = ET.fromstring(xml.text)
+            rev = root.find('.//{http://linux.duke.edu/metadata/repo}revision')
+            rev = int(rev.text)
+            if rev > max_revision:
+                max_revision = rev
+        return max_revision
+
+    def settings(self, src_prj, dst_prj, packages):
+        s = self._settings.copy()
+
+        # start with a colon so it looks cool behind 'Build' :/
+        s['BUILD'] = ':' + src_prj.split(':')[-1]
+        name = self.incident_name(src_prj)
+        repo = dst_prj.replace(':', '_')
+        repo = '{!s}/{!s}/{!s}/'.format(self.repo_prefix, src_prj.replace(':', 
':/'), repo)
+        patch_id = self.patch_id(repo)
+        if patch_id:
+            s['INCIDENT_REPO'] = repo
+            s['INCIDENT_PATCH'] = patch_id
+        s['BUILD'] += ':' + name
+        return [s]
+
+    @memoize()
+    def incident_packages(self, prj):
+        packages = []
+        for package in osc.core.meta_get_packagelist(self.apiurl, prj):
+            if package.endswith('SUSE_Channels') or 
package.startswith('patchinfo'):
+                continue
+            parts = package.split('.')
+            # remove target name
+            parts.pop()
+            packages.append('.'.join(parts))
+        return packages
+
+    # grab the updateinfo from the given repo and return its patch's id
+    @staticmethod
+    def patch_id(repo):
+        url = repo + 'repodata/repomd.xml'
+        repomd = requests.get(url)
+        if not repomd.ok:
+            return None
+        root = ET.fromstring(repomd.text)
+
+        cs = root.find(
+            
'.//{http://linux.duke.edu/metadata/repo}data[@type="updateinfo"]/{http://linux.duke.edu/metadata/repo}location')
+        try:
+            url = repo + cs.attrib['href']
+        except AttributeError:
+            return None
+
+        repomd = requests.get(url).content
+        root = ET.fromstring(decompress(repomd))
+        return root.find('.//id').text
+
+    # take the first package name we find - often enough correct
+    def incident_name(self, prj):
+        if prj not in self.incident_name_cache:
+            self.incident_name_cache[prj] = self._incident_name(prj)
+        return self.incident_name_cache[prj]
+
+    def _incident_name(self, prj):
+        shortest_pkg = None
+        for package in osc.core.meta_get_packagelist(self.apiurl, prj):
+            if package.startswith('patchinfo'):
+                continue
+            if package.endswith('SUSE_Channels'):
+                continue
+            # other tools on SLE have data from SMELT without acces to this 
attrib
+            if self.opensuse:
+                url = osc.core.makeurl(self.apiurl, ('source', prj, package, 
'_link'))
+                root = ET.parse(osc.core.http_GET(url)).getroot()
+                if root.attrib.get('cicount'):
+                    continue
+            if not shortest_pkg or len(package) < len(shortest_pkg):
+                shortest_pkg = package
+        if not shortest_pkg:
+            shortest_pkg = 'unknown'
+        match = re.match(r'^(.*)\.[^\.]*$', shortest_pkg)
+
+        return match.group(1) if match else shortest_pkg

++++++ openSUSE-release-tools.obsinfo ++++++
--- /var/tmp/diff_new_pack.1WWQxR/_old  2018-04-19 15:30:15.769151721 +0200
+++ /var/tmp/diff_new_pack.1WWQxR/_new  2018-04-19 15:30:15.773151557 +0200
@@ -1,5 +1,5 @@
 name: openSUSE-release-tools
-version: 20180416.dc30723
-mtime: 1523912010
-commit: dc30723b4a7f678dd9cd2195904ef472f49e93a1
+version: 20180417.b8337f4
+mtime: 1523962885
+commit: b8337f43a7d11b75bf5b43b52daacb806f84d6e9
 


Reply via email to