Skip to content

Commit

Permalink
fix: add checks and log_targets to ops.testing (#1268)
Browse files Browse the repository at this point in the history
Add checks and log_targets to ops.testing add_layer so it is possible to
test it via Harness.

Fixes #1112
  • Loading branch information
amandahla authored Jul 2, 2024
1 parent 2e1dbd9 commit 5a21cd2
Show file tree
Hide file tree
Showing 4 changed files with 417 additions and 6 deletions.
88 changes: 85 additions & 3 deletions ops/pebble.py
Original file line number Diff line number Diff line change
Expand Up @@ -1135,6 +1135,73 @@ def __eq__(self, other: Union['CheckDict', 'Check']) -> bool:
else:
return NotImplemented

def _merge_exec(self, other: 'ExecDict') -> None:
"""Merges this exec object with another exec definition.
For attributes present in both objects, the passed in exec
attributes take precedence.
"""
if self.exec is None:
self.exec = {}
for name, value in other.items():
if not value:
continue
if name == 'environment':
current = self.exec.get(name, {})
current.update(value) # type: ignore
self.exec[name] = current
else:
self.exec[name] = value

def _merge_http(self, other: 'HttpDict') -> None:
"""Merges this http object with another http definition.
For attributes present in both objects, the passed in http
attributes take precedence.
"""
if self.http is None:
self.http = {}
for name, value in other.items():
if not value:
continue
if name == 'headers':
current = self.http.get(name, {})
current.update(value) # type: ignore
self.http[name] = current
else:
self.http[name] = value

def _merge_tcp(self, other: 'TcpDict') -> None:
"""Merges this tcp object with another tcp definition.
For attributes present in both objects, the passed in tcp
attributes take precedence.
"""
if self.tcp is None:
self.tcp = {}
for name, value in other.items():
if not value:
continue
self.tcp[name] = value

def _merge(self, other: 'Check'):
"""Merges this check object with another check definition.
For attributes present in both objects, the passed in check
attributes take precedence.
"""
for name, value in other.__dict__.items():
if not value or name == 'name':
continue
if name == 'http':
self._merge_http(value)
elif name == 'tcp':
self._merge_tcp(value)
elif name == 'exec':
self._merge_exec(value)
else:
setattr(self, name, value)


class CheckLevel(enum.Enum):
"""Enum of check levels."""
Expand Down Expand Up @@ -1162,9 +1229,8 @@ def __init__(self, name: str, raw: Optional['LogTargetDict'] = None):
self.location = dct.get('location', '')
self.services: List[str] = list(dct.get('services', []))
labels = dct.get('labels')
if labels is not None:
labels = copy.deepcopy(labels)
self.labels: Optional[Dict[str, str]] = labels
labels = copy.deepcopy(labels) if labels is not None else {}
self.labels: Dict[str, str] = labels

def to_dict(self) -> 'LogTargetDict':
"""Convert this log target object to its dict representation."""
Expand All @@ -1189,6 +1255,22 @@ def __eq__(self, other: Union['LogTargetDict', 'LogTarget']):
else:
return NotImplemented

def _merge(self, other: 'LogTarget'):
"""Merges this log target object with another log target definition.
For attributes present in both objects, the passed in log target
attributes take precedence.
"""
for name, value in other.__dict__.items():
if not value or name == 'name':
continue
if name == 'labels':
getattr(self, name).update(value)
elif name == 'services':
getattr(self, name).extend(value)
else:
setattr(self, name, value)


class FileType(enum.Enum):
"""Enum of file types."""
Expand Down
42 changes: 40 additions & 2 deletions ops/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -3183,6 +3183,7 @@ def add_layer(
if not combine:
raise RuntimeError(f'400 Bad Request: layer "{label}" already exists')
layer = self._layers[label]

for name, service in layer_obj.services.items():
# 'override' is actually single quoted in the real error, but
# it shouldn't be, hopefully that gets cleaned up.
Expand All @@ -3200,11 +3201,48 @@ def add_layer(
layer.services[name] = service
elif service.override == 'merge':
if combine and name in layer.services:
s = layer.services[name]
s._merge(service)
layer.services[name]._merge(service)
else:
layer.services[name] = service

for name, check in layer_obj.checks.items():
if not check.override:
raise RuntimeError(
f'500 Internal Server Error: layer "{label}" must define'
f'"override" for check "{name}"'
)
if check.override not in ('merge', 'replace'):
raise RuntimeError(
f'500 Internal Server Error: layer "{label}" has invalid '
f'"override" value for check "{name}"'
)
elif check.override == 'replace':
layer.checks[name] = check
elif check.override == 'merge':
if combine and name in layer.checks:
layer.checks[name]._merge(check)
else:
layer.checks[name] = check

for name, log_target in layer_obj.log_targets.items():
if not log_target.override:
raise RuntimeError(
f'500 Internal Server Error: layer "{label}" must define'
f'"override" for log target "{name}"'
)
if log_target.override not in ('merge', 'replace'):
raise RuntimeError(
f'500 Internal Server Error: layer "{label}" has invalid '
f'"override" value for log target "{name}"'
)
elif log_target.override == 'replace':
layer.log_targets[name] = log_target
elif log_target.override == 'merge':
if combine and name in layer.log_targets:
layer.log_targets[name]._merge(log_target)
else:
layer.log_targets[name] = log_target

else:
self._layers[label] = layer_obj

Expand Down
2 changes: 1 addition & 1 deletion test/test_pebble.py
Original file line number Diff line number Diff line change
Expand Up @@ -1150,7 +1150,7 @@ def _assert_empty(self, target: pebble.LogTarget, name: str):
assert target.type == ''
assert target.location == ''
assert target.services == []
assert target.labels is None
assert target.labels == {}

def test_name_only(self):
target = pebble.LogTarget('tgt')
Expand Down
Loading

0 comments on commit 5a21cd2

Please sign in to comment.