1
0
mirror of https://github.com/GNS3/gns3-server synced 2024-11-28 03:08:14 +00:00

Watch for dynamips rom & nvram change

This monitor for change the file from dynamips by computing a
hash of the watched file.

The way dynamips work prevent the update of the modification time.

We can improve that by using native system for watching file but:
* it's require dependencies specific for each OS
* dependencies use C extensions
* this is only a backup if your router is cleanly shutdown we export
stuff
This commit is contained in:
Julien Duponchelle 2016-06-13 15:52:31 +02:00
parent 2bde02d459
commit 67c04a7855
No known key found for this signature in database
GPG Key ID: CE8B29639E07F5E8
5 changed files with 97 additions and 39 deletions

View File

@ -239,23 +239,6 @@ class Dynamips(BaseManager):
if device.project.id == project.id: if device.project.id == project.id:
yield from device.hypervisor.set_working_dir(project.module_working_directory(self.module_name.lower())) yield from device.hypervisor.set_working_dir(project.module_working_directory(self.module_name.lower()))
@asyncio.coroutine
def project_committed(self, project):
"""
Called when a project has been committed.
:param project: Project instance
"""
# save the configs when the project is committed
for node in self._nodes.copy().values():
if node.project.id == project.id:
try:
yield from node.save_configs()
except DynamipsError as e:
log.warning(e)
continue
@property @property
def dynamips_path(self): def dynamips_path(self):
""" """

View File

@ -35,6 +35,8 @@ from ...base_node import BaseNode
from ..dynamips_error import DynamipsError from ..dynamips_error import DynamipsError
from ..nios.nio_udp import NIOUDP from ..nios.nio_udp import NIOUDP
from gns3server.utils.file_watcher import FileWatcher
from gns3server.utils.asyncio import wait_run_in_executor, monitor_process from gns3server.utils.asyncio import wait_run_in_executor, monitor_process
from gns3server.utils.images import md5sum from gns3server.utils.images import md5sum
@ -92,6 +94,7 @@ class Router(BaseNode):
self._system_id = "FTX0945W0MY" # processor board ID in IOS self._system_id = "FTX0945W0MY" # processor board ID in IOS
self._slots = [] self._slots = []
self._ghost_flag = ghost_flag self._ghost_flag = ghost_flag
self._memory_watcher = None
if not ghost_flag: if not ghost_flag:
if not dynamips_id: if not dynamips_id:
@ -160,6 +163,12 @@ class Router(BaseNode):
return router_info return router_info
def _memory_changed(self, path):
"""
Called when the NVRAM file has changed
"""
asyncio.async(self.save_configs())
@property @property
def dynamips_id(self): def dynamips_id(self):
""" """
@ -248,6 +257,8 @@ class Router(BaseNode):
yield from self._hypervisor.send('vm start "{name}"'.format(name=self._name)) yield from self._hypervisor.send('vm start "{name}"'.format(name=self._name))
self.status = "started" self.status = "started"
log.info('router "{name}" [{id}] has been started'.format(name=self._name, id=self._id)) log.info('router "{name}" [{id}] has been started'.format(name=self._name, id=self._id))
self._memory_watcher = FileWatcher(self._memory_files(), self._memory_changed, strategy='hash', delay=30)
monitor_process(self._hypervisor.process, self._termination_callback) monitor_process(self._hypervisor.process, self._termination_callback)
@asyncio.coroutine @asyncio.coroutine
@ -278,6 +289,9 @@ class Router(BaseNode):
log.warn("Could not stop {}: {}".format(self._name, e)) log.warn("Could not stop {}: {}".format(self._name, e))
self.status = "stopped" self.status = "stopped"
log.info('Router "{name}" [{id}] has been stopped'.format(name=self._name, id=self._id)) log.info('Router "{name}" [{id}] has been stopped'.format(name=self._name, id=self._id))
if self._memory_watcher:
self._memory_watcher.close()
self._memory_watcher = None
yield from self.save_configs() yield from self.save_configs()
@asyncio.coroutine @asyncio.coroutine
@ -1599,3 +1613,10 @@ class Router(BaseNode):
yield from self._hypervisor.send('vm clean_delete "{}"'.format(self._name)) yield from self._hypervisor.send('vm clean_delete "{}"'.format(self._name))
self._hypervisor.devices.remove(self) self._hypervisor.devices.remove(self)
log.info('Router "{name}" [{id}] has been deleted (including associated files)'.format(name=self._name, id=self._id)) log.info('Router "{name}" [{id}] has been deleted (including associated files)'.format(name=self._name, id=self._id))
def _memory_files(self):
project_dir = os.path.join(self.project.module_working_directory(self.manager.module_name.lower()))
return [
os.path.join(project_dir, "{}_i{}_rom".format(self.platform, self.dynamips_id)),
os.path.join(project_dir, "{}_i{}_nvram".format(self.platform, self.dynamips_id))
]

View File

@ -503,7 +503,7 @@ class IOUVM(BaseNode):
# check if there is enough RAM to run # check if there is enough RAM to run
self.check_available_ram(self.ram) self.check_available_ram(self.ram)
self._nvram_watcher = FileWatcher(self._nvram_file(), self._nvram_changed) self._nvram_watcher = FileWatcher(self._nvram_file(), self._nvram_changed, delay=10)
# created a environment variable pointing to the iourc file. # created a environment variable pointing to the iourc file.
env = os.environ.copy() env = os.environ.copy()

