Skip to content

Commit fbbb19e

Browse files
authored
Merge pull request #3228 from anarkiwi/gaugerestart
Gauge can detect and reload when FAUCET's config changes.
2 parents b7f8bdc + 3624017 commit fbbb19e

File tree

4 files changed

+112
-49
lines changed

4 files changed

+112
-49
lines changed

faucet/config_parser.py

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -243,34 +243,44 @@ def _config_parser_v2(config_file, logname, meta_dp_state):
243243
def watcher_parser(config_file, logname, prom_client):
244244
"""Return Watcher instances from config."""
245245
conf = config_parser_util.read_config(config_file, logname)
246-
return _watcher_parser_v2(conf, logname, prom_client)
246+
conf_hash = config_parser_util.config_file_hash(config_file)
247+
faucet_config_files, faucet_conf_hashes, result = _watcher_parser_v2(
248+
conf, logname, prom_client)
249+
return conf_hash, faucet_config_files, faucet_conf_hashes, result
247250

248251

249252
def _parse_dps_for_watchers(conf, logname, meta_dp_state=None):
250-
dps = {}
251-
if 'faucet_configs' in conf:
252-
for faucet_file in conf['faucet_configs']:
253-
_, dp_list, _ = dp_parser(faucet_file, logname)
254-
if dp_list:
255-
for dp in dp_list:
256-
dps[dp.name] = dp
257-
258-
if 'faucet' in conf:
259-
faucet_conf = conf['faucet']
260-
dps = {dp.name: dp for dp in dp_preparsed_parser(faucet_conf, meta_dp_state)}
253+
all_dps_list = []
254+
faucet_conf_hashes = {}
261255

256+
if not isinstance(conf, dict):
257+
raise InvalidConfigError('Gauge config not valid')
258+
259+
faucet_config_files = conf.get('faucet_configs', [])
260+
for faucet_config_file in faucet_config_files:
261+
conf_hashes, dp_list, _ = dp_parser(faucet_config_file, logname)
262+
if dp_list:
263+
faucet_conf_hashes[faucet_config_file] = conf_hashes
264+
all_dps_list.extend(dp_list)
265+
266+
faucet_config = conf.get('faucet', None)
267+
if faucet_config:
268+
all_dps_list.extend(dp_preparsed_parser(faucet_config, meta_dp_state))
269+
270+
dps = {dp.name: dp for dp in all_dps_list}
262271
if not dps:
263272
raise InvalidConfigError(
264273
'Gauge configured without any FAUCET configuration')
265-
return dps
274+
return faucet_config_files, faucet_conf_hashes, dps
266275

267276

268277
def _watcher_parser_v2(conf, logname, prom_client):
269278
logger = config_parser_util.get_logger(logname)
270279

271280
if conf is None:
272281
conf = {}
273-
dps = _parse_dps_for_watchers(conf, logname)
282+
faucet_config_files, faucet_conf_hashes, dps = _parse_dps_for_watchers(
283+
conf, logname)
274284
dbs = conf.pop('dbs')
275285

276286
result = []
@@ -300,7 +310,7 @@ def _watcher_parser_v2(conf, logname, prom_client):
300310
watcher.add_dp(dp)
301311
result.append(watcher)
302312

303-
return result
313+
return faucet_config_files, faucet_conf_hashes, result
304314

305315

306316
def get_config_for_api(valves):

faucet/gauge.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ def __init__(self, *args, **kwargs):
5050
super(Gauge, self).__init__(*args, **kwargs)
5151
self.watchers = {}
5252
self.config_watcher = ConfigWatcher()
53+
self.faucet_config_watchers = []
5354
self.prom_client = GaugePrometheusClient(reg=self._reg)
5455
self.thread_managers = (self.prom_client,)
5556

@@ -70,8 +71,10 @@ def _get_watchers(self, ryu_event):
7071
def _load_config(self):
7172
"""Load Gauge config."""
7273
try:
73-
new_confs = watcher_parser(self.config_file, self.logname, self.prom_client)
74+
conf_hash, faucet_config_files, faucet_conf_hashes, new_confs = watcher_parser(
75+
self.config_file, self.logname, self.prom_client)
7476
except InvalidConfigError as err:
77+
self.config_watcher.update(self.config_file)
7578
self.logger.error('invalid config: %s', err)
7679
return
7780

@@ -97,7 +100,14 @@ def _load_config(self):
97100
self._start_watchers(ryu_dp, watchers, timestamp)
98101

