dbca-utils 3.0.2__tar.gz → 3.0.4__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 (22) hide show
  1. {dbca_utils-3.0.2 → dbca_utils-3.0.4}/PKG-INFO +21 -1
  2. {dbca_utils-3.0.2 → dbca_utils-3.0.4}/README.md +20 -0
  3. {dbca_utils-3.0.2 → dbca_utils-3.0.4}/pyproject.toml +1 -1
  4. {dbca_utils-3.0.2 → dbca_utils-3.0.4}/src/dbca_utils/healthcheck/healthcheck.py +103 -2
  5. {dbca_utils-3.0.2 → dbca_utils-3.0.4}/LICENSE +0 -0
  6. {dbca_utils-3.0.2 → dbca_utils-3.0.4}/src/dbca_utils/__init__.py +0 -0
  7. {dbca_utils-3.0.2 → dbca_utils-3.0.4}/src/dbca_utils/apps.py +0 -0
  8. {dbca_utils-3.0.2 → dbca_utils-3.0.4}/src/dbca_utils/healthcheck/__init__.py +0 -0
  9. {dbca_utils-3.0.2 → dbca_utils-3.0.4}/src/dbca_utils/healthcheck/urls.py +0 -0
  10. {dbca_utils-3.0.2 → dbca_utils-3.0.4}/src/dbca_utils/middleware.py +0 -0
  11. {dbca_utils-3.0.2 → dbca_utils-3.0.4}/src/dbca_utils/models.py +0 -0
  12. {dbca_utils-3.0.2 → dbca_utils-3.0.4}/src/dbca_utils/utils.py +0 -0
  13. {dbca_utils-3.0.2 → dbca_utils-3.0.4}/tests/__init__.py +0 -0
  14. {dbca_utils-3.0.2 → dbca_utils-3.0.4}/tests/apps.py +0 -0
  15. {dbca_utils-3.0.2 → dbca_utils-3.0.4}/tests/migrations/0001_initial.py +0 -0
  16. {dbca_utils-3.0.2 → dbca_utils-3.0.4}/tests/migrations/__init__.py +0 -0
  17. {dbca_utils-3.0.2 → dbca_utils-3.0.4}/tests/models.py +0 -0
  18. {dbca_utils-3.0.2 → dbca_utils-3.0.4}/tests/settings.py +0 -0
  19. {dbca_utils-3.0.2 → dbca_utils-3.0.4}/tests/templates/tests/test_model_list.html +0 -0
  20. {dbca_utils-3.0.2 → dbca_utils-3.0.4}/tests/tests.py +0 -0
  21. {dbca_utils-3.0.2 → dbca_utils-3.0.4}/tests/urls.py +0 -0
  22. {dbca_utils-3.0.2 → dbca_utils-3.0.4}/tests/views.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dbca-utils
