summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile8
-rw-r--r--interface-definitions/include/interface-mtu-1200-9000.xml.i13
-rw-r--r--interface-definitions/interfaces-vxlan.xml.in2
-rw-r--r--interface-definitions/snmp.xml.in24
-rw-r--r--interface-definitions/vrrp.xml.in8
-rw-r--r--python/vyos/defaults.py2
-rw-r--r--python/vyos/keepalived.py31
-rw-r--r--python/vyos/systemversions.py28
-rw-r--r--python/vyos/validate.py11
-rw-r--r--schema/interface_definition.rnc17
-rw-r--r--schema/interface_definition.rng18
-rwxr-xr-xscripts/build-command-templates2
-rwxr-xr-xscripts/build-component-versions47
-rwxr-xr-xsrc/conf_mode/dhcp_relay.py5
-rwxr-xr-xsrc/conf_mode/interfaces-vxlan.py8
-rwxr-xr-xsrc/conf_mode/ipsec-settings.py3
-rwxr-xr-xsrc/conf_mode/protocols_bfd.py11
-rwxr-xr-xsrc/conf_mode/vrrp.py21
-rwxr-xr-xsrc/op_mode/vrrp.py3
-rwxr-xr-xsrc/system/vrrp-script-wrapper.py49
20 files changed, 237 insertions, 74 deletions
diff --git a/Makefile b/Makefile
index 61e603612..d05a0adbe 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,7 @@
TMPL_DIR := templates-cfg
OP_TMPL_DIR := templates-op
BUILD_DIR := build
+DATA_DIR := data
CFLAGS :=
src = $(wildcard interface-definitions/*.xml.in)
@@ -77,8 +78,13 @@ op_mode_definitions:
rm -f $(OP_TMPL_DIR)/reset/vpn/node.def
rm -f $(OP_TMPL_DIR)/show/system/node.def
+.PHONY: component_versions
+.ONESHELL:
+component_versions: $(BUILD_DIR) $(obj)
+ $(CURDIR)/scripts/build-component-versions $(BUILD_DIR)/interface-definitions $(DATA_DIR)
+
.PHONY: all
-all: clean interface_definitions op_mode_definitions
+all: clean interface_definitions op_mode_definitions component_versions
.PHONY: clean
clean:
diff --git a/interface-definitions/include/interface-mtu-1200-9000.xml.i b/interface-definitions/include/interface-mtu-1200-9000.xml.i
new file mode 100644
index 000000000..336845b77
--- /dev/null
+++ b/interface-definitions/include/interface-mtu-1200-9000.xml.i
@@ -0,0 +1,13 @@
+<leafNode name="mtu">
+ <properties>
+ <help>Maximum Transmission Unit (MTU)</help>
+ <valueHelp>
+ <format>1200-9000</format>
+ <description>Maximum Transmission Unit</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1200-9000"/>
+ </constraint>
+ <constraintErrorMessage>MTU must be between 1200 and 9000</constraintErrorMessage>
+ </properties>
+</leafNode>
diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in
index ceb75cb34..7d86e1847 100644
--- a/interface-definitions/interfaces-vxlan.xml.in
+++ b/interface-definitions/interfaces-vxlan.xml.in
@@ -57,7 +57,7 @@
</completionHelp>
</properties>
</leafNode>
- #include <include/interface-mtu-1450-9000.xml.i>
+ #include <include/interface-mtu-1200-9000.xml.i>
<leafNode name="remote">
<properties>
<help>Remote address of VXLAN tunnel</help>
diff --git a/interface-definitions/snmp.xml.in b/interface-definitions/snmp.xml.in
index 91f1b8d71..4c6a993b2 100644
--- a/interface-definitions/snmp.xml.in
+++ b/interface-definitions/snmp.xml.in
@@ -139,6 +139,14 @@
<leafNode name="trap-source">
<properties>
<help>SNMP trap source address</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 address</description>
+ </valueHelp>
<constraint>
<validator name="ipv4-address"/>
<validator name="ipv6-address"/>
@@ -148,6 +156,14 @@
<tagNode name="trap-target">
<properties>
<help>Address of trap target</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 address</description>
+ </valueHelp>
<constraint>
<validator name="ipv4-address"/>
<validator name="ipv6-address"/>
@@ -585,6 +601,10 @@
<tagNode name="extension-name">
<properties>
<help>Extension name</help>
+ <constraint>
+ <regex>^[a-z0-9\.\-\_]+</regex>
+ </constraint>
+ <constraintErrorMessage>Script extension contains invalid characters</constraintErrorMessage>
</properties>
<children>
<leafNode name="script">
@@ -593,6 +613,10 @@
<completionHelp>
<script>ls /config/user-data</script>
</completionHelp>
+ <constraint>
+ <regex>^[a-z0-9\.\-\_\/]+</regex>
+ </constraint>
+ <constraintErrorMessage>Script extension contains invalid characters</constraintErrorMessage>
</properties>
</leafNode>
</children>
diff --git a/interface-definitions/vrrp.xml.in b/interface-definitions/vrrp.xml.in
index 2884ef613..89d22f79f 100644
--- a/interface-definitions/vrrp.xml.in
+++ b/interface-definitions/vrrp.xml.in
@@ -197,6 +197,14 @@
</constraint>
</properties>
</leafNode>
+ <leafNode name="stop">
+ <properties>
+ <help>Script to run on VRRP state transition to stop</help>
+ <constraint>
+ <validator name="script"/>
+ </constraint>
+ </properties>
+ </leafNode>
</children>
</node>
<leafNode name="virtual-address">
diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py
index dedb929b4..a2ad142bc 100644
--- a/python/vyos/defaults.py
+++ b/python/vyos/defaults.py
@@ -29,6 +29,8 @@ cfg_vintage = 'vyatta'
commit_lock = '/opt/vyatta/config/.lock'
+version_file = '/usr/share/vyos/component-versions.json'
+
https_data = {
'listen_addresses' : { '*': ['_'] }
}
diff --git a/python/vyos/keepalived.py b/python/vyos/keepalived.py
index 4114aa736..3984ca792 100644
--- a/python/vyos/keepalived.py
+++ b/python/vyos/keepalived.py
@@ -26,8 +26,6 @@ state_file = '/tmp/keepalived.data'
stats_file = '/tmp/keepalived.stats'
json_file = '/tmp/keepalived.json'
-state_dir = '/var/run/vyos/vrrp/'
-
def vrrp_running():
if not os.path.exists(vyos.keepalived.pid_file) \
or not vyos.util.process_running(vyos.keepalived.pid_file):
@@ -38,6 +36,15 @@ def vrrp_running():
def keepalived_running():
return vyos.util.process_running(pid_file)
+## Clear VRRP data after showing
+def remove_vrrp_data(data_file):
+ if data_file == "json" and os.path.exists(json_file):
+ os.remove(json_file)
+ elif data_file == "stats" and os.path.exists(stats_file):
+ os.remove(stats_file)
+ elif data_file == "state" and os.path.exists(state_file):
+ os.remove(state_file)
+
def force_state_data_dump():
pid = vyos.util.read_file(pid_file)
os.kill(int(pid), signal.SIGUSR1)
@@ -76,26 +83,6 @@ def decode_state(code):
return state
-## The functions are mainly for transition script wrappers
-## to compensate for the fact that keepalived doesn't keep persistent
-## state between reloads.
-def get_old_state(group):
- file = os.path.join(state_dir, "{0}.state".format(group))
- if os.path.exists(file):
- with open(file, 'r') as f:
- data = f.read().strip()
- return data
- else:
- return None
-
-def save_state(group, state):
- if not os.path.exists(state_dir):
- os.makedirs(state_dir)
-
- file = os.path.join(state_dir, "{0}.state".format(group))
- with open(file, 'w') as f:
- f.write(state)
-
## These functions are for the old, and hopefully obsolete plaintext
## (non machine-readable) data format introduced by Vyatta back in the days
## They are kept here just in case, if JSON output option turns out or becomes
diff --git a/python/vyos/systemversions.py b/python/vyos/systemversions.py
index 9b3f4f413..5c4deca29 100644
--- a/python/vyos/systemversions.py
+++ b/python/vyos/systemversions.py
@@ -16,12 +16,15 @@
import os
import re
import sys
+import json
+
import vyos.defaults
def get_system_versions():
"""
- Get component versions from running system; critical failure if
- unable to read migration directory.
+ Get component versions from running system: read vyatta directory
+ structure for versions, then read vyos JSON file. It is a critical
+ error if either migration directory or JSON file is unreadable.
"""
system_versions = {}
@@ -36,4 +39,25 @@ def get_system_versions():
pair = info.split('@')
system_versions[pair[0]] = int(pair[1])
+ version_dict = {}
+ path = vyos.defaults.version_file
+
+ if os.path.isfile(path):
+ with open(path, 'r') as f:
+ try:
+ version_dict = json.load(f)
+ except ValueError as err:
+ print(f"\nValue error in {path}: {err}")
+ sys.exit(1)
+
+ for k, v in version_dict.items():
+ if not isinstance(v, int):
+ print(f"\nType error in {path}; expecting Dict[str, int]")
+ sys.exit(1)
+ existing = system_versions.get(k)
+ if existing is None:
+ system_versions[k] = v
+ elif v > existing:
+ system_versions[k] = v
+
return system_versions
diff --git a/python/vyos/validate.py b/python/vyos/validate.py
index 1ce5a8467..8ffef64fa 100644
--- a/python/vyos/validate.py
+++ b/python/vyos/validate.py
@@ -52,6 +52,17 @@ def is_ipv6(addr):
return False
+def is_ipv6_link_local(addr):
+ """
+ Check addr if it is an IPv6 link-local address/network. Returns True/False
+ """
+
+ if is_ipv6(addr):
+ if ipaddress.IPv6Address(addr).is_link_local:
+ return True
+
+ return False
+
def is_intf_addr_assigned(intf, addr):
"""
Verify if the given IPv4/IPv6 address is assigned to specific interface.
diff --git a/schema/interface_definition.rnc b/schema/interface_definition.rnc
index 02175fec8..0ce8226cd 100644
--- a/schema/interface_definition.rnc
+++ b/schema/interface_definition.rnc
@@ -24,9 +24,16 @@
# Interface definition starts with interfaceDefinition tag that may contain node tags
start = element interfaceDefinition
{
+ syntaxVersion*,
node*
}
+# interfaceDefinition may contain syntax version attribute lists.
+syntaxVersion = element syntaxVersion
+{
+ (componentAttr & versionAttr)
+}
+
# node tag may contain node, leafNode, or tagNode tags
# Those are intermediate configuration nodes that may only contain
# other nodes and must not have values
@@ -97,6 +104,16 @@ properties = element properties
(element keepChildOrder { empty })?
}
+componentAttr = attribute component
+{
+ text
+}
+
+versionAttr = attribute version
+{
+ text
+}
+
# All nodes must have "name" attribute
nodeNameAttr = attribute name
{
diff --git a/schema/interface_definition.rng b/schema/interface_definition.rng
index 195ef27f4..bfd8d376f 100644
--- a/schema/interface_definition.rng
+++ b/schema/interface_definition.rng
@@ -29,10 +29,22 @@
<start>
<element name="interfaceDefinition">
<zeroOrMore>
+ <ref name="syntaxVersion"/>
+ </zeroOrMore>
+ <zeroOrMore>
<ref name="node"/>
</zeroOrMore>
</element>
</start>
+ <!-- interfaceDefinition may contain syntax version attribute lists. -->
+ <define name="syntaxVersion">
+ <element name="syntaxVersion">
+ <interleave>
+ <ref name="componentAttr"/>
+ <ref name="versionAttr"/>
+ </interleave>
+ </element>
+ </define>
<!--
node tag may contain node, leafNode, or tagNode tags
Those are intermediate configuration nodes that may only contain
@@ -184,6 +196,12 @@
</interleave>
</element>
</define>
+ <define name="componentAttr">
+ <attribute name="component"/>
+ </define>
+ <define name="versionAttr">
+ <attribute name="version"/>
+ </define>
<!-- All nodes must have "name" attribute -->
<define name="nodeNameAttr">
<attribute name="name"/>
diff --git a/scripts/build-command-templates b/scripts/build-command-templates
index 4fcdb8ade..dbf4ad9c5 100755
--- a/scripts/build-command-templates
+++ b/scripts/build-command-templates
@@ -295,4 +295,6 @@ root = xml.getroot()
nodes = root.iterfind("*")
for n in nodes:
+ if n.tag == "syntaxVersion":
+ continue
process_node(n, [output_dir])
diff --git a/scripts/build-component-versions b/scripts/build-component-versions
new file mode 100755
index 000000000..5362dbdd4
--- /dev/null
+++ b/scripts/build-component-versions
@@ -0,0 +1,47 @@
+#!/usr/bin/env python3
+
+import sys
+import os
+import argparse
+import json
+
+from lxml import etree as ET
+
+parser = argparse.ArgumentParser()
+parser.add_argument('INPUT_DIR', type=str,
+ help="Directory containing XML interface definition files")
+parser.add_argument('OUTPUT_DIR', type=str,
+ help="Output directory for JSON file")
+
+args = parser.parse_args()
+
+input_dir = args.INPUT_DIR
+output_dir = args.OUTPUT_DIR
+
+version_dict = {}
+
+for filename in os.listdir(input_dir):
+ filepath = os.path.join(input_dir, filename)
+ print(filepath)
+ try:
+ xml = ET.parse(filepath)
+ except Exception as e:
+ print("Failed to load interface definition file {0}".format(filename))
+ print(e)
+ sys.exit(1)
+
+ root = xml.getroot()
+ version_data = root.iterfind("syntaxVersion")
+ for ver in version_data:
+ component = ver.get("component")
+ version = int(ver.get("version"))
+
+ v = version_dict.get(component)
+ if v is None:
+ version_dict[component] = version
+ elif version > v:
+ version_dict[component] = version
+
+out_file = os.path.join(output_dir, 'component-versions.json')
+with open(out_file, 'w') as f:
+ json.dump(version_dict, f, indent=4, sort_keys=True)
diff --git a/src/conf_mode/dhcp_relay.py b/src/conf_mode/dhcp_relay.py
index 73e0153df..a1af2575f 100755
--- a/src/conf_mode/dhcp_relay.py
+++ b/src/conf_mode/dhcp_relay.py
@@ -105,11 +105,6 @@ def verify(relay):
if relay is None:
return None
- if len(relay['interface']) < 2:
- # We can only issue a warning otherwise old configurations might break
- print('WARNING: At least two interfaces are required for DHCP relay\n' \
- 'to work\n')
-
if 'lo' in relay['interface']:
raise ConfigError('DHCP relay does not support the loopback interface.')
diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py
index 7f1ac6c31..efdc21f89 100755
--- a/src/conf_mode/interfaces-vxlan.py
+++ b/src/conf_mode/interfaces-vxlan.py
@@ -189,13 +189,13 @@ def apply(vxlan):
# configure ARP cache timeout in milliseconds
v.set_arp_cache_tmo(vxlan['ip_arp_cache_tmo'])
# configure ARP filter configuration
- v.set_arp_filter(bond['ip_disable_arp_filter'])
+ v.set_arp_filter(vxlan['ip_disable_arp_filter'])
# configure ARP accept
- v.set_arp_accept(bond['ip_enable_arp_accept'])
+ v.set_arp_accept(vxlan['ip_enable_arp_accept'])
# configure ARP announce
- v.set_arp_announce(bond['ip_enable_arp_announce'])
+ v.set_arp_announce(vxlan['ip_enable_arp_announce'])
# configure ARP ignore
- v.set_arp_ignore(bond['ip_enable_arp_ignore'])
+ v.set_arp_ignore(vxlan['ip_enable_arp_ignore'])
# Enable proxy-arp on this interface
v.set_proxy_arp(vxlan['ip_proxy_arp'])
diff --git a/src/conf_mode/ipsec-settings.py b/src/conf_mode/ipsec-settings.py
index aab3e9734..e80c6caf0 100755
--- a/src/conf_mode/ipsec-settings.py
+++ b/src/conf_mode/ipsec-settings.py
@@ -248,7 +248,8 @@ def generate(data):
write_ipsec_ra_conn(data)
append_ipsec_conf(data)
else:
- remove_confs(delim_ipsec_l2tp_begin, delim_ipsec_l2tp_end, ipsec_ra_conn_file)
+ if os.path.exists(ipsec_ra_conn_file):
+ remove_confs(delim_ipsec_l2tp_begin, delim_ipsec_l2tp_end, ipsec_ra_conn_file)
remove_confs(delim_ipsec_l2tp_begin, delim_ipsec_l2tp_end, ipsec_secrets_flie)
remove_confs(delim_ipsec_l2tp_begin, delim_ipsec_l2tp_end, ipsec_conf_flie)
diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py
index 9ca194edd..58f5b5a0e 100755
--- a/src/conf_mode/protocols_bfd.py
+++ b/src/conf_mode/protocols_bfd.py
@@ -163,10 +163,15 @@ def verify(bfd):
conf = Config()
for peer in bfd['new_peers']:
- # IPv6 peers require an explicit local address/interface combination
- if vyos.validate.is_ipv6(peer['remote']):
+ # IPv6 link local peers require an explicit local address/interface
+ if vyos.validate.is_ipv6_link_local(peer['remote']):
if not (peer['src_if'] and peer['src_addr']):
- raise ConfigError('BFD IPv6 peers require explicit local address and interface setting')
+ raise ConfigError('BFD IPv6 link-local peers require explicit local address and interface setting')
+
+ # IPv6 peers require an explicit local address
+ if vyos.validate.is_ipv6(peer['remote']):
+ if not peer['src_addr']:
+ raise ConfigError('BFD IPv6 peers require explicit local address setting')
# multihop require source address
if peer['multihop'] and not peer['src_addr']:
diff --git a/src/conf_mode/vrrp.py b/src/conf_mode/vrrp.py
index d31be4cfb..a09e55a2f 100755
--- a/src/conf_mode/vrrp.py
+++ b/src/conf_mode/vrrp.py
@@ -27,7 +27,7 @@ import vyos.keepalived
from vyos import ConfigError
-
+daemon_file = "/etc/default/keepalived"
config_file = "/etc/keepalived/keepalived.conf"
config_tmpl = """
@@ -37,6 +37,7 @@ config_tmpl = """
global_defs {
dynamic_interfaces
+ script_user root
}
{% for group in groups -%}
@@ -117,6 +118,10 @@ vrrp_instance {{ group.name }} {
{% if group.fault_script -%}
notify_fault "/usr/libexec/vyos/system/vrrp-script-wrapper.py --state fault --group {{ group.name }} --interface {{ group.interface }} {{ group.fault_script }}"
{% endif -%}
+
+ {% if group.stop_script -%}
+ notify_stop "/usr/libexec/vyos/system/vrrp-script-wrapper.py --state stop --group {{ group.name }} --interface {{ group.interface }} {{ group.stop_script }}"
+ {% endif -%}
}
{% endfor -%}
@@ -140,6 +145,14 @@ vrrp_sync_group {{ sync_group.name }} {
"""
+daemon_tmpl = """
+# Autogenerated by VyOS
+# Options to pass to keepalived
+
+# DAEMON_ARGS are appended to the keepalived command-line
+DAEMON_ARGS="--snmp"
+"""
+
def get_config():
vrrp_groups = []
sync_groups = []
@@ -178,6 +191,7 @@ def get_config():
group["master_script"] = config.return_value("transition-script master")
group["backup_script"] = config.return_value("transition-script backup")
group["fault_script"] = config.return_value("transition-script fault")
+ group["stop_script"] = config.return_value("transition-script stop")
if config.exists("no-preempt"):
group["preempt"] = False
@@ -308,9 +322,12 @@ def generate(data):
tmpl = jinja2.Template(config_tmpl)
config_text = tmpl.render({"groups": vrrp_groups, "sync_groups": sync_groups})
-
with open(config_file, 'w') as f:
f.write(config_text)
+
+ with open(daemon_file, 'w') as f:
+ f.write(daemon_tmpl)
+
return None
def apply(data):
diff --git a/src/op_mode/vrrp.py b/src/op_mode/vrrp.py
index 54e1bfb57..8d1369823 100755
--- a/src/op_mode/vrrp.py
+++ b/src/op_mode/vrrp.py
@@ -32,6 +32,7 @@ def print_summary():
# Replace with inotify or similar if it proves problematic
time.sleep(0.2)
json_data = vyos.keepalived.get_json_data()
+ vyos.keepalived.remove_vrrp_data("json")
except:
print("VRRP information is not available")
sys.exit(1)
@@ -63,6 +64,7 @@ def print_statistics():
time.sleep(0.2)
output = vyos.keepalived.get_statistics()
print(output)
+ vyos.keepalived.remove_vrrp_data("stats")
except:
print("VRRP statistics are not available")
sys.exit(1)
@@ -73,6 +75,7 @@ def print_state_data():
time.sleep(0.2)
output = vyos.keepalived.get_state_data()
print(output)
+ vyos.keepalived.remove_vrrp_data("state")
except:
print("VRRP information is not available")
sys.exit(1)
diff --git a/src/system/vrrp-script-wrapper.py b/src/system/vrrp-script-wrapper.py
index ccd640128..c28ecba55 100755
--- a/src/system/vrrp-script-wrapper.py
+++ b/src/system/vrrp-script-wrapper.py
@@ -23,7 +23,6 @@ import argparse
import syslog
import vyos.util
-import vyos.keepalived
parser = argparse.ArgumentParser()
@@ -44,38 +43,22 @@ if not args.script or not args.state or not args.group \
# to pass arguments to the script
args.script = " ".join(args.script)
-# Get the old state if it exists and compare it to the current state received
-# in command line options to avoid executing scripts if no real transition occured.
-# This is necessary because keepalived does not keep persistent state data even between
-# config reloads and will cheerfully execute everything whether it's required or not.
-
-old_state = vyos.keepalived.get_old_state(args.group)
-
-if (old_state is None) or (old_state != args.state):
- exitcode = 0
-
- # Run the script and save the new state
-
- # Change the process GID to the config owners group to avoid screwing up
- # running config permissions
- os.setgid(vyos.util.get_cfg_group_id())
-
- syslog.syslog(syslog.LOG_NOTICE, 'Running transition script {0} for VRRP group {1}'.format(args.script, args.group))
- try:
- ret = subprocess.call("%s %s %s %s" % ( args.script, args.state, args.interface, args.group), shell=True)
- if ret != 0:
- syslog.syslog(syslog.LOG_ERR, "Transition script {0} failed, exit status: {1}".format(args.script, ret))
- exitcode = ret
- except Exception as e:
- syslog.syslog(syslog.LOG_ERR, "Failed to execute transition script {0}: {1}".format(args.script, e))
- exitcode = 1
-
- if exitcode == 0:
- syslog.syslog(syslog.LOG_NOTICE, "Transition script {0} executed successfully".format(args.script))
-
- vyos.keepalived.save_state(args.group, args.state)
-else:
- syslog.syslog(syslog.LOG_NOTICE, "State of the group {0} has not changed, not running transition script".format(args.group))
+exitcode = 0
+# Change the process GID to the config owners group to avoid screwing up
+# running config permissions
+os.setgid(vyos.util.get_cfg_group_id())
+syslog.syslog(syslog.LOG_NOTICE, 'Running transition script {0} for VRRP group {1}'.format(args.script, args.group))
+try:
+ ret = subprocess.call("%s %s %s %s" % ( args.script, args.state, args.interface, args.group), shell=True)
+ if ret != 0:
+ syslog.syslog(syslog.LOG_ERR, "Transition script {0} failed, exit status: {1}".format(args.script, ret))
+ exitcode = ret
+except Exception as e:
+ syslog.syslog(syslog.LOG_ERR, "Failed to execute transition script {0}: {1}".format(args.script, e))
+ exitcode = 1
+
+if exitcode == 0:
+ syslog.syslog(syslog.LOG_NOTICE, "Transition script {0} executed successfully".format(args.script))
syslog.closelog()
sys.exit(exitcode)