Previously, a single ``maintenance.request`` was created per equipment,
regardless of how many services were down on that equipment. The name
was ``[HTTP KO] {equipment.name}`` and deduplication relied on a
name+date+equipment search that was fragile (manual clear of the field
would lose the reference to an existing open request).
This commit reworks the whole creation logic:
- **1 request per KO service** instead of 1 per equipment. Each failing
``service.instance`` gets its own ``maintenance.request``, allowing
fine-grained tracking and independent resolution.
- **Request name** is now ``[HTTP KO] {service_url}``, making it
immediately identifiable without opening the record.
- **Description** includes the error detail: ``HTTP {status_code}`` for
HTTP errors, or a human-readable network error label when
``last_http_status_code == -1`` (timeout / DNS / SSL failures).
- **Deduplication** is now based solely on whether an open (non-done)
``maintenance.request`` already exists on the ``service.instance``
via the new ``http_maintenance_request`` field. No date boundary —
as long as the request is open, no new one is created.
- **``http_maintenance_request``** field moved from
``maintenance.equipment`` to ``service.instance``, where it belongs
given the 1-request-per-service model. It is exposed as an optional
hidden column in the service instance list view.
- ``_build_ko_services_description()`` is removed (no longer needed).
- ``create_http_maintenance_request()`` now receives a single
``service.instance`` recordset instead of a list.
Tests updated accordingly (14 tests total):
- Tests 2, 4, 9, 10, 12, 13 now assert on
``service_instance.http_maintenance_request``.
- Test 2 also verifies the request name contains the service URL and
the description contains the HTTP status code.
- New test 14 asserts that two KO services on the same equipment
produce two distinct requests with the correct names.
107 lines
3.3 KiB
Python
107 lines
3.3 KiB
Python
import logging
|
|
import time
|
|
|
|
from odoo import api, fields, models
|
|
|
|
try:
|
|
import requests
|
|
except ImportError:
|
|
requests = None
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
HTTP_CHECK_TIMEOUT = 10 # seconds
|
|
HTTP_RETRY_DELAY = 2 # seconds between pass 1 and pass 2
|
|
|
|
|
|
class ServiceInstance(models.Model):
|
|
_inherit = "service.instance"
|
|
|
|
last_http_status_code = fields.Integer(
|
|
string="Last HTTP Status Code",
|
|
readonly=True,
|
|
default=0,
|
|
)
|
|
last_http_check_date = fields.Datetime(
|
|
string="Last HTTP Check Date",
|
|
readonly=True,
|
|
)
|
|
http_status_ok = fields.Boolean(
|
|
string="HTTP Status OK",
|
|
readonly=True,
|
|
default=True,
|
|
)
|
|
http_maintenance_request = fields.Many2one(
|
|
"maintenance.request",
|
|
string="HTTP Maintenance Request",
|
|
readonly=True,
|
|
)
|
|
|
|
def check_http_status(self):
|
|
"""
|
|
Perform HTTP check for each record and return the KO recordset.
|
|
|
|
Writes last_http_status_code, last_http_check_date and http_status_ok on every
|
|
checked record. Does NOT create maintenance.request — that decision belongs to
|
|
the caller (cron) after optional retry logic.
|
|
"""
|
|
ko_records = self.browse()
|
|
for rec in self:
|
|
if not rec.service_url or not rec.equipment_id:
|
|
continue
|
|
if rec.equipment_id.maintenance_mode:
|
|
continue
|
|
status_ok = False
|
|
status_code = -1
|
|
now = fields.Datetime.now()
|
|
url = rec.service_url
|
|
if not url.lower().startswith("https://"):
|
|
url = "https://" + url.removeprefix("http://").removeprefix("HTTP://")
|
|
try:
|
|
response = requests.get(url, timeout=HTTP_CHECK_TIMEOUT)
|
|
status_code = response.status_code
|
|
status_ok = status_code == 200
|
|
except requests.exceptions.RequestException as e:
|
|
_logger.warning("HTTP check failed for %s: %s", rec.service_url, e)
|
|
rec.write(
|
|
{
|
|
"last_http_status_code": status_code,
|
|
"last_http_check_date": now,
|
|
"http_status_ok": status_ok,
|
|
}
|
|
)
|
|
if not status_ok:
|
|
ko_records |= rec
|
|
return ko_records
|
|
|
|
@api.model
|
|
def cron_check_http_services(self):
|
|
"""
|
|
Check all active services with a URL, with one retry on failure.
|
|
|
|
Pass 1: test every eligible service.
|
|
If any fail, wait HTTP_RETRY_DELAY seconds then retest only the KO ones.
|
|
maintenance.request is created only for services that fail both passes,
|
|
reducing noise from transient HTTP errors.
|
|
"""
|
|
domain = [
|
|
("active", "=", True),
|
|
("service_url", "!=", False),
|
|
("equipment_id", "!=", False),
|
|
]
|
|
services = self.search(domain).filtered(
|
|
lambda s: not s.equipment_id.maintenance_mode
|
|
)
|
|
|
|
ko_after_pass1 = services.check_http_status()
|
|
|
|
if not ko_after_pass1:
|
|
return
|
|
|
|
time.sleep(HTTP_RETRY_DELAY)
|
|
|
|
ko_confirmed = ko_after_pass1.check_http_status()
|
|
|
|
for service in ko_confirmed:
|
|
service.equipment_id.create_http_maintenance_request(service)
|