Skip to content

Commit ceb4b47

Browse files
committed
initial commit
Signed-off-by: hwassman <[email protected]>
1 parent 0e4f1b9 commit ceb4b47

File tree

5 files changed

+184
-4
lines changed

5 files changed

+184
-4
lines changed

source/bridgeLogger.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ def configureLogging(logPath, logfile, loglevel=logging.INFO):
5454

5555
logToFile = True if logfile else False
5656

57-
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)-8s - %(message)s')
58-
formatter1 = logging.Formatter('%(asctime)s - %(levelname)-8s - %(message)s', datefmt='%Y-%m-%d %H:%M')
57+
formatter = logging.Formatter('%(asctime)s - %(name)s - %(threadName)-20s - %(levelname)-8s - %(message)s')
58+
formatter1 = logging.Formatter('%(asctime)s - %(threadName)-20s - %(levelname)-8s - %(message)s', datefmt='%Y-%m-%d %H:%M')
5959

6060
# prepare the logger
6161
logging.setLoggerClass(MyLogger)
@@ -85,3 +85,7 @@ def configureLogging(logPath, logfile, loglevel=logging.INFO):
8585

8686
logger.propagate = False # prevent propagation to default (console) logger
8787
return logger
88+
89+
90+
def getBridgeLogger():
91+
return logging.getLogger(__name__)

source/messages.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,11 @@
5555
'Query2port': 'For better bridge performance multithreaded port {} will be used',
5656
'CollectorConnInfo': 'Connection to the collector server established successfully',
5757
'BridgeVersionInfo': ' *** IBM Spectrum Scale bridge for Grafana - Version: {} ***',
58-
'FileNotFound': 'The file {} not found.'
58+
'FileNotFound': 'The file {} not found.',
59+
'FileChanged': 'The file {} has been changed.',
60+
'FileAddedToWatch': 'The file {} added to watching files.',
61+
'StartWatchingFiles': 'Start watching file changes in the path {}.',
62+
'StopWatchingFiles': 'Stop watching file changes in the path {}.',
63+
'PathNoCfgFiles': 'The path does not contain any configuration files.',
64+
'UnhandledError': 'Unhandled error: {}'
5965
}

source/watcher.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import os
2+
import sys
3+
'''
4+
##############################################################################
5+
# Copyright 2023 IBM Corp.
6+
#
7+
# Licensed under the Apache License, Version 2.0 (the "License");
8+
# you may not use this file except in compliance with the License.
9+
# You may obtain a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing, software
14+
# distributed under the License is distributed on an "AS IS" BASIS,
15+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
# See the License for the specific language governing permissions and
17+
# limitations under the License.
18+
##############################################################################
19+
20+
Created on Sep 22, 2023
21+
22+
@author: HWASSMAN
23+
'''
24+
25+
import time
26+
from bridgeLogger import getBridgeLogger
27+
from messages import MSG
28+
29+
30+
class ConfigWatcher(object):
31+
running = False
32+
refresh_delay_secs = 1
33+
34+
# Constructor
35+
def __init__(self, watch_paths, call_func_on_change=None, *args, **kwargs):
36+
self._cached_stamp = {}
37+
self.logger = getBridgeLogger()
38+
self.paths = watch_paths
39+
self.filenames = set()
40+
self.call_func_on_change = call_func_on_change
41+
self.args = args
42+
self.kwargs = kwargs
43+
44+
45+
def update_files_list(self):
46+
oldfiles = self.filenames.copy()
47+
for path in self.paths:
48+
if os.path.isfile(path):
49+
self.filenames.add(path)
50+
elif os.path.isdir(path):
51+
for root, _, files in os.walk(path):
52+
for file in files:
53+
if file.endswith(".cfg"):
54+
self.filenames.add(os.path.join(root, file))
55+
else:
56+
self.logger.trace(MSG['PathNoCfgFiles'].format(path))
57+
for file in self.filenames.difference(oldfiles):
58+
self.logger.debug(MSG['FileAddedToWatch'].format(file))
59+
60+
61+
# Look for changes
62+
def look(self):
63+
for filename in self.filenames:
64+
stamp = os.stat(filename).st_mtime
65+
if filename not in self._cached_stamp:
66+
self._cached_stamp[filename] = stamp
67+
elif stamp != self._cached_stamp[filename]:
68+
self._cached_stamp[filename] = stamp
69+
# File has changed, so do something...
70+
self.logger.info(MSG['FileChanged'].format(filename))
71+
if self.call_func_on_change is not None:
72+
self.call_func_on_change(*self.args, **self.kwargs)
73+
74+
# Keep watching in a loop
75+
def watch(self):
76+
self.running = True
77+
self.logger.debug(MSG['StartWatchingFiles'].format(self.paths))
78+
while self.running:
79+
try:
80+
# Look for changes
81+
time.sleep(self.refresh_delay_secs)
82+
self.update_files_list()
83+
self.look()
84+
except KeyboardInterrupt:
85+
self.logger.details(MSG['StopWatchingFiles'].format(self.paths))
86+
break
87+
except FileNotFoundError as e:
88+
# Action on file not found
89+
self.logger.warning(MSG['FileNotFound'].format(e.filename))
90+
pass
91+
except Exception as e:
92+
self.logger.warning(MSG['StopWatchingFiles'].format(self.paths))
93+
self.logger.details(MSG['UnhandledError'].format(type(e).__name__))
94+
break
95+
96+
97+
# break watching
98+
def stop_watch(self):
99+
self.running = False
100+

