dbca-utils 3.0.7__tar.gz → 3.0.8__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.7 → dbca_utils-3.0.8}/PKG-INFO +9 -4
  2. {dbca_utils-3.0.7 → dbca_utils-3.0.8}/README.md +8 -3
  3. {dbca_utils-3.0.7 → dbca_utils-3.0.8}/pyproject.toml +1 -1
  4. {dbca_utils-3.0.7 → dbca_utils-3.0.8}/src/dbca_utils/healthcheck/healthcheck.py +274 -242
  5. {dbca_utils-3.0.7 → dbca_utils-3.0.8}/LICENSE +0 -0
  6. {dbca_utils-3.0.7 → dbca_utils-3.0.8}/src/dbca_utils/__init__.py +0 -0
  7. {dbca_utils-3.0.7 → dbca_utils-3.0.8}/src/dbca_utils/apps.py +0 -0
  8. {dbca_utils-3.0.7 → dbca_utils-3.0.8}/src/dbca_utils/healthcheck/__init__.py +0 -0
  9. {dbca_utils-3.0.7 → dbca_utils-3.0.8}/src/dbca_utils/healthcheck/urls.py +0 -0
  10. {dbca_utils-3.0.7 → dbca_utils-3.0.8}/src/dbca_utils/middleware.py +0 -0
  11. {dbca_utils-3.0.7 → dbca_utils-3.0.8}/src/dbca_utils/models.py +0 -0
  12. {dbca_utils-3.0.7 → dbca_utils-3.0.8}/src/dbca_utils/utils.py +0 -0
  13. {dbca_utils-3.0.7 → dbca_utils-3.0.8}/tests/__init__.py +0 -0
  14. {dbca_utils-3.0.7 → dbca_utils-3.0.8}/tests/apps.py +0 -0
  15. {dbca_utils-3.0.7 → dbca_utils-3.0.8}/tests/migrations/0001_initial.py +0 -0
  16. {dbca_utils-3.0.7 → dbca_utils-3.0.8}/tests/migrations/__init__.py +0 -0
  17. {dbca_utils-3.0.7 → dbca_utils-3.0.8}/tests/models.py +0 -0
  18. {dbca_utils-3.0.7 → dbca_utils-3.0.8}/tests/settings.py +0 -0
  19. {dbca_utils-3.0.7 → dbca_utils-3.0.8}/tests/templates/tests/test_model_list.html +0 -0
  20. {dbca_utils-3.0.7 → dbca_utils-3.0.8}/tests/tests.py +0 -0
  21. {dbca_utils-3.0.7 → dbca_utils-3.0.8}/tests/urls.py +0 -0
  22. {dbca_utils-3.0.7 → dbca_utils-3.0.8}/tests/views.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dbca-utils