99102
self.watchers = new_watchers
100-
self.config_watcher.update(self.config_file)
103+
self.config_watcher.update(
104+
self.config_file, {self.config_file: conf_hash})
105+
self.faucet_config_watchers = []
106+
for faucet_config_file, faucet_conf_hash in faucet_conf_hashes.items():
107+
faucet_config_watcher = ConfigWatcher()
108+
faucet_config_watcher.update(faucet_config_file, faucet_conf_hash)
109+
self.faucet_config_watchers.append(faucet_config_watcher)
110+
self.logger.info('watching FAUCET config %s' % faucet_config_file)
101111
self.logger.info('config complete')
102112

103113
@kill_on_exception(exc_logname)
@@ -111,7 +121,10 @@ def _update_watcher(self, name, ryu_event):
111121
watcher.update(ryu_event.timestamp, ryu_dp.id, msg)
112122

113123
def _config_files_changed(self):
114-
return self.config_watcher.files_changed()
124+
for config_watcher in [self.config_watcher] + self.faucet_config_watchers:
125+
if config_watcher.files_changed():
126+
return True
127+
return False
115128

116129
@set_ev_cls(EventReconfigure, MAIN_DISPATCHER)
117130
def reload_config(self, ryu_event):

tests/unit/gauge/test_config_gauge.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ def test_all_dps(self):
9494
"""
9595
conf = self.get_config(GAUGE_CONF)
9696
gauge_file, _ = self.create_config_files(conf)
97-
watcher_confs = cp.watcher_parser(gauge_file, 'gauge_config_test', None)
97+
_, _, _, watcher_confs = cp.watcher_parser(gauge_file, 'gauge_config_test', None)
9898
self.assertEqual(len(watcher_confs), 2, 'failed to create config for each dp')
9999
for watcher_conf in watcher_confs:
100100
msg = 'all_dps config not applied to each dp'
@@ -162,8 +162,9 @@ def test_no_faucet_config_file(self):
162162
type: 'prometheus'
163163
"""
164164
gauge_file, _ = self.create_config_files(GAUGE_CONF, '')
165-
watcher_conf = cp.watcher_parser(
166-
gauge_file, 'gauge_config_test', None)[0]
165+
_, _, _, watcher_confs = cp.watcher_parser(
166+
gauge_file, 'gauge_config_test', None)
167+
watcher_conf = watcher_confs[0]
167168
msg = 'failed to create watcher correctly when dps configured in gauge.yaml'
168169
self.assertEqual(watcher_conf.dps[0], 'dp1', msg)
169170
self.assertEqual(watcher_conf.type, 'port_stats', msg)

tests/unit/gauge/test_gauge.py

Lines changed: 67 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626

2727
from prometheus_client import CollectorRegistry
2828

29-
from faucet import gauge, gauge_prom, gauge_influx, gauge_pollers, watcher
29+
from faucet import gauge, gauge_prom, gauge_influx, gauge_pollers, watcher, valve_util
3030

3131

3232
class QuietHandler(BaseHTTPRequestHandler):
@@ -853,8 +853,15 @@ def test_flow_stats(self):
853853
class RyuAppSmokeTest(unittest.TestCase): # pytype: disable=module-attr
854854

855855
def setUp(self):
856-
os.environ['GAUGE_LOG'] = '/dev/null'
857-
os.environ['GAUGE_EXCEPTION_LOG'] = '/dev/null'
856+
self.tmpdir = tempfile.mkdtemp()
857+
os.environ['GAUGE_LOG'] = os.path.join(self.tmpdir, 'gauge.log')
858+
os.environ['GAUGE_EXCEPTION_LOG'] = os.path.join(self.tmpdir, 'gauge-exception.log')
859+
self.ryu_app = None
860+
861+
def tearDown(self):
862+
valve_util.close_logger(self.ryu_app.logger)
863+
valve_util.close_logger(self.ryu_app.exc_logger)
864+
shutil.rmtree(self.tmpdir)
858865

859866
@staticmethod
860867
def _fake_dp():
@@ -868,29 +875,28 @@ def _fake_event(self):
868875
event.dp = msg.datapath
869876
return event
870877

878+
def _write_config(self, config_file_name, config):
879+
with open(config_file_name, 'w') as config_file:
880+
config_file.write(config)
881+
871882
def test_gauge(self):
872883
"""Test Gauge can be initialized."""
873884
os.environ['GAUGE_CONFIG'] = '/dev/null'
874-
ryu_app = gauge.Gauge(
885+
self.ryu_app = gauge.Gauge(
875886
dpset={},
876887
reg=CollectorRegistry())
877-
ryu_app.reload_config(None)
878-
self.assertFalse(ryu_app._config_files_changed())
879-
ryu_app._update_watcher(None, self._fake_event())
880-
ryu_app._start_watchers(self._fake_dp(), {}, time.time())
888+
self.ryu_app.reload_config(None)
889+
self.assertFalse(self.ryu_app._config_files_changed())
890+
self.ryu_app._update_watcher(None, self._fake_event())
891+
self.ryu_app._start_watchers(self._fake_dp(), {}, time.time())
881892
for event_handler in (
882-
ryu_app._datapath_connect,
883-
ryu_app._datapath_disconnect):
893+
self.ryu_app._datapath_connect,
894+
self.ryu_app._datapath_disconnect):
884895
event_handler(self._fake_event())
885896

