Page Menu
Home
VyOS Platform
Search
Configure Global Search
Log In
Files
F119064549
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
32 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/data/templates/dhcp-server/dhcpdv6.conf.tmpl b/data/templates/dhcp-server/dhcpdv6.conf.tmpl
index de7c9b29c..25e5fa592 100644
--- a/data/templates/dhcp-server/dhcpdv6.conf.tmpl
+++ b/data/templates/dhcp-server/dhcpdv6.conf.tmpl
@@ -1,117 +1,123 @@
### Autogenerated by dhcpv6_server.py ###
# For options please consult the following website:
# https://www.isc.org/wp-content/uploads/2017/08/dhcp43options.html
log-facility local7;
{% if preference is defined and preference is not none %}
option dhcp6.preference {{ preference }};
{% endif %}
+{% if global_parameters is defined and global_parameters.name_server is defined and global_parameters.name_server is not none %}
+{% for nameserver in global_parameters.name_server %}
+option dhcp6.name-servers {{ nameserver }};
+{% endfor %}
+{% endif %}
+
# Shared network configration(s)
{% if shared_network_name is defined and shared_network_name is not none %}
{% for network, network_config in shared_network_name.items() if network_config.disable is not defined %}
shared-network {{ network | replace('_','-') }} {
{% if network_config.common_options is defined and network_config.common_options is not none %}
{% if network_config.common_options.info_refresh_time is defined and network_config.common_options.info_refresh_time is not none %}
option dhcp6.info-refresh-time {{ network_config.common_options.info_refresh_time }};
{% endif %}
{% if network_config.common_options.domain_search is defined and network_config.common_options.domain_search is not none %}
option dhcp6.domain-search "{{ network_config.common_options.domain_search | join('", "') }}";
{% endif %}
{% if network_config.common_options.name_server is defined and network_config.common_options.name_server is not none %}
option dhcp6.name-servers {{ network_config.common_options.name_server | join(', ') }};
{% endif %}
{% endif %}
{% if network_config.subnet is defined and network_config.subnet is not none %}
{% for subnet, subnet_config in network_config.subnet.items() %}
subnet6 {{ subnet }} {
{% if subnet_config.address_range is defined and subnet_config.address_range is not none %}
{% if subnet_config.address_range.prefix is defined and subnet_config.address_range.prefix is not none %}
{% for prefix, prefix_config in subnet_config.address_range.prefix.items() %}
range6 {{ prefix }} {{ "temporary" if prefix_config.temporary is defined }};
{% endfor %}
{% endif %}
{% if subnet_config.address_range.start is defined and subnet_config.address_range.start is not none %}
{% for address, address_config in subnet_config.address_range.start.items() %}
range6 {{ address }} {{ address_config.stop }};
{% endfor %}
{% endif %}
{% endif %}
{% if subnet_config.domain_search is defined and subnet_config.domain_search is not none %}
option dhcp6.domain-search "{{ subnet_config.domain_search | join('", "') }}";
{% endif %}
{% if subnet_config.lease_time is defined and subnet_config.lease_time is not none %}
{% if subnet_config.lease_time.default is defined and subnet_config.lease_time.default is not none %}
default-lease-time {{ subnet_config.lease_time.default }};
{% endif %}
{% if subnet_config.lease_time.maximum is defined and subnet_config.lease_time.maximum is not none %}
max-lease-time {{ subnet_config.lease_time.maximum }};
{% endif %}
{% if subnet_config.lease_time.minimum is defined and subnet_config.lease_time.minimum is not none %}
min-lease-time {{ subnet_config.lease_time.minimum }};
{% endif %}
{% endif %}
{% if subnet_config.name_server is defined and subnet_config.name_server is not none %}
option dhcp6.name-servers {{ subnet_config.name_server | join(', ') }};
{% endif %}
{% if subnet_config.nis_domain is defined and subnet_config.nis_domain is not none %}
option dhcp6.nis-domain-name "{{ subnet_config.nis_domain }}";
{% endif %}
{% if subnet_config.nis_server is defined and subnet_config.nis_server is not none %}
option dhcp6.nis-servers {{ subnet_config.nis_server | join(', ') }};
{% endif %}
{% if subnet_config.nisplus_domain is defined and subnet_config.nisplus_domain is not none %}
option dhcp6.nisp-domain-name "{{ subnet_config.nisplus_domain }}";
{% endif %}
{% if subnet_config.nisplus_server is defined and subnet_config.nisplus_server is not none %}
option dhcp6.nisp-servers {{ subnet_config.nisplus_server | join(', ') }};
{% endif %}
{% if subnet_config.sip_server is defined and subnet_config.sip_server is not none %}
{% set server_ip = [] %}
{% set server_fqdn = [] %}
{% for address in subnet_config.sip_server %}
{% if address | is_ipv6 %}
{% set server_ip = server_ip.append(address) %}
{% else %}
{% set server_fqdn = server_fqdn.append(address) %}
{% endif %}
{% endfor %}
{% if server_ip is defined and server_ip | length > 0 %}
option dhcp6.sip-servers-addresses {{ server_ip | join(', ') }};
{% endif %}
{% if server_fqdn is defined and server_fqdn | length > 0 %}
option dhcp6.sip-servers-names "{{ server_fqdn | join('", "') }}";
{% endif %}
{% endif %}
{% if subnet_config.sntp_server is defined and subnet_config.sntp_server is not none %}
option dhcp6.sntp-servers {{ subnet_config.sntp_server | join(', ') }};
{% endif %}
{% if subnet_config.prefix_delegation is defined and subnet_config.prefix_delegation.start is defined and subnet_config.prefix_delegation.start is not none %}
{% for prefix, prefix_config in subnet_config.prefix_delegation.start.items() %}
prefix6 {{ prefix }} {{ prefix_config.stop }} /{{ prefix_config.prefix_length }};
{% endfor %}
{% endif %}
{% if subnet_config.static_mapping is defined and subnet_config.static_mapping is not none %}
# begin configuration of static client mappings
{% for host, host_config in subnet_config.static_mapping.items() if host_config.disable is not defined %}
host {{ network | replace('_','-') }}_{{ host | replace('_','-') }} {
{% if host_config.identifier is defined and host_config.identifier is not none %}
host-identifier option dhcp6.client-id {{ host_config.identifier }};
{% endif %}
{% if host_config.ipv6_address is defined and host_config.ipv6_address is not none %}
fixed-address6 {{ host_config.ipv6_address }};
{% endif %}
}
{% endfor %}
{% endif %}
}
{% endfor %}
{% endif %}
on commit {
set shared-networkname = "{{ network | replace('_','-') }}";
}
}
{% endfor %}
{% endif %}
diff --git a/interface-definitions/dhcpv6-server.xml.in b/interface-definitions/dhcpv6-server.xml.in
index fb0e79c47..a3cca06da 100644
--- a/interface-definitions/dhcpv6-server.xml.in
+++ b/interface-definitions/dhcpv6-server.xml.in
@@ -1,353 +1,373 @@
<?xml version="1.0"?>
<!-- DHCPv6 server configuration -->
<interfaceDefinition>
<node name="service">
<children>
<node name="dhcpv6-server" owner="${vyos_conf_scripts_dir}/dhcpv6_server.py">
<properties>
<help>DHCP for IPv6 (DHCPv6) server</help>
<priority>900</priority>
</properties>
<children>
#include <include/generic-disable-node.xml.i>
+ <node name="global-parameters">
+ <properties>
+ <help>Additional global parameters for DHCPv6 server</help>
+ </properties>
+ <children>
+ <leafNode name="name-server">
+ <properties>
+ <help>IPv6 address of a Recursive DNS Server</help>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 address of DNS name server</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-address"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
<leafNode name="preference">
<properties>
<help>Preference of this DHCPv6 server compared with others</help>
<valueHelp>
<format>0-255</format>
<description>DHCPv6 server preference (0-255)</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 0-255"/>
</constraint>
<constraintErrorMessage>Preference must be between 0 and 255</constraintErrorMessage>
</properties>
</leafNode>
<tagNode name="shared-network-name">
<properties>
<help>DHCPv6 shared network name [REQUIRED]</help>
<constraint>
<regex>[-_a-zA-Z0-9.]+</regex>
</constraint>
<constraintErrorMessage>Invalid DHCPv6 shared network name. May only contain letters, numbers and .-_</constraintErrorMessage>
</properties>
<children>
#include <include/generic-disable-node.xml.i>
<node name="common-options">
<properties>
<help>Common options to distribute to all clients, including stateless clients</help>
</properties>
<children>
<leafNode name="info-refresh-time">
<properties>
<help>Time (in seconds) that stateless clients should wait between refreshing the information they were given</help>
<valueHelp>
<format>1-4294967295</format>
<description>DHCPv6 information refresh time</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1-4294967295"/>
</constraint>
</properties>
</leafNode>
#include <include/dhcp-server-domain-search.xml.i>
<leafNode name="name-server">
<properties>
<help>IPv6 address of a Recursive DNS Server</help>
<valueHelp>
<format>ipv6</format>
<description>IPv6 address of DNS name server</description>
</valueHelp>
<constraint>
<validator name="ipv6-address"/>
</constraint>
<multi/>
</properties>
</leafNode>
</children>
</node>
<tagNode name="subnet">
<properties>
<help>IPv6 DHCP subnet for this shared network [REQUIRED]</help>
<valueHelp>
<format>ipv6net</format>
<description>IPv6 address and prefix length</description>
</valueHelp>
<constraint>
<validator name="ipv6-prefix"/>
</constraint>
</properties>
<children>
<node name="address-range">
<properties>
<help>Parameters setting ranges for assigning IPv6 addresses</help>
</properties>
<children>
<tagNode name="prefix">
<properties>
<help>IPv6 prefix defining range of addresses to assign</help>
<valueHelp>
<format>ipv6net</format>
<description>IPv6 address and prefix length</description>
</valueHelp>
<constraint>
<validator name="ipv6-prefix"/>
</constraint>
</properties>
<children>
<leafNode name="temporary">
<properties>
<help>Address range will be used for temporary addresses</help>
<valueless/>
</properties>
</leafNode>
</children>
</tagNode>
<tagNode name="start">
<properties>
<help>First in range of consecutive IPv6 addresses to assign</help>
<valueHelp>
<format>ipv6</format>
<description>IPv6 address</description>
</valueHelp>
<constraint>
<validator name="ipv6-address"/>
</constraint>
</properties>
<children>
<leafNode name="stop">
<properties>
<help>Last in range of consecutive IPv6 addresses</help>
<valueHelp>
<format>ipv6</format>
<description>IPv6 address</description>
</valueHelp>
<constraint>
<validator name="ipv6-address"/>
</constraint>
</properties>
</leafNode>
</children>
</tagNode>
</children>
</node>
#include <include/dhcp-server-domain-search.xml.i>
<node name="lease-time">
<properties>
<help>Parameters relating to the lease time</help>
</properties>
<children>
<leafNode name="default">
<properties>
<help>Default time (in seconds) that will be assigned to a lease</help>
<valueHelp>
<format>1-4294967295</format>
<description>DHCPv6 valid lifetime</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1-4294967295"/>
</constraint>
</properties>
</leafNode>
<leafNode name="maximum">
<properties>
<help>Maximum time (in seconds) that will be assigned to a lease</help>
<valueHelp>
<format>1-4294967295</format>
<description>Maximum lease time in seconds</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1-4294967295"/>
</constraint>
</properties>
</leafNode>
<leafNode name="minimum">
<properties>
<help>Minimum time (in seconds) that will be assigned to a lease</help>
<valueHelp>
<format>1-4294967295</format>
<description>Minimum lease time in seconds</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1-4294967295"/>
</constraint>
</properties>
</leafNode>
</children>
</node>
<leafNode name="name-server">
<properties>
<help>IPv6 address of a Recursive DNS Server</help>
<valueHelp>
<format>ipv6</format>
<description>IPv6 address of DNS name server</description>
</valueHelp>
<constraint>
<validator name="ipv6-address"/>
</constraint>
<multi/>
</properties>
</leafNode>
<leafNode name="nis-domain">
<properties>
<help>NIS domain name for client to use</help>
<constraint>
<regex>[-_a-zA-Z0-9.]+</regex>
</constraint>
<constraintErrorMessage>Invalid NIS domain name</constraintErrorMessage>
</properties>
</leafNode>
<leafNode name="nis-server">
<properties>
<help>IPv6 address of a NIS Server</help>
<valueHelp>
<format>ipv6</format>
<description>IPv6 address of NIS server</description>
</valueHelp>
<constraint>
<validator name="ipv6-address"/>
</constraint>
<multi/>
</properties>
</leafNode>
<leafNode name="nisplus-domain">
<properties>
<help>NIS+ domain name for client to use</help>
<constraint>
<regex>[-_a-zA-Z0-9.]+</regex>
</constraint>
<constraintErrorMessage>Invalid NIS+ domain name. May only contain letters, numbers and .-_</constraintErrorMessage>
</properties>
</leafNode>
<leafNode name="nisplus-server">
<properties>
<help>IPv6 address of a NIS+ Server</help>
<valueHelp>
<format>ipv6</format>
<description>IPv6 address of NIS+ server</description>
</valueHelp>
<constraint>
<validator name="ipv6-address"/>
</constraint>
<multi/>
</properties>
</leafNode>
<node name="prefix-delegation">
<properties>
<help>Parameters relating to IPv6 prefix delegation</help>
</properties>
<children>
<tagNode name="start">
<properties>
<help>First in range of IPv6 addresses to be used in prefix delegation</help>
<valueHelp>
<format>ipv6</format>
<description>IPv6 address used in prefix delegation</description>
</valueHelp>
<constraint>
<validator name="ipv6-address"/>
</constraint>
</properties>
<children>
<leafNode name="prefix-length">
<properties>
<help>Length in bits of prefixes to be delegated</help>
<valueHelp>
<format>32-64</format>
<description>Delagated prefix length (32-64)</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 32-64"/>
</constraint>
<constraintErrorMessage>Delegated prefix length must be between 32 and 64</constraintErrorMessage>
</properties>
</leafNode>
<leafNode name="stop">
<properties>
<help>Last in range of IPv6 addresses to be used in prefix delegation</help>
<valueHelp>
<format>ipv6</format>
<description>IPv6 address used in prefix delegation</description>
</valueHelp>
<constraint>
<validator name="ipv6-address"/>
</constraint>
</properties>
</leafNode>
</children>
</tagNode>
</children>
</node>
<leafNode name="sip-server">
<properties>
<help>IPv6 address of SIP server</help>
<valueHelp>
<format>ipv6</format>
<description>IPv6 address of SIP server</description>
</valueHelp>
<valueHelp>
<format>hostname</format>
<description>FQDN of SIP server</description>
</valueHelp>
<constraint>
<validator name="ipv6-address"/>
<validator name="fqdn"/>
</constraint>
<multi/>
</properties>
</leafNode>
<leafNode name="sntp-server">
<properties>
<help>IPv6 address of an SNTP server for client to use</help>
<constraint>
<validator name="ipv6-address"/>
</constraint>
<multi/>
</properties>
</leafNode>
<tagNode name="static-mapping">
<properties>
<help>Name of static mapping</help>
<constraint>
<regex>[-_a-zA-Z0-9.]+</regex>
</constraint>
<constraintErrorMessage>Invalid static mapping name. May only contain letters, numbers and .-_</constraintErrorMessage>
</properties>
<children>
#include <include/generic-disable-node.xml.i>
<leafNode name="identifier">
<properties>
<help>Client identifier (DUID) for this static mapping</help>
<valueHelp>
<format>h[[:h]...]</format>
<description>DUID: colon-separated hex list (as used by isc-dhcp option dhcpv6.client-id)</description>
</valueHelp>
<constraint>
<regex>([0-9A-Fa-f]{1,2}[:])*([0-9A-Fa-f]{1,2})</regex>
</constraint>
<constraintErrorMessage>Invalid DUID, must be in the format h[[:h]...]</constraintErrorMessage>
</properties>
</leafNode>
<leafNode name="ipv6-address">
<properties>
<help>Client IPv6 address for this static mapping</help>
<valueHelp>
<format>ipv6</format>
<description>IPv6 address for this static mapping</description>
</valueHelp>
<constraint>
<validator name="ipv6-address"/>
</constraint>
</properties>
</leafNode>
</children>
</tagNode>
</children>
</tagNode>
</children>
</tagNode>
</children>
</node>
</children>
</node>
</interfaceDefinition>
diff --git a/smoketest/scripts/cli/test_service_dhcpv6-server.py b/smoketest/scripts/cli/test_service_dhcpv6-server.py
index 319891a94..a364eee11 100755
--- a/smoketest/scripts/cli/test_service_dhcpv6-server.py
+++ b/smoketest/scripts/cli/test_service_dhcpv6-server.py
@@ -1,155 +1,176 @@
#!/usr/bin/env python3
#
# Copyright (C) 2020 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
# published by the Free Software Foundation.
#
# 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/>.
import re
import os
import unittest
from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
from vyos.template import inc_ip
from vyos.util import cmd
from vyos.util import process_named_running
from vyos.util import read_file
PROCESS_NAME = 'dhcpd'
DHCPD_CONF = '/run/dhcp-server/dhcpdv6.conf'
base_path = ['service', 'dhcpv6-server']
subnet = '2001:db8:f00::/64'
dns_1 = '2001:db8::1'
dns_2 = '2001:db8::2'
domain = 'vyos.net'
nis_servers = ['2001:db8:ffff::1', '2001:db8:ffff::2']
interface = 'eth1'
interface_addr = inc_ip(subnet, 1) + '/64'
class TestServiceDHCPServer(unittest.TestCase):
def setUp(self):
self.session = ConfigSession(os.getpid())
self.session.set(['interfaces', 'ethernet', interface, 'address', interface_addr])
def tearDown(self):
self.session.delete(base_path)
self.session.delete(['interfaces', 'ethernet', interface, 'address', interface_addr])
self.session.commit()
del self.session
def test_single_pool(self):
shared_net_name = 'SMOKE-1'
search_domains = ['foo.vyos.net', 'bar.vyos.net']
lease_time = '1200'
max_lease_time = '72000'
min_lease_time = '600'
preference = '10'
sip_server = 'sip.vyos.net'
sntp_server = inc_ip(subnet, 100)
range_start = inc_ip(subnet, 256) # ::100
range_stop = inc_ip(subnet, 65535) # ::ffff
pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet]
self.session.set(base_path + ['preference', preference])
# we use the first subnet IP address as default gateway
self.session.set(pool + ['name-server', dns_1])
self.session.set(pool + ['name-server', dns_2])
self.session.set(pool + ['name-server', dns_2])
self.session.set(pool + ['lease-time', 'default', lease_time])
self.session.set(pool + ['lease-time', 'maximum', max_lease_time])
self.session.set(pool + ['lease-time', 'minimum', min_lease_time])
self.session.set(pool + ['nis-domain', domain])
self.session.set(pool + ['nisplus-domain', domain])
self.session.set(pool + ['sip-server', sip_server])
self.session.set(pool + ['sntp-server', sntp_server])
self.session.set(pool + ['address-range', 'start', range_start, 'stop', range_stop])
for server in nis_servers:
self.session.set(pool + ['nis-server', server])
self.session.set(pool + ['nisplus-server', server])
for search in search_domains:
self.session.set(pool + ['domain-search', search])
client_base = 1
for client in ['client1', 'client2', 'client3']:
cid = '00:01:00:01:12:34:56:78:aa:bb:cc:dd:ee:{}'.format(client_base)
self.session.set(pool + ['static-mapping', client, 'identifier', cid])
self.session.set(pool + ['static-mapping', client, 'ipv6-address', inc_ip(subnet, client_base)])
client_base += 1
# commit changes
self.session.commit()
config = read_file(DHCPD_CONF)
self.assertIn(f'option dhcp6.preference {preference};', config)
self.assertIn(f'subnet6 {subnet}' + r' {', config)
search = '"' + '", "'.join(search_domains) + '"'
nissrv = ', '.join(nis_servers)
self.assertIn(f'range6 {range_start} {range_stop};', config)
self.assertIn(f'default-lease-time {lease_time};', config)
self.assertIn(f'default-lease-time {lease_time};', config)
self.assertIn(f'max-lease-time {max_lease_time};', config)
self.assertIn(f'min-lease-time {min_lease_time};', config)
self.assertIn(f'option dhcp6.domain-search {search};', config)
self.assertIn(f'option dhcp6.name-servers {dns_1}, {dns_2};', config)
self.assertIn(f'option dhcp6.nis-domain-name "{domain}";', config)
self.assertIn(f'option dhcp6.nis-servers {nissrv};', config)
self.assertIn(f'option dhcp6.nisp-domain-name "{domain}";', config)
self.assertIn(f'option dhcp6.nisp-servers {nissrv};', config)
self.assertIn(f'set shared-networkname = "{shared_net_name}";', config)
client_base = 1
for client in ['client1', 'client2', 'client3']:
cid = '00:01:00:01:12:34:56:78:aa:bb:cc:dd:ee:{}'.format(client_base)
ip = inc_ip(subnet, client_base)
self.assertIn(f'host {shared_net_name}_{client}' + ' {', config)
self.assertIn(f'fixed-address6 {ip};', config)
self.assertIn(f'host-identifier option dhcp6.client-id {cid};', config)
client_base += 1
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
def test_prefix_delegation(self):
shared_net_name = 'SMOKE-2'
range_start = inc_ip(subnet, 256) # ::100
range_stop = inc_ip(subnet, 65535) # ::ffff
delegate_start = '2001:db8:ee::'
delegate_stop = '2001:db8:ee:ff00::'
delegate_len = '56'
pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet]
self.session.set(pool + ['address-range', 'start', range_start, 'stop', range_stop])
self.session.set(pool + ['prefix-delegation', 'start', delegate_start, 'stop', delegate_stop])
self.session.set(pool + ['prefix-delegation', 'start', delegate_start, 'prefix-length', delegate_len])
# commit changes
self.session.commit()
config = read_file(DHCPD_CONF)
self.assertIn(f'subnet6 {subnet}' + r' {', config)
self.assertIn(f'range6 {range_start} {range_stop};', config)
self.assertIn(f'prefix6 {delegate_start} {delegate_stop} /{delegate_len};', config)
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
+ def test_global_nameserver(self):
+ shared_net_name = 'SMOKE-3'
+ ns_global_1 = '2001:db8::1111'
+ ns_global_2 = '2001:db8::2222'
+
+ self.session.set(base_path + ['global-parameters', 'name-server', ns_global_1])
+ self.session.set(base_path + ['global-parameters', 'name-server', ns_global_2])
+ self.session.set(base_path + ['shared-network-name', shared_net_name, 'subnet', subnet])
+
+ # commit changes
+ self.session.commit()
+
+ config = read_file(DHCPD_CONF)
+ self.assertIn(f'option dhcp6.name-servers {ns_global_1};', config)
+ self.assertIn(f'option dhcp6.name-servers {ns_global_2};', config)
+ self.assertIn(f'subnet6 {subnet}' + r' {', config)
+ self.assertIn(f'set shared-networkname = "{shared_net_name}";', config)
+
+ # Check for running process
+ self.assertTrue(process_named_running(PROCESS_NAME))
+
if __name__ == '__main__':
unittest.main(verbosity=2)
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Fri, Nov 6, 10:27 AM (1 h, 3 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4654210
Default Alt Text
(32 KB)
Attached To
Mode
rVYOSONEX vyos-1x
Attached
Detach File
Event Timeline
Log In to Comment