Skip to content

Commit

Permalink
Merge pull request #856 from GNS3/dynamips_directory_layout
Browse files Browse the repository at this point in the history
Change directory layout for dynamips.
  • Loading branch information
grossmj committed Jan 13, 2017
2 parents e87f5fd + df694ca commit ec0181a
Show file tree
Hide file tree
Showing 22 changed files with 871 additions and 63 deletions.
3 changes: 2 additions & 1 deletion docs/file_format.rst
Expand Up @@ -23,7 +23,8 @@ A minimal version:
The revision is the version of file format:

* 6: GNS3 2.0
* 7: GNS3 2.0
* 6: GNS3 2.0 < beta 3
* 5: GNS3 2.0 < alpha 4
* 4: GNS3 1.5
* 3: GNS3 1.4
Expand Down
20 changes: 12 additions & 8 deletions gns3server/compute/dynamips/__init__.py
Expand Up @@ -219,10 +219,12 @@ def project_closed(self, project):
project_dir = project.module_working_path(self.module_name.lower())

files = glob.glob(os.path.join(glob.escape(project_dir), "*.ghost"))
files += glob.glob(os.path.join(glob.escape(project_dir), "*_lock"))
files += glob.glob(os.path.join(glob.escape(project_dir), "*", "*_lock"))
files += glob.glob(os.path.join(glob.escape(project_dir), "*_log.txt"))
files += glob.glob(os.path.join(glob.escape(project_dir), "*_stdout.txt"))
files += glob.glob(os.path.join(glob.escape(project_dir), "ilt_*"))
files += glob.glob(os.path.join(glob.escape(project_dir), "c[0-9][0-9][0-9][0-9]_i[0-9]*_rommon_vars"))
files += glob.glob(os.path.join(glob.escape(project_dir), "c[0-9][0-9][0-9][0-9]_i[0-9]*_log.txt"))
files += glob.glob(os.path.join(glob.escape(project_dir), "*", "c[0-9][0-9][0-9][0-9]_i[0-9]*_rommon_vars"))
files += glob.glob(os.path.join(glob.escape(project_dir), "*", "c[0-9][0-9][0-9][0-9]_i[0-9]*_log.txt"))
for file in files:
try:
log.debug("Deleting file {}".format(file))
Expand Down Expand Up @@ -409,7 +411,9 @@ def _set_ghost_ios(self, vm):
return

ghost_file = vm.formatted_ghost_file()
ghost_file_path = os.path.join(vm.hypervisor.working_dir, ghost_file)

module_workdir = vm.project.module_working_directory(self.module_name.lower())
ghost_file_path = os.path.join(module_workdir, ghost_file)
if ghost_file_path not in self._ghost_files:
# create a new ghost IOS instance
ghost_id = str(uuid4())
Expand All @@ -418,7 +422,7 @@ def _set_ghost_ios(self, vm):
yield from ghost.create()
yield from ghost.set_image(vm.image)
yield from ghost.set_ghost_status(1)
yield from ghost.set_ghost_file(ghost_file)
yield from ghost.set_ghost_file(ghost_file_path)
yield from ghost.set_ram(vm.ram)
try:
yield from ghost.start()
Expand All @@ -434,7 +438,7 @@ def _set_ghost_ios(self, vm):
if vm.ghost_file != ghost_file and os.path.isfile(ghost_file_path):
# set the ghost file to the router
yield from vm.set_ghost_status(2)
yield from vm.set_ghost_file(ghost_file)
yield from vm.set_ghost_file(ghost_file_path)

@asyncio.coroutine
def update_vm_settings(self, vm, settings):
Expand Down Expand Up @@ -508,8 +512,8 @@ def set_vm_configs(self, vm, settings):
"""

module_workdir = vm.project.module_working_directory(self.module_name.lower())
default_startup_config_path = os.path.join(module_workdir, "configs", "i{}_startup-config.cfg".format(vm.dynamips_id))
default_private_config_path = os.path.join(module_workdir, "configs", "i{}_private-config.cfg".format(vm.dynamips_id))
default_startup_config_path = os.path.join(module_workdir, vm.id, "configs", "i{}_startup-config.cfg".format(vm.dynamips_id))
default_private_config_path = os.path.join(module_workdir, vm.id, "configs", "i{}_private-config.cfg".format(vm.dynamips_id))

startup_config_path = settings.get("startup_config")
startup_config_content = settings.get("startup_config_content")
Expand Down
111 changes: 60 additions & 51 deletions gns3server/compute/dynamips/nodes/router.py
Expand Up @@ -25,10 +25,12 @@
import sys
import os
import glob
import shlex
import base64
import shutil
import binascii

import logging

log = logging.getLogger(__name__)

from ...base_node import BaseNode
Expand Down Expand Up @@ -67,6 +69,11 @@ def __init__(self, name, node_id, project, manager, dynamips_id=None, console=No

super().__init__(name, node_id, project, manager, console=console, aux=aux, allocate_aux=aux)

self._working_directory = os.path.join(self.project.module_working_directory(self.manager.module_name.lower()), self.id)
os.makedirs(os.path.join(self._working_directory, "configs"), exist_ok=True)
if dynamips_id:
self._convert_before_2_0_0_b3(dynamips_id)

self._hypervisor = hypervisor
self._dynamips_id = dynamips_id
self._platform = platform
Expand Down Expand Up @@ -113,11 +120,33 @@ def __init__(self, name, node_id, project, manager, dynamips_id=None, console=No
self._dynamips_id = 0
self._name = "Ghost"

def _convert_before_2_0_0_b3(self, dynamips_id):
"""
Before 2.0.0 beta3 the node didn't have a folder by node
when we start we move the file, we can't do it in the topology
conversion due to case of remote servers
"""
dynamips_dir = self.project.module_working_directory(self.manager.module_name.lower())
for path in glob.glob(os.path.join(glob.escape(dynamips_dir), "configs", "i{}_*".format(dynamips_id))):
dst = os.path.join(self._working_directory, "configs", os.path.basename(path))
if not os.path.exists(dst):
try:
shutil.move(path, dst)
except OSError as e:
raise DynamipsError("Can't move {}: {}".format(path, str(e)))
for path in glob.glob(os.path.join(glob.escape(dynamips_dir), "*_i{}_*".format(dynamips_id))):
dst = os.path.join(self._working_directory, os.path.basename(path))
if not os.path.exists(dst):
try:
shutil.move(path, dst)
except OSError as e:
raise DynamipsError("Can't move {}: {}".format(path, str(e)))

def __json__(self):

router_info = {"name": self.name,
"node_id": self.id,
"node_directory": os.path.join(self.project.module_working_directory(self.manager.module_name.lower())),
"node_directory": os.path.join(self._working_directory),
"project_id": self.project.id,
"dynamips_id": self._dynamips_id,
"platform": self._platform,
Expand Down Expand Up @@ -187,8 +216,10 @@ def dynamips_id(self):
def create(self):

if not self._hypervisor:
module_workdir = self.project.module_working_directory(self.manager.module_name.lower())
self._hypervisor = yield from self.manager.start_new_hypervisor(working_dir=module_workdir)
# We start the hypervisor is the dynamips folder and next we change to node dir
# this allow the creation of common files in the dynamips folder
self._hypervisor = yield from self.manager.start_new_hypervisor(working_dir=self.project.module_working_directory(self.manager.module_name.lower()))
yield from self._hypervisor.set_working_dir(self._working_directory)

yield from self._hypervisor.send('vm create "{name}" {id} {platform}'.format(name=self._name,
id=self._dynamips_id,
Expand Down Expand Up @@ -368,14 +399,13 @@ def close(self):

if self._auto_delete_disks:
# delete nvram and disk files
project_dir = os.path.join(self.project.module_working_directory(self.manager.module_name.lower()))
files = glob.glob(os.path.join(glob.escape(project_dir), "{}_i{}_disk[0-1]".format(self.platform, self.dynamips_id)))
files += glob.glob(os.path.join(glob.escape(project_dir), "{}_i{}_slot[0-1]".format(self.platform, self.dynamips_id)))
files += glob.glob(os.path.join(glob.escape(project_dir), "{}_i{}_nvram".format(self.platform, self.dynamips_id)))
files += glob.glob(os.path.join(glob.escape(project_dir), "{}_i{}_flash[0-1]".format(self.platform, self.dynamips_id)))
files += glob.glob(os.path.join(glob.escape(project_dir), "{}_i{}_rom".format(self.platform, self.dynamips_id)))
files += glob.glob(os.path.join(glob.escape(project_dir), "{}_i{}_bootflash".format(self.platform, self.dynamips_id)))
files += glob.glob(os.path.join(glob.escape(project_dir), "{}_i{}_ssa".format(self.platform, self.dynamips_id)))
files = glob.glob(os.path.join(glob.escape(self._working_directory), "{}_i{}_disk[0-1]".format(self.platform, self.dynamips_id)))
files += glob.glob(os.path.join(glob.escape(self._working_directory), "{}_i{}_slot[0-1]".format(self.platform, self.dynamips_id)))
files += glob.glob(os.path.join(glob.escape(self._working_directory), "{}_i{}_nvram".format(self.platform, self.dynamips_id)))
files += glob.glob(os.path.join(glob.escape(self._working_directory), "{}_i{}_flash[0-1]".format(self.platform, self.dynamips_id)))
files += glob.glob(os.path.join(glob.escape(self._working_directory), "{}_i{}_rom".format(self.platform, self.dynamips_id)))
files += glob.glob(os.path.join(glob.escape(self._working_directory), "{}_i{}_bootflash".format(self.platform, self.dynamips_id)))
files += glob.glob(os.path.join(glob.escape(self._working_directory), "{}_i{}_ssa".format(self.platform, self.dynamips_id)))
for file in files:
try:
log.debug("Deleting file {}".format(file))
Expand Down Expand Up @@ -763,7 +793,7 @@ def set_ghost_file(self, ghost_file):
"""

yield from self._hypervisor.send('vm set_ghost_file "{name}" {ghost_file}'.format(name=self._name,
ghost_file=ghost_file))
ghost_file=shlex.quote(ghost_file)))

log.info('Router "{name}" [{id}]: ghost file set to {ghost_file}'.format(name=self._name,
id=self._id,
Expand Down Expand Up @@ -1456,10 +1486,9 @@ def set_name(self, new_name):
:param new_name: new name string
"""

module_workdir = self.project.module_working_directory(self.manager.module_name.lower())
if self._startup_config:
# change the hostname in the startup-config
startup_config_path = os.path.join(module_workdir, "configs", "i{}_startup-config.cfg".format(self._dynamips_id))
startup_config_path = os.path.join(self._working_directory, "configs", "i{}_startup-config.cfg".format(self._dynamips_id))
if os.path.isfile(startup_config_path):
try:
with open(startup_config_path, "r+", encoding="utf-8", errors="replace") as f:
Expand All @@ -1473,7 +1502,7 @@ def set_name(self, new_name):

if self._private_config:
# change the hostname in the private-config
private_config_path = os.path.join(module_workdir, "configs", "i{}_private-config.cfg".format(self._dynamips_id))
private_config_path = os.path.join(self._working_directory, "configs", "i{}_private-config.cfg".format(self._dynamips_id))
if os.path.isfile(private_config_path):
try:
with open(private_config_path, "r+", encoding="utf-8", errors="replace") as f:
Expand Down Expand Up @@ -1507,9 +1536,8 @@ def set_configs(self, startup_config, private_config=''):
self._startup_config = startup_config
self._private_config = private_config

module_workdir = self.project.module_working_directory(self.manager.module_name.lower())
if private_config:
private_config_path = os.path.join(module_workdir, private_config)
private_config_path = os.path.join(self._working_directory, private_config)
try:
if not os.path.getsize(private_config_path):
# an empty private-config can prevent a router to boot.
Expand All @@ -1522,7 +1550,7 @@ def set_configs(self, startup_config, private_config=''):
raise DynamipsError("Cannot access the private-config {}: {}".format(private_config_path, e))

try:
startup_config_path = os.path.join(module_workdir, startup_config)
startup_config_path = os.path.join(self._working_directory, startup_config)
with open(startup_config_path) as f:
self._startup_config_content = f.read()
except OSError as e:
Expand Down Expand Up @@ -1568,9 +1596,8 @@ def save_configs(self):

if self.startup_config or self.private_config:

module_workdir = self.project.module_working_directory(self.manager.module_name.lower())
try:
config_path = os.path.join(module_workdir, "configs")
config_path = os.path.join(self._working_directory, "configs")
os.makedirs(config_path, exist_ok=True)
except OSError as e:
raise DynamipsError("Could could not create configuration directory {}: {}".format(config_path, e))
Expand All @@ -1582,7 +1609,7 @@ def save_configs(self):
try:
config = base64.b64decode(startup_config_base64).decode("utf-8", errors="replace")
config = "!\n" + config.replace("\r", "")
config_path = os.path.join(module_workdir, self.startup_config)
config_path = os.path.join(self._working_directory, self.startup_config)
with open(config_path, "wb") as f:
log.info("saving startup-config to {}".format(self.startup_config))
self._startup_config_content = config
Expand All @@ -1595,7 +1622,7 @@ def save_configs(self):
self._private_config = os.path.join("configs", "i{}_private-config.cfg".format(self._dynamips_id))
try:
config = base64.b64decode(private_config_base64).decode("utf-8", errors="replace")
config_path = os.path.join(module_workdir, self.private_config)
config_path = os.path.join(self._working_directory, self.private_config)
with open(config_path, "wb") as f:
log.info("saving private-config to {}".format(self.private_config))
self._private_config_content = config
Expand All @@ -1607,31 +1634,10 @@ def delete(self):
"""
Delete this VM (including all its files).
"""

# delete the VM files
project_dir = os.path.join(self.project.module_working_directory(self.manager.module_name.lower()))
files = glob.glob(os.path.join(project_dir, "{}_i{}*".format(self._platform, self._dynamips_id)))

module_workdir = self.project.module_working_directory(self.manager.module_name.lower())
# delete the startup-config
if self._startup_config:
startup_config_path = os.path.join(module_workdir, "configs", "i{}_startup-config.cfg".format(self._dynamips_id))
if os.path.isfile(startup_config_path):
files.append(startup_config_path)

# delete the private-config
if self._private_config:
private_config_path = os.path.join(module_workdir, "configs", "i{}_private-config.cfg".format(self._dynamips_id))
if os.path.isfile(private_config_path):
files.append(private_config_path)

for file in files:
try:
log.debug("Deleting file {}".format(file))
yield from wait_run_in_executor(os.remove, file)
except OSError as e:
log.warn("Could not delete file {}: {}".format(file, e))
continue
try:
yield from wait_run_in_executor(shutil.rmtree, self._working_directory)
except OSError as e:
log.warn("Could not delete file {}".format(e))

self.manager.release_dynamips_id(self._project.id, self._dynamips_id)

Expand All @@ -1643,11 +1649,14 @@ def clean_delete(self):

yield from self._hypervisor.send('vm clean_delete "{}"'.format(self._name))
self._hypervisor.devices.remove(self)
try:
yield from wait_run_in_executor(shutil.rmtree, self._working_directory)
except OSError as e:
log.warn("Could not delete file {}".format(e))
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))
os.path.join(self._working_directory, "{}_i{}_rom".format(self.platform, self.dynamips_id)),
os.path.join(self._working_directory, "{}_i{}_nvram".format(self.platform, self.dynamips_id))
]
6 changes: 5 additions & 1 deletion gns3server/compute/iou/iou_vm.py
Expand Up @@ -97,7 +97,9 @@ def _nvram_changed(self, path):
"""
Called when the NVRAM file has changed
"""
log.debug("NVRAM changed: {}".format(path))
self.save_configs()
self.updated()

@asyncio.coroutine
def close(self):
Expand Down Expand Up @@ -206,6 +208,8 @@ def __json__(self):
"nvram": self._nvram,
"l1_keepalives": self._l1_keepalives,
"startup_config": self.relative_startup_config_file,
"startup_config_content": self.startup_config_content,
"private_config_content": self.private_config_content,
"private_config": self.relative_private_config_file,
"use_default_iou_values": self._use_default_iou_values,
"command_line": self.command_line}
Expand Down Expand Up @@ -485,7 +489,7 @@ def start(self):
# check if there is enough RAM to run
self.check_available_ram(self.ram)

self._nvram_watcher = FileWatcher(self._nvram_file(), self._nvram_changed, delay=10)
self._nvram_watcher = FileWatcher(self._nvram_file(), self._nvram_changed, delay=2)

# created a environment variable pointing to the iourc file.
env = os.environ.copy()
Expand Down

0 comments on commit ec0181a

Please sign in to comment.