1from typing import TYPE_CHECKING, Optional
2
3from ..config import Config
4from ..utils import RichStatus
5from .ircluster import IRCluster
6from .irresource import IRResource
7
8if TYPE_CHECKING:
9 from .ir import IR # pragma: no cover
10
11
12class IRTracing(IRResource):
13 cluster: Optional[IRCluster]
14 service: str
15 driver: str
16 driver_config: dict
17 # TODO: tag_headers is deprecated and should be removed once migrated to CRD v3
18 tag_headers: list
19 custom_tags: list
20 host_rewrite: Optional[str]
21 sampling: dict
22
23 def __init__(
24 self,
25 ir: "IR",
26 aconf: Config,
27 rkey: str = "ir.tracing",
28 kind: str = "ir.tracing",
29 name: str = "tracing",
30 namespace: Optional[str] = None,
31 **kwargs
32 ) -> None:
33 del kwargs # silence unused-variable warning
34
35 super().__init__(ir=ir, aconf=aconf, rkey=rkey, kind=kind, name=name, namespace=namespace)
36 self.cluster = None
37
38 def setup(self, ir: "IR", aconf: Config) -> bool:
39 # Some of the validations might go away if JSON Schema is doing the validations, but need to check on that
40
41 config_info = aconf.get_config("tracing_configs")
42
43 if not config_info:
44 ir.logger.debug("IRTracing: no tracing config, bailing")
45 # No tracing info. Be done.
46 return False
47
48 configs = config_info.values()
49 number_configs = len(configs)
50 if number_configs != 1:
51 self.post_error(
52 RichStatus.fromError(
53 "exactly one TracingService is supported, got {}".format(number_configs),
54 module=aconf,
55 )
56 )
57 return False
58
59 config = list(configs)[0]
60
61 service = config.get("service")
62 if not service:
63 self.post_error(RichStatus.fromError("service field is required in TracingService"))
64 return False
65
66 driver = config.get("driver")
67 if not driver:
68 self.post_error(RichStatus.fromError("driver field is required in TracingService"))
69 return False
70
71 self.namespace = config.get("namespace", self.namespace)
72
73 grpc = False
74
75 if driver == "lightstep":
76 self.post_error(
77 RichStatus.fromError(
78 "as of v3.4+ the 'lightstep' driver is no longer supported in the TracingService, please see docs for migration options"
79 )
80 )
81 return False
82 if driver == "opentelemetry":
83 ir.logger.warning(
84 "The OpenTelemetry tracing driver is work-in-progress. Functionality is incomplete and it is not intended for production use. This extension has an unknown security posture and should only be used in deployments where both the downstream and upstream are trusted."
85 )
86 grpc = True
87
88 if driver == "datadog":
89 driver = "envoy.tracers.datadog"
90
91 # This "config" is a field on the aconf for the TracingService, not to be confused with the
92 # envoyv2 untyped "config" field. We actually use a "typed_config" in the final Envoy
93 # config, see envoy/v2/v2tracer.py.
94 driver_config = config.get("config", {})
95
96 if driver == "zipkin":
97 # fill zipkin defaults
98 if not driver_config.get("collector_endpoint"):
99 driver_config["collector_endpoint"] = "/api/v2/spans"
100 if not driver_config.get("collector_endpoint_version"):
101 driver_config["collector_endpoint_version"] = "HTTP_JSON"
102 if not "trace_id_128bit" in driver_config:
103 # Make 128-bit traceid the default
104 driver_config["trace_id_128bit"] = True
105 # validate
106 if driver_config["collector_endpoint_version"] not in ["HTTP_JSON", "HTTP_PROTO"]:
107 self.post_error(
108 RichStatus.fromError(
109 "collector_endpoint_version must be one of HTTP_JSON, HTTP_PROTO'"
110 )
111 )
112 return False
113
114 # OK, we have a valid config.
115 self.sourced_by(config)
116
117 self.service = service
118 self.driver = driver
119 self.grpc = grpc
120 self.cluster = None
121 self.driver_config = driver_config
122 self.tag_headers = config.get("tag_headers", [])
123 self.custom_tags = config.get("custom_tags", [])
124 self.sampling = config.get("sampling", {})
125
126 self.stats_name = config.get("stats_name", None)
127
128 # XXX host_rewrite actually isn't in the schema right now.
129 self.host_rewrite = config.get("host_rewrite", None)
130
131 # Remember that the config references us.
132 self.referenced_by(config)
133
134 return True
135
136 def add_mappings(self, ir: "IR", aconf: Config):
137 cluster = ir.add_cluster(
138 IRCluster(
139 ir=ir,
140 aconf=aconf,
141 parent_ir_resource=self,
142 location=self.location,
143 service=self.service,
144 host_rewrite=self.get("host_rewrite", None),
145 marker="tracing",
146 grpc=self.grpc,
147 stats_name=self.get("stats_name", None),
148 )
149 )
150
151 cluster.referenced_by(self)
152 self.cluster = cluster
153
154 def finalize(self):
155 assert self.cluster
156 self.ir.logger.debug("tracing cluster envoy name: %s" % self.cluster.envoy_name)
157 # Opentelemetry is the only one that does not use collector_cluster
158 if self.driver == "opentelemetry":
159 self.driver_config["grpc_service"] = {
160 "envoy_grpc": {"cluster_name": self.cluster.envoy_name}
161 }
162 else:
163 self.driver_config["collector_cluster"] = self.cluster.envoy_name
View as plain text