Add basic telemetry support
This patch adds support for Opentelemetry. If OTEL_EXPORTER_OTLP_ENDPOINT env variable is defined, it will send traces there. Otherwise there is no change. The whole compose is wrapped in a single span. Nested under that are spans for operations that involve a remote server. * Talking to CTS * Sending API requests to Koji * Any git repo clone Signed-off-by: Lubomír Sedlář <lsedlar@redhat.com> (cherry picked from commit c15ddbc946cc6a820dfb2f0bbacb72ca118100ba)
This commit is contained in:
parent
49a3e6cd12
commit
feffd284a4
@ -50,6 +50,7 @@ from pungi.util import (
|
|||||||
translate_path_raw,
|
translate_path_raw,
|
||||||
)
|
)
|
||||||
from pungi.metadata import compose_to_composeinfo
|
from pungi.metadata import compose_to_composeinfo
|
||||||
|
from pungi.otel import tracing
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# This is available since productmd >= 1.18
|
# This is available since productmd >= 1.18
|
||||||
@ -130,15 +131,16 @@ def cts_auth(pungi_conf):
|
|||||||
cts_oidc_client_id = os.environ.get(
|
cts_oidc_client_id = os.environ.get(
|
||||||
"CTS_OIDC_CLIENT_ID", ""
|
"CTS_OIDC_CLIENT_ID", ""
|
||||||
) or pungi_conf.get("cts_oidc_client_id", "")
|
) or pungi_conf.get("cts_oidc_client_id", "")
|
||||||
token = retry_request(
|
with tracing.span("obtain-oidc-token"):
|
||||||
"post",
|
token = retry_request(
|
||||||
cts_oidc_token_url,
|
"post",
|
||||||
data={
|
cts_oidc_token_url,
|
||||||
"grant_type": "client_credentials",
|
data={
|
||||||
"client_id": cts_oidc_client_id,
|
"grant_type": "client_credentials",
|
||||||
"client_secret": os.environ.get("CTS_OIDC_CLIENT_SECRET", ""),
|
"client_id": cts_oidc_client_id,
|
||||||
},
|
"client_secret": os.environ.get("CTS_OIDC_CLIENT_SECRET", ""),
|
||||||
).json()["access_token"]
|
},
|
||||||
|
).json()["access_token"]
|
||||||
auth = BearerAuth(token)
|
auth = BearerAuth(token)
|
||||||
del token
|
del token
|
||||||
|
|
||||||
@ -194,8 +196,9 @@ def get_compose_info(
|
|||||||
"parent_compose_ids": parent_compose_ids,
|
"parent_compose_ids": parent_compose_ids,
|
||||||
"respin_of": respin_of,
|
"respin_of": respin_of,
|
||||||
}
|
}
|
||||||
with cts_auth(conf) as authentication:
|
with tracing.span("create-compose-in-cts"):
|
||||||
rv = retry_request("post", url, json_data=data, auth=authentication)
|
with cts_auth(conf) as authentication:
|
||||||
|
rv = retry_request("post", url, json_data=data, auth=authentication)
|
||||||
|
|
||||||
# Update local ComposeInfo with received ComposeInfo.
|
# Update local ComposeInfo with received ComposeInfo.
|
||||||
cts_ci = ComposeInfo()
|
cts_ci = ComposeInfo()
|
||||||
@ -231,8 +234,9 @@ def update_compose_url(compose_id, compose_dir, conf):
|
|||||||
"action": "set_url",
|
"action": "set_url",
|
||||||
"compose_url": compose_url,
|
"compose_url": compose_url,
|
||||||
}
|
}
|
||||||
with cts_auth(conf) as authentication:
|
with tracing.span("update-compose-url"):
|
||||||
return retry_request("patch", url, json_data=data, auth=authentication)
|
with cts_auth(conf) as authentication:
|
||||||
|
return retry_request("patch", url, json_data=data, auth=authentication)
|
||||||
|
|
||||||
|
|
||||||
def get_compose_dir(
|
def get_compose_dir(
|
||||||
@ -373,6 +377,7 @@ class Compose(kobo.log.LoggingBase):
|
|||||||
self.ci_base.load(
|
self.ci_base.load(
|
||||||
os.path.join(self.paths.work.topdir(arch="global"), "composeinfo-base.json")
|
os.path.join(self.paths.work.topdir(arch="global"), "composeinfo-base.json")
|
||||||
)
|
)
|
||||||
|
tracing.set_attribute("compose_id", self.compose_id)
|
||||||
|
|
||||||
self.supported = supported
|
self.supported = supported
|
||||||
if (
|
if (
|
||||||
@ -557,6 +562,7 @@ class Compose(kobo.log.LoggingBase):
|
|||||||
old_status = self.get_status()
|
old_status = self.get_status()
|
||||||
if stat_msg == old_status:
|
if stat_msg == old_status:
|
||||||
return
|
return
|
||||||
|
tracing.set_attribute("compose_status", stat_msg)
|
||||||
if old_status == "FINISHED":
|
if old_status == "FINISHED":
|
||||||
msg = "Could not modify a FINISHED compose: %s" % self.topdir
|
msg = "Could not modify a FINISHED compose: %s" % self.topdir
|
||||||
self.log_error(msg)
|
self.log_error(msg)
|
||||||
|
199
pungi/otel.py
Normal file
199
pungi/otel.py
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
import itertools
|
||||||
|
import os
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
"""
|
||||||
|
This module contains two classes with the same interface. An instance of one of
|
||||||
|
them is available as `tracing`. Which class is instantiated is selected
|
||||||
|
depending on whether environment variables configuring OTel are configured.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class DummyTracing:
|
||||||
|
"""A dummy tracing module that doesn't actually do anything."""
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def span(self, *args, **kwargs):
|
||||||
|
yield
|
||||||
|
|
||||||
|
def set_attribute(self, name, value):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def force_flush(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def instrument_xmlrpc_proxy(self, proxy):
|
||||||
|
return proxy
|
||||||
|
|
||||||
|
def get_traceparent(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def set_context(self, traceparent):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class OtelTracing:
|
||||||
|
"""This class implements the actual integration with opentelemetry."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
from opentelemetry import trace
|
||||||
|
from opentelemetry.sdk.resources import Resource
|
||||||
|
from opentelemetry.sdk.trace import TracerProvider
|
||||||
|
from opentelemetry.sdk.trace.export import (
|
||||||
|
BatchSpanProcessor,
|
||||||
|
ConsoleSpanExporter,
|
||||||
|
)
|
||||||
|
from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
|
||||||
|
OTLPSpanExporter,
|
||||||
|
)
|
||||||
|
from opentelemetry.instrumentation.requests import RequestsInstrumentor
|
||||||
|
|
||||||
|
otel_endpoint = os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"]
|
||||||
|
provider = TracerProvider(
|
||||||
|
resource=Resource(attributes={"service.name": "pungi"})
|
||||||
|
)
|
||||||
|
if "console" == otel_endpoint:
|
||||||
|
# This is for debugging the tracing locally.
|
||||||
|
self.processor = BatchSpanProcessor(ConsoleSpanExporter())
|
||||||
|
else:
|
||||||
|
self.processor = BatchSpanProcessor(OTLPSpanExporter())
|
||||||
|
provider.add_span_processor(self.processor)
|
||||||
|
trace.set_tracer_provider(provider)
|
||||||
|
self.tracer = trace.get_tracer(__name__)
|
||||||
|
|
||||||
|
traceparent = os.environ.get("TRACEPARENT")
|
||||||
|
if traceparent:
|
||||||
|
self.set_context(traceparent)
|
||||||
|
|
||||||
|
RequestsInstrumentor().instrument()
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def span(self, name, **attributes):
|
||||||
|
"""Create a new span as a child of the current one. Attributes can be
|
||||||
|
passed via kwargs."""
|
||||||
|
with self.tracer.start_as_current_span(name, attributes=attributes) as span:
|
||||||
|
yield span
|
||||||
|
|
||||||
|
def get_traceparent(self):
|
||||||
|
from opentelemetry.trace.propagation.tracecontext import (
|
||||||
|
TraceContextTextMapPropagator,
|
||||||
|
)
|
||||||
|
|
||||||
|
carrier = {}
|
||||||
|
TraceContextTextMapPropagator().inject(carrier)
|
||||||
|
return carrier["traceparent"]
|
||||||
|
|
||||||
|
def set_attribute(self, name, value):
|
||||||
|
"""Set an attribute on the current span."""
|
||||||
|
from opentelemetry import trace
|
||||||
|
|
||||||
|
span = trace.get_current_span()
|
||||||
|
span.set_attribute(name, value)
|
||||||
|
|
||||||
|
def force_flush(self):
|
||||||
|
"""Ensure all spans and traces are sent out. Call this before the
|
||||||
|
process exits."""
|
||||||
|
self.processor.force_flush()
|
||||||
|
|
||||||
|
def instrument_xmlrpc_proxy(self, proxy):
|
||||||
|
return InstrumentedClientSession(proxy)
|
||||||
|
|
||||||
|
def set_context(self, traceparent):
|
||||||
|
"""Configure current context to match the given traceparent."""
|
||||||
|
from opentelemetry import context
|
||||||
|
from opentelemetry.trace.propagation.tracecontext import (
|
||||||
|
TraceContextTextMapPropagator,
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx = TraceContextTextMapPropagator().extract(
|
||||||
|
carrier={"traceparent": traceparent}
|
||||||
|
)
|
||||||
|
context.attach(ctx)
|
||||||
|
|
||||||
|
|
||||||
|
class InstrumentedClientSession:
|
||||||
|
"""Wrapper around koji.ClientSession that creates spans for each API call.
|
||||||
|
RequestsInstrumentor can create spans at the HTTP requests level, but since
|
||||||
|
those all go the same XML-RPC endpoint, they are not very informative.
|
||||||
|
|
||||||
|
Multicall is not handled very well here. The spans will only have a
|
||||||
|
`multicall` boolean attribute, but they don't carry any additional data
|
||||||
|
that could group them.
|
||||||
|
|
||||||
|
Koji ClientSession supports three ways of making multicalls, but Pungi only
|
||||||
|
uses one, and that one is supported here.
|
||||||
|
|
||||||
|
Supported:
|
||||||
|
|
||||||
|
c.multicall = True
|
||||||
|
c.getBuild(1)
|
||||||
|
c.getBuild(2)
|
||||||
|
results = c.multiCall()
|
||||||
|
|
||||||
|
Not supported:
|
||||||
|
|
||||||
|
with c.multicall() as m:
|
||||||
|
r1 = m.getBuild(1)
|
||||||
|
r2 = m.getBuild(2)
|
||||||
|
|
||||||
|
Also not supported:
|
||||||
|
|
||||||
|
m = c.multicall()
|
||||||
|
r1 = m.getBuild(1)
|
||||||
|
r2 = m.getBuild(2)
|
||||||
|
m.call_all()
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, session):
|
||||||
|
self.session = session
|
||||||
|
|
||||||
|
def _name(self, name):
|
||||||
|
"""Helper for generating span names."""
|
||||||
|
return "%s.%s" % (self.session.__class__.__name__, name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def system(self):
|
||||||
|
"""This is only ever used to get list of available API calls. It is
|
||||||
|
rather awkward though. Ideally we wouldn't really trace this at all,
|
||||||
|
but there's the underlying POST request to the hub, which is quite
|
||||||
|
confusing in the trace if there is no additional context."""
|
||||||
|
return self.session.system
|
||||||
|
|
||||||
|
@property
|
||||||
|
def multicall(self):
|
||||||
|
return self.session.multicall
|
||||||
|
|
||||||
|
@multicall.setter
|
||||||
|
def multicall(self, value):
|
||||||
|
self.session.multicall = value
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
return self._instrument_method(name, getattr(self.session, name))
|
||||||
|
|
||||||
|
def _instrument_method(self, name, callable):
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
with tracing.span(self._name(name)) as span:
|
||||||
|
span.set_attribute("arguments", _format_args(args, kwargs))
|
||||||
|
if self.session.multicall:
|
||||||
|
tracing.set_attribute("multicall", True)
|
||||||
|
return callable(*args, **kwargs)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def _format_args(args, kwargs):
|
||||||
|
"""Turn args+kwargs into a single string. OTel could choke on more
|
||||||
|
complicated data."""
|
||||||
|
return ", ".join(
|
||||||
|
itertools.chain(
|
||||||
|
(repr(arg) for arg in args),
|
||||||
|
(f"{key}={value!r}" for key, value in kwargs.items()),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if "OTEL_EXPORTER_OTLP_ENDPOINT" in os.environ:
|
||||||
|
tracing = OtelTracing()
|
||||||
|
else:
|
||||||
|
tracing = DummyTracing()
|
@ -23,7 +23,7 @@ import shutil
|
|||||||
import re
|
import re
|
||||||
from copy import copy
|
from copy import copy
|
||||||
|
|
||||||
from kobo.threads import ThreadPool, WorkerThread
|
from kobo.threads import ThreadPool
|
||||||
from kobo.shortcuts import run, force_list
|
from kobo.shortcuts import run, force_list
|
||||||
import kobo.rpmlib
|
import kobo.rpmlib
|
||||||
from productmd.images import Image
|
from productmd.images import Image
|
||||||
@ -39,6 +39,7 @@ from pungi.wrappers.scm import get_file_from_scm
|
|||||||
from pungi.wrappers import kojiwrapper
|
from pungi.wrappers import kojiwrapper
|
||||||
from pungi.phases.base import PhaseBase
|
from pungi.phases.base import PhaseBase
|
||||||
from pungi.runroot import Runroot, download_and_extract_archive
|
from pungi.runroot import Runroot, download_and_extract_archive
|
||||||
|
from pungi.threading import TelemetryWorkerThread as WorkerThread
|
||||||
|
|
||||||
|
|
||||||
class BuildinstallPhase(PhaseBase):
|
class BuildinstallPhase(PhaseBase):
|
||||||
|
@ -24,7 +24,7 @@ import json
|
|||||||
|
|
||||||
import productmd.treeinfo
|
import productmd.treeinfo
|
||||||
from productmd.images import Image
|
from productmd.images import Image
|
||||||
from kobo.threads import ThreadPool, WorkerThread
|
from kobo.threads import ThreadPool
|
||||||
from kobo.shortcuts import run, relative_path, compute_file_checksums
|
from kobo.shortcuts import run, relative_path, compute_file_checksums
|
||||||
|
|
||||||
from pungi.wrappers import iso
|
from pungi.wrappers import iso
|
||||||
@ -43,6 +43,7 @@ from pungi.util import (
|
|||||||
from pungi.media_split import MediaSplitter, convert_media_size
|
from pungi.media_split import MediaSplitter, convert_media_size
|
||||||
from pungi.compose_metadata.discinfo import read_discinfo, write_discinfo
|
from pungi.compose_metadata.discinfo import read_discinfo, write_discinfo
|
||||||
from pungi.runroot import Runroot
|
from pungi.runroot import Runroot
|
||||||
|
from pungi.threading import TelemetryWorkerThread as WorkerThread
|
||||||
|
|
||||||
from .. import createiso
|
from .. import createiso
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ import xml.dom.minidom
|
|||||||
import productmd.modules
|
import productmd.modules
|
||||||
import productmd.rpms
|
import productmd.rpms
|
||||||
from kobo.shortcuts import relative_path, run
|
from kobo.shortcuts import relative_path, run
|
||||||
from kobo.threads import ThreadPool, WorkerThread
|
from kobo.threads import ThreadPool
|
||||||
|
|
||||||
from ..module_util import Modulemd, collect_module_defaults, collect_module_obsoletes
|
from ..module_util import Modulemd, collect_module_defaults, collect_module_obsoletes
|
||||||
from ..util import (
|
from ..util import (
|
||||||
@ -38,6 +38,7 @@ from ..util import (
|
|||||||
from ..wrappers.createrepo import CreaterepoWrapper
|
from ..wrappers.createrepo import CreaterepoWrapper
|
||||||
from ..wrappers.scm import get_dir_from_scm
|
from ..wrappers.scm import get_dir_from_scm
|
||||||
from .base import PhaseBase
|
from .base import PhaseBase
|
||||||
|
from ..threading import TelemetryWorkerThread as WorkerThread
|
||||||
|
|
||||||
CACHE_TOPDIR = "/var/cache/pungi/createrepo_c/"
|
CACHE_TOPDIR = "/var/cache/pungi/createrepo_c/"
|
||||||
createrepo_lock = threading.Lock()
|
createrepo_lock = threading.Lock()
|
||||||
|
@ -18,7 +18,8 @@ import hashlib
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from kobo.shortcuts import force_list
|
from kobo.shortcuts import force_list
|
||||||
from kobo.threads import ThreadPool, WorkerThread
|
from kobo.threads import ThreadPool
|
||||||
|
from pungi.threading import TelemetryWorkerThread as WorkerThread
|
||||||
import productmd.treeinfo
|
import productmd.treeinfo
|
||||||
from productmd.extra_files import ExtraFiles
|
from productmd.extra_files import ExtraFiles
|
||||||
|
|
||||||
|
@ -13,7 +13,8 @@ from pungi.util import as_local_file, translate_path, get_repo_urls, version_gen
|
|||||||
from pungi.phases import base
|
from pungi.phases import base
|
||||||
from pungi.linker import Linker
|
from pungi.linker import Linker
|
||||||
from pungi.wrappers.kojiwrapper import KojiWrapper
|
from pungi.wrappers.kojiwrapper import KojiWrapper
|
||||||
from kobo.threads import ThreadPool, WorkerThread
|
from kobo.threads import ThreadPool
|
||||||
|
from pungi.threading import TelemetryWorkerThread as WorkerThread
|
||||||
from kobo.shortcuts import force_list
|
from kobo.shortcuts import force_list
|
||||||
from productmd.images import Image
|
from productmd.images import Image
|
||||||
from productmd.rpms import Rpms
|
from productmd.rpms import Rpms
|
||||||
|
@ -2,12 +2,13 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from kobo.threads import ThreadPool, WorkerThread
|
from kobo.threads import ThreadPool
|
||||||
|
|
||||||
from .base import ConfigGuardedPhase, PhaseLoggerMixin
|
from .base import ConfigGuardedPhase, PhaseLoggerMixin
|
||||||
from .. import util
|
from .. import util
|
||||||
from ..wrappers import kojiwrapper
|
from ..wrappers import kojiwrapper
|
||||||
from ..phases.osbs import add_metadata
|
from ..phases.osbs import add_metadata
|
||||||
|
from ..threading import TelemetryWorkerThread as WorkerThread
|
||||||
|
|
||||||
|
|
||||||
class ImageContainerPhase(PhaseLoggerMixin, ConfigGuardedPhase):
|
class ImageContainerPhase(PhaseLoggerMixin, ConfigGuardedPhase):
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from kobo.threads import ThreadPool, WorkerThread
|
from kobo.threads import ThreadPool
|
||||||
from kobo import shortcuts
|
from kobo import shortcuts
|
||||||
from productmd.images import Image
|
from productmd.images import Image
|
||||||
|
|
||||||
@ -10,6 +10,7 @@ from .. import util
|
|||||||
from ..linker import Linker
|
from ..linker import Linker
|
||||||
from ..wrappers import kojiwrapper
|
from ..wrappers import kojiwrapper
|
||||||
from .image_build import EXTENSIONS
|
from .image_build import EXTENSIONS
|
||||||
|
from ..threading import TelemetryWorkerThread as WorkerThread
|
||||||
|
|
||||||
KIWIEXTENSIONS = [
|
KIWIEXTENSIONS = [
|
||||||
("vhd-compressed", ["vhdfixed.xz"], "vhd.xz"),
|
("vhd-compressed", ["vhdfixed.xz"], "vhd.xz"),
|
||||||
|
@ -9,8 +9,9 @@ from pungi.util import translate_path, get_repo_urls
|
|||||||
from pungi.phases.base import ConfigGuardedPhase, ImageConfigMixin, PhaseLoggerMixin
|
from pungi.phases.base import ConfigGuardedPhase, ImageConfigMixin, PhaseLoggerMixin
|
||||||
from pungi.linker import Linker
|
from pungi.linker import Linker
|
||||||
from pungi.wrappers.kojiwrapper import KojiWrapper
|
from pungi.wrappers.kojiwrapper import KojiWrapper
|
||||||
from kobo.threads import ThreadPool, WorkerThread
|
from kobo.threads import ThreadPool
|
||||||
from productmd.images import Image
|
from productmd.images import Image
|
||||||
|
from pungi.threading import TelemetryWorkerThread as WorkerThread
|
||||||
|
|
||||||
|
|
||||||
class LiveMediaPhase(PhaseLoggerMixin, ImageConfigMixin, ConfigGuardedPhase):
|
class LiveMediaPhase(PhaseLoggerMixin, ImageConfigMixin, ConfigGuardedPhase):
|
||||||
|
@ -5,7 +5,7 @@ import copy
|
|||||||
import fnmatch
|
import fnmatch
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from kobo.threads import ThreadPool, WorkerThread
|
from kobo.threads import ThreadPool
|
||||||
from kobo import shortcuts
|
from kobo import shortcuts
|
||||||
from productmd.rpms import Rpms
|
from productmd.rpms import Rpms
|
||||||
|
|
||||||
@ -13,6 +13,7 @@ from .base import ConfigGuardedPhase, PhaseLoggerMixin
|
|||||||
from .. import util
|
from .. import util
|
||||||
from ..wrappers import kojiwrapper
|
from ..wrappers import kojiwrapper
|
||||||
from ..wrappers.scm import get_file_from_scm
|
from ..wrappers.scm import get_file_from_scm
|
||||||
|
from ..threading import TelemetryWorkerThread as WorkerThread
|
||||||
|
|
||||||
|
|
||||||
class OSBSPhase(PhaseLoggerMixin, ConfigGuardedPhase):
|
class OSBSPhase(PhaseLoggerMixin, ConfigGuardedPhase):
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from kobo.threads import ThreadPool, WorkerThread
|
from kobo.threads import ThreadPool
|
||||||
from kobo import shortcuts
|
from kobo import shortcuts
|
||||||
from productmd.images import Image
|
from productmd.images import Image
|
||||||
|
|
||||||
@ -10,6 +10,7 @@ from .. import util
|
|||||||
from ..linker import Linker
|
from ..linker import Linker
|
||||||
from ..wrappers import kojiwrapper
|
from ..wrappers import kojiwrapper
|
||||||
from .image_build import EXTENSIONS
|
from .image_build import EXTENSIONS
|
||||||
|
from ..threading import TelemetryWorkerThread as WorkerThread
|
||||||
|
|
||||||
# copy and modify EXTENSIONS with some that osbuild produces but which
|
# copy and modify EXTENSIONS with some that osbuild produces but which
|
||||||
# do not exist as `koji image-build` formats
|
# do not exist as `koji image-build` formats
|
||||||
|
@ -4,7 +4,7 @@ import copy
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from kobo import shortcuts
|
from kobo import shortcuts
|
||||||
from kobo.threads import ThreadPool, WorkerThread
|
from kobo.threads import ThreadPool
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from pungi.arch_utils import getBaseArch
|
from pungi.arch_utils import getBaseArch
|
||||||
@ -14,6 +14,7 @@ from .. import util
|
|||||||
from ..ostree.utils import get_ref_from_treefile, get_commitid_from_commitid_file
|
from ..ostree.utils import get_ref_from_treefile, get_commitid_from_commitid_file
|
||||||
from ..util import get_repo_dicts, translate_path
|
from ..util import get_repo_dicts, translate_path
|
||||||
from ..wrappers import scm
|
from ..wrappers import scm
|
||||||
|
from ..threading import TelemetryWorkerThread as WorkerThread
|
||||||
|
|
||||||
|
|
||||||
class OSTreePhase(ConfigGuardedPhase):
|
class OSTreePhase(ConfigGuardedPhase):
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from kobo.threads import ThreadPool, WorkerThread
|
from kobo.threads import ThreadPool
|
||||||
import shlex
|
import shlex
|
||||||
import shutil
|
import shutil
|
||||||
from productmd import images
|
from productmd import images
|
||||||
@ -20,6 +20,7 @@ from ..util import (
|
|||||||
)
|
)
|
||||||
from ..wrappers import iso, lorax, scm
|
from ..wrappers import iso, lorax, scm
|
||||||
from ..runroot import Runroot
|
from ..runroot import Runroot
|
||||||
|
from ..threading import TelemetryWorkerThread as WorkerThread
|
||||||
|
|
||||||
|
|
||||||
class OstreeInstallerPhase(PhaseLoggerMixin, ConfigGuardedPhase):
|
class OstreeInstallerPhase(PhaseLoggerMixin, ConfigGuardedPhase):
|
||||||
|
@ -33,11 +33,12 @@ import kobo.pkgset
|
|||||||
import kobo.rpmlib
|
import kobo.rpmlib
|
||||||
from kobo.shortcuts import compute_file_checksums
|
from kobo.shortcuts import compute_file_checksums
|
||||||
|
|
||||||
from kobo.threads import WorkerThread, ThreadPool
|
from kobo.threads import ThreadPool
|
||||||
|
|
||||||
from pungi.util import pkg_is_srpm, copy_all
|
from pungi.util import pkg_is_srpm, copy_all
|
||||||
from pungi.arch import get_valid_arches, is_excluded
|
from pungi.arch import get_valid_arches, is_excluded
|
||||||
from pungi.errors import UnsignedPackagesError
|
from pungi.errors import UnsignedPackagesError
|
||||||
|
from pungi.threading import TelemetryWorkerThread as WorkerThread
|
||||||
|
|
||||||
|
|
||||||
class ExtendedRpmWrapper(kobo.pkgset.SimpleRpmWrapper):
|
class ExtendedRpmWrapper(kobo.pkgset.SimpleRpmWrapper):
|
||||||
@ -547,7 +548,7 @@ class KojiPackageSet(PackageSetBase):
|
|||||||
pathinfo = self.koji_wrapper.koji_module.pathinfo
|
pathinfo = self.koji_wrapper.koji_module.pathinfo
|
||||||
paths = []
|
paths = []
|
||||||
|
|
||||||
if "getRPMChecksums" in self.koji_proxy.system.listMethods():
|
if "getRPMChecksums" in self.koji_wrapper.koji_methods:
|
||||||
|
|
||||||
def checksum_validator(keyname, pkg_path):
|
def checksum_validator(keyname, pkg_path):
|
||||||
checksums = self.koji_proxy.getRPMChecksums(
|
checksums = self.koji_proxy.getRPMChecksums(
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from kobo import shortcuts
|
from kobo import shortcuts
|
||||||
from kobo.threads import ThreadPool, WorkerThread
|
from kobo.threads import ThreadPool
|
||||||
|
|
||||||
|
from pungi.threading import TelemetryWorkerThread as WorkerThread
|
||||||
|
|
||||||
|
|
||||||
class WeaverPhase(object):
|
class WeaverPhase(object):
|
||||||
|
@ -23,6 +23,7 @@ from pungi import get_full_version, util
|
|||||||
from pungi.errors import UnsignedPackagesError
|
from pungi.errors import UnsignedPackagesError
|
||||||
from pungi.wrappers import kojiwrapper
|
from pungi.wrappers import kojiwrapper
|
||||||
from pungi.util import rmtree
|
from pungi.util import rmtree
|
||||||
|
from pungi.otel import tracing
|
||||||
|
|
||||||
|
|
||||||
# force C locales
|
# force C locales
|
||||||
@ -652,22 +653,25 @@ def cli_main():
|
|||||||
signal.signal(signal.SIGINT, sigterm_handler)
|
signal.signal(signal.SIGINT, sigterm_handler)
|
||||||
signal.signal(signal.SIGTERM, sigterm_handler)
|
signal.signal(signal.SIGTERM, sigterm_handler)
|
||||||
|
|
||||||
try:
|
with tracing.span("run-compose"):
|
||||||
main()
|
try:
|
||||||
except (Exception, KeyboardInterrupt) as ex:
|
main()
|
||||||
if COMPOSE:
|
except (Exception, KeyboardInterrupt) as ex:
|
||||||
COMPOSE.log_error("Compose run failed: %s" % ex)
|
if COMPOSE:
|
||||||
COMPOSE.traceback(show_locals=getattr(ex, "show_locals", True))
|
COMPOSE.log_error("Compose run failed: %s" % ex)
|
||||||
COMPOSE.log_critical("Compose failed: %s" % COMPOSE.topdir)
|
COMPOSE.traceback(show_locals=getattr(ex, "show_locals", True))
|
||||||
COMPOSE.write_status("DOOMED")
|
COMPOSE.log_critical("Compose failed: %s" % COMPOSE.topdir)
|
||||||
else:
|
COMPOSE.write_status("DOOMED")
|
||||||
print("Exception: %s" % ex)
|
else:
|
||||||
raise
|
print("Exception: %s" % ex)
|
||||||
sys.stdout.flush()
|
raise
|
||||||
sys.stderr.flush()
|
sys.stdout.flush()
|
||||||
sys.exit(1)
|
sys.stderr.flush()
|
||||||
finally:
|
sys.exit(1)
|
||||||
# Remove repositories cloned during ExtraFiles phase
|
finally:
|
||||||
process_id = os.getpid()
|
# Remove repositories cloned during ExtraFiles phase
|
||||||
directoy_to_remove = "/tmp/pungi-temp-git-repos-" + str(process_id) + "/"
|
process_id = os.getpid()
|
||||||
rmtree(directoy_to_remove)
|
directoy_to_remove = "/tmp/pungi-temp-git-repos-" + str(process_id) + "/"
|
||||||
|
rmtree(directoy_to_remove)
|
||||||
|
# Wait for all traces to be sent...
|
||||||
|
tracing.force_flush()
|
||||||
|
21
pungi/threading.py
Normal file
21
pungi/threading.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
from kobo.threads import WorkerThread
|
||||||
|
|
||||||
|
from .otel import tracing
|
||||||
|
|
||||||
|
|
||||||
|
class TelemetryWorkerThread(WorkerThread):
|
||||||
|
"""
|
||||||
|
Subclass of WorkerThread that captures current context when the thread is
|
||||||
|
created, and restores the context in the new thread.
|
||||||
|
|
||||||
|
A regular WorkerThread would start from an empty context, leading to any
|
||||||
|
spans created in the thread disconnected from the overall trace.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.traceparent = tracing.get_traceparent()
|
||||||
|
super(TelemetryWorkerThread, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def run(self, *args, **kwargs):
|
||||||
|
tracing.set_context(self.traceparent)
|
||||||
|
super(TelemetryWorkerThread, self).run(*args, **kwargs)
|
@ -32,9 +32,11 @@ import functools
|
|||||||
|
|
||||||
import kobo.conf
|
import kobo.conf
|
||||||
from kobo.shortcuts import run, force_list
|
from kobo.shortcuts import run, force_list
|
||||||
from kobo.threads import WorkerThread, ThreadPool
|
from kobo.threads import ThreadPool
|
||||||
from productmd.common import get_major_version
|
from productmd.common import get_major_version
|
||||||
from pungi.module_util import Modulemd
|
from pungi.module_util import Modulemd
|
||||||
|
from pungi.otel import tracing
|
||||||
|
from pungi.threading import TelemetryWorkerThread as WorkerThread
|
||||||
|
|
||||||
# Patterns that match all names of debuginfo packages
|
# Patterns that match all names of debuginfo packages
|
||||||
DEBUG_PATTERNS = ["*-debuginfo", "*-debuginfo-*", "*-debugsource"]
|
DEBUG_PATTERNS = ["*-debuginfo", "*-debuginfo-*", "*-debugsource"]
|
||||||
@ -877,11 +879,12 @@ def retry(timeout=120, interval=30, wait_on=Exception):
|
|||||||
|
|
||||||
@retry(wait_on=RuntimeError)
|
@retry(wait_on=RuntimeError)
|
||||||
def git_ls_remote(baseurl, ref, credential_helper=None):
|
def git_ls_remote(baseurl, ref, credential_helper=None):
|
||||||
cmd = ["git"]
|
with tracing.span("git-ls-remote", baseurl=baseurl, ref=ref):
|
||||||
if credential_helper:
|
cmd = ["git"]
|
||||||
cmd.extend(["-c", "credential.useHttpPath=true"])
|
if credential_helper:
|
||||||
cmd.extend(["-c", "credential.helper=%s" % credential_helper])
|
cmd.extend(["-c", "credential.useHttpPath=true"])
|
||||||
return run(cmd + ["ls-remote", baseurl, ref], text=True, errors="replace")
|
cmd.extend(["-c", "credential.helper=%s" % credential_helper])
|
||||||
|
return run(cmd + ["ls-remote", baseurl, ref], text=True, errors="replace")
|
||||||
|
|
||||||
|
|
||||||
def get_tz_offset():
|
def get_tz_offset():
|
||||||
|
@ -34,6 +34,7 @@ from datetime import timedelta
|
|||||||
|
|
||||||
from .kojimock import KojiMock
|
from .kojimock import KojiMock
|
||||||
from .. import util
|
from .. import util
|
||||||
|
from ..otel import tracing
|
||||||
from ..arch_utils import getBaseArch
|
from ..arch_utils import getBaseArch
|
||||||
|
|
||||||
|
|
||||||
@ -68,9 +69,11 @@ class KojiWrapper(object):
|
|||||||
value = getattr(self.koji_module.config, key, None)
|
value = getattr(self.koji_module.config, key, None)
|
||||||
if value is not None:
|
if value is not None:
|
||||||
session_opts[key] = value
|
session_opts[key] = value
|
||||||
self.koji_proxy = koji.ClientSession(
|
self.koji_proxy = tracing.instrument_xmlrpc_proxy(
|
||||||
self.koji_module.config.server, session_opts
|
koji.ClientSession(self.koji_module.config.server, session_opts)
|
||||||
)
|
)
|
||||||
|
with tracing.span("koji.system.listMethods"):
|
||||||
|
self.koji_methods = self.koji_proxy.system.listMethods()
|
||||||
|
|
||||||
# This retry should be removed once https://pagure.io/koji/issue/3170 is
|
# This retry should be removed once https://pagure.io/koji/issue/3170 is
|
||||||
# fixed and released.
|
# fixed and released.
|
||||||
@ -1011,7 +1014,8 @@ class KojiDownloadProxy:
|
|||||||
os.utime(destination_file)
|
os.utime(destination_file)
|
||||||
return destination_file
|
return destination_file
|
||||||
|
|
||||||
return self._atomic_download(url, destination_file, validator)
|
with tracing.span("download-rpm", url=url):
|
||||||
|
return self._atomic_download(url, destination_file, validator)
|
||||||
|
|
||||||
def get_file(self, path, validator=None):
|
def get_file(self, path, validator=None):
|
||||||
"""
|
"""
|
||||||
|
@ -28,6 +28,7 @@ import kobo.log
|
|||||||
from kobo.shortcuts import run, force_list
|
from kobo.shortcuts import run, force_list
|
||||||
from pungi.util import explode_rpm_package, makedirs, copy_all, temp_dir, retry
|
from pungi.util import explode_rpm_package, makedirs, copy_all, temp_dir, retry
|
||||||
from .kojiwrapper import KojiWrapper
|
from .kojiwrapper import KojiWrapper
|
||||||
|
from ..otel import tracing
|
||||||
|
|
||||||
lock = threading.Lock()
|
lock = threading.Lock()
|
||||||
|
|
||||||
@ -229,7 +230,8 @@ class GitWrapper(ScmBase):
|
|||||||
tmp_dir = self.get_temp_repo_path(scm_root, scm_branch)
|
tmp_dir = self.get_temp_repo_path(scm_root, scm_branch)
|
||||||
if not os.path.isdir(tmp_dir):
|
if not os.path.isdir(tmp_dir):
|
||||||
makedirs(tmp_dir)
|
makedirs(tmp_dir)
|
||||||
self._clone(scm_root, scm_branch, tmp_dir)
|
with tracing.span("git-clone", repo=scm_root, ref=scm_branch):
|
||||||
|
self._clone(scm_root, scm_branch, tmp_dir)
|
||||||
self.run_process_command(tmp_dir)
|
self.run_process_command(tmp_dir)
|
||||||
return tmp_dir
|
return tmp_dir
|
||||||
|
|
||||||
@ -377,7 +379,8 @@ class ContainerImageScmWrapper(ScmBase):
|
|||||||
self.log_debug(
|
self.log_debug(
|
||||||
"Exporting container %s to %s: %s", scm_root, target_dir, cmd
|
"Exporting container %s to %s: %s", scm_root, target_dir, cmd
|
||||||
)
|
)
|
||||||
self.retry_run(cmd, can_fail=False)
|
with tracing.span("skopeo-copy", arch=arch, image=scm_root):
|
||||||
|
self.retry_run(cmd, can_fail=False)
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
self.log_error(
|
self.log_error(
|
||||||
"Failed to copy container image: %s %s", e, getattr(e, "output", "")
|
"Failed to copy container image: %s %s", e, getattr(e, "output", "")
|
||||||
|
@ -130,14 +130,6 @@ class PkgsetCompareMixin(object):
|
|||||||
self.assertEqual({}, actual)
|
self.assertEqual({}, actual)
|
||||||
|
|
||||||
|
|
||||||
class DummySystem(object):
|
|
||||||
def __init__(self):
|
|
||||||
self.methods = ["_listapi", "Dummy", "getRPM", "getRPMChecksums"]
|
|
||||||
|
|
||||||
def listMethods(self):
|
|
||||||
return self.methods
|
|
||||||
|
|
||||||
|
|
||||||
@ddt.ddt
|
@ddt.ddt
|
||||||
@mock.patch("pungi.phases.pkgset.pkgsets.ReaderPool", new=FakePool)
|
@mock.patch("pungi.phases.pkgset.pkgsets.ReaderPool", new=FakePool)
|
||||||
@mock.patch("kobo.pkgset.FileCache", new=MockFileCache)
|
@mock.patch("kobo.pkgset.FileCache", new=MockFileCache)
|
||||||
@ -166,7 +158,7 @@ class TestKojiPkgset(PkgsetCompareMixin, helpers.PungiTestCase):
|
|||||||
self.koji_downloader = helpers.FSKojiDownloader()
|
self.koji_downloader = helpers.FSKojiDownloader()
|
||||||
self.koji_wrapper = mock.Mock()
|
self.koji_wrapper = mock.Mock()
|
||||||
self.koji_wrapper.koji_proxy.listTaggedRPMS.return_value = self.tagged_rpms
|
self.koji_wrapper.koji_proxy.listTaggedRPMS.return_value = self.tagged_rpms
|
||||||
self.koji_wrapper.koji_proxy.system = DummySystem()
|
self.koji_wrapper.koji_methods = ["getRPM", "getRPMChecksums"]
|
||||||
self.koji_wrapper.koji_module.pathinfo = self.path_info
|
self.koji_wrapper.koji_module.pathinfo = self.path_info
|
||||||
|
|
||||||
def _touch_files(self, filenames):
|
def _touch_files(self, filenames):
|
||||||
|
Loading…
Reference in New Issue
Block a user