3
- Version: 3.0.2
3
+ Version: 3.0.4
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
@@ -101,3 +101,23 @@ MIDDLEWARE = [
101
101
  - `modifier` - FK to `AUTH_USER_MODEL`, used to record who the object was last modified by
102
102
  - `created` - a timestamp that is set on initial object save
103
103
  - `modified` - an auto-updating timestamp (on each object save)
104
+
105
+ ## Healthcheck feature
106
+ ### Requirements
107
+ - Django 5.2 or later
108
+ - Declared the default cache and also the cache is shared by all pod instances
109
+ - The image has the command 'ps' which is used to collect the cpu and memory data
110
+
111
+ ### Usage
112
+ - Install the app 'dbca_utils' in INSTALLED_APPS
113
+ - Service Configuration
114
+ - HEALTHCHECK_ENABLED: Optional. enable/disable the healthcheck service. default is 'true'
115
+ - PROCESS_FILTER: Optional. find the web app related processes from command 'ps aux'. default is '| grep python'
116
+ - CACHE_PREFIX: Optional. used as the prefix of the cache key. default is ''
117
+ - PORT: Optional. The listening port of the web application. default is '8080'
118
+ - WORKLOADS: Optional. Used if the web app has a fixed replicas.
119
+ - WORKLOAD_DEPLOYMENT: Optional. the workload is deployment if it is true; otherwise it is statefulset. default is 'true'
120
+ - WORKLOAD_FAILED_THRESHOLD: Optional. The number of continuous failed times to treat a pod is offline.
121
+ - Nginx Configuration.
122
+ - Add a location 'location /healthcheck/' and configure it to use basic auth in nginx.
123
+ - Access the url : https://xxx.dbca.wa.gov.au/healthcheck/healthdata to get the health json data
@@ -73,3 +73,23 @@ MIDDLEWARE = [
73
73
  - `modifier` - FK to `AUTH_USER_MODEL`, used to record who the object was last modified by
74
74
  - `created` - a timestamp that is set on initial object save
75
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
+ - The image has the command 'ps' which is used to collect the cpu and memory data
82
+
83
+ ### Usage
84
+ - Install the app 'dbca_utils' in INSTALLED_APPS
85
+ - Service Configuration
86
+ - HEALTHCHECK_ENABLED: Optional. enable/disable the healthcheck service. default is 'true'
87
+ - PROCESS_FILTER: Optional. find the web app related processes from command 'ps aux'. default is '| grep python'
88
+ - CACHE_PREFIX: Optional. used as the prefix of the cache key. default is ''
89
+ - PORT: Optional. The listening port of the web application. default is '8080'
90
+ - WORKLOADS: Optional. Used if the web app has a fixed replicas.
91
+ - WORKLOAD_DEPLOYMENT: Optional. the workload is deployment if it is true; otherwise it is statefulset. default is 'true'
92
+ - WORKLOAD_FAILED_THRESHOLD: Optional. The number of continuous failed times to treat a pod is offline.
93
+ - Nginx Configuration.
94
+ - Add a location 'location /healthcheck/' and configure it to use basic auth in nginx.
95
+ - Access the url : https://xxx.dbca.wa.gov.au/healthcheck/healthdata to get the health json data
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "dbca-utils"
3
- version = "3.0.2"
3
+ version = "3.0.4"
4
4
  description = "Utilities for DBCA Django apps"
5
5
  authors = [
6
6
  { name = "Rocky Chen", email = "rocky.chen@dbca.wa.gov.au" },
@@ -33,6 +33,18 @@ if WORKLOADS < 0 :
33
33
  WORKLOADS = 0
34
34
  WORKLOAD_FAILED_THRESHOLD = int(os.environ.get("WORKLOAD_FAILED_THRESHOLD",2))
35
35
 
36
+ WORKLOAD_VOLUMES = os.environ.get("WORKLOAD_VOLUMES","automatic")
37
+
38
+ if not WORKLOAD_VOLUMES or WORKLOAD_VOLUMES.lower() in ("disabled","false"):
39
+ WORKLOAD_VOLUMES_ENABLED = False
40
+ WORKLOAD_VOLUMES = None
41
+ elif WORKLOAD_VOLUMES.lower() == "automatic":
42
+ WORKLOAD_VOLUMES_ENABLED = True
43
+ WORKLOAD_VOLUMES = None
44
+ else:
45
+ WORKLOAD_VOLUMES = [v.strip() for v in WORKLOAD_VOLUMES.split(",") if v.strip()]
46
+ WORKLOAD_VOLUMES_ENABLED = True if WORKLOAD_VOLUMES else False
47
+
36
48
 
37
49
  RANDOM_CHARS="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYA0123456789~!@#$%^&*()-_+=`{}[];':\",./<>?"
38
50
  RANDOM_CHARS_MAX_INDEX = len(RANDOM_CHARS) - 1
@@ -104,10 +116,97 @@ def unregister_webappprocess():
104
116
  logger.error("Failed to unregister the webapp process '{}({}).{}'.".format(hostname,ip,pid))
105
117
 
106
118
 
119
+ GET_VOLUMEUSAGE_CMD = None
120
+ def get_persistent_volumes_data():
121
+ global WORKLOAD_VOLUMES
122
+ global GET_VOLUMEUSAGE_CMD
123
+ try:
124
+ if WORKLOAD_VOLUMES == []:
125
+ return {}
126
+ elif GET_VOLUMEUSAGE_CMD:
127
+ cmd = GET_VOLUMEUSAGE_CMD
128
+ else:
129
+ if WORKLOAD_VOLUMES is None:
130
+ #not configured. should find them automatically,
131
+ #the detect logic is dedicated for kubenetes
132
+ cmd = 'df --output="source,target,size"'
133
+ result = subprocess.run(cmd,shell=True,capture_output=True,text=True)
134
+ volumes = []
135
+ datarow = False
136
+ volumesdata = {}
137
+ overlaysize = 0
138
+ for line in result.stdout.split("\n"):
139
+ line = line.strip()
140
+ if not line:
141
+ continue
142
+ if not datarow:
143
+ line = line.lower()
144
+ if line.startswith("filesystem") :
145
+ datarow = True
146
+ continue
147
+ source,target,size = line.split()
148
+ size = int(size)
149
+ source = source.lower()
150
+ if source == "overlay":
151
+ overlaysize = size
152
+ elif target.startswith("/dev/sd"):
153
+ volumesdata[targer] = size
154
+ for k,v in volumesdata.items():
155
+ if v == overlaysize:
156
+ #the volume is the same volume as overlay
157
+ continue
158
+ volumes.append(k)
159
+
160
+ WORKLOAD_VOLUMES = volumes
161
+ if volumes:
162
+ cmd = 'df --output="target,size,used" -BK {}'.format(" ".join(volumes))
163
+ GET_VOLUMEUSAGE_CMD = cmd
164
+ else:
165
+ return {}
166
+ else:
167
+ volumes = WORKLOAD_VOLUMES
168
+ cmd = 'df --output="target,size,used" -BK {}'.format(" ".join(volumes))
169
+
170
+ result = subprocess.run(cmd,shell=True,capture_output=True,text=True)
171
+ volumesdata = {}
172
+ datarow = False
173
+ for line in result.stdout.split("\n"):
174
+ line = line.strip()
175
+ if not line:
176
+ continue
177
+ if not datarow:
178
+ line = line.lower()
179
+ if line.startswith("mounted on") :
180
+ datarow = True
181
+ continue
182
+ target,size,used = line.split()
183
+ size = int(size[:-1])
184
+ used = int(used[:-1])
185
+ if size / 1048576 >= 10:
186
+ #large than 10G, use 'G' as unit
187
+ volumesdata[target] = {"size":size/1048576,"used":used / 1048576,"pcent":100 * used/size,"unit":"G"}
188
+ elif size / 1024 >= 10:
189
+ #large than 10M, use 'M' as unit
190
+ volumesdata[target] = {"size":size/1024,"used":used / 1024,"pcent":100 * used/size,"unit":"M"}
191
+ else:
192
+ volumesdata[target] = {"size":size,"used":used,"pcent":100 * used/size}
193
+
194
+ if not GET_VOLUMEUSAGE_CMD:
195
+ #This is the first time to get the volume usage, delete the non-exist volume from volumes
196
+ for i in range(len(volumes) - 1,-1,-1):
197
+ if volumes[i] not in volumesdata:
198
+ del volumes[i]
199
+ WORKLOAD_VOLUMES = volumes
200
+ GET_VOLUMEUSAGE_CMD = 'df --output="target,size,used" -BK {}'.format(" ".join(volumes))
201
+ return volumesdata
202
+ except Exception as ex:
203
+ return "Failed to volumes usage data.{}: {}".format(ex.__class__.__name__,str(ex))
204
+
107
205
  item_version = "__version__"
108
206
  key_workloads = "{}__workloads__".format(CACHE_PREFIX)
109
207
  key_workloads_lock = "{}lock__".format(key_workloads)
110
208
 
209
+
111
210
  def register_webappserver(sender,environ,**kwargs):
112
211
  """
113
212
  Register a web server running in the same workload
@@ -234,6 +333,8 @@ def get_workload_healthcheckdata():
234
333
  if result["max_pmemory"] is None or result["max_pmemory"] < data[2]:
235
334
  result["max_pmemory"] = data[2]
236
335
 
336
+ if WORKLOAD_VOLUMES_ENABLED:
337
+ result["volumes"] = get_persistent_volumes_data()
237
338
  return (200,result)
238
339
 
239
340
  bearer_token_re = re.compile("^Bearer\\s+(?P<token>\\S+)\\s*$")
@@ -284,7 +385,7 @@ def save_workloads(workloads,unreached_servers=None):
284
385
  workloads[item_version] += 1
285
386
 
286
387
  #save the new workloads data
287
- cache.set(key_workloads,workloads)
388
+ cache.set(key_workloads,workloads,timeout=None)
288
389
  logger.debug("Successfully save the workloads:{}".format(str_workloads(workloads)))
289
390
  return
290
391
  finally:
@@ -325,7 +426,7 @@ def save_assignedworkloads(assignedworkloads):
325
426
  assignedworkloads[item_version] += 1
326
427
 
327
428
  #save the new workloads data
328
- cache.set(key_assignedworkloads,assignedworkloads)
429
+ cache.set(key_assignedworkloads,assignedworkloads,timeout=None)
329
430
  logger.debug("Successfully save the assigned workloads:{}".format(assignedworkloads))
330
431
  return
331
432
  finally:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes