Skip to content

Commit 7a104cb

Browse files
authored
Merge pull request #118 from edx/robrap/code-owner-squad-and-theme
ARCHBOM-1759: code owner squad and theme and other cleanup
2 parents 310d247 + bdccc5a commit 7a104cb

File tree

9 files changed

+307
-96
lines changed

9 files changed

+307
-96
lines changed

CHANGELOG.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,23 @@ Change Log
1414
Unreleased
1515
----------
1616

17+
[4.0.0] = 2021-04-30
18+
19+
Removed
20+
-------
21+
22+
* Removed the old location of ``CodeOwnerMonitoringMiddleware``. It had moved in a past commit. Although technically a breaking change, all references in the Open edX platform have already been updated to point to the new location.
23+
24+
Added
25+
-----
26+
27+
* Added new ``code_owner_theme`` and ``code_owner_squad`` custom attributes. This is useful in cases where the ``code_owner`` combines a theme and squad name, because monitoring can instead reference ``code_owner_squad`` to be resilient to theme name updates. For the decision doc, see edx_django_utils/monitoring/docs/decisions/0004-code-owner-theme-and-squad.rst.
28+
29+
Updated
30+
-------
31+
32+
* Misconfigurations of CODE_OWNER_MAPPINGS will now fail fast, rather than just logging. Although technically a breaking change, if CODE_OWNER_MAPPINGS is in use, it is probably correctly configured and this change should be a no-op.
33+
1734
[3.16.0] - 2021-03-24
1835
---------------------
1936

