Greenwave query class and configuration update

Signed-off-by: Valerij Maljulin <vmaljuli@redhat.com>
This commit is contained in:
Valerij Maljulin
2019-04-02 19:43:07 +02:00
parent 7a39e1e893
commit 1b486b1625
8 changed files with 228 additions and 10 deletions

View File

@@ -120,7 +120,11 @@ class TestConfiguration(BaseConfiguration):
RESOLVER = "db"
ALLOWED_GROUPS_TO_IMPORT_MODULE = set(["mbs-import-module"])
GREENWAVE_DECISION_CONTEXT = "osci_compose_gate_modules"
# Greenwave configuration
GREENWAVE_URL = "https://greenwave.example.local/api/v1.0/"
GREENWAVE_DECISION_CONTEXT = "test_dec_context"
GREENWAVE_SUBJECT_TYPE = "some-module"
STREAM_SUFFIXES = {r"^el\d+\.\d+\.\d+\.z$": 0.1}

View File

@@ -295,14 +295,9 @@ class KojiContentGenerator(object):
return rpms
def _get_build(self):
ret = {}
ret[u"name"] = self.module.name
ret = self.module.nvr
if self.devel:
ret["name"] += "-devel"
ret[u"version"] = self.module.stream.replace("-", "_")
# Append the context to the version to make NVRs of modules unique in the event of
# module stream expansion
ret[u"release"] = "{0}.{1}".format(self.module.version, self.module.context)
ret[u"source"] = self.module.scmurl
ret[u"start_time"] = calendar.timegm(self.module.time_submitted.utctimetuple())
ret[u"end_time"] = calendar.timegm(self.module.time_completed.utctimetuple())

View File

@@ -603,6 +603,22 @@ class Config(object):
"corresponding suffix added to formatted stream version. "
'For example, {r"regexp": 0.1, ...}',
},
"greenwave_url": {
"type": str,
"default": "",
"desc": "The URL of the server where Greenwave is running (should include "
"the root of the API)"
},
"greenwave_subject_type": {
"type": str,
"default": "",
"desc": "Subject type for Greenwave requests"
},
"greenwave_timeout": {
"type": int,
"default": 60,
"desc": "Greenwave response timeout"
}
}
def __init__(self, conf_section_obj):

View File

@@ -55,6 +55,10 @@ class StreamAmbigous(ValueError):
pass
class GreenwaveError(RuntimeError):
pass
def json_error(status, error, message):
response = jsonify({"status": status, "error": error, "message": message})
response.status_code = status

View File

@@ -34,6 +34,7 @@ from collections import OrderedDict
from datetime import datetime
import sqlalchemy
import kobo.rpmlib
from flask import has_app_context
from sqlalchemy import func, and_
from sqlalchemy.orm import lazyload
@@ -601,6 +602,18 @@ class ModuleBuild(MBSBase):
)
return [build.id for build in query.all()]
@property
def nvr(self):
return {
u"name": self.name,
u"version": self.stream.replace("-", "_"),
u"release": "{0}.{1}".format(self.version, self.context)
}
@property
def nvr_string(self):
return kobo.rpmlib.make_nvr(self.nvr)
@classmethod
def create(
cls,

View File

@@ -0,0 +1,116 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019 Red Hat, Inc.
#
# 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.
#
# Written by Valerij Maljulin <vmaljuli@redhat.com>
import requests
import json
from module_build_service import log, conf
from module_build_service.errors import GreenwaveError
class Greenwave(object):
def __init__(self):
"""
Initialize greenwave instance with config
"""
self.url = conf.greenwave_url
if not self.url:
raise GreenwaveError("No Greenwave URL set")
self._decision_context = conf.greenwave_decision_context
if not self.decision_context:
raise GreenwaveError("No Greenwave decision context set")
self._subj_type = conf.greenwave_subject_type
self._gw_timeout = conf.greenwave_timeout
def query_decision(self, build, prod_version):
"""
Query decision to greenwave
:param build: build object
:type build: module_build_service.models.ModuleBuild
:param prod_version: The product version string used for querying WaiverDB
:type prod_version: str
:return: response
:rtype: dict
"""
payload = {
"decision_context": self.decision_context,
"product_version": prod_version,
"subject_type": self.subject_type,
"subject_identifier": build.nvr_string
}
url = "{0}/decision".format(self.url)
headers = {"Content-Type": "application/json"}
try:
response = requests.post(
url=url, headers=headers, data=json.dumps(payload), timeout=self.timeout)
except requests.exceptions.Timeout:
raise GreenwaveError("Greenwave request timed out")
except Exception as exc:
log.exception(str(exc))
raise GreenwaveError("Greenwave request error")
try:
resp_json = response.json()
except ValueError:
log.debug("Greenwave response content (status {0}): {1}".format(
response.status_code, response.text))
raise GreenwaveError("Greenwave returned invalid JSON.")
log.debug('Query to Greenwave result: status=%d, content="%s"',
(response.status_code, resp_json))
if response.status_code == 200:
return resp_json
try:
err_msg = resp_json["message"]
except KeyError:
err_msg = response.text
raise GreenwaveError("Greenwave returned {0} status code. Message: {1}".format(
response.status_code, err_msg))
@property
def url(self):
return self._url
@url.setter
def url(self, value):
value = value.rstrip("/")
if value:
self._url = value
@property
def decision_context(self):
return self._decision_context
@property
def subject_type(self):
return self._subj_type
@property
def timeout(self):
return self._gw_timeout
@timeout.setter
def timeout(self, value):
self._gw_timeout = value

View File

@@ -98,14 +98,14 @@ class TestDecisionUpdateHandler:
log.debug.assert_called_once_with(
'Skip Greenwave message %s as MBS only handles messages with the decision context "%s"',
"msg-id-1",
"osci_compose_gate_modules",
"test_dec_context"
)
@patch("module_build_service.scheduler.handlers.greenwave.log")
def test_not_satisfy_policies(self, log):
msg = Mock(
msg_id="msg-id-1",
decision_context="osci_compose_gate_modules",
decision_context="test_dec_context",
policies_satisfied=False,
subject_identifier="pkg-0.1-1.c1",
)
@@ -144,7 +144,7 @@ class TestDecisionUpdateHandler:
"msg_id": "msg-id-1",
"topic": "org.fedoraproject.prod.greenwave.decision.update",
"msg": {
"decision_context": "osci_compose_gate_modules",
"decision_context": "test_dec_context",
"policies_satisfied": True,
"subject_identifier": "pkg-0.1-1.c1",
},

View File

@@ -0,0 +1,70 @@
# Copyright (c) 2019 Red Hat, Inc.
#
# 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.
#
# Written by Valerij Maljulin <vmaljuli@redhat.com>
import json
from mock import patch, Mock
import module_build_service.utils.greenwave
from tests import make_module
class TestGreenwaveQuery():
@patch("module_build_service.utils.greenwave.requests")
def test_greenwave_decision(self, mock_requests):
resp_status = 200
resp_content = {
"applicable_policies": ["osci_compose_modules"],
"policies_satisfied": True,
"satisfied_requirements": [
{
"result_id": 7336633,
"testcase": "test-ci.test-module.tier1",
"type": "test-result-passed"
},
{
"result_id": 7336650,
"testcase": "test-ci.test-module.tier2",
"type": "test-result-passed"
}
],
"summary": "All required tests passed",
"unsatisfied_requirements": []
}
response = Mock()
response.json.return_value = resp_content
response.status_code = resp_status
mock_requests.post.return_value = response
fake_build = make_module("pkg:0.1:1:c1", requires_list={"platform": "el8"})
gw = module_build_service.utils.greenwave.Greenwave()
got_response = gw.query_decision(fake_build, prod_version="xxxx-8")
assert got_response == resp_content
assert json.loads(mock_requests.post.call_args_list[0][1]["data"]) == {
"decision_context": "test_dec_context",
"product_version": "xxxx-8", "subject_type": "some-module",
"subject_identifier": "pkg-0.1-1.c1"}
assert mock_requests.post.call_args_list[0][1]["headers"] == {
"Content-Type": "application/json"}
assert mock_requests.post.call_args_list[0][1]["url"] == \
"https://greenwave.example.local/api/v1.0/decision"