View File

@ -15,6 +15,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import zlib
import asyncio import asyncio
import os import os
@ -22,20 +23,43 @@ import os
class FileWatcher: class FileWatcher:
""" """
Watch for file change and call the callback when something happen Watch for file change and call the callback when something happen
:param paths: A path or a list of file to watch
:param delay: Delay between file check (seconds)
:param strategy: File change strategy (mtime: modification time, hash: hash compute)
""" """
def __init__(self, path, callback, delay=1): def __init__(self, paths, callback, delay=1, strategy='mtime'):
if not isinstance(path, str): self._paths = []
path = str(path) if not isinstance(paths, list):
self._path = path paths = [paths]
for path in paths:
if not isinstance(path, str):
path = str(path)
self._paths.append(path)
self._callback = callback self._callback = callback
self._delay = delay self._delay = delay
self._closed = False self._closed = False
self._strategy = strategy
try: if self._strategy == 'mtime':
self._mtime = os.stat(path).st_mtime_ns # Store modification time
except OSError: self._mtime = {}
self._mtime = None for path in self._paths:
try:
self._mtime[path] = os.stat(path).st_mtime_ns
except OSError:
self._mtime[path] = None
else:
# Store hash
self._hashed = {}
for path in self._paths:
try:
# Alder32 is a fast bu insecure hash algorithm
self._hashed[path] = zlib.adler32(open(path, 'rb').read())
except OSError:
self._hashed[path] = None
asyncio.get_event_loop().call_later(self._delay, self._check_config_file_change) asyncio.get_event_loop().call_later(self._delay, self._check_config_file_change)
def __del__(self): def __del__(self):
@ -48,15 +72,26 @@ class FileWatcher:
if self._closed: if self._closed:
return return
changed = False changed = False
try:
mtime = os.stat(self._path).st_mtime_ns for path in self._paths:
if mtime != self._mtime: if self._strategy == 'mtime':
changed = True try:
self._mtime = mtime mtime = os.stat(path).st_mtime_ns
except OSError: if mtime != self._mtime[path]:
self._mtime = None changed = True
if changed: self._mtime[path] = mtime
self._callback(self._path) except OSError:
self._mtime[path] = None
else:
try:
hashc = zlib.adler32(open(path, 'rb').read())
if hashc != self._hashed[path]:
changed = True
self._hashed[path] = hashc
except OSError:
self._hashed[path] = None
if changed:
self._callback(path)
asyncio.get_event_loop().call_later(self._delay, self._check_config_file_change) asyncio.get_event_loop().call_later(self._delay, self._check_config_file_change)
@property @property

View File

@ -15,6 +15,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest
import asyncio import asyncio
from unittest.mock import MagicMock from unittest.mock import MagicMock
@ -22,11 +23,12 @@ from unittest.mock import MagicMock
from gns3server.utils.file_watcher import FileWatcher from gns3server.utils.file_watcher import FileWatcher
def test_file_watcher(async_run, tmpdir): @pytest.mark.parametrize("strategy", ['mtime', 'hash'])
def test_file_watcher(async_run, tmpdir, strategy):
file = tmpdir / "test" file = tmpdir / "test"
file.write("a") file.write("a")
callback = MagicMock() callback = MagicMock()
fw = FileWatcher(file, callback, delay=0.5) fw = FileWatcher(file, callback, delay=0.5, strategy=strategy)
async_run(asyncio.sleep(1)) async_run(asyncio.sleep(1))
assert not callback.called assert not callback.called
file.write("b") file.write("b")
@ -34,12 +36,29 @@ def test_file_watcher(async_run, tmpdir):
callback.assert_called_with(str(file)) callback.assert_called_with(str(file))
def test_file_watcher_not_existing(async_run, tmpdir): @pytest.mark.parametrize("strategy", ['mtime', 'hash'])
def test_file_watcher_not_existing(async_run, tmpdir, strategy):
file = tmpdir / "test" file = tmpdir / "test"
callback = MagicMock() callback = MagicMock()
fw = FileWatcher(file, callback, delay=0.5) fw = FileWatcher(file, callback, delay=0.5, strategy=strategy)
async_run(asyncio.sleep(1)) async_run(asyncio.sleep(1))
assert not callback.called assert not callback.called
file.write("b") file.write("b")
async_run(asyncio.sleep(1.5)) async_run(asyncio.sleep(1.5))
callback.assert_called_with(str(file)) callback.assert_called_with(str(file))
@pytest.mark.parametrize("strategy", ['mtime', 'hash'])
def test_file_watcher_list(async_run, tmpdir, strategy):
file = tmpdir / "test"
file.write("a")
file2 = tmpdir / "test2"
callback = MagicMock()
fw = FileWatcher([file, file2], callback, delay=0.5, strategy=strategy)
async_run(asyncio.sleep(1))
assert not callback.called
file2.write("b")
async_run(asyncio.sleep(1.5))
callback.assert_called_with(str(file2))