edx_django_utils/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
EdX utilities for Django Application development..
33
"""
44

5-
__version__ = "3.16.0"
5+
__version__ = "4.0.0"
66

77
default_app_config = (
88
"edx_django_utils.apps.EdxDjangoUtilsConfig"

edx_django_utils/monitoring/code_owner/middleware.py

Lines changed: 0 additions & 36 deletions
This file was deleted.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
Code Owner Theme and Squad
2+
==========================
3+
4+
Status
5+
------
6+
7+
Accepted
8+
9+
Context
10+
-------
11+
12+
As detailed in the `Monitoring by Code Owner ADR`_, we added a ``code_owner`` custom attribute for monitoring by code owner. The value for this attribute had the format 'theme-squad'.
13+
14+
The problems with this configuration is that for theme name changes, or when squads transfer themes, any monitoring referencing the full name would also need to be updated.
15+
16+
.. _Monitoring by Code Owner ADR: https://github.com/edx/edx-platform/blob/master/lms/djangoapps/monitoring/docs/decisions/0001-monitoring-by-code-owner.rst
17+
18+
Decision
19+
--------
20+
21+
We will add a ``code_owner_squad`` custom attribute. Monitoring will now be able to refer to ``code_owner_squad`` with a value of 'squad', and will be unaffected by theme name changes.
22+
23+
Additionally, we are adding ``code_owner_theme`` for similar convenience if there is a need for theme-based monitoring.
24+
25+
We will leave the original ``code_owner`` custom attribute for backward compatability, and for cases where theme and squad are not used.
26+
27+
Consequences
28+
------------
29+
30+
* Theme name changes may now result in a one time fix for those that were relying on ``code_owner``. Monitoring can switch from ``code_owner`` to ``code_owner_squad``, rather than a change for every theme update in the future.

edx_django_utils/monitoring/docs/how_tos/add_code_owner_custom_attribute_to_an_ida.rst

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,20 @@ Add Code_Owner Custom Attribute to an IDA
55
:local:
66
:depth: 2
77

8-
What is the code_owner custom attribute?
9-
----------------------------------------
8+
What are the code owner custom attributes?
9+
------------------------------------------
1010

11-
The code_owner custom attribute can be used to create custom dashboards and alerts for monitoring the things that you own. It was originally introduced for the LMS, as is described in this `ADR on monitoring by code owner`_.
11+
The code owner custom attributes can be used to create custom dashboards and alerts for monitoring the things that you own. It was originally introduced for the LMS, as is described in this `ADR on monitoring by code owner`_.
12+
13+
The code owner custom attributes consist of:
14+
15+
* code_owner: The owner name. When themes and squads are used, this will be the theme and squad names joined by a hyphen.
16+
* code_owner_theme: The theme name of the owner.
17+
* code_owner_squad: The squad name of the owner. Use this to avoid issues when theme name changes.
1218

1319
You can now easily add this same attribute to any IDA so that your dashboards and alerts can work across multiple IDAs at once.
1420

15-
If you want to know about custom attributes in general, see: using_custom_attributes.rst.
21+
If you want to know about custom attributes in general, see :doc:`using_custom_attributes`.
1622

1723
.. _ADR on monitoring by code owner: https://github.com/edx/edx-platform/blob/master/lms/djangoapps/monitoring/docs/decisions/0001-monitoring-by-code-owner.rst
1824

@@ -24,7 +30,7 @@ You simply need to add ``edx_django_utils.monitoring.CodeOwnerMonitoringMiddlewa
2430
Handling celery tasks
2531
---------------------
2632

27-
Celery tasks require use of a special decorator to set the ``code_owner`` custom attribute because no middleware will be run.
33+
Celery tasks require use of a special decorator to set the ``code_owner`` custom attributes because no middleware will be run.
2834

2935
Here is an example::
3036

@@ -47,23 +53,29 @@ An untested potential alternative to the decorator is documented in the `Code Ow
4753
Configuring your app settings
4854
-----------------------------
4955

50-
Once the Middleware is made available, simply set the Django Setting ``CODE_OWNER_MAPPINGS`` appropriately.
56+
Once the Middleware is made available, simply set the Django Settings ``CODE_OWNER_MAPPINGS`` and ``CODE_OWNER_THEMES`` appropriately.
5157

5258
The following example shows how you can include an optional config for a catch-all using ``'*'``. Although you might expect this example to use Python, it is intentionally illustrated in YAML because the catch-all requires special care in YAML.
5359

5460
::
5561

5662
# YAML format of example CODE_OWNER_MAPPINGS
5763
CODE_OWNER_MAPPINGS:
58-
team-red:
64+
theme-x-team-red:
5965
- xblock_django
6066
- openedx.core.djangoapps.xblock
61-
team-blue:
67+
theme-x-team-blue:
6268
- '*' # IMPORTANT: you must surround * with quotes in yml
6369

70+
# YAML format of example CODE_OWNER_THEMES
71+
CODE_OWNER_THEMES:
72+
theme-x:
73+
- theme-x-team-red
74+
- theme-x-team-blue
75+
6476
How to find and fix code_owner mappings
6577
---------------------------------------
6678

67-
If you are missing the `code_owner` custom attribute on a particular Transaction or Error, or if `code_owner` is matching the catch-all, but you want to add a more specific mapping, you can use the other `code_owner supporting attributes`_ to determine what the appropriate mappings should be.
79+
If you are missing the `code_owner` custom attributes on a particular Transaction or Error, or if `code_owner` is matching the catch-all, but you want to add a more specific mapping, you can use the other `code_owner supporting attributes`_ to determine what the appropriate mappings should be.
6880

6981
.. _code_owner supporting attributes: https://github.com/edx/edx-django-utils/blob/7db8301af21760f8bca188b3c6c95a8ae873baf7/edx_django_utils/monitoring/code_owner/middleware.py#L28-L34

edx_django_utils/monitoring/internal/code_owner/middleware.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@
88

99
from ..transactions import get_current_transaction
1010
from ..utils import set_custom_attribute
11-
from .utils import _get_catch_all_code_owner, get_code_owner_from_module, is_code_owner_mappings_configured
11+
from .utils import (
12+
_get_catch_all_code_owner,
13+
get_code_owner_from_module,
14+
is_code_owner_mappings_configured,
15+
set_code_owner_custom_attributes
16+
)
1217

1318
log = logging.getLogger(__name__)
1419

@@ -52,7 +57,7 @@ def _set_code_owner_attribute(self, request):
5257
code_owner = _get_catch_all_code_owner()
5358

5459
if code_owner:
55-
set_custom_attribute('code_owner', code_owner)
60+
set_code_owner_custom_attributes(code_owner)
5661

5762
def _get_module_from_request(self, request):
5863
"""

