summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/high-availability/keepalived.conf.tmpl (renamed from data/templates/vrrp/keepalived.conf.tmpl)57
-rw-r--r--interface-definitions/high-availability.xml.in (renamed from interface-definitions/vrrp.xml.in)144
-rwxr-xr-xsmoketest/scripts/cli/test_ha_virtual_server.py146
-rwxr-xr-xsmoketest/scripts/cli/test_ha_vrrp.py10
-rwxr-xr-xsrc/conf_mode/conntrack_sync.py2
-rwxr-xr-xsrc/conf_mode/high-availability.py (renamed from src/conf_mode/vrrp.py)63
6 files changed, 386 insertions, 36 deletions
diff --git a/data/templates/vrrp/keepalived.conf.tmpl b/data/templates/high-availability/keepalived.conf.tmpl
index 6585fc60b..817c65ff0 100644
--- a/data/templates/vrrp/keepalived.conf.tmpl
+++ b/data/templates/high-availability/keepalived.conf.tmpl
@@ -9,8 +9,8 @@ global_defs {
notify_fifo_script /usr/libexec/vyos/system/keepalived-fifo.py
}
-{% if group is defined and group is not none %}
-{% for name, group_config in group.items() if group_config.disable is not defined %}
+{% if vrrp is defined and vrrp.group is defined and vrrp.group is not none %}
+{% for name, group_config in vrrp.group.items() if group_config.disable is not defined %}
{% if group_config.health_check is defined and group_config.health_check.script is defined and group_config.health_check.script is not none %}
vrrp_script healthcheck_{{ name }} {
script "{{ group_config.health_check.script }}"
@@ -82,8 +82,8 @@ vrrp_instance {{ name }} {
{% endfor %}
{% endif %}
-{% if sync_group is defined and sync_group is not none %}
-{% for name, sync_group_config in sync_group.items() if sync_group_config.disable is not defined %}
+{% if vrrp is defined and vrrp.sync_group is defined and vrrp.sync_group is not none %}
+{% for name, sync_group_config in vrrp.sync_group.items() if sync_group_config.disable is not defined %}
vrrp_sync_group {{ name }} {
group {
{% if sync_group_config.member is defined and sync_group_config.member is not none %}
@@ -94,14 +94,14 @@ vrrp_sync_group {{ name }} {
}
{# Health-check scripts should be in section sync-group if member is part of the sync-group T4081 #}
-{% for name, group_config in group.items() if group_config.disable is not defined %}
+{% for name, group_config in vrrp.group.items() if group_config.disable is not defined %}
{% if group_config.health_check is defined and group_config.health_check.script is defined and group_config.health_check.script is not none and name in sync_group_config.member %}
track_script {
healthcheck_{{ name }}
}
{% endif %}
{% endfor %}
-{% if conntrack_sync_group is defined and conntrack_sync_group == name %}
+{% if vrrp.conntrack_sync_group is defined and vrrp.conntrack_sync_group == name %}
{% set vyos_helper = "/usr/libexec/vyos/vyos-vrrp-conntracksync.sh" %}
notify_master "{{ vyos_helper }} master {{ name }}"
notify_backup "{{ vyos_helper }} backup {{ name }}"
@@ -110,3 +110,48 @@ vrrp_sync_group {{ name }} {
}
{% endfor %}
{% endif %}
+
+# Virtual-server configuration
+{% if virtual_server is defined and virtual_server is not none %}
+{% for vserver, vserver_config in virtual_server.items() %}
+virtual_server {{ vserver }} {{ vserver_config.port }} {
+ delay_loop {{ vserver_config.delay_loop }}
+{% if vserver_config.algorithm == 'round-robin' %}
+ lb_algo rr
+{% elif vserver_config.algorithm == 'weighted-round-robin' %}
+ lb_algo wrr
+{% elif vserver_config.algorithm == 'least-connection' %}
+ lb_algo lc
+{% elif vserver_config.algorithm == 'weighted-least-connection' %}
+ lb_algo wlc
+{% elif vserver_config.algorithm == 'source-hashing' %}
+ lb_algo sh
+{% elif vserver_config.algorithm == 'destination-hashing' %}
+ lb_algo dh
+{% elif vserver_config.algorithm == 'locality-based-least-connection' %}
+ lb_algo lblc
+{% endif %}
+{% if vserver_config.forward_method == "nat" %}
+ lb_kind NAT
+{% elif vserver_config.forward_method == "direct" %}
+ lb_kind DR
+{% elif vserver_config.forward_method == "tunnel" %}
+ lb_kind TUN
+{% endif %}
+ persistence_timeout {{ vserver_config.persistence_timeout }}
+ protocol {{ vserver_config.protocol | upper }}
+{% if vserver_config.real_server is defined and vserver_config.real_server is not none %}
+{% for rserver, rserver_config in vserver_config.real_server.items() %}
+ real_server {{ rserver }} {{ rserver_config.port }} {
+ weight 1
+ {{ vserver_config.protocol | upper }}_CHECK {
+{% if rserver_config.connection_timeout is defined and rserver_config.connection_timeout is not none %}
+ connect_timeout {{ rserver_config.connection_timeout }}
+{% endif %}
+ }
+ }
+{% endfor %}
+{% endif %}
+}
+{% endfor %}
+{% endif %}
diff --git a/interface-definitions/vrrp.xml.in b/interface-definitions/high-availability.xml.in
index 53d79caac..f46343c76 100644
--- a/interface-definitions/vrrp.xml.in
+++ b/interface-definitions/high-availability.xml.in
@@ -1,13 +1,13 @@
<?xml version="1.0"?>
<interfaceDefinition>
- <node name="high-availability">
+ <node name="high-availability" owner="${vyos_conf_scripts_dir}/high-availability.py">
<properties>
+ <priority>800</priority> <!-- after all interfaces and conntrack-sync -->
<help>High availability settings</help>
</properties>
<children>
- <node name="vrrp" owner="${vyos_conf_scripts_dir}/vrrp.py">
+ <node name="vrrp">
<properties>
- <priority>800</priority> <!-- after all interfaces and conntrack-sync -->
<help>Virtual Router Redundancy Protocol settings</help>
</properties>
<children>
@@ -252,6 +252,144 @@
</tagNode>
</children>
</node>
+ <tagNode name="virtual-server">
+ <properties>
+ <help>Load-balancing virtual server address</help>
+ </properties>
+ <children>
+ <leafNode name="algorithm">
+ <properties>
+ <help>Schedule algorithm (default - least-connection)</help>
+ <completionHelp>
+ <list>round-robin weighted-round-robin least-connection weighted-least-connection source-hashing destination-hashing locality-based-least-connection</list>
+ </completionHelp>
+ <valueHelp>
+ <format>round-robin</format>
+ <description>Round robin</description>
+ </valueHelp>
+ <valueHelp>
+ <format>weighted-round-robin</format>
+ <description>Weighted round robin</description>
+ </valueHelp>
+ <valueHelp>
+ <format>least-connection</format>
+ <description>Least connection</description>
+ </valueHelp>
+ <valueHelp>
+ <format>weighted-least-connection</format>
+ <description>Weighted least connection</description>
+ </valueHelp>
+ <valueHelp>
+ <format>source-hashing</format>
+ <description>Source hashing</description>
+ </valueHelp>
+ <valueHelp>
+ <format>destination-hashing</format>
+ <description>Destination hashing</description>
+ </valueHelp>
+ <valueHelp>
+ <format>locality-based-least-connection</format>
+ <description>Locality-Based least connection</description>
+ </valueHelp>
+ <constraint>
+ <regex>^(round-robin|weighted-round-robin|least-connection|weighted-least-connection|source-hashing|destination-hashing|locality-based-least-connection)$</regex>
+ </constraint>
+ </properties>
+ <defaultValue>least-connection</defaultValue>
+ </leafNode>
+ <leafNode name="delay-loop">
+ <properties>
+ <help>Interval between health-checks (in seconds)</help>
+ <valueHelp>
+ <format>u32:1-600</format>
+ <description>Interval in seconds (default: 10)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-3600"/>
+ </constraint>
+ </properties>
+ <defaultValue>10</defaultValue>
+ </leafNode>
+ <leafNode name="forward-method">
+ <properties>
+ <help>Forwarding method (default: NAT)</help>
+ <completionHelp>
+ <list>direct nat tunnel</list>
+ </completionHelp>
+ <valueHelp>
+ <format>direct</format>
+ <description>Direct routing</description>
+ </valueHelp>
+ <valueHelp>
+ <format>nat</format>
+ <description>NAT</description>
+ </valueHelp>
+ <valueHelp>
+ <format>tunnel</format>
+ <description>Tunneling</description>
+ </valueHelp>
+ <constraint>
+ <regex>^(direct|nat|tunnel)$</regex>
+ </constraint>
+ </properties>
+ <defaultValue>nat</defaultValue>
+ </leafNode>
+ #include <include/port-number.xml.i>
+ <leafNode name="persistence-timeout">
+ <properties>
+ <help>Timeout for persistent connections</help>
+ <valueHelp>
+ <format>u32:1-86400</format>
+ <description>Timeout for persistent connections (default: 300)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-86400"/>
+ </constraint>
+ </properties>
+ <defaultValue>300</defaultValue>
+ </leafNode>
+ <leafNode name="protocol">
+ <properties>
+ <help>Protocol for port checks (default: TCP)</help>
+ <completionHelp>
+ <list>tcp udp</list>
+ </completionHelp>
+ <valueHelp>
+ <format>tcp</format>
+ <description>TCP</description>
+ </valueHelp>
+ <valueHelp>
+ <format>udp</format>
+ <description>UDP</description>
+ </valueHelp>
+ <constraint>
+ <regex>^(tcp|udp)$</regex>
+ </constraint>
+ </properties>
+ <defaultValue>tcp</defaultValue>
+ </leafNode>
+ <tagNode name="real-server">
+ <properties>
+ <help>Real server address</help>
+ </properties>
+ <children>
+ #include <include/port-number.xml.i>
+ <leafNode name="connection-timeout">
+ <properties>
+ <help>Server connection timeout</help>
+ <valueHelp>
+ <format>u32:1-86400</format>
+ <description>Connection timeout to remote server</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-86400"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </tagNode>
</children>
</node>
</interfaceDefinition>
diff --git a/smoketest/scripts/cli/test_ha_virtual_server.py b/smoketest/scripts/cli/test_ha_virtual_server.py
new file mode 100755
index 000000000..e3a91283e
--- /dev/null
+++ b/smoketest/scripts/cli/test_ha_virtual_server.py
@@ -0,0 +1,146 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021-2022 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 unittest
+
+from base_vyostest_shim import VyOSUnitTestSHIM
+
+from vyos.configsession import ConfigSessionError
+from vyos.ifconfig.vrrp import VRRP
+from vyos.util import cmd
+from vyos.util import process_named_running
+from vyos.util import read_file
+from vyos.template import inc_ip
+
+PROCESS_NAME = 'keepalived'
+KEEPALIVED_CONF = VRRP.location['config']
+base_path = ['high-availability']
+vrrp_interface = 'eth1'
+
+class TestHAVirtualServer(VyOSUnitTestSHIM.TestCase):
+ def tearDown(self):
+ # Check for running process
+ self.assertTrue(process_named_running(PROCESS_NAME))
+
+ self.cli_delete(['interfaces', 'ethernet', vrrp_interface, 'address'])
+ self.cli_delete(base_path)
+ self.cli_commit()
+
+ # Process must be terminated after deleting the config
+ self.assertFalse(process_named_running(PROCESS_NAME))
+
+ def test_01_ha_virtual_server(self):
+ algo = 'least-connection'
+ delay = '10'
+ method = 'nat'
+ persistence_timeout = '600'
+ vip = '203.0.113.111'
+ vport = '2222'
+ rservers = ['192.0.2.21', '192.0.2.22', '192.0.2.23']
+ rport = '22'
+ proto = 'tcp'
+ connection_timeout = '30'
+
+ vserver_base = base_path + ['virtual-server']
+
+ self.cli_set(vserver_base + [vip, 'algorithm', algo])
+ self.cli_set(vserver_base + [vip, 'delay-loop', delay])
+ self.cli_set(vserver_base + [vip, 'forward-method', method])
+ self.cli_set(vserver_base + [vip, 'persistence-timeout', persistence_timeout])
+ self.cli_set(vserver_base + [vip, 'port', vport])
+ self.cli_set(vserver_base + [vip, 'protocol', proto])
+ for rs in rservers:
+ self.cli_set(vserver_base + [vip, 'real-server', rs, 'connection-timeout', connection_timeout])
+ self.cli_set(vserver_base + [vip, 'real-server', rs, 'port', rport])
+
+ # commit changes
+ self.cli_commit()
+
+ config = read_file(KEEPALIVED_CONF)
+
+ self.assertIn(f'delay_loop {delay}', config)
+ self.assertIn(f'lb_algo lc', config)
+ self.assertIn(f'lb_kind {method.upper()}', config)
+ self.assertIn(f'persistence_timeout {persistence_timeout}', config)
+ self.assertIn(f'protocol {proto.upper()}', config)
+ for rs in rservers:
+ self.assertIn(f'real_server {rs} {rport}', config)
+ self.assertIn(f'{proto.upper()}_CHECK', config)
+ self.assertIn(f'connect_timeout {connection_timeout}', config)
+
+ def test_02_ha_virtual_server_and_vrrp(self):
+ algo = 'least-connection'
+ delay = '15'
+ method = 'nat'
+ persistence_timeout = '300'
+ vip = '203.0.113.222'
+ vport = '22322'
+ rservers = ['192.0.2.11', '192.0.2.12']
+ rport = '222'
+ proto = 'tcp'
+ connection_timeout = '23'
+ group = 'VyOS'
+ vrid = '99'
+
+ vrrp_base = base_path + ['vrrp', 'group']
+ vserver_base = base_path + ['virtual-server']
+
+ self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'address', '203.0.113.10/24'])
+
+ # VRRP config
+ self.cli_set(vrrp_base + [group, 'description', group])
+ self.cli_set(vrrp_base + [group, 'interface', vrrp_interface])
+ self.cli_set(vrrp_base + [group, 'address', vip + '/24'])
+ self.cli_set(vrrp_base + [group, 'vrid', vrid])
+
+ # Virtual-server config
+ self.cli_set(vserver_base + [vip, 'algorithm', algo])
+ self.cli_set(vserver_base + [vip, 'delay-loop', delay])
+ self.cli_set(vserver_base + [vip, 'forward-method', method])
+ self.cli_set(vserver_base + [vip, 'persistence-timeout', persistence_timeout])
+ self.cli_set(vserver_base + [vip, 'port', vport])
+ self.cli_set(vserver_base + [vip, 'protocol', proto])
+ for rs in rservers:
+ self.cli_set(vserver_base + [vip, 'real-server', rs, 'connection-timeout', connection_timeout])
+ self.cli_set(vserver_base + [vip, 'real-server', rs, 'port', rport])
+
+ # commit changes
+ self.cli_commit()
+
+ config = read_file(KEEPALIVED_CONF)
+
+ # Keepalived vrrp
+ self.assertIn(f'# {group}', config)
+ self.assertIn(f'interface {vrrp_interface}', config)
+ self.assertIn(f'virtual_router_id {vrid}', config)
+ self.assertIn(f'priority 100', config) # default value
+ self.assertIn(f'advert_int 1', config) # default value
+ self.assertIn(f'preempt_delay 0', config) # default value
+
+ # Keepalived virtual-server
+ self.assertIn(f'delay_loop {delay}', config)
+ self.assertIn(f'lb_algo lc', config)
+ self.assertIn(f'lb_kind {method.upper()}', config)
+ self.assertIn(f'persistence_timeout {persistence_timeout}', config)
+ self.assertIn(f'protocol {proto.upper()}', config)
+ for rs in rservers:
+ self.assertIn(f'real_server {rs} {rport}', config)
+ self.assertIn(f'{proto.upper()}_CHECK', config)
+ self.assertIn(f'connect_timeout {connection_timeout}', config)
+
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_ha_vrrp.py b/smoketest/scripts/cli/test_ha_vrrp.py
index 23a9f7796..a37ce7e64 100755
--- a/smoketest/scripts/cli/test_ha_vrrp.py
+++ b/smoketest/scripts/cli/test_ha_vrrp.py
@@ -27,7 +27,7 @@ from vyos.template import inc_ip
PROCESS_NAME = 'keepalived'
KEEPALIVED_CONF = VRRP.location['config']
-base_path = ['high-availability', 'vrrp']
+base_path = ['high-availability']
vrrp_interface = 'eth1'
groups = ['VLAN77', 'VLAN78', 'VLAN201']
@@ -56,7 +56,7 @@ class TestVRRP(VyOSUnitTestSHIM.TestCase):
for group in groups:
vlan_id = group.lstrip('VLAN')
vip = f'100.64.{vlan_id}.1/24'
- group_base = base_path + ['group', group]
+ group_base = base_path + ['vrrp', 'group', group]
self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', inc_ip(vip, 1) + '/' + vip.split('/')[-1]])
@@ -91,7 +91,7 @@ class TestVRRP(VyOSUnitTestSHIM.TestCase):
for group in groups:
vlan_id = group.lstrip('VLAN')
vip = f'100.64.{vlan_id}.1/24'
- group_base = base_path + ['group', group]
+ group_base = base_path + ['vrrp', 'group', group]
self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', inc_ip(vip, 1) + '/' + vip.split('/')[-1]])
@@ -138,7 +138,7 @@ class TestVRRP(VyOSUnitTestSHIM.TestCase):
for group in groups:
vlan_id = group.lstrip('VLAN')
vip = f'100.64.{vlan_id}.1/24'
- group_base = base_path + ['group', group]
+ group_base = base_path + ['vrrp', 'group', group]
self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', inc_ip(vip, 1) + '/' + vip.split('/')[-1]])
@@ -146,7 +146,7 @@ class TestVRRP(VyOSUnitTestSHIM.TestCase):
self.cli_set(group_base + ['address', vip])
self.cli_set(group_base + ['vrid', vlan_id])
- self.cli_set(base_path + ['sync-group', sync_group, 'member', group])
+ self.cli_set(base_path + ['vrrp', 'sync-group', sync_group, 'member', group])
# commit changes
self.cli_commit()
diff --git a/src/conf_mode/conntrack_sync.py b/src/conf_mode/conntrack_sync.py
index f82a077e6..8f9837c2b 100755
--- a/src/conf_mode/conntrack_sync.py
+++ b/src/conf_mode/conntrack_sync.py
@@ -36,7 +36,7 @@ airbag.enable()
config_file = '/run/conntrackd/conntrackd.conf'
def resync_vrrp():
- tmp = run('/usr/libexec/vyos/conf_mode/vrrp.py')
+ tmp = run('/usr/libexec/vyos/conf_mode/high-availability.py')
if tmp > 0:
print('ERROR: error restarting VRRP daemon!')
diff --git a/src/conf_mode/vrrp.py b/src/conf_mode/high-availability.py
index c72efc61f..7d51bb393 100755
--- a/src/conf_mode/vrrp.py
+++ b/src/conf_mode/high-availability.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2021 VyOS maintainers and contributors
+# Copyright (C) 2018-2022 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
@@ -40,33 +40,41 @@ def get_config(config=None):
else:
conf = Config()
- base = ['high-availability', 'vrrp']
+ base = ['high-availability']
+ base_vrrp = ['high-availability', 'vrrp']
if not conf.exists(base):
return None
- vrrp = conf.get_config_dict(base, key_mangling=('-', '_'),
+ ha = conf.get_config_dict(base, key_mangling=('-', '_'),
get_first_key=True, no_tag_node_value_mangle=True)
# We have gathered the dict representation of the CLI, but there are default
# options which we need to update into the dictionary retrived.
- if 'group' in vrrp:
- default_values = defaults(base + ['group'])
- for group in vrrp['group']:
- vrrp['group'][group] = dict_merge(default_values, vrrp['group'][group])
+ if 'vrrp' in ha:
+ if 'group' in ha['vrrp']:
+ default_values_vrrp = defaults(base_vrrp + ['group'])
+ for group in ha['vrrp']['group']:
+ ha['vrrp']['group'][group] = dict_merge(default_values_vrrp, ha['vrrp']['group'][group])
+
+ # Merge per virtual-server default values
+ if 'virtual_server' in ha:
+ default_values = defaults(base + ['virtual-server'])
+ for vs in ha['virtual_server']:
+ ha['virtual_server'][vs] = dict_merge(default_values, ha['virtual_server'][vs])
## Get the sync group used for conntrack-sync
conntrack_path = ['service', 'conntrack-sync', 'failover-mechanism', 'vrrp', 'sync-group']
if conf.exists(conntrack_path):
- vrrp['conntrack_sync_group'] = conf.return_value(conntrack_path)
+ ha['conntrack_sync_group'] = conf.return_value(conntrack_path)
- return vrrp
+ return ha
-def verify(vrrp):
- if not vrrp:
+def verify(ha):
+ if not ha:
return None
used_vrid_if = []
- if 'group' in vrrp:
- for group, group_config in vrrp['group'].items():
+ if 'vrrp' in ha and 'group' in ha['vrrp']:
+ for group, group_config in ha['vrrp']['group'].items():
# Check required fields
if 'vrid' not in group_config:
raise ConfigError(f'VRID is required but not set in VRRP group "{group}"')
@@ -119,24 +127,37 @@ def verify(vrrp):
if is_ipv4(group_config['peer_address']):
raise ConfigError(f'VRRP group "{group}" uses IPv6 but peer-address is IPv4!')
# Check sync groups
- if 'sync_group' in vrrp:
- for sync_group, sync_config in vrrp['sync_group'].items():
+ if 'vrrp' in ha and 'sync_group' in ha['vrrp']:
+ for sync_group, sync_config in ha['vrrp']['sync_group'].items():
if 'member' in sync_config:
for member in sync_config['member']:
- if member not in vrrp['group']:
+ if member not in ha['vrrp']['group']:
raise ConfigError(f'VRRP sync-group "{sync_group}" refers to VRRP group "{member}", '\
'but it does not exist!')
-def generate(vrrp):
- if not vrrp:
+ # Virtual-server
+ if 'virtual_server' in ha:
+ for vs, vs_config in ha['virtual_server'].items():
+ if 'port' not in vs_config:
+ raise ConfigError(f'Port is required but not set for virtual-server "{vs}"')
+ if 'real_server' not in vs_config:
+ raise ConfigError(f'Real-server ip is required but not set for virtual-server "{vs}"')
+ # Real-server
+ for rs, rs_config in vs_config['real_server'].items():
+ if 'port' not in rs_config:
+ raise ConfigError(f'Port is required but not set for virtual-server "{vs}" real-server "{rs}"')
+
+
+def generate(ha):
+ if not ha:
return None
- render(VRRP.location['config'], 'vrrp/keepalived.conf.tmpl', vrrp)
+ render(VRRP.location['config'], 'high-availability/keepalived.conf.tmpl', ha)
return None
-def apply(vrrp):
+def apply(ha):
service_name = 'keepalived.service'
- if not vrrp:
+ if not ha:
call(f'systemctl stop {service_name}')
return None