mirror of
https://github.com/GNS3/gns3-server
synced 2024-11-30 20:28:08 +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:
parent
2bde02d459
commit
67c04a7855
@ -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):
|
||||||
"""
|
"""
|
||||||
|
@ -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))
|
||||||
|
]
|
||||||
|
@ -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()
|
||||||
|
@ -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'):
|
||||||
|
self._paths = []
|
||||||
|
if not isinstance(paths, list):
|
||||||
|
paths = [paths]
|
||||||
|
for path in paths:
|
||||||
if not isinstance(path, str):
|
if not isinstance(path, str):
|
||||||
path = str(path)
|
path = str(path)
|
||||||
self._path = 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
|
||||||
|
|
||||||
|
if self._strategy == 'mtime':
|
||||||
|
# Store modification time
|
||||||
|
self._mtime = {}
|
||||||
|
for path in self._paths:
|
||||||
try:
|
try:
|
||||||
self._mtime = os.stat(path).st_mtime_ns
|
self._mtime[path] = os.stat(path).st_mtime_ns
|
||||||
except OSError:
|
except OSError:
|
||||||
self._mtime = None
|
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
|
||||||
|
|
||||||
|
for path in self._paths:
|
||||||
|
if self._strategy == 'mtime':
|
||||||
try:
|
try:
|
||||||
mtime = os.stat(self._path).st_mtime_ns
|
mtime = os.stat(path).st_mtime_ns
|
||||||
if mtime != self._mtime:
|
if mtime != self._mtime[path]:
|
||||||
changed = True
|
changed = True
|
||||||
self._mtime = mtime
|
self._mtime[path] = mtime
|
||||||
except OSError:
|
except OSError:
|
||||||
self._mtime = None
|
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:
|
if changed:
|
||||||
self._callback(self._path)
|
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
|
||||||
|
@ -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))
|
||||||
|
Loading…
Reference in New Issue
Block a user