3
- Version: 3.0.7
3
+ Version: 3.0.8
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
@@ -106,18 +106,23 @@ MIDDLEWARE = [
106
106
  ### Requirements
107
107
  - Django 5.2 or later
108
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
109
+ - The image has the command 'ps' which is used to collect the cpu,memory and persistent volume usage data.
110
110
 
111
111
  ### Usage
112
112
  - Install the app 'dbca_utils' in INSTALLED_APPS
113
113
  - Service Configuration
114
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 ''
115
+ - CACHE_PREFIX: Optional. used as the prefix WORKLOAD_VOLUMESof WORKLOAD_VOLUMESthe cache key. default is ''
117
116
  - PORT: Optional. The listening port of the web application. default is '8080'
118
117
  - WORKLOADS: Optional. Used if the web app has a fixed replicas.
119
118
  - WORKLOAD_DEPLOYMENT: Optional. the workload is deployment if it is true; otherwise it is statefulset. default is 'true'
120
119
  - WORKLOAD_FAILED_THRESHOLD: Optional. The number of continuous failed times to treat a pod is offline.
120
+ - WORKLOAD_VOLUMES: Optional. can be 'disabled', 'false','automatic' or "," separated volume mounted point. default is "automatic".
121
+ - disabled|false: Don't harvest volume usage data
122
+ - automatic: Detect the persistent volume automatically.
123
+ - "," separated volume mounted points: the volume list
124
+ - HEALTHCHECK_SYSTEMDATA_ENABLED: Enable system resource data
125
+ - HEALTHCHECK_PROCESSDATA_ENABLED: Enable process resource data
121
126
  - Nginx Configuration.
122
127
  - Add a location 'location /healthcheck/' and configure it to use basic auth in nginx.
123
128
  - Access the url : https://xxx.dbca.wa.gov.au/healthcheck/healthdata to get the health json data
@@ -78,18 +78,23 @@ MIDDLEWARE = [
78
78
  ### Requirements
79
79
  - Django 5.2 or later
80
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
81
+ - The image has the command 'ps' which is used to collect the cpu,memory and persistent volume usage data.
82
82
 
83
83
  ### Usage
84
84
  - Install the app 'dbca_utils' in INSTALLED_APPS
85
85
  - Service Configuration
86
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 ''
87
+ - CACHE_PREFIX: Optional. used as the prefix WORKLOAD_VOLUMESof WORKLOAD_VOLUMESthe cache key. default is ''
89
88
  - PORT: Optional. The listening port of the web application. default is '8080'
90
89
  - WORKLOADS: Optional. Used if the web app has a fixed replicas.
91
90
  - WORKLOAD_DEPLOYMENT: Optional. the workload is deployment if it is true; otherwise it is statefulset. default is 'true'
92
91
  - WORKLOAD_FAILED_THRESHOLD: Optional. The number of continuous failed times to treat a pod is offline.
92
+ - WORKLOAD_VOLUMES: Optional. can be 'disabled', 'false','automatic' or "," separated volume mounted point. default is "automatic".
93
+ - disabled|false: Don't harvest volume usage data
94
+ - automatic: Detect the persistent volume automatically.
95
+ - "," separated volume mounted points: the volume list
96
+ - HEALTHCHECK_SYSTEMDATA_ENABLED: Enable system resource data
97
+ - HEALTHCHECK_PROCESSDATA_ENABLED: Enable process resource data
93
98
  - Nginx Configuration.
94
99
  - Add a location 'location /healthcheck/' and configure it to use basic auth in nginx.
95
100
  - 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.7"
3
+ version = "3.0.8"
4
4
  description = "Utilities for DBCA Django apps"
5
5
  authors = [
6
6
  { name = "Rocky Chen", email = "rocky.chen@dbca.wa.gov.au" },
@@ -1,6 +1,7 @@
1
1
  import os
2
2
  import importlib
3
3
  import logging
4
+ import psutil
4
5
  import subprocess
5
6
  import random
6
7
  import re
@@ -10,6 +11,7 @@ import requests
10
11
  from datetime import datetime
11
12
 
12
13
  from django.urls import reverse,path,include
14
+ from django.utils import timezone
13
15
  from django.conf import settings
14
16
  from django.http import HttpResponseForbidden, JsonResponse,HttpResponseServerError
15
17
  from django.core.signals import request_started
@@ -24,7 +26,9 @@ HEALTHCHECK_ENABLED = os.environ.get("HEALTHCHECK_ENABLED","true").lower() == "t
24
26
  if not HEALTHCHECK_ENABLED:
25
27
  HEALTHCHECK_ENABLED = True if cache else None
26
28
 
27
- PROCESS_FILTER = os.environ.get("WORKLOAD_PROCESS_FILTER","| grep python")
29
+ HEALTHCHECK_SYSTEMDATA_ENABLED = os.environ.get("HEALTHCHECK_SYSTEMDATA_ENABLED","true").lower() == "true"
30
+ HEALTHCHECK_PROCESSDATA_ENABLED = os.environ.get("HEALTHCHECK_PROCESSDATA_ENABLED","true").lower() == "true"
31
+
28
32
  CACHE_PREFIX = os.environ.get("CACHE_PREFIX","")
29
33
  PORT = int(os.environ.get("WORKLOAD_PORT",8080))
30
34
  WORKLOADS = int(os.environ.get("WORKLOADS",0))
@@ -80,138 +84,11 @@ else:
80
84
 
81
85
  ip = get_local_ip()
82
86
 
83
- webapp_process_registerfolder = "/tmp/__webapp__/proc"
84
-
85
- def get_processregisterfile(pid):
86
- return os.path.join(webapp_process_registerfolder,str(pid))
87
-
88
-
89
- def register_webappprocess():
90
- """
91
- Register all webapp related processes
92
- Healthcheck will use the processes to calculate the resources used by webapp
93
- """
94
- pid = os.getpid()
95
- logger.debug("Register the webapp process '{}({}).{}'.".format(hostname,ip,pid))
96
- try:
97
- if not os.path.exists(webapp_process_registerfolder):
98
- os.makedirs(webapp_process_registerfolder)
99
-
100
- registerfile = get_processregisterfile(pid)
101
- #register the webapp process first
102
- with open(registerfile,"wt") as f:
103
- f.write(datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%f"))
104
- except Exception as ex:
105
- logger.error("Failed to register the webapp process '{}({}).{}'.".format(hostname,ip,pid))
106
-
107
- def unregister_webappprocess():
108
- pid = os.getpid()
109
- logger.debug("Unregister the webapp process '{}({}).{}'.".format(hostname,ip,pid))
110
- try:
111
- registerfile = get_processregisterfile(pid)
112
- #register the webapp process first
113
- os.remove(registerfile)
114
- except Exception as ex:
115
- if os.path.exists(registerfile):
116
- logger.error("Failed to unregister the webapp process '{}({}).{}'.".format(hostname,ip,pid))
117
-
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 source.startswith("//"):
153
- volumes.append(target)
154
- elif source.startswith("/dev/sd"):
155
- #the volume can be same filesystem as the volume 'overlay'
156
- volumesdata[target] = size
157
-
158
- for k,v in volumesdata.items():
159
- if v == overlaysize:
160
- #the volume is the same volume as overlay
161
- continue
162
- volumes.append(k)
163
-
164
- WORKLOAD_VOLUMES = volumes
165
- if volumes:
166
- cmd = 'df --output="target,size,used" -BK {}'.format(" ".join(volumes))
167
- GET_VOLUMEUSAGE_CMD = cmd
168
- else:
169
- return {}
170
- else:
171
- volumes = WORKLOAD_VOLUMES
172
- cmd = 'df --output="target,size,used" -BK {}'.format(" ".join(volumes))
173
-
174
- result = subprocess.run(cmd,shell=True,capture_output=True,text=True)
175
- volumesdata = {}
176
- datarow = False
177
- for line in result.stdout.split("\n"):
178
- line = line.strip()
179
- if not line:
180
- continue
181
- if not datarow:
182
- line = line.lower()
183
- if line.startswith("mounted on") :
184
- datarow = True
185
- continue
186
- target,size,used = line.split()
187
- size = int(size[:-1])
188
- used = int(used[:-1])
189
- if size / 1048576 >= 10:
190
- #large than 10G, use 'G' as unit
191
- volumesdata[target] = {"size":size/1048576,"used":used / 1048576,"pcent":100 * used/size,"unit":"G"}
192
- elif size / 1024 >= 10:
193
- #large than 10M, use 'M' as unit
194
- volumesdata[target] = {"size":size/1024,"used":used / 1024,"pcent":100 * used/size,"unit":"M"}
195
- else:
196
- volumesdata[target] = {"size":size,"used":used,"pcent":100 * used/size}
197
-
198
- if not GET_VOLUMEUSAGE_CMD:
199
- #This is the first time to get the volume usage, delete the non-exist volume from volumes
200
- for i in range(len(volumes) - 1,-1,-1):
201
- if volumes[i] not in volumesdata:
202
- del volumes[i]
203
- WORKLOAD_VOLUMES = volumes
204
- GET_VOLUMEUSAGE_CMD = 'df --output="target,size,used" -BK {}'.format(" ".join(volumes))
205
- return volumesdata
206
- except Exception as ex:
207
- return "Failed to volumes usage data.{}: {}".format(ex.__class__.__name__,str(ex))
208
-
209
87
  item_version = "__version__"
210
88
  key_workloads = "{}__workloads__".format(CACHE_PREFIX)
211
89
  key_workloads_lock = "{}lock__".format(key_workloads)
212
90
 
213
-
214
- def register_webappserver(sender,environ,**kwargs):
91
+ def register_webappserver(sender,*args,**kwargs):
215
92
  """
216
93
  Register a web server running in the same workload
217
94
  1. Write a server register file in workload's local file system
@@ -279,66 +156,180 @@ if HEALTHCHECK_ENABLED:
279
156
  #healthcheck is not initied
280
157
  request_started.connect(register_webappserver,dispatch_uid="register_webappserver")
281
158
 
282
- GET_RESOURCEUSAGE_CMD = "ps ax -o %cpu=,vsz=,rss=,cmd= {}".format(PROCESS_FILTER).strip()
283
- GET_RESOURCEUSAGE_PIPECMDS = [c.strip() for c in GET_RESOURCEUSAGE_CMD.split("|")]
284
159
 
285
- def get_workload_healthcheckdata():
286
- #find all running web app processes
287
- #find the resource usage for all processes
288
- result = subprocess.run(GET_RESOURCEUSAGE_CMD,shell=True,capture_output=True,text=True)
289
- if result.returncode != 0:
290
- return (500,"Failed to get the resource usage data for webapp processes.{}".format(result.stderr))
160
+ VALID_WORKLOAD_VOLUMES = None
161
+ def get_volumes_healthdata():
162
+ global VALID_WORKLOAD_VOLUMES
163
+ try:
164
+ if VALID_WORKLOAD_VOLUMES is None:
165
+ volumes = []
166
+ for partition in psutil.disk_partitions(all=True):
167
+ if WORKLOAD_VOLUMES is None:
168
+ if partition.fstype.lower() not in ("cifs","nfs","sshfs","davfs2"):
169
+ continue
170
+ volumes.append(partition.mountpoint)
171
+ elif partition.mountpoint in WORKLOAD_VOLUMES:
172
+ volumes.append(partition.mountpoint)
173
+
174
+ VALID_WORKLOAD_VOLUMES = volumes
175
+
176
+ if not VALID_WORKLOAD_VOLUMES:
177
+ return {}
178
+
179
+ volumesdata = {}
180
+ for volume in VALID_WORKLOAD_VOLUMES:
181
+ diskusage = psutil.disk_usage(volume)
182
+ if diskusage.total / 1073741824 >= 10:
183
+ #large than 10G, use 'G' as unit
184
+ volumesdata[volume] = {"size":round(diskusage.total / 1073741824),"used":round(diskusage.used / 1073741824),"pcent":100 * diskusage.used/diskusage.total,"unit":"G"}
185
+ elif diskusage.total / 1048576 >= 10:
186
+ #large than 10M, use 'M' as unit
187
+ volumesdata[volume] = {"size":round(diskusage.total / 1048576),"used":round(diskusage.used / 1048576),"pcent":100 * diskusage.used/diskusage.total,"unit":"M"}
188
+ else:
189
+ volumesdata[volume] = {"size":round(diskusage.total / 1024),"used":round(diskusage.used / 1024),"pcent":100 * diskusage.used/diskusage.total,"unit":"K"}
190
+
191
+ return volumesdata
192
+ except Exception as ex:
193
+ import traceback;
194
+ traceback.print_exc()
195
+ return "Failed to volumes usage data.{}: {}".format(ex.__class__.__name__,str(ex))
196
+
197
+ def get_workload_system_healthdata():
198
+ cpu_pcent = system_cpu_pcents = psutil.cpu_percent(percpu=False)
199
+ cpucores_pcent = system_cpu_pcents = psutil.cpu_percent(percpu=True)
200
+ memoryinfo = psutil.virtual_memory()
201
+ netio = psutil.net_io_counters()
202
+
203
+ return {
204
+ "cpu_pcent":cpu_pcent,
205
+ "cpucores_pcent":cpucores_pcent,
206
+ "memory_total": memoryinfo.total / 1073741824,
207
+ "memory_used": (memoryinfo.total - memoryinfo.available) / 1073741824,
208
+ "memory_pcent": (memoryinfo.total - memoryinfo.available) * 100 / memoryinfo.total,
209
+ "bytes_sent": netio.bytes_sent,
210
+ "bytes_recv": netio.bytes_recv
211
+ }
212
+
213
+ def get_process_healthdata(proc):
214
+ memoryinfo = proc.memory_info()
215
+ result = {
216
+ "start_time":timezone.make_aware(datetime.fromtimestamp(proc.create_time())).strftime("%Y-%m-%dT%H:%M:%S"),
217
+ "cmdline":proc.cmdline(),
218
+ "cpu_num": proc.cpu_num(),
219
+ "cpu_pcent": proc.cpu_percent(),
220
+ "pmemory":memoryinfo.rss / 1048576,
221
+ "vmemory":memoryinfo.vms / 1048576
222
+ }
223
+ if proc.pid == curprocpid:
224
+ result["currentprocess"] = True
225
+ return result
226
+
227
+ rootproc = None
228
+ curprocpid = None
229
+ def get_workload_app_healthdata(perprocess=True):
230
+ """
231
+ All processes belonging to the webapp should have
232
+ 1. same parent process
233
+ 2. the cmdline of all processes should be same
234
+ """
235
+ global rootproc
236
+ global curprocpid
237
+ if not curprocpid:
238
+ curprocpid = os.getpid()
239
+
240
+ if not rootproc:
241
+ #the the root proc
242
+ #get the pid of the current process
243
+ curproc = psutil.Process(curprocpid)
244
+ #find the parent
245
+ pproc = curproc
246
+ rootproc = None
247
+ app_cmdline = curproc.cmdline()
248
+ while not rootproc:
249
+ ppid = pproc.ppid()
250
+ if not ppid:
251
+ rootproc = pproc
252
+ continue
253
+
254
+ tmpproc = psutil.Process(ppid)
255
+ tmpproc_cmdline = tmpproc.cmdline()
256
+ if tmpproc_cmdline == app_cmdline:
257
+ #the pproc has the same cmd line as current proc. the pproc is also related app python process
258
+ pproc = tmpproc
259
+ elif any(any(key in p for key in ("python","gunicorn","uwsgi","django")) for p in tmpproc_cmdline):
260
+ #the pproc is still the python process.
261
+ pproc = tmpproc
262
+ else:
263
+ rootproc = pproc
264
+
265
+
266
+ #find all realted processes and its health data
267
+ rootproc_data = get_process_healthdata(rootproc)
268
+ result = {
269
+ "start_time": rootproc_data["start_time"],
270
+ "cpu_total" : rootproc_data["cpu_pcent"],
271
+ "cpu_min" : rootproc_data["cpu_pcent"],
272
+ "cpu_max" : rootproc_data["cpu_pcent"],
273
+ "pmemory_total" : rootproc_data["pmemory"],
274
+ "pmemory_min" : rootproc_data["pmemory"],
275
+ "pmemory_max" : rootproc_data["pmemory"],
276
+ "vmemory_total" : rootproc_data["vmemory"],
277
+ "vmemory_min" : rootproc_data["vmemory"],
278
+ "vmemory_max" : rootproc_data["vmemory"],
279
+ "processes" : 1
280
+ }
281
+ if perprocess:
282
+ result["process"] = rootproc_data
283
+ result["process"]["children"] = []
284
+
285
+ processes = [(rootproc.children(),result["process"]["children"] if perprocess else None)]
286
+ while processes:
287
+ childproces,childrendatas = processes.pop(0)
288
+ for childproc in childproces:
289
+ childproc_data = get_process_healthdata(childproc)
290
+
291
+ result["cpu_total"] += childproc_data["cpu_pcent"]
292
+ if result["cpu_min"] > childproc_data["cpu_pcent"]:
293
+ result["cpu_min"] = childproc_data["cpu_pcent"]
294
+ if result["cpu_max"] < childproc_data["cpu_pcent"]:
295
+ result["cpu_max"] = childproc_data["cpu_pcent"]
296
+
297
+ result["pmemory_total"] += childproc_data["pmemory"]
298
+ if result["pmemory_min"] > childproc_data["pmemory"]:
299
+ result["pmemory_min"] = childproc_data["pmemory"]
300
+ if result["pmemory_max"] < childproc_data["pmemory"]:
301
+ result["pmemory_max"] = childproc_data["pmemory"]
302
+
303
+ result["vmemory_total"] += childproc_data["vmemory"]
304
+ if result["vmemory_min"] > childproc_data["vmemory"]:
305
+ result["vmemory_min"] = childproc_data["vmemory"]
306
+ if result["vmemory_max"] < childproc_data["vmemory"]:
307
+ result["vmemory_max"] = childproc_data["vmemory"]
308
+
309
+ result["processes"] += 1
310
+
311
+ if perprocess:
312
+ childrendatas.append(childproc_data)
313
+
314
+ childproc_children = childproc.children()
315
+ if childproc_children:
316
+ if perprocess:
317
+ childproc_data["children"] = []
318
+ processes.append((childproc_children,childproc_data["children"] if perprocess else None))
319
+
320
+ return result
321
+
322
+ def get_workload_healthdata():
291
323
 
292
- processesdata = []
293
- for line in result.stdout.split("\n"):
294
- line = line.strip()
295
- if not line:
296
- continue
297
- if any(c in line for c in GET_RESOURCEUSAGE_PIPECMDS):
298
- continue
299
- data = line.split(maxsplit=3)
300
- data[0] = float(data[0])
301
- data[1] = float(data[1]) / 1024
302
- data[2] = float(data[2]) / 1024
303
- del data[3]
304
- processesdata.append(data)
305
-
306
- #populate the resource data
307
324
  result = {
308
- "total_cpu":0,
309
- "total_vmemory":0,
310
- "total_pmemory":0,
311
- "processes":0,
312
- "min_cpu":None,
313
- "max_cpu":None,
314
- "min_vmemory":None,
315
- "max_vmemory":None,
316
- "min_pmemory":None,
317
- "max_pmemory":None
325
+ "resources": get_workload_app_healthdata(HEALTHCHECK_PROCESSDATA_ENABLED)
318
326
  }
319
- for data in processesdata:
320
- result["total_cpu"] += data[0]
321
- result["total_vmemory"] += data[1]
322
- result["total_pmemory"] += data[2]
323
- result["processes"] += 1
324
-
325
- if result["min_cpu"] is None or result["min_cpu"] > data[0]:
326
- result["min_cpu"] = data[0]
327
- if result["max_cpu"] is None or result["max_cpu"] < data[0]:
328
- result["max_cpu"] = data[0]
329
-
330
- if result["min_vmemory"] is None or result["min_vmemory"] > data[1]:
331
- result["min_vmemory"] = data[1]
332
- if result["max_vmemory"] is None or result["max_vmemory"] < data[1]:
333
- result["max_vmemory"] = data[1]
334
-
335
- if result["min_pmemory"] is None or result["min_pmemory"] > data[2]:
336
- result["min_pmemory"] = data[2]
337
- if result["max_pmemory"] is None or result["max_pmemory"] < data[2]:
338
- result["max_pmemory"] = data[2]
327
+ if HEALTHCHECK_SYSTEMDATA_ENABLED:
328
+ result["system"] = get_workload_system_healthdata()
339
329
 
340
330
  if WORKLOAD_VOLUMES_ENABLED:
341
- result["volumes"] = get_persistent_volumes_data()
331
+ result["volumes"] = get_volumes_healthdata()
332
+
342
333
  return (200,result)
343
334
 
344
335
  bearer_token_re = re.compile("^Bearer\\s+(?P<token>\\S+)\\s*$")
@@ -446,43 +437,64 @@ def populate_summary_data(datas):
446
437
  Populate the resource summary data from workloads' resource usage data
447
438
  """
448
439
  summary = {
449
- "total_cpu":0,
450
- "total_vmemory":0,
451
- "total_pmemory":0,
452
- "total_processes":0,
453
- "running_workloads":0,
454
- "failed_workloads":0,
455
- "min_process_cpu":None,
456
- "max_process_cpu":None,
457
- "min_process_vmemory":None,
458
- "max_process_vmemory":None,
459
- "min_process_pmemory":None,
460
- "max_process_pmemory":None
440
+ "cpu_total":0,
441
+ "cpu_min":None,
442
+ "cpu_max":None,
443
+ "process_cpu_min":None,
444
+ "process_cpu_max":None,
445
+ "pmemory_total":0,
446
+ "pmemory_min":None,
447
+ "pmemory_max":None,
448
+ "process_pmemory_min":None,
449
+ "process_pmemory_max":None,
450
+ "vmemory_total":0,
451
+ "vmemory_min":None,
452
+ "vmemory_max":None,
453
+ "process_vmemory_min":None,
454
+ "process_vmemory_max":None,
455
+ "processes_total":0,
456
+ "workloads_running":0,
457
+ "workloads_failed":0,
461
458
  }
462
459
  for servername,serverdata in datas.items():
463
460
  if isinstance(serverdata,str):
464
- summary["failed_workloads"] += 1
461
+ summary["workloads_failed"] += 1
465
462
  continue
466
- summary["running_workloads"] += 1
467
- summary["total_cpu"] += serverdata["total_cpu"]
468
- summary["total_vmemory"] += serverdata["total_vmemory"]
469
- summary["total_pmemory"] += serverdata["total_pmemory"]
470
- summary["total_processes"] += serverdata["processes"]
471
-
472
- if summary["min_process_cpu"] is None or summary["min_process_cpu"] > serverdata["min_cpu"]:
473
- summary["min_process_cpu"] = serverdata["min_cpu"]
474
- if summary["max_process_cpu"] is None or summary["max_process_cpu"] < serverdata["max_cpu"]:
475
- summary["max_process_cpu"] = serverdata["max_cpu"]
476
-
477
- if summary["min_process_vmemory"] is None or summary["min_process_vmemory"] > serverdata["min_vmemory"]:
478
- summary["min_process_vmemory"] = serverdata["min_vmemory"]
479
- if summary["max_process_vmemory"] is None or summary["max_process_vmemory"] < serverdata["max_vmemory"]:
480
- summary["max_process_vmemory"] = serverdata["max_vmemory"]
481
-
482
- if summary["min_process_pmemory"] is None or summary["min_process_pmemory"] > serverdata["min_pmemory"]:
483
- summary["min_process_pmemory"] = serverdata["min_pmemory"]
484
- if summary["max_process_pmemory"] is None or summary["max_process_pmemory"] < serverdata["max_pmemory"]:
485
- summary["max_process_pmemory"] = serverdata["max_pmemory"]
463
+ summary["processes_total"] += serverdata["resources"]["processes"]
464
+
465
+ summary["cpu_total"] += serverdata["resources"]["cpu_total"]
466
+
467
+ summary["cpu_total"] += serverdata["resources"]["cpu_total"]
468
+ if summary["cpu_min"] is None or summary["cpu_min"] > serverdata["resources"]["cpu_total"]:
469
+ summary["cpu_min"] = serverdata["resources"]["cpu_total"]
470
+ if summary["cpu_max"] is None or summary["cpu_max"] < serverdata["resources"]["cpu_total"]:
471
+ summary["cpu_max"] = serverdata["resources"]["cpu_total"]
472
+ if summary["process_cpu_min"] is None or summary["process_cpu_min"] > serverdata["resources"]["cpu_min"]:
473
+ summary["process_cpu_min"] = serverdata["resources"]["cpu_min"]
474
+ if summary["process_cpu_max"] is None or summary["process_cpu_max"] < serverdata["resources"]["cpu_max"]:
475
+ summary["process_cpu_max"] = serverdata["resources"]["cpu_max"]
476
+
477
+ summary["pmemory_total"] += serverdata["resources"]["pmemory_total"]
478
+ if summary["pmemory_min"] is None or summary["pmemory_min"] > serverdata["resources"]["pmemory_total"]:
479
+ summary["pmemory_min"] = serverdata["resources"]["pmemory_total"]
480
+ if summary["pmemory_max"] is None or summary["pmemory_max"] < serverdata["resources"]["pmemory_total"]:
481
+ summary["pmemory_max"] = serverdata["resources"]["pmemory_total"]
482
+ if summary["process_pmemory_min"] is None or summary["process_pmemory_min"] > serverdata["resources"]["pmemory_min"]:
483
+ summary["process_pmemory_min"] = serverdata["resources"]["pmemory_min"]
484
+ if summary["process_pmemory_max"] is None or summary["process_pmemory_max"] < serverdata["resources"]["pmemory_max"]:
485
+ summary["process_pmemory_max"] = serverdata["resources"]["pmemory_max"]
486
+
487
+ summary["vmemory_total"] += serverdata["resources"]["vmemory_total"]
488
+ if summary["vmemory_min"] is None or summary["vmemory_min"] > serverdata["resources"]["vmemory_total"]:
489
+ summary["vmemory_min"] = serverdata["resources"]["vmemory_total"]
490
+ if summary["vmemory_max"] is None or summary["vmemory_max"] < serverdata["resources"]["vmemory_total"]:
491
+ summary["vmemory_max"] = serverdata["resources"]["vmemory_total"]
492
+ if summary["process_vmemory_min"] is None or summary["process_vmemory_min"] > serverdata["resources"]["vmemory_min"]:
493
+ summary["process_vmemory_min"] = serverdata["resources"]["vmemory_min"]
494
+ if summary["process_vmemory_max"] is None or summary["process_vmemory_max"] < serverdata["resources"]["vmemory_max"]:
495
+ summary["process_vmemory_max"] = serverdata["resources"]["vmemory_max"]
496
+
497
+ summary["workloads_running"] += 1
486
498
 
487
499
  datas["summary"] = summary
488
500
 
@@ -512,7 +524,7 @@ def harvest_healthdata(request):
512
524
  if servername == item_version:
513
525
  continue
514
526
  if servername == registerhostname:
515
- servers_res[servername] = get_workload_healthcheckdata()
527
+ servers_res[servername] = get_workload_healthdata()
516
528
  continue
517
529
 
518
530
  serverip,port = serverdata[0]
@@ -544,24 +556,33 @@ def harvest_healthdata(request):
544
556
  if serverdata[2] > 0:
545
557
  serverdata[2] -= 1
546
558
  workloads_changed = True
547
- else:
548
- #the server is online, but running into error, add the error message to servers_res
549
- servers_res[servername] = (res.status_code,"{1}: {2}. url={0}".format(res.status_code,res.text,url))
550
- if serverdata[2] > 0:
551
- serverdata[2] -= 1
559
+ elif res.status_code == 599:
560
+ #the server is in good health, add the health data to servers_res
561
+ data = res.json()
562
+ if data["status"] == 401:
563
+ #authentication error, caused by different workload.
552
564
  workloads_changed = True
565
+ unreached_servers.append(servername)
566
+ servers_res[servername] = (res.status_code,"{1}:{2},url={0}".format(url,data["status"],data["message"]))
567
+ else:
568
+ servers_res[servername] = (res.status_code,"{1}:{2}. url={0}".format(url,data["status"],data["message"]))
569
+ else:
570
+ #unexpected error, caused by different workload
571
+ workloads_changed = True
572
+ unreached_servers.append(servername)
573
+ servers_res[servername] = (res.status_code,"{1}:{2},url={0}".format(url,res.status_code,res.text))
553
574
 
554
575
  for servername in unreached_servers:
555
576
  del workloads[servername]
556
577
 
557
- logger.debug("healthdata harvest result :{}".format(servers_res))
578
+ logger.debug("healthdata harvest result :workloads={}, resources={}".format(workloads,servers_res))
558
579
 
559
580
  if workloads_changed:
560
581
  save_workloads(workloads,unreached_servers)
561
582
 
562
583
  return (workloads,servers_res)
563
584
 
564
- OFFLINE_STATUSCODE_LIST = (502,503,504,-1,-2)
585
+ OFFLINE_STATUSCODE_LIST = (502,503,504,401,403,-1,-2)
565
586
  if WORKLOADS > 0 and WORKLOAD_DEPLOYMENT:
566
587
  #has a fixed number of workloads and it is a deployment
567
588
  WORKLOADNAMES = [get_workloadname(index) for index in range(WORKLOADS)]
@@ -593,6 +614,14 @@ if WORKLOADS > 0 and WORKLOAD_DEPLOYMENT:
593
614
  del servers_res[servername]
594
615
 
595
616
  assignedworkloads_changed = False
617
+ if len(WORKLOADNAMES) != len(assignedworkloads):
618
+ for key in [k for k in assignedworkloads.keys()]:
619
+ if key == item_version:
620
+ continue
621
+ if key not in WORKLOADNAMES:
622
+ assignedworkloads_changed = True
623
+ del assignedworkloads[key]
624
+
596
625
  if reassign_workloads > 0:
597
626
  #Some workloads are not assigned a workload name or are not available
598
627
  #Using the following to replace the exisint one with new one if possible
@@ -691,24 +720,27 @@ else:
691
720
 
692
721
  def workload_healthdata_view(request):
693
722
  global secret
694
- token = get_auth_bearer(request)
695
- if not token:
696
- return HttpResponseForbidden("Missing access token")
697
-
698
- if not secret or secret != token:
699
- workloads = cache.get(key_workloads)
700
- data = workloads.get(registerhostname)
701
- if data:
702
- secret = data[1]
703
-
704
- if secret != token:
705
- return HttpResponseForbidden("Access token doesn't match")
706
-
707
- statuscode,data = get_workload_healthcheckdata()
708
- if statuscode == 200:
709
- return JsonResponse(data)
710
- else:
711
- return HttpResponseServerError(data)
723
+ try:
724
+ token = get_auth_bearer(request)
725
+ if not token:
726
+ return JsonResponse({"status":401,"message":"Missing access token."},status=599)
727
+
728
+ if not secret or secret != token:
729
+ workloads = cache.get(key_workloads)
730
+ data = workloads.get(registerhostname)
731
+ if data:
732
+ secret = data[1]
733
+
734
+ if secret != token:
735
+ return JsonResponse({"status":401,"message":"Access token doesn't match."},status=599)
736
+
737
+ statuscode,data = get_workload_healthdata()
738
+ if statuscode == 200:
739
+ return JsonResponse(data)
740
+ else:
741
+ return JsonResponse({"status":500,"message":"Server Error"},status=599)
742
+ except Exception as ex:
743
+ return JsonResponse({"status":500,"message":"{}:{}".format(ex.__class__.__name__,str(ex))},status=599)
712
744
 
713
745
  def register_healtcheckurls():
714
746
  #Add urls
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes