dbca-utils 3.0.9__tar.gz → 3.0.11__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. {dbca_utils-3.0.9 → dbca_utils-3.0.11}/PKG-INFO +149 -183
  2. dbca_utils-3.0.11/README.md +251 -0
  3. {dbca_utils-3.0.9 → dbca_utils-3.0.11}/pyproject.toml +1 -1
  4. {dbca_utils-3.0.9 → dbca_utils-3.0.11}/src/dbca_utils/healthcheck/healthcheck.py +73 -27
  5. dbca_utils-3.0.9/README.md +0 -285
  6. {dbca_utils-3.0.9 → dbca_utils-3.0.11}/LICENSE +0 -0
  7. {dbca_utils-3.0.9 → dbca_utils-3.0.11}/src/dbca_utils/__init__.py +0 -0
  8. {dbca_utils-3.0.9 → dbca_utils-3.0.11}/src/dbca_utils/apps.py +0 -0
  9. {dbca_utils-3.0.9 → dbca_utils-3.0.11}/src/dbca_utils/healthcheck/__init__.py +0 -0
  10. {dbca_utils-3.0.9 → dbca_utils-3.0.11}/src/dbca_utils/healthcheck/urls.py +0 -0
  11. {dbca_utils-3.0.9 → dbca_utils-3.0.11}/src/dbca_utils/middleware.py +0 -0
  12. {dbca_utils-3.0.9 → dbca_utils-3.0.11}/src/dbca_utils/models.py +0 -0
  13. {dbca_utils-3.0.9 → dbca_utils-3.0.11}/src/dbca_utils/utils.py +0 -0
  14. {dbca_utils-3.0.9 → dbca_utils-3.0.11}/tests/__init__.py +0 -0
  15. {dbca_utils-3.0.9 → dbca_utils-3.0.11}/tests/apps.py +0 -0
  16. {dbca_utils-3.0.9 → dbca_utils-3.0.11}/tests/migrations/0001_initial.py +0 -0
  17. {dbca_utils-3.0.9 → dbca_utils-3.0.11}/tests/migrations/__init__.py +0 -0
  18. {dbca_utils-3.0.9 → dbca_utils-3.0.11}/tests/models.py +0 -0
  19. {dbca_utils-3.0.9 → dbca_utils-3.0.11}/tests/settings.py +0 -0
  20. {dbca_utils-3.0.9 → dbca_utils-3.0.11}/tests/templates/tests/test_model_list.html +0 -0
  21. {dbca_utils-3.0.9 → dbca_utils-3.0.11}/tests/tests.py +0 -0
  22. {dbca_utils-3.0.9 → dbca_utils-3.0.11}/tests/urls.py +0 -0
  23. {dbca_utils-3.0.9 → dbca_utils-3.0.11}/tests/views.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dbca-utils
3
- Version: 3.0.9
3
+ Version: 3.0.11
4
4
  Summary: Utilities for DBCA Django apps
5
5
  Author-Email: Rocky Chen <rocky.chen@dbca.wa.gov.au>, Ashley Felton <ashley.felton@dbca.wa.gov.au>
6
6
  License-Expression: Apache-2.0
@@ -129,185 +129,151 @@ MIDDLEWARE = [
129
129
  - Add a location 'location /healthcheck/' and configure it to use basic auth in nginx.
130
130
  - Access the url : https://xxx.dbca.wa.gov.au/healthcheck/healthdata to get the health json data
131
131
  - The sample data of the health data:
132
- {
133
- "workload0":{
134
- "resources":{
135
- "start_time":"2026-07-24T06:10:23",
136
- "cpu_total":0.0,
137
- "cpu_min":0.0,
138
- "cpu_max":0.0,
139
- "pmemory_total":292.28125,
140
- "pmemory_min":95.53515625,
141
- "pmemory_max":100.828125,
142
- "vmemory_total":764.12890625,
143
- "vmemory_min":207.8671875,
144
- "vmemory_max":278.59765625,
145
- "processes":3,
146
- "process":{
147
- "start_time":"2026-07-24T06:10:23",
148
- "cmdline":[
149
- "/app/.venv/bin/python",
150
- "/app/.venv/bin/gunicorn",
151
- "authome.wsgi",
152
- "--config=gunicorn_gevent.py"
153
- ],
154
- "cpu_num":0,
155
- "cpu_pcent":0.0,
156
- "pmemory":100.828125,
157
- "vmemory":207.8671875,
158
- "children":[
159
- {
160
- "start_time":"2026-07-24T06:10:24",
161
- "cmdline":[
162
- "/app/.venv/bin/python",
163
- "/app/.venv/bin/gunicorn",
164
- "authome.wsgi",
165
- "--config=gunicorn_gevent.py"
166
- ],
167
- "cpu_num":1,
168
- "cpu_pcent":0.0,
169
- "pmemory":95.53515625,
170
- "vmemory":277.6640625
171
- },
172
- {
173
- "start_time":"2026-07-24T06:10:24",
174
- "cmdline":[
175
- "/app/.venv/bin/python",
176
- "/app/.venv/bin/gunicorn",
177
- "authome.wsgi",
178
- "--config=gunicorn_gevent.py"
179
- ],
180
- "cpu_num":3,
181
- "cpu_pcent":0.0,
182
- "pmemory":95.91796875,
183
- "vmemory":278.59765625,
184
- "currentprocess":true
185
- }
186
- ]
187
- }
188
- },
189
- "system":{
190
- "cpu_pcent":0.0,
191
- "cpucores_pcent":[
192
- 0.0,
193
- 0.0,
194
- 0.0,
195
- 0.0
196
- ],
197
- "memory_total":31.279705047607422,
198
- "memory_used":12.220211029052734,
199
- "memory_pcent":39.06753919339612,
200
- "bytes_sent":9028205,
201
- "bytes_recv":9246094
202
- },
203
- "volumes":{
204
- "/app/captcha":{
205
- "size":1024,
206
- "used":0,
207
- "pcent":0.006103515625,
208
- "unit":"M"
209
- }
210
- },
211
- "hostname":"auth2-uat11-5b495b67cb-z8vwj"
212
- },
213
- "workload1":{
214
- "resources":{
215
- "start_time":"2026-07-24T06:11:45",
216
- "cpu_total":0.0,
217
- "cpu_min":0.0,
218
- "cpu_max":0.0,
219
- "pmemory_total":292.02734375,
220
- "pmemory_min":95.4140625,
221
- "pmemory_max":100.6171875,
222
- "vmemory_total":763.84375,
223
- "vmemory_min":207.8359375,
224
- "vmemory_max":278.546875,
225
- "processes":3,
226
- "process":{
227
- "start_time":"2026-07-24T06:11:45",
228
- "cmdline":[
229
- "/app/.venv/bin/python",
230
- "/app/.venv/bin/gunicorn",
231
- "authome.wsgi",
232
- "--config=gunicorn_gevent.py"
233
- ],
234
- "cpu_num":1,
235
- "cpu_pcent":0.0,
236
- "pmemory":100.6171875,
237
- "vmemory":207.8359375,
238
- "children":[
239
- {
240
- "start_time":"2026-07-24T06:11:46",
241
- "cmdline":[
242
- "/app/.venv/bin/python",
243
- "/app/.venv/bin/gunicorn",
244
- "authome.wsgi",
245
- "--config=gunicorn_gevent.py"
246
- ],
247
- "cpu_num":2,
248
- "cpu_pcent":0.0,
249
- "pmemory":95.4140625,
250
- "vmemory":277.4609375
251
- },
252
- {
253
- "start_time":"2026-07-24T06:11:46",
254
- "cmdline":[
255
- "/app/.venv/bin/python",
256
- "/app/.venv/bin/gunicorn",
257
- "authome.wsgi",
258
- "--config=gunicorn_gevent.py"
259
- ],
260
- "cpu_num":3,
261
- "cpu_pcent":0.0,
262
- "pmemory":95.99609375,
263
- "vmemory":278.546875,
264
- "currentprocess":true
265
- }
266
- ]
267
- }
268
- },
269
- "system":{
270
- "cpu_pcent":13.1,
271
- "cpucores_pcent":[
272
- 15.4,
273
- 12.2,
274
- 12.0,
275
- 12.7
276
- ],
277
- "memory_total":31.279705047607422,
278
- "memory_used":12.220211029052734,
279
- "memory_pcent":39.06753919339612,
280
- "bytes_sent":9099207,
281
- "bytes_recv":9055029
282
- },
283
- "volumes":{
284
- "/app/captcha":{
285
- "size":1024,
286
- "used":0,
287
- "pcent":0.006103515625,
288
- "unit":"M"
289
- }
290
- },
291
- "hostname":"auth2-uat11-5b495b67cb-dpdjp"
292
- },
293
- "summary":{
294
- "cpu_total":0.0,
295
- "cpu_min":0.0,
296
- "cpu_max":0.0,
297
- "process_cpu_min":0.0,
298
- "process_cpu_max":0.0,
299
- "pmemory_total":584.30859375,
300
- "pmemory_min":292.02734375,
301
- "pmemory_max":292.28125,
302
- "process_pmemory_min":95.4140625,
303
- "process_pmemory_max":100.828125,
304
- "vmemory_total":1527.97265625,
305
- "vmemory_min":763.84375,
306
- "vmemory_max":764.12890625,
307
- "process_vmemory_min":207.8359375,
308
- "process_vmemory_max":278.59765625,
309
- "processes_total":6,
310
- "workloads_running":2,
311
- "workloads_failed":0
312
- }
313
- }
132
+
133
+ {
134
+ "workload0":{
135
+ "resources":{
136
+ "start_time":"2026-07-24T06:10:23",
137
+ "cpu_total":0.0,
138
+ "cpu_min":0.0,
139
+ "cpu_max":0.0,
140
+ "pmemory_total":292.28125,
141
+ "pmemory_min":95.53515625,
142
+ "pmemory_max":100.828125,
143
+ "vmemory_total":764.12890625,
144
+ "vmemory_min":207.8671875,
145
+ "vmemory_max":278.59765625,
146
+ "processes":3,
147
+ "process":{
148
+ "start_time":"2026-07-24T06:10:23",
149
+ "cpu_num":0,
150
+ "cpu_pcent":0.0,
151
+ "pmemory":100.828125,
152
+ "vmemory":207.8671875,
153
+ "children":[
154
+ {
155
+ "start_time":"2026-07-24T06:10:24",
156
+ "cpu_num":1,
157
+ "cpu_pcent":0.0,
158
+ "pmemory":95.53515625,
159
+ "vmemory":277.6640625
160
+ },
161
+ {
162
+ "start_time":"2026-07-24T06:10:24",
163
+ "cpu_num":3,
164
+ "cpu_pcent":0.0,
165
+ "pmemory":95.91796875,
166
+ "vmemory":278.59765625,
167
+ "currentprocess":true
168
+ }
169
+ ]
170
+ }
171
+ },
172
+ "system":{
173
+ "cpu_pcent":0.0,
174
+ "cpucores_pcent":[
175
+ 0.0,
176
+ 0.0,
177
+ 0.0,
178
+ 0.0
179
+ ],
180
+ "memory_total":31.279705047607422,
181
+ "memory_used":12.220211029052734,
182
+ "memory_pcent":39.06753919339612,
183
+ "bytes_sent":9028205,
184
+ "bytes_recv":9246094
185
+ },
186
+ "volumes":{
187
+ "/app/captcha":{
188
+ "size":1024,
189
+ "used":0,
190
+ "pcent":0.006103515625,
191
+ "unit":"M"
192
+ }
193
+ },
194
+ "hostname":"auth2-uat11-5b495b67cb-z8vwj"
195
+ },
196
+ "workload1":{
197
+ "resources":{
198
+ "start_time":"2026-07-24T06:11:45",
199
+ "cpu_total":0.0,
200
+ "cpu_min":0.0,
201
+ "cpu_max":0.0,
202
+ "pmemory_total":292.02734375,
203
+ "pmemory_min":95.4140625,
204
+ "pmemory_max":100.6171875,
205
+ "vmemory_total":763.84375,
206
+ "vmemory_min":207.8359375,
207
+ "vmemory_max":278.546875,
208
+ "processes":3,
209
+ "process":{
210
+ "start_time":"2026-07-24T06:11:45",
211
+ "cpu_num":1,
212
+ "cpu_pcent":0.0,
213
+ "pmemory":100.6171875,
214
+ "vmemory":207.8359375,
215
+ "children":[
216
+ {
217
+ "start_time":"2026-07-24T06:11:46",
218
+ "cpu_num":2,
219
+ "cpu_pcent":0.0,
220
+ "pmemory":95.4140625,
221
+ "vmemory":277.4609375
222
+ },
223
+ {
224
+ "start_time":"2026-07-24T06:11:46",
225
+ "cpu_num":3,
226
+ "cpu_pcent":0.0,
227
+ "pmemory":95.99609375,
228
+ "vmemory":278.546875,
229
+ }
230
+ ]
231
+ }
232
+ },
233
+ "system":{
234
+ "cpu_pcent":13.1,
235
+ "cpucores_pcent":[
236
+ 15.4,
237
+ 12.2,
238
+ 12.0,
239
+ 12.7
240
+ ],
241
+ "memory_total":31.279705047607422,
242
+ "memory_used":12.220211029052734,
243
+ "memory_pcent":39.06753919339612,
244
+ "bytes_sent":9099207,
245
+ "bytes_recv":9055029
246
+ },
247
+ "volumes":{
248
+ "/app/captcha":{
249
+ "size":1024,
250
+ "used":0,
251
+ "pcent":0.006103515625,
252
+ "unit":"M"
253
+ }
254
+ },
255
+ "hostname":"auth2-uat11-5b495b67cb-dpdjp"
256
+ },
257
+ "summary":{
258
+ "cpu_total":0.0,
259
+ "cpu_min":0.0,
260
+ "cpu_max":0.0,
261
+ "process_cpu_min":0.0,
262
+ "process_cpu_max":0.0,
263
+ "pmemory_total":584.30859375,
264
+ "pmemory_min":292.02734375,
265
+ "pmemory_max":292.28125,
266
+ "process_pmemory_min":95.4140625,
267
+ "process_pmemory_max":100.828125,
268
+ "vmemory_total":1527.97265625,
269
+ "vmemory_min":763.84375,
270
+ "vmemory_max":764.12890625,
271
+ "process_vmemory_min":207.8359375,
272
+ "process_vmemory_max":278.59765625,
273
+ "processes_total":6,
274
+ "workloads_running":2,
275
+ "workloads_failed":0
276
+ }
277
+ }
278
+
279
+
@@ -0,0 +1,251 @@
1
+ # Overview
2
+
3
+ DBCA Django utility classes and functions.
4
+
5
+ ## Requirements
6
+
7
+ - Python 3.12 or later
8
+ - Django 5.2 or later
9
+
10
+ ## Development
11
+
12
+ Dependencies for this project are managed using [uv](https://docs.astral.sh/uv/).
13
+ With uv installed, change into the project directory and run:
14
+
15
+ uv sync
16
+
17
+ Activate the virtualenv like so:
18
+
19
+ source .venv/bin/activate
20
+
21
+ Run unit tests using `pytest` (or `tox`, to test against multiple Python versions):
22
+
23
+ pytest -sv
24
+ tox -v
25
+
26
+ ## Releases
27
+
28
+ Tagged releases are built and pushed to PyPI automatically using a GitHub
29
+ workflow in the project. Update the project version in `pyproject.toml` and
30
+ tag the required commit with the same value to trigger a release. Packages
31
+ can also be built and uploaded manually to PyPI using [uv](https://docs.astral.sh/uv/guides/publish/#publishing-your-package),
32
+ if required:
33
+
34
+ uv build
35
+ uv publish
36
+
37
+ ## Installation
38
+
39
+ 1. Install via uv/pip/etc.: `pip install dbca-utils`
40
+
41
+ ## SSO Login Middleware
42
+
43
+ This will automatically login and create users using headers from an upstream proxy (REMOTE_USER and some others).
44
+ The logout view will redirect to a separate logout page which clears the SSO session.
45
+
46
+ ### Usage
47
+
48
+ Add `dbca_utils.middleware.SSOLoginMiddleware` to `settings.MIDDLEWARE` (after both of
49
+ `django.contrib.sessions.middleware.SessionMiddleware` and
50
+ `django.contrib.auth.middleware.AuthenticationMiddleware`.
51
+ Ensure that `AUTHENTICATION_BACKENDS` contains `django.contrib.auth.backends.ModelBackend`,
52
+ as this middleware depends on it for retrieving the logged in user for a session.
53
+ Note that the middleware will still work without it, but will reauthenticate the session
54
+ on every request, and `request.user.is_authenticated` won't work properly/will be false.
55
+
56
+ Example:
57
+
58
+ ```python
59
+ MIDDLEWARE = [
60
+ ...,
61
+ 'django.contrib.sessions.middleware.SessionMiddleware',
62
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
63
+ 'dbca_utils.middleware.SSOLoginMiddleware'
64
+ ...,
65
+ ]
66
+ ```
67
+
68
+ ## Audit model mixin
69
+
70
+ `AuditMixin` is an extension of `Django.db.model.Model` that adds a number of additional fields:
71
+
72
+ - `creator` - FK to `AUTH_USER_MODEL`, used to record the object creator
73
+ - `modifier` - FK to `AUTH_USER_MODEL`, used to record who the object was last modified by
74
+ - `created` - a timestamp that is set on initial object save
75
+ - `modified` - an auto-updating timestamp (on each object save)
76
+
77
+ ## Healthcheck feature
78
+ ### Requirements
79
+ - Django 5.2 or later
80
+ - Declared the default cache and also the cache is shared by all pod instances
81
+
82
+ ### Usage
83
+ - Install the app 'dbca_utils' in INSTALLED_APPS
84
+ - Service Configuration
85
+ - HEALTHCHECK_ENABLED: Optional. enable/disable the healthcheck service. default is 'true'
86
+ - CACHE_PREFIX: Optional. used as the prefix of the cache key. default is ''
87
+ - PORT: Optional. The listening port of the web application. default is '8080', used to send request to peer pod instance to get the resouces usage of the peer pod instance.
88
+ - WORKLOADS: Optional. Used if the web app has a fixed replicas.
89
+ - WORKLOAD_DEPLOYMENT: Optional. the workload is deployment if it is true; otherwise it is statefulset. default is 'true'
90
+ - WORKLOAD_FAILED_THRESHOLD: Optional. The number of continuous failed times to decide that a pod is offline.
91
+ - WORKLOAD_VOLUMES: Optional. can be 'disabled', 'false','automatic' or "," separated volume mounted point. default is "automatic".
92
+ - disabled|false: Don't harvest volume usage data
93
+ - automatic: Detect the persistent volume automatically.
94
+ - "," separated volume mounted points: the volume list
95
+ - HEALTHCHECK_SYSTEMDATA_ENABLED: Enable system resource data
96
+ - HEALTHCHECK_PROCESSDATA_ENABLED: Enable process resource data
97
+ - Endpoints added by this app
98
+ - /healthcheck/healthdata: An endpoint for client to harvest the resources usage data. This endpoint should be cofigured to use basic auth in nginx reverse proxy.
99
+ - /workload/healthcheck/healthdata: An internal endpoint used to get the resource usage of the peer pod instance. This endpoing should not be exposed in nginx reverse proxy.
100
+ - Nginx Configuration.
101
+ - Add a location 'location /healthcheck/' and configure it to use basic auth in nginx.
102
+ - Access the url : https://xxx.dbca.wa.gov.au/healthcheck/healthdata to get the health json data
103
+ - The sample data of the health data:
104
+
105
+ {
106
+ "workload0":{
107
+ "resources":{
108
+ "start_time":"2026-07-24T06:10:23",
109
+ "cpu_total":0.0,
110
+ "cpu_min":0.0,
111
+ "cpu_max":0.0,
112
+ "pmemory_total":292.28125,
113
+ "pmemory_min":95.53515625,
114
+ "pmemory_max":100.828125,
115
+ "vmemory_total":764.12890625,
116
+ "vmemory_min":207.8671875,
117
+ "vmemory_max":278.59765625,
118
+ "processes":3,
119
+ "process":{
120
+ "start_time":"2026-07-24T06:10:23",
121
+ "cpu_num":0,
122
+ "cpu_pcent":0.0,
123
+ "pmemory":100.828125,
124
+ "vmemory":207.8671875,
125
+ "children":[
126
+ {
127
+ "start_time":"2026-07-24T06:10:24",
128
+ "cpu_num":1,
129
+ "cpu_pcent":0.0,
130
+ "pmemory":95.53515625,
131
+ "vmemory":277.6640625
132
+ },
133
+ {
134
+ "start_time":"2026-07-24T06:10:24",
135
+ "cpu_num":3,
136
+ "cpu_pcent":0.0,
137
+ "pmemory":95.91796875,
138
+ "vmemory":278.59765625,
139
+ "currentprocess":true
140
+ }
141
+ ]
142
+ }
143
+ },
144
+ "system":{
145
+ "cpu_pcent":0.0,
146
+ "cpucores_pcent":[
147
+ 0.0,
148
+ 0.0,
149
+ 0.0,
150
+ 0.0
151
+ ],
152
+ "memory_total":31.279705047607422,
153
+ "memory_used":12.220211029052734,
154
+ "memory_pcent":39.06753919339612,
155
+ "bytes_sent":9028205,
156
+ "bytes_recv":9246094
157
+ },
158
+ "volumes":{
159
+ "/app/captcha":{
160
+ "size":1024,
161
+ "used":0,
162
+ "pcent":0.006103515625,
163
+ "unit":"M"
164
+ }
165
+ },
166
+ "hostname":"auth2-uat11-5b495b67cb-z8vwj"
167
+ },
168
+ "workload1":{
169
+ "resources":{
170
+ "start_time":"2026-07-24T06:11:45",
171
+ "cpu_total":0.0,
172
+ "cpu_min":0.0,
173
+ "cpu_max":0.0,
174
+ "pmemory_total":292.02734375,
175
+ "pmemory_min":95.4140625,
176
+ "pmemory_max":100.6171875,
177
+ "vmemory_total":763.84375,
178
+ "vmemory_min":207.8359375,
179
+ "vmemory_max":278.546875,
180
+ "processes":3,
181
+ "process":{
182
+ "start_time":"2026-07-24T06:11:45",
183
+ "cpu_num":1,
184
+ "cpu_pcent":0.0,
185
+ "pmemory":100.6171875,
186
+ "vmemory":207.8359375,
187
+ "children":[
188
+ {
189
+ "start_time":"2026-07-24T06:11:46",
190
+ "cpu_num":2,
191
+ "cpu_pcent":0.0,
192
+ "pmemory":95.4140625,
193
+ "vmemory":277.4609375
194
+ },
195
+ {
196
+ "start_time":"2026-07-24T06:11:46",
197
+ "cpu_num":3,
198
+ "cpu_pcent":0.0,
199
+ "pmemory":95.99609375,
200
+ "vmemory":278.546875,
201
+ }
202
+ ]
203
+ }
204
+ },
205
+ "system":{
206
+ "cpu_pcent":13.1,
207
+ "cpucores_pcent":[
208
+ 15.4,
209
+ 12.2,
210
+ 12.0,
211
+ 12.7
212
+ ],
213
+ "memory_total":31.279705047607422,
214
+ "memory_used":12.220211029052734,
215
+ "memory_pcent":39.06753919339612,
216
+ "bytes_sent":9099207,
217
+ "bytes_recv":9055029
218
+ },
219
+ "volumes":{
220
+ "/app/captcha":{
221
+ "size":1024,
222
+ "used":0,
223
+ "pcent":0.006103515625,
224
+ "unit":"M"
225
+ }
226
+ },
227
+ "hostname":"auth2-uat11-5b495b67cb-dpdjp"
228
+ },
229
+ "summary":{
230
+ "cpu_total":0.0,
231
+ "cpu_min":0.0,
232
+ "cpu_max":0.0,
233
+ "process_cpu_min":0.0,
234
+ "process_cpu_max":0.0,
235
+ "pmemory_total":584.30859375,
236
+ "pmemory_min":292.02734375,
237
+ "pmemory_max":292.28125,
238
+ "process_pmemory_min":95.4140625,
239
+ "process_pmemory_max":100.828125,
240
+ "vmemory_total":1527.97265625,
241
+ "vmemory_min":763.84375,
242
+ "vmemory_max":764.12890625,
243
+ "process_vmemory_min":207.8359375,
244
+ "process_vmemory_max":278.59765625,
245
+ "processes_total":6,
246
+ "workloads_running":2,
247
+ "workloads_failed":0
248
+ }
249
+ }
250
+
251
+
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "dbca-utils"
3
- version = "3.0.9"
3
+ version = "3.0.11"
4
4
  description = "Utilities for DBCA Django apps"
5
5
  authors = [
6
6
  { name = "Rocky Chen", email = "rocky.chen@dbca.wa.gov.au" },
@@ -1,5 +1,6 @@
1
1
  import os
2
2
  import importlib
3
+ from collections import OrderedDict
3
4
  import logging
4
5
  import psutil
5
6
  import subprocess
@@ -322,17 +323,19 @@ def get_workload_app_healthdata(perprocess=True):
322
323
  return result
323
324
 
324
325
  def get_workload_healthdata():
326
+ try:
327
+ result = {
328
+ "resources": get_workload_app_healthdata(HEALTHCHECK_PROCESSDATA_ENABLED)
329
+ }
330
+ if HEALTHCHECK_SYSTEMDATA_ENABLED:
331
+ result["system"] = get_workload_system_healthdata()
325
332
 
326
- result = {
327
- "resources": get_workload_app_healthdata(HEALTHCHECK_PROCESSDATA_ENABLED)
328
- }
329
- if HEALTHCHECK_SYSTEMDATA_ENABLED:
330
- result["system"] = get_workload_system_healthdata()
331
-
332
- if WORKLOAD_VOLUMES_ENABLED:
333
- result["volumes"] = get_volumes_healthdata()
333
+ if WORKLOAD_VOLUMES_ENABLED:
334
+ result["volumes"] = get_volumes_healthdata()
334
335
 
335
- return (200,result)
336
+ return (200,result)
337
+ except Exception as ex:
338
+ return (500,"{}:{}".format(ex.__classs__.__name__,str(ex)))
336
339
 
337
340
  bearer_token_re = re.compile("^Bearer\\s+(?P<token>\\S+)\\s*$")
338
341
  def get_auth_bearer(request):
@@ -458,6 +461,9 @@ def populate_summary_data(datas):
458
461
  "workloads_running":0,
459
462
  "workloads_failed":0,
460
463
  }
464
+ if settings.DEBUG:
465
+ summary["currentworkload"] = registerhostname
466
+
461
467
  for servername,serverdata in datas.items():
462
468
  if isinstance(serverdata,str):
463
469
  summary["workloads_failed"] += 1
@@ -527,8 +533,6 @@ def harvest_healthdata(request):
527
533
  continue
528
534
  if servername == registerhostname:
529
535
  servers_res[servername] = get_workload_healthdata()
530
- if settings.DEBUG:
531
- servers_res[servername]["currentworkload"] = True
532
536
  continue
533
537
 
534
538
  serverip,port = serverdata[0]
@@ -562,19 +566,25 @@ def harvest_healthdata(request):
562
566
  workloads_changed = True
563
567
  elif res.status_code == 599:
564
568
  #the server is in good health, add the health data to servers_res
565
- data = res.json()
566
- if data["status"] == 401:
567
- #authentication error, caused by different workload.
569
+ try:
570
+ data = res.json()
571
+ if data["status"] == 401:
572
+ #authentication error, caused by different workload.
573
+ workloads_changed = True
574
+ unreached_servers.append(servername)
575
+ servers_res[servername] = (503,"{1}:{2},url={0}".format(url,data["status"],data["message"]))
576
+ else:
577
+ servers_res[servername] = (500,"{1}:{2}. url={0}".format(url,data["status"],data["message"]))
578
+ except:
568
579
  workloads_changed = True
569
580
  unreached_servers.append(servername)
570
- servers_res[servername] = (res.status_code,"{1}:{2},url={0}".format(url,data["status"],data["message"]))
571
- else:
572
- servers_res[servername] = (res.status_code,"{1}:{2}. url={0}".format(url,data["status"],data["message"]))
581
+ servers_res[servername] = (503,"Web server is offline.url={0}".format(url))
582
+
573
583
  else:
574
584
  #unexpected error, caused by different workload
575
585
  workloads_changed = True
576
586
  unreached_servers.append(servername)
577
- servers_res[servername] = (res.status_code,"{1}:{2},url={0}".format(url,res.status_code,res.text))
587
+ servers_res[servername] = (500,"{1}:{2},url={0}".format(url,res.status_code,res.text))
578
588
 
579
589
  for servername in unreached_servers:
580
590
  del workloads[servername]
@@ -586,7 +596,7 @@ def harvest_healthdata(request):
586
596
 
587
597
  return (workloads,servers_res)
588
598
 
589
- OFFLINE_STATUSCODE_LIST = (502,503,504,401,403,-1,-2)
599
+ OFFLINE_STATUSCODE_LIST = (502,503,504,401,403,-1)
590
600
  if WORKLOADS > 0 and WORKLOAD_DEPLOYMENT:
591
601
  #has a fixed number of workloads and it is a deployment
592
602
  WORKLOADNAMES = [get_workloadname(index) for index in range(WORKLOADS)]
@@ -619,6 +629,7 @@ if WORKLOADS > 0 and WORKLOAD_DEPLOYMENT:
619
629
 
620
630
  assignedworkloads_changed = False
621
631
  if len(WORKLOADNAMES) != len(assignedworkloads):
632
+ #remove the unexisted workloads from assignedworkloads.
622
633
  for key in [k for k in assignedworkloads.keys()]:
623
634
  if key == item_version:
624
635
  continue
@@ -640,7 +651,7 @@ if WORKLOADS > 0 and WORKLOAD_DEPLOYMENT:
640
651
  #related server is online.no need to reassign
641
652
  continue
642
653
  elif step == 1:
643
- #step 1 only reassign the assigned workloads
654
+ #step 1 only reassign the already assigned workloads
644
655
  if workloadname not in assignedworkloads:
645
656
  continue
646
657
  replacedservername = None
@@ -675,13 +686,20 @@ if WORKLOADS > 0 and WORKLOAD_DEPLOYMENT:
675
686
  save_assignedworkloads(assignedworkloads)
676
687
 
677
688
  #map the healthdata result to workload. and remove status code
678
- result = {}
689
+ result = OrderedDict()
679
690
  for workloadname in WORKLOADNAMES:
680
691
  servername = assignedworkloads.get(workloadname)
681
692
  if not servername:
682
- result[workloadname] = "Can't find an available host for this non-assigned host.registered workloads: {0}, assigned workloads:{1}".format(str_workloads(workloads),assignedworkloads)
693
+ if settings.DEBUG:
694
+ result[workloadname] = "Can't find an available host for this non-assigned host.registered workloads: {0}, assigned workloads:{1}".format(str_workloads(workloads),assignedworkloads)
695
+ else:
696
+ result[workloadname] = "Can't find an available host for this non-assigned host."
683
697
  elif servername not in datas:
684
- result[workloadname] = "Can't find an available host for this assigned offline host({2}).registered workloads: {0}, assigned workloads:{1}".format(str_workloads(workloads),assignedworkloads,servername)
698
+ if settings.DEBUG:
699
+ result[workloadname] = "Can't find an available host for this assigned offline host({2}).registered workloads: {0}, assigned workloads:{1}".format(str_workloads(workloads),assignedworkloads,servername)
700
+ else:
701
+ result[workloadname] = "Can't find an available host for this assigned offline host({0}).".format(servername)
702
+
685
703
  elif datas[servername][0] == 200:
686
704
  result[workloadname] = datas[servername][1]
687
705
  result[workloadname]["hostname"] = servername
@@ -700,7 +718,7 @@ elif WORKLOADS > 0 and not WORKLOAD_DEPLOYMENT:
700
718
  def healthdata_view(request):
701
719
  workloads,servers_res = harvest_healthdata(request)
702
720
 
703
- result = {}
721
+ result = OrderedDict()
704
722
  for servername in WORKLOADNAMES:
705
723
  if result in servers_res:
706
724
  result[servername] = servers_res[servername][1]
@@ -710,17 +728,45 @@ elif WORKLOADS > 0 and not WORKLOAD_DEPLOYMENT:
710
728
  populate_summary_data(result)
711
729
 
712
730
  return JsonResponse(result)
713
- else:
731
+ elif WORKLOAD_DEPLOYMENT:
714
732
  def healthdata_view(request):
715
733
  workloads,servers_res = harvest_healthdata(request)
716
734
 
717
- result = {}
735
+ result = OrderedDict()
718
736
  for servername, serverdata in servers_res.items():
737
+ if serverdata[0] in OFFLINE_STATUSCODE_LIST:
738
+ continue
719
739
  result[servername] = serverdata[1]
720
740
 
721
741
  populate_summary_data(result)
722
742
 
723
743
  return JsonResponse(result)
744
+ else:
745
+ def healthdata_view(request):
746
+ workloads,servers_res = harvest_healthdata(request)
747
+
748
+ #get all workload names
749
+ workloadnames = [k for k in workloads.keys() if k != item_version ]
750
+
751
+ #find the name of the last available workload
752
+ workloadnames.sort(key=lambda d:int(d[8:]))
753
+ last_workloadname = next((name for name in reversed(workloadnames) if servers_res.get(name,[503,"Workload is Offline"])[0] not in OFFLINE_STATUSCODE_LSIT ) ,None)
754
+
755
+ result = OrderedDict()
756
+ if last_workloadname:
757
+ #find the index of the last available workload
758
+ last_workloadindex = int(last_workloadname[8:])
759
+
760
+ #Return the healthdata of the workloads whose index is from 0 to last_workloadindex(include)
761
+ for i in range(last_workloadindex + 1):
762
+ servername = get_workloadname(i)
763
+ serverdata = servers_res.get(servername,[503,"Workload is offline"])
764
+ result[servername] = serverdata[1]
765
+
766
+ populate_summary_data(result)
767
+
768
+ return JsonResponse(result)
769
+
724
770
 
725
771
  def workload_healthdata_view(request):
726
772
  global secret
@@ -742,7 +788,7 @@ def workload_healthdata_view(request):
742
788
  if statuscode == 200:
743
789
  return JsonResponse(data)
744
790
  else:
745
- return JsonResponse({"status":500,"message":"Server Error"},status=599)
791
+ return JsonResponse({"status":statuscode,"message":data},status=599)
746
792
  except Exception as ex:
747
793
  return JsonResponse({"status":500,"message":"{}:{}".format(ex.__class__.__name__,str(ex))},status=599)
748
794
 
@@ -1,285 +0,0 @@
1
- # Overview
2
-
3
- DBCA Django utility classes and functions.
4
-
5
- ## Requirements
6
-
7
- - Python 3.12 or later
8
- - Django 5.2 or later
9
-
10
- ## Development
11
-
12
- Dependencies for this project are managed using [uv](https://docs.astral.sh/uv/).
13
- With uv installed, change into the project directory and run:
14
-
15
- uv sync
16
-
17
- Activate the virtualenv like so:
18
-
19
- source .venv/bin/activate
20
-
21
- Run unit tests using `pytest` (or `tox`, to test against multiple Python versions):
22
-
23
- pytest -sv
24
- tox -v
25
-
26
- ## Releases
27
-
28
- Tagged releases are built and pushed to PyPI automatically using a GitHub
29
- workflow in the project. Update the project version in `pyproject.toml` and
30
- tag the required commit with the same value to trigger a release. Packages
31
- can also be built and uploaded manually to PyPI using [uv](https://docs.astral.sh/uv/guides/publish/#publishing-your-package),
32
- if required:
33
-
34
- uv build
35
- uv publish
36
-
37
- ## Installation
38
-
39
- 1. Install via uv/pip/etc.: `pip install dbca-utils`
40
-
41
- ## SSO Login Middleware
42
-
43
- This will automatically login and create users using headers from an upstream proxy (REMOTE_USER and some others).
44
- The logout view will redirect to a separate logout page which clears the SSO session.
45
-
46
- ### Usage
47
-
48
- Add `dbca_utils.middleware.SSOLoginMiddleware` to `settings.MIDDLEWARE` (after both of
49
- `django.contrib.sessions.middleware.SessionMiddleware` and
50
- `django.contrib.auth.middleware.AuthenticationMiddleware`.
51
- Ensure that `AUTHENTICATION_BACKENDS` contains `django.contrib.auth.backends.ModelBackend`,
52
- as this middleware depends on it for retrieving the logged in user for a session.
53
- Note that the middleware will still work without it, but will reauthenticate the session
54
- on every request, and `request.user.is_authenticated` won't work properly/will be false.
55
-
56
- Example:
57
-
58
- ```python
59
- MIDDLEWARE = [
60
- ...,
61
- 'django.contrib.sessions.middleware.SessionMiddleware',
62
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
63
- 'dbca_utils.middleware.SSOLoginMiddleware'
64
- ...,
65
- ]
66
- ```
67
-
68
- ## Audit model mixin
69
-
70
- `AuditMixin` is an extension of `Django.db.model.Model` that adds a number of additional fields:
71
-
72
- - `creator` - FK to `AUTH_USER_MODEL`, used to record the object creator
73
- - `modifier` - FK to `AUTH_USER_MODEL`, used to record who the object was last modified by
74
- - `created` - a timestamp that is set on initial object save
75
- - `modified` - an auto-updating timestamp (on each object save)
76
-
77
- ## Healthcheck feature
78
- ### Requirements
79
- - Django 5.2 or later
80
- - Declared the default cache and also the cache is shared by all pod instances
81
-
82
- ### Usage
83
- - Install the app 'dbca_utils' in INSTALLED_APPS
84
- - Service Configuration
85
- - HEALTHCHECK_ENABLED: Optional. enable/disable the healthcheck service. default is 'true'
86
- - CACHE_PREFIX: Optional. used as the prefix of the cache key. default is ''
87
- - PORT: Optional. The listening port of the web application. default is '8080', used to send request to peer pod instance to get the resouces usage of the peer pod instance.
88
- - WORKLOADS: Optional. Used if the web app has a fixed replicas.
89
- - WORKLOAD_DEPLOYMENT: Optional. the workload is deployment if it is true; otherwise it is statefulset. default is 'true'
90
- - WORKLOAD_FAILED_THRESHOLD: Optional. The number of continuous failed times to decide that a pod is offline.
91
- - WORKLOAD_VOLUMES: Optional. can be 'disabled', 'false','automatic' or "," separated volume mounted point. default is "automatic".
92
- - disabled|false: Don't harvest volume usage data
93
- - automatic: Detect the persistent volume automatically.
94
- - "," separated volume mounted points: the volume list
95
- - HEALTHCHECK_SYSTEMDATA_ENABLED: Enable system resource data
96
- - HEALTHCHECK_PROCESSDATA_ENABLED: Enable process resource data
97
- - Endpoints added by this app
98
- - /healthcheck/healthdata: An endpoint for client to harvest the resources usage data. This endpoint should be cofigured to use basic auth in nginx reverse proxy.
99
- - /workload/healthcheck/healthdata: An internal endpoint used to get the resource usage of the peer pod instance. This endpoing should not be exposed in nginx reverse proxy.
100
- - Nginx Configuration.
101
- - Add a location 'location /healthcheck/' and configure it to use basic auth in nginx.
102
- - Access the url : https://xxx.dbca.wa.gov.au/healthcheck/healthdata to get the health json data
103
- - The sample data of the health data:
104
- {
105
- "workload0":{
106
- "resources":{
107
- "start_time":"2026-07-24T06:10:23",
108
- "cpu_total":0.0,
109
- "cpu_min":0.0,
110
- "cpu_max":0.0,
111
- "pmemory_total":292.28125,
112
- "pmemory_min":95.53515625,
113
- "pmemory_max":100.828125,
114
- "vmemory_total":764.12890625,
115
- "vmemory_min":207.8671875,
116
- "vmemory_max":278.59765625,
117
- "processes":3,
118
- "process":{
119
- "start_time":"2026-07-24T06:10:23",
120
- "cmdline":[
121
- "/app/.venv/bin/python",
122
- "/app/.venv/bin/gunicorn",
123
- "authome.wsgi",
124
- "--config=gunicorn_gevent.py"
125
- ],
126
- "cpu_num":0,
127
- "cpu_pcent":0.0,
128
- "pmemory":100.828125,
129
- "vmemory":207.8671875,
130
- "children":[
131
- {
132
- "start_time":"2026-07-24T06:10:24",
133
- "cmdline":[
134
- "/app/.venv/bin/python",
135
- "/app/.venv/bin/gunicorn",
136
- "authome.wsgi",
137
- "--config=gunicorn_gevent.py"
138
- ],
139
- "cpu_num":1,
140
- "cpu_pcent":0.0,
141
- "pmemory":95.53515625,
142
- "vmemory":277.6640625
143
- },
144
- {
145
- "start_time":"2026-07-24T06:10:24",
146
- "cmdline":[
147
- "/app/.venv/bin/python",
148
- "/app/.venv/bin/gunicorn",
149
- "authome.wsgi",
150
- "--config=gunicorn_gevent.py"
151
- ],
152
- "cpu_num":3,
153
- "cpu_pcent":0.0,
154
- "pmemory":95.91796875,
155
- "vmemory":278.59765625,
156
- "currentprocess":true
157
- }
158
- ]
159
- }
160
- },
161
- "system":{
162
- "cpu_pcent":0.0,
163
- "cpucores_pcent":[
164
- 0.0,
165
- 0.0,
166
- 0.0,
167
- 0.0
168
- ],
169
- "memory_total":31.279705047607422,
170
- "memory_used":12.220211029052734,
171
- "memory_pcent":39.06753919339612,
172
- "bytes_sent":9028205,
173
- "bytes_recv":9246094
174
- },
175
- "volumes":{
176
- "/app/captcha":{
177
- "size":1024,
178
- "used":0,
179
- "pcent":0.006103515625,
180
- "unit":"M"
181
- }
182
- },
183
- "hostname":"auth2-uat11-5b495b67cb-z8vwj"
184
- },
185
- "workload1":{
186
- "resources":{
187
- "start_time":"2026-07-24T06:11:45",
188
- "cpu_total":0.0,
189
- "cpu_min":0.0,
190
- "cpu_max":0.0,
191
- "pmemory_total":292.02734375,
192
- "pmemory_min":95.4140625,
193
- "pmemory_max":100.6171875,
194
- "vmemory_total":763.84375,
195
- "vmemory_min":207.8359375,
196
- "vmemory_max":278.546875,
197
- "processes":3,
198
- "process":{
199
- "start_time":"2026-07-24T06:11:45",
200
- "cmdline":[
201
- "/app/.venv/bin/python",
202
- "/app/.venv/bin/gunicorn",
203
- "authome.wsgi",
204
- "--config=gunicorn_gevent.py"
205
- ],
206
- "cpu_num":1,
207
- "cpu_pcent":0.0,
208
- "pmemory":100.6171875,
209
- "vmemory":207.8359375,
210
- "children":[
211
- {
212
- "start_time":"2026-07-24T06:11:46",
213
- "cmdline":[
214
- "/app/.venv/bin/python",
215
- "/app/.venv/bin/gunicorn",
216
- "authome.wsgi",
217
- "--config=gunicorn_gevent.py"
218
- ],
219
- "cpu_num":2,
220
- "cpu_pcent":0.0,
221
- "pmemory":95.4140625,
222
- "vmemory":277.4609375
223
- },
224
- {
225
- "start_time":"2026-07-24T06:11:46",
226
- "cmdline":[
227
- "/app/.venv/bin/python",
228
- "/app/.venv/bin/gunicorn",
229
- "authome.wsgi",
230
- "--config=gunicorn_gevent.py"
231
- ],
232
- "cpu_num":3,
233
- "cpu_pcent":0.0,
234
- "pmemory":95.99609375,
235
- "vmemory":278.546875,
236
- "currentprocess":true
237
- }
238
- ]
239
- }
240
- },
241
- "system":{
242
- "cpu_pcent":13.1,
243
- "cpucores_pcent":[
244
- 15.4,
245
- 12.2,
246
- 12.0,
247
- 12.7
248
- ],
249
- "memory_total":31.279705047607422,
250
- "memory_used":12.220211029052734,
251
- "memory_pcent":39.06753919339612,
252
- "bytes_sent":9099207,
253
- "bytes_recv":9055029
254
- },
255
- "volumes":{
256
- "/app/captcha":{
257
- "size":1024,
258
- "used":0,
259
- "pcent":0.006103515625,
260
- "unit":"M"
261
- }
262
- },
263
- "hostname":"auth2-uat11-5b495b67cb-dpdjp"
264
- },
265
- "summary":{
266
- "cpu_total":0.0,
267
- "cpu_min":0.0,
268
- "cpu_max":0.0,
269
- "process_cpu_min":0.0,
270
- "process_cpu_max":0.0,
271
- "pmemory_total":584.30859375,
272
- "pmemory_min":292.02734375,
273
- "pmemory_max":292.28125,
274
- "process_pmemory_min":95.4140625,
275
- "process_pmemory_max":100.828125,
276
- "vmemory_total":1527.97265625,
277
- "vmemory_min":763.84375,
278
- "vmemory_max":764.12890625,
279
- "process_vmemory_min":207.8359375,
280
- "process_vmemory_max":278.59765625,
281
- "processes_total":6,
282
- "workloads_running":2,
283
- "workloads_failed":0
284
- }
285
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes