mirror of
https://github.com/GNS3/gns3-server
synced 2024-11-28 19:28:07 +00:00
1634 lines
66 KiB
Python
1634 lines
66 KiB
Python
# -*- coding: utf-8 -*-
|
|
#
|
|
# Copyright (C) 2013 GNS3 Technologies Inc.
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
"""
|
|
Interface for Dynamips virtual Machine module ("vm")
|
|
http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L77
|
|
"""
|
|
|
|
from ..dynamips_error import DynamipsError
|
|
from ...attic import find_unused_port
|
|
|
|
import time
|
|
import sys
|
|
import os
|
|
|
|
import logging
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class Router(object):
|
|
"""
|
|
Dynamips router implementation.
|
|
|
|
:param hypervisor: Dynamips hypervisor instance
|
|
:param name: name for this router
|
|
:param router_id: router instance ID
|
|
:param platform: c7200, c3745, c3725, c3600, c2691, c2600 or c1700
|
|
:param ghost_flag: used when creating a ghost IOS.
|
|
"""
|
|
|
|
_instances = []
|
|
_allocated_console_ports = []
|
|
_allocated_aux_ports = []
|
|
_status = {0: "inactive",
|
|
1: "shutting down",
|
|
2: "running",
|
|
3: "suspended"}
|
|
|
|
def __init__(self, hypervisor, name, router_id=None, platform="c7200", ghost_flag=False):
|
|
|
|
if not ghost_flag:
|
|
|
|
if not router_id:
|
|
# find an instance identifier if none is provided (0 < id <= 4096)
|
|
self._id = 0
|
|
for identifier in range(1, 4097):
|
|
if identifier not in self._instances:
|
|
self._id = identifier
|
|
self._instances.append(self._id)
|
|
break
|
|
|
|
if self._id == 0:
|
|
raise DynamipsError("Maximum number of instances reached")
|
|
else:
|
|
if router_id in self._instances:
|
|
raise DynamipsError("Router identifier {} is already used by another router".format(router_id))
|
|
self._id = router_id
|
|
self._instances.append(self._id)
|
|
|
|
else:
|
|
log.info("creating a new ghost IOS file")
|
|
self._id = 0
|
|
name = "Ghost"
|
|
|
|
self._hypervisor = hypervisor
|
|
self._name = '"' + name + '"' # put name into quotes to protect spaces
|
|
self._platform = platform
|
|
self._image = ""
|
|
self._startup_config = ""
|
|
self._private_config = ""
|
|
self._ram = 128 # Megabytes
|
|
self._nvram = 128 # Kilobytes
|
|
self._mmap = True
|
|
self._sparsemem = True
|
|
self._clock_divisor = 8
|
|
self._idlepc = ""
|
|
self._idlemax = 500
|
|
self._idlesleep = 30
|
|
self._ghost_file = ""
|
|
self._ghost_status = 0
|
|
if sys.platform.startswith("win"):
|
|
self._exec_area = 16 # 16 MB by default on Windows (Cygwin)
|
|
else:
|
|
self._exec_area = 64 # 64 MB on other systems
|
|
self._jit_sharing_group = None
|
|
self._disk0 = 0 # Megabytes
|
|
self._disk1 = 0 # Megabytes
|
|
self._confreg = "0x2102"
|
|
self._console = None
|
|
self._aux = None
|
|
self._mac_addr = None
|
|
self._system_id = "FTX0945W0MY" # processor board ID in IOS
|
|
self._slots = []
|
|
|
|
self._hypervisor.send("vm create {name} {id} {platform}".format(name=self._name,
|
|
id=self._id,
|
|
platform=self._platform))
|
|
|
|
if not ghost_flag:
|
|
log.info("router {platform} {name} [id={id}] has been created".format(name=self._name,
|
|
platform=platform,
|
|
id=self._id))
|
|
|
|
try:
|
|
# allocate a console port
|
|
self._console = find_unused_port(self._hypervisor.console_start_port_range,
|
|
self._hypervisor.console_end_port_range,
|
|
self._hypervisor.host,
|
|
ignore_ports=self._allocated_console_ports)
|
|
|
|
self._hypervisor.send("vm set_con_tcp_port {name} {console}".format(name=self._name,
|
|
console=self._console))
|
|
self._allocated_console_ports.append(self._console)
|
|
|
|
# allocate a auxiliary console port
|
|
self._aux = find_unused_port(self._hypervisor.aux_start_port_range,
|
|
self._hypervisor.aux_end_port_range,
|
|
self._hypervisor.host,
|
|
ignore_ports=self._allocated_aux_ports)
|
|
|
|
self._hypervisor.send("vm set_aux_tcp_port {name} {aux}".format(name=self._name,
|
|
aux=self._aux))
|
|
|
|
self._allocated_aux_ports.append(self._aux)
|
|
except Exception as e:
|
|
raise DynamipsError(e)
|
|
|
|
# get the default base MAC address
|
|
self._mac_addr = self._hypervisor.send("{platform} get_mac_addr {name}".format(platform=self._platform,
|
|
name=self._name))[0]
|
|
|
|
self._hypervisor.devices.append(self)
|
|
|
|
@classmethod
|
|
def reset(cls):
|
|
"""
|
|
Resets the instance count and the allocated instances list.
|
|
"""
|
|
|
|
cls._instances.clear()
|
|
cls._allocated_console_ports.clear()
|
|
cls._allocated_aux_ports.clear()
|
|
|
|
def defaults(self):
|
|
"""
|
|
Returns all the default base attribute values for routers.
|
|
|
|
:returns: default values (dictionary)
|
|
"""
|
|
|
|
router_defaults = {"platform": self._platform,
|
|
"image": self._image,
|
|
"startup_config": self._startup_config,
|
|
"private_config": self._private_config,
|
|
"ram": self._ram,
|
|
"nvram": self._nvram,
|
|
"mmap": self._mmap,
|
|
"sparsemem": self._sparsemem,
|
|
"clock_divisor": self._clock_divisor,
|
|
"idlepc": self._idlepc,
|
|
"idlemax": self._idlemax,
|
|
"idlesleep": self._idlesleep,
|
|
"exec_area": self._exec_area,
|
|
"jit_sharing_group": self._jit_sharing_group,
|
|
"disk0": self._disk0,
|
|
"disk1": self._disk1,
|
|
"confreg": self._confreg,
|
|
"console": self._console,
|
|
"aux": self._aux,
|
|
"mac_addr": self._mac_addr,
|
|
"system_id": self._system_id}
|
|
|
|
slot_id = 0
|
|
for slot in self._slots:
|
|
if slot:
|
|
slot = str(slot)
|
|
router_defaults["slot" + str(slot_id)] = slot
|
|
slot_id += 1
|
|
|
|
if self._slots[0] and self._slots[0].wics:
|
|
for wic_slot_id in range(0, len(self._slots[0].wics)):
|
|
router_defaults["wic" + str(wic_slot_id)] = None
|
|
|
|
return router_defaults
|
|
|
|
@property
|
|
def id(self):
|
|
"""
|
|
Returns the unique ID for this router.
|
|
|
|
:returns: id (integer)
|
|
"""
|
|
|
|
return self._id
|
|
|
|
@property
|
|
def name(self):
|
|
"""
|
|
Returns the name of this router.
|
|
|
|
:returns: name (string)
|
|
"""
|
|
|
|
return self._name[1:-1] # remove quotes
|
|
|
|
@name.setter
|
|
def name(self, new_name):
|
|
"""
|
|
Renames this router.
|
|
|
|
:param new_name: new name string
|
|
"""
|
|
|
|
if self._startup_config:
|
|
# change the hostname in the startup-config
|
|
startup_config_path = os.path.join(self.hypervisor.working_dir, "configs", "i{}_startup-config.cfg".format(self.id))
|
|
if os.path.isfile(startup_config_path):
|
|
try:
|
|
with open(startup_config_path, "r+", errors="replace") as f:
|
|
old_config = f.read()
|
|
new_config = old_config.replace(self.name, new_name)
|
|
f.seek(0)
|
|
f.write(new_config)
|
|
except OSError as e:
|
|
raise DynamipsError("Could not amend the configuration {}: {}".format(startup_config_path, e))
|
|
|
|
if self._private_config:
|
|
# change the hostname in the private-config
|
|
private_config_path = os.path.join(self.hypervisor.working_dir, "configs", "i{}_private-config.cfg".format(self.id))
|
|
if os.path.isfile(private_config_path):
|
|
try:
|
|
with open(private_config_path, "r+", errors="replace") as f:
|
|
old_config = f.read()
|
|
new_config = old_config.replace(self.name, new_name)
|
|
f.seek(0)
|
|
f.write(new_config)
|
|
except OSError as e:
|
|
raise DynamipsError("Could not amend the configuration {}: {}".format(private_config_path, e))
|
|
|
|
new_name = '"' + new_name + '"' # put the new name into quotes to protect spaces
|
|
self._hypervisor.send("vm rename {name} {new_name}".format(name=self._name,
|
|
new_name=new_name))
|
|
|
|
log.info("router {name} [id={id}]: renamed to {new_name}".format(name=self._name,
|
|
id=self._id,
|
|
new_name=new_name))
|
|
self._name = new_name
|
|
|
|
@property
|
|
def platform(self):
|
|
"""
|
|
Returns the platform of this router.
|
|
|
|
:returns: platform name (string):
|
|
c7200, c3745, c3725, c3600, c2691, c2600 or c1700
|
|
"""
|
|
|
|
return self._platform
|
|
|
|
@property
|
|
def hypervisor(self):
|
|
"""
|
|
Returns the current hypervisor.
|
|
|
|
:returns: hypervisor instance
|
|
"""
|
|
|
|
return self._hypervisor
|
|
|
|
def list(self):
|
|
"""
|
|
Returns all VM instances
|
|
|
|
:returns: list of all VM instances
|
|
"""
|
|
|
|
return self._hypervisor.send("vm list")
|
|
|
|
def list_con_ports(self):
|
|
"""
|
|
Returns all VM console TCP ports
|
|
|
|
:returns: list of port numbers
|
|
"""
|
|
|
|
return self._hypervisor.send("vm list_con_ports")
|
|
|
|
def delete(self):
|
|
"""
|
|
Deletes this router.
|
|
"""
|
|
|
|
self._hypervisor.send("vm delete {}".format(self._name))
|
|
self._hypervisor.devices.remove(self)
|
|
log.info("router {name} [id={id}] has been deleted".format(name=self._name, id=self._id))
|
|
if self._id in self._instances:
|
|
self._instances.remove(self._id)
|
|
if self.console:
|
|
self._allocated_console_ports.remove(self.console)
|
|
if self.aux:
|
|
self._allocated_aux_ports.remove(self.aux)
|
|
|
|
def clean_delete(self):
|
|
"""
|
|
Deletes this router & associated files (nvram, disks etc.)
|
|
"""
|
|
|
|
self._hypervisor.send("vm clean_delete {}".format(self._name))
|
|
self._hypervisor.devices.remove(self)
|
|
|
|
if self._startup_config:
|
|
# delete the startup-config
|
|
startup_config_path = os.path.join(self.hypervisor.working_dir, "configs", "{}.cfg".format(self.name))
|
|
if os.path.isfile(startup_config_path):
|
|
os.remove(startup_config_path)
|
|
|
|
if self._private_config:
|
|
# delete the private-config
|
|
private_config_path = os.path.join(self.hypervisor.working_dir, "configs", "{}-private.cfg".format(self.name))
|
|
if os.path.isfile(private_config_path):
|
|
os.remove(private_config_path)
|
|
|
|
log.info("router {name} [id={id}] has been deleted (including associated files)".format(name=self._name, id=self._id))
|
|
if self._id in self._instances:
|
|
self._instances.remove(self._id)
|
|
if self.console:
|
|
self._allocated_console_ports.remove(self.console)
|
|
if self.aux:
|
|
self._allocated_aux_ports.remove(self.aux)
|
|
|
|
def start(self):
|
|
"""
|
|
Starts this router.
|
|
At least the IOS image must be set before starting it.
|
|
"""
|
|
|
|
status = self.get_status()
|
|
if status == "suspended":
|
|
self.resume()
|
|
elif status == "inactive":
|
|
|
|
if not os.path.isfile(self._image):
|
|
raise DynamipsError("IOS image '{}' is not accessible".format(self._image))
|
|
|
|
try:
|
|
with open(self._image, "rb") as f:
|
|
# read the first 7 bytes of the file.
|
|
elf_header_start = f.read(7)
|
|
except OSError as e:
|
|
raise DynamipsError("Cannot read ELF header for IOS image {}: {}".format(self._image, e))
|
|
|
|
# IOS images must start with the ELF magic number, be 32-bit, big endian and have an ELF version of 1
|
|
if elf_header_start != b'\x7fELF\x01\x02\x01':
|
|
raise DynamipsError("'{}' is not a valid IOS image".format(self._image))
|
|
|
|
self._hypervisor.send("vm start {}".format(self._name))
|
|
log.info("router {name} [id={id}] has been started".format(name=self._name, id=self._id))
|
|
|
|
def stop(self):
|
|
"""
|
|
Stops this router.
|
|
The settings are kept.
|
|
"""
|
|
|
|
if self.get_status() != "inactive":
|
|
self._hypervisor.send("vm stop {}".format(self._name))
|
|
log.info("router {name} [id={id}] has been stopped".format(name=self._name, id=self._id))
|
|
|
|
def suspend(self):
|
|
"""
|
|
Suspends this router
|
|
"""
|
|
|
|
if self.get_status() == "running":
|
|
self._hypervisor.send("vm suspend {}".format(self._name))
|
|
log.info("router {name} [id={id}] has been suspended".format(name=self._name, id=self._id))
|
|
|
|
def resume(self):
|
|
"""
|
|
Resumes this suspended router
|
|
"""
|
|
|
|
self._hypervisor.send("vm resume {}".format(self._name))
|
|
log.info("router {name} [id={id}] has been resumed".format(name=self._name, id=self._id))
|
|
|
|
def get_status(self):
|
|
"""
|
|
Returns the status of this router
|
|
|
|
:returns: inactive, shutting down, running or suspended.
|
|
"""
|
|
|
|
status_id = int(self._hypervisor.send("vm get_status {}".format(self._name))[0])
|
|
return self._status[status_id]
|
|
|
|
def is_running(self):
|
|
"""
|
|
Checks if this router is running.
|
|
|
|
:returns: True if running, False otherwise
|
|
"""
|
|
|
|
if self.get_status() == "running":
|
|
return True
|
|
return False
|
|
|
|
@property
|
|
def jit_sharing_group(self):
|
|
"""
|
|
Returns the JIT sharing group for this router.
|
|
|
|
:returns: translation sharing group ID
|
|
"""
|
|
|
|
return self._jit_sharing_group
|
|
|
|
@jit_sharing_group.setter
|
|
def jit_sharing_group(self, group_id):
|
|
"""
|
|
Sets the translation sharing group (unstable).
|
|
|
|
:param group_id: translation sharing group ID
|
|
"""
|
|
|
|
if not self._image:
|
|
raise DynamipsError("Register an IOS image fist")
|
|
|
|
try:
|
|
self._hypervisor.send("vm set_tsg {name} {group_id}".format(name=self._name,
|
|
group_id=group_id))
|
|
except DynamipsError:
|
|
raise DynamipsError("JIT sharing is only supported in Dynamips >= 0.2.8-RC3 unstable")
|
|
|
|
log.info("router {name} [id={id}]: set in JIT sharing group {group_id}".format(name=self._name,
|
|
id=self._id,
|
|
group_id=group_id))
|
|
|
|
self._jit_sharing_group = group_id
|
|
self._hypervisor.add_jitsharing_group(os.path.basename(self._image), group_id)
|
|
|
|
def set_debug_level(self, level):
|
|
"""
|
|
Sets the debug level for this router (default is 0).
|
|
|
|
:param level: level number
|
|
"""
|
|
|
|
self._hypervisor.send("vm set_debug_level {name} {level}".format(name=self._name,
|
|
level=level))
|
|
|
|
@property
|
|
def image(self):
|
|
"""
|
|
Returns this IOS image for this router.
|
|
|
|
:returns: path to IOS image file
|
|
"""
|
|
|
|
return self._image
|
|
|
|
@image.setter
|
|
def image(self, image):
|
|
"""
|
|
Sets the IOS image for this router.
|
|
There is no default.
|
|
|
|
:param image: path to IOS image file
|
|
"""
|
|
|
|
# encase image in quotes to protect spaces in the path
|
|
self._hypervisor.send("vm set_ios {name} {image}".format(name=self._name,
|
|
image='"' + image + '"'))
|
|
|
|
log.info("router {name} [id={id}]: has a new IOS image set: {image}".format(name=self._name,
|
|
id=self._id,
|
|
image='"' + image + '"'))
|
|
|
|
self._image = image
|
|
|
|
@property
|
|
def startup_config(self):
|
|
"""
|
|
Returns the startup-config for this router.
|
|
|
|
:returns: path to startup-config file
|
|
"""
|
|
|
|
return self._startup_config
|
|
|
|
@startup_config.setter
|
|
def startup_config(self, startup_config):
|
|
"""
|
|
Sets the startup-config for this router.
|
|
|
|
:param startup_config: path to startup-config file
|
|
"""
|
|
|
|
self._startup_config = startup_config
|
|
|
|
@property
|
|
def private_config(self):
|
|
"""
|
|
Returns the private-config for this router.
|
|
|
|
:returns: path to private-config file
|
|
"""
|
|
|
|
return self._private_config
|
|
|
|
@private_config.setter
|
|
def private_config(self, private_config):
|
|
"""
|
|
Sets the private-config for this router.
|
|
|
|
:param private_config: path to private-config file
|
|
"""
|
|
|
|
self._private_config = private_config
|
|
|
|
def set_config(self, startup_config, private_config=''):
|
|
"""
|
|
Sets the config files that are pushed to startup-config and
|
|
private-config in NVRAM when the instance is started.
|
|
|
|
:param startup_config: path to statup-config file
|
|
:param private_config: path to private-config file
|
|
(keep existing data when if an empty string)
|
|
"""
|
|
|
|
if self._startup_config != startup_config or self._private_config != private_config:
|
|
|
|
self._hypervisor.send("vm set_config {name} {startup} {private}".format(name=self._name,
|
|
startup='"' + startup_config + '"',
|
|
private='"' + private_config + '"'))
|
|
|
|
log.info("router {name} [id={id}]: has a startup-config set: {startup}".format(name=self._name,
|
|
id=self._id,
|
|
startup='"' + startup_config + '"'))
|
|
|
|
self._startup_config = startup_config
|
|
|
|
if private_config:
|
|
log.info("router {name} [id={id}]: has a private-config set: {private}".format(name=self._name,
|
|
id=self._id,
|
|
private='"' + private_config + '"'))
|
|
|
|
self._private_config = private_config
|
|
|
|
def extract_config(self):
|
|
"""
|
|
Gets the contents of the config files
|
|
startup-config and private-config from NVRAM.
|
|
|
|
:returns: tuple (startup-config, private-config) base64 encoded
|
|
"""
|
|
|
|
try:
|
|
reply = self._hypervisor.send("vm extract_config {}".format(self._name))[0].rsplit(' ', 2)[-2:]
|
|
except IOError:
|
|
#for some reason Dynamips gets frozen when it does not find the magic number in the NVRAM file.
|
|
return None, None
|
|
startup_config = reply[0][1:-1] # get statup-config and remove single quotes
|
|
private_config = reply[1][1:-1] # get private-config and remove single quotes
|
|
return startup_config, private_config
|
|
|
|
def push_config(self, startup_config, private_config='(keep)'):
|
|
"""
|
|
Pushes configuration to the config files startup-config and private-config in NVRAM.
|
|
The data is a Base64 encoded string, or '(keep)' to keep existing data.
|
|
|
|
:param startup_config: statup-config string base64 encoded
|
|
:param private_config: private-config string base64 encoded
|
|
(keep existing data when if the value is ('keep'))
|
|
"""
|
|
|
|
self._hypervisor.send("vm push_config {name} {startup} {private}".format(name=self._name,
|
|
startup=startup_config,
|
|
private=private_config))
|
|
|
|
log.info("router {name} [id={id}]: new startup-config pushed".format(name=self._name,
|
|
id=self._id))
|
|
|
|
if private_config != '(keep)':
|
|
log.info("router {name} [id={id}]: new private-config pushed".format(name=self._name,
|
|
id=self._id))
|
|
|
|
@property
|
|
def ram(self):
|
|
"""
|
|
Returns the amount of RAM allocated to this router.
|
|
|
|
:returns: amount of RAM in Mbytes (integer)
|
|
"""
|
|
|
|
return self._ram
|
|
|
|
@ram.setter
|
|
def ram(self, ram):
|
|
"""
|
|
Sets amount of RAM allocated to this router
|
|
|
|
:param ram: amount of RAM in Mbytes (integer)
|
|
"""
|
|
|
|
if self._ram == ram:
|
|
return
|
|
|
|
self._hypervisor.send("vm set_ram {name} {ram}".format(name=self._name,
|
|
ram=ram))
|
|
|
|
log.info("router {name} [id={id}]: RAM updated from {old_ram}MB to {new_ram}MB".format(name=self._name,
|
|
id=self._id,
|
|
old_ram=self._ram,
|
|
new_ram=ram))
|
|
|
|
self._hypervisor.decrease_memory_load(self._ram)
|
|
self._ram = ram
|
|
self._hypervisor.increase_memory_load(ram)
|
|
|
|
@property
|
|
def nvram(self):
|
|
"""
|
|
Returns the mount of NVRAM allocated to this router.
|
|
|
|
:returns: amount of NVRAM in Kbytes (integer)
|
|
"""
|
|
|
|
return self._nvram
|
|
|
|
@nvram.setter
|
|
def nvram(self, nvram):
|
|
"""
|
|
Sets amount of NVRAM allocated to this router
|
|
|
|
:param nvram: amount of NVRAM in Kbytes (integer)
|
|
"""
|
|
|
|
if self._nvram == nvram:
|
|
return
|
|
|
|
self._hypervisor.send("vm set_nvram {name} {nvram}".format(name=self._name,
|
|
nvram=nvram))
|
|
|
|
log.info("router {name} [id={id}]: NVRAM updated from {old_nvram}KB to {new_nvram}KB".format(name=self._name,
|
|
id=self._id,
|
|
old_nvram=self._nvram,
|
|
new_nvram=nvram))
|
|
self._nvram = nvram
|
|
|
|
@property
|
|
def mmap(self):
|
|
"""
|
|
Returns True if a mapped file is used to simulate this router memory.
|
|
|
|
:returns: boolean either mmap is activated or not
|
|
"""
|
|
|
|
return self._mmap
|
|
|
|
@mmap.setter
|
|
def mmap(self, mmap):
|
|
"""
|
|
Enable/Disable use of a mapped file to simulate router memory.
|
|
By default, a mapped file is used. This is a bit slower, but requires less memory.
|
|
|
|
:param mmap: activate/deactivate mmap (boolean)
|
|
"""
|
|
|
|
if mmap:
|
|
flag = 1
|
|
else:
|
|
flag = 0
|
|
self._hypervisor.send("vm set_ram_mmap {name} {mmap}".format(name=self._name,
|
|
mmap=flag))
|
|
|
|
if mmap:
|
|
log.info("router {name} [id={id}]: mmap enabled".format(name=self._name,
|
|
id=self._id))
|
|
else:
|
|
log.info("router {name} [id={id}]: mmap disabled".format(name=self._name,
|
|
id=self._id))
|
|
self._mmap = mmap
|
|
|
|
@property
|
|
def sparsemem(self):
|
|
"""
|
|
Returns True if sparse memory is used on this router.
|
|
|
|
:returns: boolean either mmap is activated or not
|
|
"""
|
|
|
|
return self._sparsemem
|
|
|
|
@sparsemem.setter
|
|
def sparsemem(self, sparsemem):
|
|
"""
|
|
Enable/disable use of sparse memory
|
|
|
|
:param sparsemem: activate/deactivate sparsemem (boolean)
|
|
"""
|
|
|
|
if sparsemem:
|
|
flag = 1
|
|
else:
|
|
flag = 0
|
|
self._hypervisor.send("vm set_sparse_mem {name} {sparsemem}".format(name=self._name,
|
|
sparsemem=flag))
|
|
|
|
if sparsemem:
|
|
log.info("router {name} [id={id}]: sparse memory enabled".format(name=self._name,
|
|
id=self._id))
|
|
else:
|
|
log.info("router {name} [id={id}]: sparse memory disabled".format(name=self._name,
|
|
id=self._id))
|
|
self._sparsemem = sparsemem
|
|
|
|
@property
|
|
def clock_divisor(self):
|
|
"""
|
|
Returns the clock divisor value for this router.
|
|
|
|
:returns: clock divisor value (integer)
|
|
"""
|
|
|
|
return self._clock_divisor
|
|
|
|
@clock_divisor.setter
|
|
def clock_divisor(self, clock_divisor):
|
|
"""
|
|
Sets the clock divisor value. The higher is the value, the faster is the clock in the
|
|
virtual machine. The default is 4, but it is often required to adjust it.
|
|
|
|
:param clock_divisor: clock divisor value (integer)
|
|
"""
|
|
|
|
self._hypervisor.send("vm set_clock_divisor {name} {clock}".format(name=self._name,
|
|
clock=clock_divisor))
|
|
|
|
log.info("router {name} [id={id}]: clock divisor updated from {old_clock} to {new_clock}".format(name=self._name,
|
|
id=self._id,
|
|
old_clock=self._clock_divisor,
|
|
new_clock=clock_divisor))
|
|
self._clock_divisor = clock_divisor
|
|
|
|
@property
|
|
def idlepc(self):
|
|
"""
|
|
Returns the idle Pointer Counter (PC).
|
|
|
|
:returns: idlepc value (string)
|
|
"""
|
|
|
|
return self._idlepc
|
|
|
|
@idlepc.setter
|
|
def idlepc(self, idlepc):
|
|
"""
|
|
Sets the idle Pointer Counter (PC)
|
|
|
|
:param idlepc: idlepc value (string)
|
|
"""
|
|
|
|
if not idlepc:
|
|
idlepc = "0x0"
|
|
|
|
if not self.is_running():
|
|
# router is not running
|
|
self._hypervisor.send("vm set_idle_pc {name} {idlepc}".format(name=self._name,
|
|
idlepc=idlepc))
|
|
else:
|
|
self._hypervisor.send("vm set_idle_pc_online {name} 0 {idlepc}".format(name=self._name,
|
|
idlepc=idlepc))
|
|
|
|
log.info("router {name} [id={id}]: idle-PC set to {idlepc}".format(name=self._name,
|
|
id=self._id,
|
|
idlepc=idlepc))
|
|
|
|
self._idlepc = idlepc
|
|
|
|
def get_idle_pc_prop(self):
|
|
"""
|
|
Gets the idle PC proposals.
|
|
Takes 1000 measurements and records up to 10 idle PC proposals.
|
|
There is a 10ms wait between each measurement.
|
|
|
|
:returns: list of idle PC proposal
|
|
"""
|
|
|
|
if not self.is_running():
|
|
# router is not running
|
|
raise DynamipsError("router {name} is not running".format(name=self._name))
|
|
|
|
log.info("router {name} [id={id}] has started calculating idle-pc values".format(name=self._name, id=self._id))
|
|
begin = time.time()
|
|
idlepcs = self._hypervisor.send("vm get_idle_pc_prop {} 0".format(self._name))
|
|
log.info("router {name} [id={id}] has finished calculating idle-pc values after {time:.4f} seconds".format(name=self._name,
|
|
id=self._id,
|
|
time=time.time() - begin))
|
|
return idlepcs
|
|
|
|
def show_idle_pc_prop(self):
|
|
"""
|
|
Dumps the idle PC proposals (previously generated).
|
|
|
|
:returns: list of idle PC proposal
|
|
"""
|
|
|
|
if not self.is_running():
|
|
# router is not running
|
|
raise DynamipsError("router {name} is not running".format(name=self._name))
|
|
|
|
return self._hypervisor.send("vm show_idle_pc_prop {} 0".format(self._name))
|
|
|
|
@property
|
|
def idlemax(self):
|
|
"""
|
|
Returns CPU idle max value.
|
|
|
|
:returns: idle max (integer)
|
|
"""
|
|
|
|
return self._idlemax
|
|
|
|
@idlemax.setter
|
|
def idlemax(self, idlemax):
|
|
"""
|
|
Sets CPU idle max value
|
|
|
|
:param idlemax: idle max value (integer)
|
|
"""
|
|
|
|
if self.is_running(): # router is running
|
|
self._hypervisor.send("vm set_idle_max {name} 0 {idlemax}".format(name=self._name,
|
|
idlemax=idlemax))
|
|
|
|
log.info("router {name} [id={id}]: idlemax updated from {old_idlemax} to {new_idlemax}".format(name=self._name,
|
|
id=self._id,
|
|
old_idlemax=self._idlemax,
|
|
new_idlemax=idlemax))
|
|
|
|
self._idlemax = idlemax
|
|
|
|
@property
|
|
def idlesleep(self):
|
|
"""
|
|
Returns CPU idle sleep time value.
|
|
|
|
:returns: idle sleep (integer)
|
|
"""
|
|
|
|
return self._idlesleep
|
|
|
|
@idlesleep.setter
|
|
def idlesleep(self, idlesleep):
|
|
"""
|
|
Sets CPU idle sleep time value.
|
|
|
|
:param idlesleep: idle sleep value (integer)
|
|
"""
|
|
|
|
if self.is_running(): # router is running
|
|
self._hypervisor.send("vm set_idle_sleep_time {name} 0 {idlesleep}".format(name=self._name,
|
|
idlesleep=idlesleep))
|
|
|
|
log.info("router {name} [id={id}]: idlesleep updated from {old_idlesleep} to {new_idlesleep}".format(name=self._name,
|
|
id=self._id,
|
|
old_idlesleep=self._idlesleep,
|
|
new_idlesleep=idlesleep))
|
|
|
|
self._idlesleep = idlesleep
|
|
|
|
def show_timer_drift(self):
|
|
"""
|
|
Shows info about potential timer drift.
|
|
|
|
:returns: timer drift info.
|
|
"""
|
|
|
|
return self._hypervisor.send("vm show_timer_drift {} 0".format(self._name))
|
|
|
|
@property
|
|
def ghost_file(self):
|
|
"""
|
|
Returns ghost RAM file.
|
|
|
|
:returns: path to ghost file
|
|
"""
|
|
|
|
return self._ghost_file
|
|
|
|
@ghost_file.setter
|
|
def ghost_file(self, ghost_file):
|
|
"""
|
|
Sets ghost RAM file
|
|
|
|
:ghost_file: path to ghost file
|
|
"""
|
|
|
|
self._hypervisor.send("vm set_ghost_file {name} {ghost_file}".format(name=self._name,
|
|
ghost_file=ghost_file))
|
|
|
|
log.info("router {name} [id={id}]: ghost file set to {ghost_file}".format(name=self._name,
|
|
id=self._id,
|
|
ghost_file=ghost_file))
|
|
|
|
self._ghost_file = ghost_file
|
|
|
|
# if this is a ghost instance, track this as a hosted ghost instance by this hypervisor
|
|
if self.ghost_status == 1:
|
|
self._hypervisor.add_ghost(ghost_file, self)
|
|
|
|
def formatted_ghost_file(self):
|
|
"""
|
|
Returns a properly formatted ghost file name.
|
|
|
|
:returns: formatted ghost_file name (string)
|
|
"""
|
|
|
|
# replace specials characters in 'drive:\filename' in Linux and Dynamips in MS Windows or viceversa.
|
|
ghost_file = "{}-{}.ghost".format(os.path.basename(self._image), self._ram)
|
|
ghost_file = ghost_file.replace('\\', '-').replace('/', '-').replace(':', '-')
|
|
return ghost_file
|
|
|
|
@property
|
|
def ghost_status(self):
|
|
"""Returns ghost RAM status
|
|
|
|
:returns: ghost status (integer)
|
|
"""
|
|
|
|
return self._ghost_status
|
|
|
|
@ghost_status.setter
|
|
def ghost_status(self, ghost_status):
|
|
"""
|
|
Sets ghost RAM status
|
|
|
|
:param ghost_status: state flag indicating status
|
|
0 => Do not use IOS ghosting
|
|
1 => This is a ghost instance
|
|
2 => Use an existing ghost instance
|
|
"""
|
|
|
|
self._hypervisor.send("vm set_ghost_status {name} {ghost_status}".format(name=self._name,
|
|
ghost_status=ghost_status))
|
|
|
|
log.info("router {name} [id={id}]: ghost status set to {ghost_status}".format(name=self._name,
|
|
id=self._id,
|
|
ghost_status=ghost_status))
|
|
self._ghost_status = ghost_status
|
|
|
|
@property
|
|
def exec_area(self):
|
|
"""
|
|
Returns the exec area value.
|
|
|
|
:returns: exec area value (integer)
|
|
"""
|
|
|
|
return self._exec_area
|
|
|
|
@exec_area.setter
|
|
def exec_area(self, exec_area):
|
|
"""
|
|
Sets the exec area value.
|
|
The exec area is a pool of host memory used to store pages
|
|
translated by the JIT (they contain the native code
|
|
corresponding to MIPS code pages).
|
|
|
|
:param exec_area: exec area value (integer)
|
|
"""
|
|
|
|
self._hypervisor.send("vm set_exec_area {name} {exec_area}".format(name=self._name,
|
|
exec_area=exec_area))
|
|
|
|
log.info("router {name} [id={id}]: exec area updated from {old_exec}MB to {new_exec}MB".format(name=self._name,
|
|
id=self._id,
|
|
old_exec=self._exec_area,
|
|
new_exec=exec_area))
|
|
self._exec_area = exec_area
|
|
|
|
@property
|
|
def disk0(self):
|
|
"""
|
|
Returns the size (MB) for PCMCIA disk0.
|
|
|
|
:returns: disk0 size (integer)
|
|
"""
|
|
|
|
return self._disk0
|
|
|
|
@disk0.setter
|
|
def disk0(self, disk0):
|
|
"""
|
|
Sets the size (MB) for PCMCIA disk0.
|
|
|
|
:param disk0: disk0 size (integer)
|
|
"""
|
|
|
|
self._hypervisor.send("vm set_disk0 {name} {disk0}".format(name=self._name,
|
|
disk0=disk0))
|
|
|
|
log.info("router {name} [id={id}]: disk0 updated from {old_disk0}MB to {new_disk0}MB".format(name=self._name,
|
|
id=self._id,
|
|
old_disk0=self._disk0,
|
|
new_disk0=disk0))
|
|
self._disk0 = disk0
|
|
|
|
@property
|
|
def disk1(self):
|
|
"""
|
|
Returns the size (MB) for PCMCIA disk1.
|
|
|
|
:returns: disk1 size (integer)
|
|
"""
|
|
|
|
return self._disk1
|
|
|
|
@disk1.setter
|
|
def disk1(self, disk1):
|
|
"""
|
|
Sets the size (MB) for PCMCIA disk1.
|
|
|
|
:param disk1: disk1 size (integer)
|
|
"""
|
|
|
|
self._hypervisor.send("vm set_disk1 {name} {disk1}".format(name=self._name,
|
|
disk1=disk1))
|
|
|
|
log.info("router {name} [id={id}]: disk1 updated from {old_disk1}MB to {new_disk1}MB".format(name=self._name,
|
|
id=self._id,
|
|
old_disk1=self._disk1,
|
|
new_disk1=disk1))
|
|
self._disk1 = disk1
|
|
|
|
@property
|
|
def confreg(self):
|
|
"""
|
|
Returns the configuration register.
|
|
The default is 0x2102.
|
|
|
|
:returns: configuration register value (string)
|
|
"""
|
|
|
|
return self._confreg
|
|
|
|
@confreg.setter
|
|
def confreg(self, confreg):
|
|
"""
|
|
Sets the configuration register.
|
|
|
|
:param confreg: configuration register value (string)
|
|
"""
|
|
|
|
self._hypervisor.send("vm set_conf_reg {name} {confreg}".format(name=self._name,
|
|
confreg=confreg))
|
|
|
|
log.info("router {name} [id={id}]: confreg updated from {old_confreg} to {new_confreg}".format(name=self._name,
|
|
id=self._id,
|
|
old_confreg=self._confreg,
|
|
new_confreg=confreg))
|
|
self._confreg = confreg
|
|
|
|
@property
|
|
def console(self):
|
|
"""
|
|
Returns the TCP console port.
|
|
|
|
:returns: console port (integer)
|
|
"""
|
|
|
|
return self._console
|
|
|
|
@console.setter
|
|
def console(self, console):
|
|
"""
|
|
Sets the TCP console port.
|
|
|
|
:param console: console port (integer)
|
|
"""
|
|
|
|
if console == self._console:
|
|
return
|
|
|
|
if console in self._allocated_console_ports:
|
|
raise DynamipsError("Console port {} is already used by another router".format(console))
|
|
|
|
self._hypervisor.send("vm set_con_tcp_port {name} {console}".format(name=self._name,
|
|
console=console))
|
|
|
|
log.info("router {name} [id={id}]: console port updated from {old_console} to {new_console}".format(name=self._name,
|
|
id=self._id,
|
|
old_console=self._console,
|
|
new_console=console))
|
|
self._allocated_console_ports.remove(self._console)
|
|
self._console = console
|
|
self._allocated_console_ports.append(self._console)
|
|
|
|
@property
|
|
def aux(self):
|
|
"""
|
|
Returns the TCP auxiliary port.
|
|
|
|
:returns: console auxiliary port (integer)
|
|
"""
|
|
|
|
return self._aux
|
|
|
|
@aux.setter
|
|
def aux(self, aux):
|
|
"""
|
|
Sets the TCP auxiliary port.
|
|
|
|
:param aux: console auxiliary port (integer)
|
|
"""
|
|
|
|
if aux == self._aux:
|
|
return
|
|
|
|
if aux in self._allocated_aux_ports:
|
|
raise DynamipsError("Auxiliary console port {} is already used by another router".format(aux))
|
|
|
|
self._hypervisor.send("vm set_aux_tcp_port {name} {aux}".format(name=self._name,
|
|
aux=aux))
|
|
|
|
log.info("router {name} [id={id}]: aux port updated from {old_aux} to {new_aux}".format(name=self._name,
|
|
id=self._id,
|
|
old_aux=self._aux,
|
|
new_aux=aux))
|
|
|
|
self._allocated_aux_ports.remove(self._aux)
|
|
self._aux = aux
|
|
self._allocated_aux_ports.append(self._aux)
|
|
|
|
def get_cpu_info(self, cpu_id=0):
|
|
"""
|
|
Shows info about the CPU identified by cpu_id.
|
|
The boot CPU (which is typically the only CPU) has ID 0.
|
|
|
|
:returns: ? (could not test)
|
|
"""
|
|
|
|
# FIXME: nothing returned by Dynamips.
|
|
return self._hypervisor.send("vm cpu_info {name} {cpu_id}".format(name=self._name,
|
|
cpu_id=cpu_id))
|
|
|
|
def get_cpu_usage(self, cpu_id=0):
|
|
"""
|
|
Shows cpu usage in seconds, "cpu_id" is ignored.
|
|
|
|
:returns: cpu usage in seconds
|
|
"""
|
|
|
|
return int(self._hypervisor.send("vm cpu_usage {name} {cpu_id}".format(name=self._name,
|
|
cpu_id=cpu_id))[0])
|
|
|
|
def send_console_msg(self, message):
|
|
"""
|
|
Sends a message to the console.
|
|
|
|
:param message: message to send to the console
|
|
"""
|
|
|
|
self._hypervisor.send("vm send_con_msg {name} {message}".format(name=self._name,
|
|
message=message))
|
|
|
|
def send_aux_msg(self, message):
|
|
"""
|
|
Sends a message to the auxiliary console.
|
|
|
|
:param message: message to send to the auxiliary console
|
|
"""
|
|
|
|
self._hypervisor.send("vm send_aux_msg {name} {message}".format(name=self._name,
|
|
message=message))
|
|
|
|
@property
|
|
def mac_addr(self):
|
|
"""
|
|
Returns the MAC address.
|
|
|
|
:returns: the MAC address (hexadecimal format: hh:hh:hh:hh:hh:hh)
|
|
"""
|
|
|
|
return self._mac_addr
|
|
|
|
@mac_addr.setter
|
|
def mac_addr(self, mac_addr):
|
|
"""
|
|
Sets the MAC address.
|
|
|
|
:param mac_addr: a MAC address (hexadecimal format: hh:hh:hh:hh:hh:hh)
|
|
"""
|
|
|
|
self._hypervisor.send("{platform} set_mac_addr {name} {mac_addr}".format(platform=self._platform,
|
|
name=self._name,
|
|
mac_addr=mac_addr))
|
|
|
|
log.info("router {name} [id={id}]: MAC address updated from {old_mac} to {new_mac}".format(name=self._name,
|
|
id=self._id,
|
|
old_mac=self._mac_addr,
|
|
new_mac=mac_addr))
|
|
self._mac_addr = mac_addr
|
|
|
|
@property
|
|
def system_id(self):
|
|
"""
|
|
Returns the system ID.
|
|
|
|
:returns: the system ID (also called board processor ID)
|
|
"""
|
|
|
|
return self._system_id
|
|
|
|
@system_id.setter
|
|
def system_id(self, system_id):
|
|
"""
|
|
Sets the system ID.
|
|
|
|
:param system_id: a system ID (also called board processor ID)
|
|
"""
|
|
|
|
self._hypervisor.send("{platform} set_system_id {name} {system_id}".format(platform=self._platform,
|
|
name=self._name,
|
|
system_id=system_id))
|
|
|
|
log.info("router {name} [id={id}]: system ID updated from {old_id} to {new_id}".format(name=self._name,
|
|
id=self._id,
|
|
old_id=self._system_id,
|
|
new_id=system_id))
|
|
self._system_id = system_id
|
|
|
|
def get_hardware_info(self):
|
|
"""
|
|
Get some hardware info about this router.
|
|
|
|
:returns: ? (could not test)
|
|
"""
|
|
|
|
# FIXME: nothing returned by Dynamips.
|
|
return (self._hypervisor.send("{platform} show_hardware {name}".format(platform=self._platform,
|
|
name=self._name)))
|
|
|
|
def get_slot_bindings(self):
|
|
"""
|
|
Returns slot bindings.
|
|
|
|
:returns: slot bindings (adapter names) list
|
|
"""
|
|
|
|
return self._hypervisor.send("vm slot_bindings {}".format(self._name))
|
|
|
|
def slot_add_binding(self, slot_id, adapter):
|
|
"""
|
|
Adds a slot binding (a module into a slot).
|
|
|
|
:param slot_id: slot ID
|
|
:param adapter: device to add in the corresponding slot
|
|
"""
|
|
|
|
try:
|
|
slot = self._slots[slot_id]
|
|
except IndexError:
|
|
raise DynamipsError("Slot {slot_id} doesn't exist on router {name}".format(name=self._name,
|
|
slot_id=slot_id))
|
|
|
|
if slot is not None:
|
|
current_adapter = slot
|
|
raise DynamipsError("Slot {slot_id} is already occupied by adapter {adapter} on router {name}".format(name=self._name,
|
|
slot_id=slot_id,
|
|
adapter=current_adapter))
|
|
|
|
# Only c7200, c3600 and c3745 (NM-4T only) support new adapter while running
|
|
if self.is_running() and not (self._platform == 'c7200'
|
|
and not (self._platform == 'c3600' and self.chassis == '3660')
|
|
and not (self._platform == 'c3745' and adapter == 'NM-4T')):
|
|
raise DynamipsError("Adapter {adapter} cannot be added while router {name} is running".format(adapter=adapter,
|
|
name=self._name))
|
|
|
|
self._hypervisor.send("vm slot_add_binding {name} {slot_id} 0 {adapter}".format(name=self._name,
|
|
slot_id=slot_id,
|
|
adapter=adapter))
|
|
|
|
log.info("router {name} [id={id}]: adapter {adapter} inserted into slot {slot_id}".format(name=self._name,
|
|
id=self._id,
|
|
adapter=adapter,
|
|
slot_id=slot_id))
|
|
|
|
self._slots[slot_id] = adapter
|
|
|
|
# Generate an OIR event if the router is running
|
|
if self.is_running():
|
|
|
|
self._hypervisor.send("vm slot_oir_start {name} {slot_id} 0".format(name=self._name,
|
|
slot_id=slot_id))
|
|
|
|
log.info("router {name} [id={id}]: OIR start event sent to slot {slot_id}".format(name=self._name,
|
|
id=self._id,
|
|
slot_id=slot_id))
|
|
|
|
def slot_remove_binding(self, slot_id):
|
|
"""
|
|
Removes a slot binding (a module from a slot).
|
|
|
|
:param slot_id: slot ID
|
|
"""
|
|
|
|
try:
|
|
adapter = self._slots[slot_id]
|
|
except IndexError:
|
|
raise DynamipsError("Slot {slot_id} doesn't exist on router {name}".format(name=self._name,
|
|
slot_id=slot_id))
|
|
|
|
if adapter is None:
|
|
raise DynamipsError("No adapter in slot {slot_id} on router {name}".format(name=self._name,
|
|
slot_id=slot_id))
|
|
|
|
# Only c7200, c3600 and c3745 (NM-4T only) support to remove adapter while running
|
|
if self.is_running() and not (self._platform == 'c7200'
|
|
and not (self._platform == 'c3600' and self.chassis == '3660')
|
|
and not (self._platform == 'c3745' and adapter == 'NM-4T')):
|
|
raise DynamipsError("Adapter {adapter} cannot be removed while router {name} is running".format(adapter=adapter,
|
|
name=self._name))
|
|
|
|
# Generate an OIR event if the router is running
|
|
if self.is_running():
|
|
|
|
self._hypervisor.send("vm slot_oir_stop {name} {slot_id} 0".format(name=self._name,
|
|
slot_id=slot_id))
|
|
|
|
log.info("router {name} [id={id}]: OIR stop event sent to slot {slot_id}".format(name=self._name,
|
|
id=self._id,
|
|
slot_id=slot_id))
|
|
|
|
self._hypervisor.send("vm slot_remove_binding {name} {slot_id} 0".format(name=self._name,
|
|
slot_id=slot_id))
|
|
|
|
log.info("router {name} [id={id}]: adapter {adapter} removed from slot {slot_id}".format(name=self._name,
|
|
id=self._id,
|
|
adapter=adapter,
|
|
slot_id=slot_id))
|
|
self._slots[slot_id] = None
|
|
|
|
def install_wic(self, wic_slot_id, wic):
|
|
"""
|
|
Installs a WIC adapter into this router.
|
|
|
|
:param wic_slot_id: WIC slot ID
|
|
:param wic: WIC to be installed
|
|
"""
|
|
|
|
# WICs are always installed on adapters in slot 0
|
|
slot_id = 0
|
|
|
|
# Do not check if slot has an adapter because adapters with WICs interfaces
|
|
# must be inserted by default in the router and cannot be removed.
|
|
adapter = self._slots[slot_id]
|
|
|
|
if wic_slot_id > len(adapter.wics) - 1:
|
|
raise DynamipsError("WIC slot {wic_slot_id} doesn't exist".format(name=self._name,
|
|
wic_slot_id=wic_slot_id))
|
|
|
|
if not adapter.wic_slot_available(wic_slot_id):
|
|
raise DynamipsError("WIC slot {wic_slot_id} is already occupied by another WIC".format(name=self._name,
|
|
wic_slot_id=wic_slot_id))
|
|
|
|
# Dynamips WICs slot IDs start on a multiple of 16
|
|
# WIC1 = 16, WIC2 = 32 and WIC3 = 48
|
|
internal_wic_slot_id = 16 * (wic_slot_id + 1)
|
|
self._hypervisor.send("vm slot_add_binding {name} {slot_id} {wic_slot_id} {wic}".format(name=self._name,
|
|
slot_id=slot_id,
|
|
wic_slot_id=internal_wic_slot_id,
|
|
wic=wic))
|
|
|
|
log.info("router {name} [id={id}]: {wic} inserted into WIC slot {wic_slot_id}".format(name=self._name,
|
|
id=self._id,
|
|
wic=wic,
|
|
wic_slot_id=wic_slot_id))
|
|
|
|
adapter.install_wic(wic_slot_id, wic)
|
|
|
|
def uninstall_wic(self, wic_slot_id):
|
|
"""
|
|
Uninstalls a WIC adapter from this router.
|
|
|
|
:param wic_slot_id: WIC slot ID
|
|
"""
|
|
|
|
# WICs are always installed on adapters in slot 0
|
|
slot_id = 0
|
|
|
|
# Do not check if slot has an adapter because adapters with WICs interfaces
|
|
# must be inserted by default in the router and cannot be removed.
|
|
adapter = self._slots[slot_id]
|
|
|
|
if wic_slot_id > len(adapter.wics) - 1:
|
|
raise DynamipsError("WIC slot {wic_slot_id} doesn't exist".format(name=self._name,
|
|
wic_slot_id=wic_slot_id))
|
|
|
|
if adapter.wic_slot_available(wic_slot_id):
|
|
raise DynamipsError("No WIC is installed in WIC slot {wic_slot_id}".format(name=self._name,
|
|
wic_slot_id=wic_slot_id))
|
|
# Dynamips WICs slot IDs start on a multiple of 16
|
|
# WIC1 = 16, WIC2 = 32 and WIC3 = 48
|
|
internal_wic_slot_id = 16 * (wic_slot_id + 1)
|
|
self._hypervisor.send("vm slot_remove_binding {name} {slot_id} {wic_slot_id}".format(name=self._name,
|
|
slot_id=slot_id,
|
|
wic_slot_id=internal_wic_slot_id))
|
|
|
|
log.info("router {name} [id={id}]: {wic} removed from WIC slot {wic_slot_id}".format(name=self._name,
|
|
id=self._id,
|
|
wic=adapter.wics[wic_slot_id],
|
|
wic_slot_id=wic_slot_id))
|
|
adapter.uninstall_wic(wic_slot_id)
|
|
|
|
def get_slot_nio_bindings(self, slot_id):
|
|
"""
|
|
Returns slot NIO bindings.
|
|
|
|
:param slot_id: slot ID
|
|
|
|
:returns: list of NIO bindings
|
|
"""
|
|
|
|
return (self._hypervisor.send("vm slot_nio_bindings {name} {slot_id}".format(name=self._name,
|
|
slot_id=slot_id)))
|
|
|
|
def slot_add_nio_binding(self, slot_id, port_id, nio):
|
|
"""
|
|
Adds a slot NIO binding.
|
|
|
|
:param slot_id: slot ID
|
|
:param port_id: port ID
|
|
:param nio: NIO instance to add to the slot/port
|
|
"""
|
|
|
|
try:
|
|
adapter = self._slots[slot_id]
|
|
except IndexError:
|
|
raise DynamipsError("Slot {slot_id} doesn't exist on router {name}".format(name=self._name,
|
|
slot_id=slot_id))
|
|
if not adapter.port_exists(port_id):
|
|
raise DynamipsError("Port {port_id} doesn't exist in adapter {adapter}".format(adapter=adapter,
|
|
port_id=port_id))
|
|
|
|
self._hypervisor.send("vm slot_add_nio_binding {name} {slot_id} {port_id} {nio}".format(name=self._name,
|
|
slot_id=slot_id,
|
|
port_id=port_id,
|
|
nio=nio))
|
|
|
|
log.info("router {name} [id={id}]: NIO {nio_name} bound to port {slot_id}/{port_id}".format(name=self._name,
|
|
id=self._id,
|
|
nio_name=nio.name,
|
|
slot_id=slot_id,
|
|
port_id=port_id))
|
|
|
|
self.slot_enable_nio(slot_id, port_id)
|
|
adapter.add_nio(port_id, nio)
|
|
|
|
def slot_remove_nio_binding(self, slot_id, port_id):
|
|
"""
|
|
Removes a slot NIO binding.
|
|
|
|
:param slot_id: slot ID
|
|
:param port_id: port ID
|
|
|
|
:returns: removed NIO instance
|
|
"""
|
|
|
|
try:
|
|
adapter = self._slots[slot_id]
|
|
except IndexError:
|
|
raise DynamipsError("Slot {slot_id} doesn't exist on router {name}".format(name=self._name,
|
|
slot_id=slot_id))
|
|
if not adapter.port_exists(port_id):
|
|
raise DynamipsError("Port {port_id} doesn't exist in adapter {adapter}".format(adapter=adapter,
|
|
port_id=port_id))
|
|
|
|
self.slot_disable_nio(slot_id, port_id)
|
|
self._hypervisor.send("vm slot_remove_nio_binding {name} {slot_id} {port_id}".format(name=self._name,
|
|
slot_id=slot_id,
|
|
port_id=port_id))
|
|
|
|
nio = adapter.get_nio(port_id)
|
|
adapter.remove_nio(port_id)
|
|
|
|
log.info("router {name} [id={id}]: NIO {nio_name} removed from port {slot_id}/{port_id}".format(name=self._name,
|
|
id=self._id,
|
|
nio_name=nio.name,
|
|
slot_id=slot_id,
|
|
port_id=port_id))
|
|
|
|
return nio
|
|
|
|
def slot_enable_nio(self, slot_id, port_id):
|
|
"""
|
|
Enables a slot NIO binding.
|
|
|
|
:param slot_id: slot ID
|
|
:param port_id: port ID
|
|
"""
|
|
|
|
if self.is_running(): # running router
|
|
self._hypervisor.send("vm slot_enable_nio {name} {slot_id} {port_id}".format(name=self._name,
|
|
slot_id=slot_id,
|
|
port_id=port_id))
|
|
|
|
log.info("router {name} [id={id}]: NIO enabled on port {slot_id}/{port_id}".format(name=self._name,
|
|
id=self._id,
|
|
slot_id=slot_id,
|
|
port_id=port_id))
|
|
|
|
def slot_disable_nio(self, slot_id, port_id):
|
|
"""
|
|
Disables a slot NIO binding.
|
|
|
|
:param slot_id: slot ID
|
|
:param port_id: port ID
|
|
"""
|
|
|
|
if self.is_running(): # running router
|
|
self._hypervisor.send("vm slot_disable_nio {name} {slot_id} {port_id}".format(name=self._name,
|
|
slot_id=slot_id,
|
|
port_id=port_id))
|
|
|
|
log.info("router {name} [id={id}]: NIO disabled on port {slot_id}/{port_id}".format(name=self._name,
|
|
id=self._id,
|
|
slot_id=slot_id,
|
|
port_id=port_id))
|
|
|
|
def start_capture(self, slot_id, port_id, output_file, data_link_type="DLT_EN10MB"):
|
|
"""
|
|
Starts a packet capture.
|
|
|
|
:param slot_id: slot ID
|
|
:param port_id: port ID
|
|
:param output_file: PCAP destination file for the capture
|
|
:param data_link_type: PCAP data link type (DLT_*), default is DLT_EN10MB
|
|
"""
|
|
|
|
try:
|
|
adapter = self._slots[slot_id]
|
|
except IndexError:
|
|
raise DynamipsError("Slot {slot_id} doesn't exist on router {name}".format(name=self._name,
|
|
slot_id=slot_id))
|
|
if not adapter.port_exists(port_id):
|
|
raise DynamipsError("Port {port_id} doesn't exist in adapter {adapter}".format(adapter=adapter,
|
|
port_id=port_id))
|
|
|
|
data_link_type = data_link_type.lower()
|
|
if data_link_type.startswith("dlt_"):
|
|
data_link_type = data_link_type[4:]
|
|
|
|
nio = adapter.get_nio(port_id)
|
|
|
|
if nio.input_filter[0] is not None and nio.output_filter[0] is not None:
|
|
raise DynamipsError("Port {port_id} has already a filter applied on {adapter}".format(adapter=adapter,
|
|
port_id=port_id))
|
|
|
|
try:
|
|
os.makedirs(os.path.dirname(output_file))
|
|
except FileExistsError:
|
|
pass
|
|
except OSError as e:
|
|
raise DynamipsError("Could not create captures directory {}".format(e))
|
|
|
|
nio.bind_filter("both", "capture")
|
|
nio.setup_filter("both", '{} "{}"'.format(data_link_type, output_file))
|
|
|
|
log.info("router {name} [id={id}]: starting packet capture on port {slot_id}/{port_id}".format(name=self._name,
|
|
id=self._id,
|
|
nio_name=nio.name,
|
|
slot_id=slot_id,
|
|
port_id=port_id))
|
|
|
|
def stop_capture(self, slot_id, port_id):
|
|
"""
|
|
Stops a packet capture.
|
|
|
|
:param slot_id: slot ID
|
|
:param port_id: port ID
|
|
"""
|
|
|
|
try:
|
|
adapter = self._slots[slot_id]
|
|
except IndexError:
|
|
raise DynamipsError("Slot {slot_id} doesn't exist on router {name}".format(name=self._name,
|
|
slot_id=slot_id))
|
|
if not adapter.port_exists(port_id):
|
|
raise DynamipsError("Port {port_id} doesn't exist in adapter {adapter}".format(adapter=adapter,
|
|
port_id=port_id))
|
|
|
|
nio = adapter.get_nio(port_id)
|
|
nio.unbind_filter("both")
|
|
|
|
log.info("router {name} [id={id}]: stopping packet capture on port {slot_id}/{port_id}".format(name=self._name,
|
|
id=self._id,
|
|
nio_name=nio.name,
|
|
slot_id=slot_id,
|
|
port_id=port_id))
|
|
|
|
def _create_slots(self, numslots):
|
|
"""
|
|
Creates the appropriate number of slots for this router.
|
|
|
|
:param numslots: number of slots to create
|
|
"""
|
|
|
|
self._slots = numslots * [None]
|
|
|
|
@property
|
|
def slots(self):
|
|
"""
|
|
Returns the slots for this router.
|
|
|
|
:return: slot list
|
|
"""
|
|
|
|
return self._slots
|