886897
def test_gauge_config(self):
887898
"""Test Gauge minimal config."""
888-
tmpdir = tempfile.mkdtemp()
889-
os.environ['FAUCET_CONFIG'] = os.path.join(tmpdir, 'faucet.yaml')
890-
os.environ['GAUGE_CONFIG'] = os.path.join(tmpdir, 'gauge.yaml')
891-
with open(os.environ['FAUCET_CONFIG'], 'w') as faucet_config:
892-
faucet_config.write(
893-
"""
899+
faucet_conf1 = """
894900
vlans:
895901
100:
896902
description: "100"
@@ -901,11 +907,23 @@ def test_gauge_config(self):
901907
1:
902908
description: "1"
903909
native_vlan: 100
904-
""")
905-
os.environ['GAUGE_CONFIG'] = os.path.join(tmpdir, 'gauge.yaml')
906-
with open(os.environ['GAUGE_CONFIG'], 'w') as gauge_config:
907-
gauge_config.write(
908-
"""
910+
"""
911+
faucet_conf2 = """
912+
vlans:
913+
100:
914+
description: "200"
915+
dps:
916+
dp1:
917+
dp_id: 0x1
918+
interfaces:
919+
2:
920+
description: "2"
921+
native_vlan: 100
922+
"""
923+
os.environ['FAUCET_CONFIG'] = os.path.join(self.tmpdir, 'faucet.yaml')
924+
self._write_config(os.environ['FAUCET_CONFIG'], faucet_conf1)
925+
os.environ['GAUGE_CONFIG'] = os.path.join(self.tmpdir, 'gauge.yaml')
926+
gauge_conf = """
909927
faucet_configs:
910928
- '%s'
911929
watchers:
@@ -928,15 +946,36 @@ def test_gauge_config(self):
928946
type: 'prometheus'
929947
prometheus_addr: '0.0.0.0'
930948
prometheus_port: 0
931-
""" % os.environ['FAUCET_CONFIG'])
932-
ryu_app = gauge.Gauge(
949+
""" % os.environ['FAUCET_CONFIG']
950+
self._write_config(os.environ['GAUGE_CONFIG'], gauge_conf)
951+
self.ryu_app = gauge.Gauge(
933952
dpset={},
934953
reg=CollectorRegistry())
935-
ryu_app.reload_config(None)
936-
self.assertTrue(ryu_app.watchers)
937-
ryu_app.reload_config(None)
938-
self.assertTrue(ryu_app.watchers)
939-
shutil.rmtree(tmpdir)
954+
self.ryu_app.reload_config(None)
955+
self.assertFalse(self.ryu_app._config_files_changed())
956+
self.assertTrue(self.ryu_app.watchers)
957+
self.ryu_app.reload_config(None)
958+
self.assertTrue(self.ryu_app.watchers)
959+
self.assertFalse(self.ryu_app._config_files_changed())
960+
# Load a new FAUCET config.
961+
self._write_config(os.environ['FAUCET_CONFIG'], faucet_conf2)
962+
self.assertTrue(self.ryu_app._config_files_changed())
963+
self.ryu_app.reload_config(None)
964+
self.assertTrue(self.ryu_app.watchers)
965+
self.assertFalse(self.ryu_app._config_files_changed())
966+
# Load an invalid Gauge config
967+
self._write_config(os.environ['GAUGE_CONFIG'], 'invalid')
968+
self.assertTrue(self.ryu_app._config_files_changed())
969+
self.ryu_app.reload_config(None)
970+
self.assertTrue(self.ryu_app.watchers)
971+
# Keep trying to load a valid version.
972+
self.assertTrue(self.ryu_app._config_files_changed())
973+
# Load good Gauge config back
974+
self._write_config(os.environ['GAUGE_CONFIG'], gauge_conf)
975+
self.assertTrue(self.ryu_app._config_files_changed())
976+
self.ryu_app.reload_config(None)
977+
self.assertTrue(self.ryu_app.watchers)
978+
self.assertFalse(self.ryu_app._config_files_changed())
940979

941980

942981
if __name__ == "__main__":

0 commit comments

Comments
 (0)