edx_django_utils/monitoring/internal/code_owner/utils.py

Lines changed: 118 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def get_code_owner_from_module(module):
2929
return None
3030

3131
code_owner_mappings = get_code_owner_mappings()
32-
if code_owner_mappings is None:
32+
if not code_owner_mappings:
3333
return None
3434

3535
module_parts = module.split('.')
@@ -88,9 +88,7 @@ def get_code_owner_mappings():
8888
# .. setting_description: Used for monitoring and reporting of ownership. Use a
8989
# dict with keys of code owner name and value as a list of dotted path
9090
# module names owned by the code owner.
91-
code_owner_mappings = getattr(settings, 'CODE_OWNER_MAPPINGS', None)
92-
if code_owner_mappings is None:
93-
return None
91+
code_owner_mappings = getattr(settings, 'CODE_OWNER_MAPPINGS', {})
9492

9593
try:
9694
for code_owner in code_owner_mappings:
@@ -102,10 +100,9 @@ def get_code_owner_mappings():
102100
if optional_module_prefix_match:
103101
path_without_prefix = path[optional_module_prefix_match.end():]
104102
path_to_code_owner_mapping[path_without_prefix] = code_owner
105-
except Exception as e: # pylint: disable=broad-except
106-
# will remove broad exceptions after ensuring all proper cases are covered
107-
set_custom_attribute('deprecated_broad_except_get_code_owner_mappings', e.__class__)
108-
log.exception('Error processing code_owner_mappings. {}'.format(e))
103+
except TypeError as e:
104+
log.exception('Error processing CODE_OWNER_MAPPINGS. {}'.format(e))
105+
raise e
109106

110107
_PATH_TO_CODE_OWNER_MAPPINGS = path_to_code_owner_mapping
111108
return _PATH_TO_CODE_OWNER_MAPPINGS
@@ -149,7 +146,22 @@ def set_code_owner_attribute_from_module(module):
149146
code_owner = _get_catch_all_code_owner()
150147

151148
if code_owner:
152-
set_custom_attribute('code_owner', code_owner)
149+
set_code_owner_custom_attributes(code_owner)
150+
151+
152+
def set_code_owner_custom_attributes(code_owner):
153+
"""
154+
Sets custom metrics for code_owner, code_owner_theme, and code_owner_squad
155+
"""
156+
if not code_owner: # pragma: no cover
157+
return
158+
set_custom_attribute('code_owner', code_owner)
159+
theme = _get_theme_from_code_owner(code_owner)
160+
if theme:
161+
set_custom_attribute('code_owner_theme', theme)
162+
squad = _get_squad_from_code_owner(code_owner)
163+
if squad:
164+
set_custom_attribute('code_owner_squad', squad)
153165

154166

155167
def set_code_owner_attribute(wrapped_function):
@@ -182,12 +194,108 @@ def new_function(*args, **kwargs):
182194

183195
def clear_cached_mappings():
184196
"""
185-
Clears the cached path to code owner mappings. Useful for testing.
197+
Clears the cached code owner mappings. Useful for testing.
186198
"""
187199
global _PATH_TO_CODE_OWNER_MAPPINGS
188200
_PATH_TO_CODE_OWNER_MAPPINGS = None
201+
global _CODE_OWNER_TO_THEME_AND_SQUAD_MAPPINGS
202+
_CODE_OWNER_TO_THEME_AND_SQUAD_MAPPINGS = None
189203

190204

191205
# TODO: Retire this once edx-platform import_shims is no longer used.
206+
# Note: This should be ready for removal because import_shims has been removed.
192207
# See https://github.com/edx/edx-platform/tree/854502b560bda74ef898501bb2a95ce238cf794c/import_shims
193208
_OPTIONAL_MODULE_PREFIX_PATTERN = re.compile(r'^(lms|common|openedx\.core)\.djangoapps\.')
209+
210+
211+
# Cached lookup table for code owner theme and squad given a code owner.
212+
# - Although code owner is "theme-squad", a hyphen may also be in the theme or squad name, so this ensures we get both
213+
# correctly from config.
214+
# Do not access this directly, but instead use get_code_owner_theme_squad_mappings.
215+
_CODE_OWNER_TO_THEME_AND_SQUAD_MAPPINGS = None
216+
217+
218+
def get_code_owner_theme_squad_mappings():
219+
"""
220+
Returns the contents of the CODE_OWNER_THEMES Django Setting, processed
221+
for efficient lookup by path.
222+
223+
Returns:
224+
(dict): dict mapping code owners to a dict containing the squad and theme, or
225+
an empty dict if there are no configured mappings.
226+
227+
Example return value::
228+
229+
{
230+
'theme-x-team-red': {
231+
'theme': 'theme-x',
232+
'squad': 'team-red',
233+
},
234+
'theme-x-team-blue': {
235+
'theme': 'theme-x',
236+
'squad': 'team-blue',
237+
},
238+
}
239+
240+
"""
241+
global _CODE_OWNER_TO_THEME_AND_SQUAD_MAPPINGS
242+
243+
# Return cached processed mappings if already processed
244+
if _CODE_OWNER_TO_THEME_AND_SQUAD_MAPPINGS is not None:
245+
return _CODE_OWNER_TO_THEME_AND_SQUAD_MAPPINGS
246+
247+
# Uses temporary variable to build mappings to avoid multi-threading issue with a partially
248+
# processed map. Worst case, it is processed more than once at start-up.
249+
code_owner_to_theme_and_squad_mapping = {}
250+
251+
# .. setting_name: CODE_OWNER_THEMES
252+
# .. setting_default: None
253+
# .. setting_description: Used for monitoring and reporting of ownership. Use a
254+
# dict with keys of code owner themes and values as a list of code owner names
255+
# including theme and squad, separated with a hyphen.
256+
code_owner_themes = getattr(settings, 'CODE_OWNER_THEMES', {})
257+
258+
try:
259+
for theme in code_owner_themes:
260+
code_owner_list = code_owner_themes[theme]
261+
for code_owner in code_owner_list:
262+
squad = code_owner.split(theme + '-', 1)[1]
263+
code_owner_details = {
264+
'theme': theme,
265+
'squad': squad,
266+
}
267+
code_owner_to_theme_and_squad_mapping[code_owner] = code_owner_details
268+
except TypeError as e:
269+
log.exception('Error processing CODE_OWNER_THEMES setting. {}'.format(e))
270+
raise e
271+
272+
_CODE_OWNER_TO_THEME_AND_SQUAD_MAPPINGS = code_owner_to_theme_and_squad_mapping
273+
return _CODE_OWNER_TO_THEME_AND_SQUAD_MAPPINGS
274+
275+
276+
def _get_theme_from_code_owner(code_owner):
277+
"""
278+
Returns theme for a code_owner (e.g. 'theme-my-squad' => 'theme')
279+
"""
280+
mappings = get_code_owner_theme_squad_mappings()
281+
if mappings is None: # pragma: no cover
282+
return None
283+
284+
if code_owner in mappings:
285+
return mappings[code_owner]['theme']
286+
287+
return None
288+
289+
290+
def _get_squad_from_code_owner(code_owner):
291+
"""
292+
Returns squad for a code_owner (e.g. 'theme-my-squad' => 'my-squad')
293+
"""
294+
mappings = get_code_owner_theme_squad_mappings()
295+
if mappings is None: # pragma: no cover
296+
return None
297+
298+
if code_owner in mappings:
299+
return mappings[code_owner]['squad']
300+
301+
return None

0 commit comments

Comments
 (0)