source/zimonGrafanaIntf.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,11 @@
3737
from metaclasses import Singleton
3838
from bridgeLogger import configureLogging
3939
from confParser import getSettings
40+
from watcher import ConfigWatcher
4041
from collections import defaultdict
4142
from timeit import default_timer as timer
4243
from time import sleep
44+
from threading import Thread
4345

4446

4547
class MetadataHandler(metaclass=Singleton):
@@ -108,11 +110,14 @@ def __initializeTables(self):
108110
return
109111
raise ValueError(MSG['NoData'])
110112

111-
def update(self):
113+
def update(self, refresh_all=False):
112114
'''Read the topology from ZIMon and update
113115
the tables for metrics, keys, key elements (tag keys)
114116
and key values (tag values)'''
115117

118+
if refresh_all:
119+
self.__sensorsConf = SensorConfig.readSensorsConfigFromMMSDRFS(self.logger)
120+
116121
tstart = timer()
117122
self.__metaData = Topo(self.qh.getTopology())
118123
tend = timer()
@@ -606,6 +611,22 @@ def resolveAPIKeyValue(storedKey):
606611
return storedKey
607612

608613

614+
def refresh_metadata(refresh_all=False):
615+
md = MetadataHandler()
616+
md.update(refresh_all)
617+
618+
619+
def watch_config():
620+
files_to_watch = []
621+
if os.path.isfile(SensorConfig.mmsdrfsFile):
622+
files_to_watch.append(SensorConfig.mmsdrfsFile)
623+
else:
624+
files_to_watch.append(SensorConfig.zimonFile)
625+
626+
watcher = ConfigWatcher(files_to_watch, refresh_metadata, refresh_all=True)
627+
watcher.watch()
628+
629+
609630
def main(argv):
610631
# parse input arguments
611632
args, msg = getSettings(argv)
@@ -695,6 +716,9 @@ def main(argv):
695716
try:
696717
cherrypy.engine.start()
697718
logger.info("server started")
719+
t = Thread(name='ConfigWatchThread', target=watch_config)
720+
t.start()
721+
# t.join()
698722
with open("/proc/{}/stat".format(os.getpid())) as f:
699723
data = f.read()
700724
foreground_pid_of_group = data.rsplit(" ", 45)[1]

tests/test_configWatcher.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import os
2+
import time
3+
from source.watcher import ConfigWatcher
4+
from source.bridgeLogger import configureLogging
5+
from source.__version__ import __version__ as version
6+
from nose2.tools.such import helper as assert_helper
7+
from nose2.tools.decorators import with_setup
8+
from threading import Thread
9+
10+
11+
def my_setup():
12+
global path, logger, mainSensorsConfig, wrongSensorsConfig, zimonFile, sensorsCount
13+
path = os.getcwd()
14+
logger = configureLogging(path, None)
15+
mainSensorsConfig = 'ZIMonSensors.cfg'
16+
wrongSensorsConfig = 'ZIMonSensors-protocols-wrong.cfg'
17+
sensorsCount = 0
18+
19+
20+
@with_setup(my_setup)
21+
def test_case01():
22+
dummyFile = os.path.join(path, wrongSensorsConfig)
23+
cw = ConfigWatcher([dummyFile])
24+
assert len(cw.paths) == 1
25+
assert len(cw.filenames) == 0
26+
t = Thread(name='ConfigWatchThread', target=cw.watch)
27+
t.start()
28+
time.sleep(3)
29+
cw.stop_watch()
30+
t.join()
31+
assert len(cw.paths) == 1
32+
assert len(cw.filenames) == 0
33+
34+
35+
@with_setup(my_setup)
36+
def test_case02():
37+
cw = ConfigWatcher([path])
38+
assert len(cw.paths) > 0
39+
assert len(cw.filenames) == 0
40+
t = Thread(name='ConfigWatchThread', target=cw.watch)
41+
t.start()
42+
time.sleep(3)
43+
cw.stop_watch()
44+
t.join()
45+
assert len(cw.paths) > 0
46+
assert len(cw.filenames) > 1

0 commit comments

Comments
 (0)