diff --git a/libvirt/tests/cfg/virtual_network/network/virsh_cmds/virsh_net_desc.cfg b/libvirt/tests/cfg/virtual_network/network/virsh_cmds/virsh_net_desc.cfg
new file mode 100644
index 00000000000..d55a4c72daf
--- /dev/null
+++ b/libvirt/tests/cfg/virtual_network/network/virsh_cmds/virsh_net_desc.cfg
@@ -0,0 +1,47 @@
+- virtual_network.network.net_desc:
+ type = virsh_net_desc
+ net_name = "default"
+ func_supported_since_libvirt_ver = (9, 8, 0)
+ variants update_method:
+ - cmdline:
+ - edit_space:
+ variants update_item:
+ - title:
+ net_title = "network title"
+ execute_cmd =" --title '${net_title}'"
+ expected_xml = '
${net_title}'
+ expected_str = ${net_title}
+ net_title_update = "network title update"
+ execute_update_cmd =" --title '${net_title_update}'"
+ expected_update_xml = '${net_title_update}'
+ expected_update_str = ${net_title_update}
+ remove_opt = " --title '' "
+ removed_msg = "No title for network: default"
+ get_cmd = ' --title'
+ - description:
+ net_desc = "network description"
+ execute_cmd = "${net_desc}"
+ expected_xml = '${net_desc}'
+ expected_str = ${net_desc}
+ net_desc_update = "network description update"
+ execute_update_cmd =" ${net_desc_update}"
+ expected_update_xml = '${net_desc_update}'
+ expected_update_str = ${net_desc_update}
+ remove_opt = "''"
+ removed_msg = "No description for network: default"
+ get_cmd = ' '
+ variants network_states:
+ - active_net:
+ - inactive_net:
+ variants:
+ - live:
+ opt = ' --live'
+ inactive_net:
+ error_msg = "error: Requested operation is not valid: network is not running"
+ - config:
+ opt = ' --config'
+ - current:
+ opt = ' --current'
+ - opt_none:
+ no inactive_net
+ opt = ' '
diff --git a/libvirt/tests/src/virtual_network/network/virsh_cmd/virsh_net_desc.py b/libvirt/tests/src/virtual_network/network/virsh_cmd/virsh_net_desc.py
new file mode 100644
index 00000000000..6b139e7d417
--- /dev/null
+++ b/libvirt/tests/src/virtual_network/network/virsh_cmd/virsh_net_desc.py
@@ -0,0 +1,196 @@
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#
+# Copyright Redhat
+#
+# SPDX-License-Identifier: GPL-2.0
+
+# Author: Nannan Li
+#
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+import aexpect
+import re
+
+from virttest import libvirt_version
+from virttest import remote
+from virttest import virsh
+from virttest.libvirt_xml import vm_xml
+from virttest.utils_test import libvirt
+from virttest.utils_libvirt import libvirt_network
+from virttest.libvirt_xml.network_xml import NetworkXML
+
+VIRSH_ARGS = {'ignore_status': False, 'debug': True}
+
+
+def virsh_net_desc(test, params, cmd):
+ """
+ Edit description or title with virsh net-desc by cmdline or
+ edit space according to scenario.
+
+ :params: test: test object.
+ :params: params: cfg parameter dict.
+ :params: cmd: virsh desc updating cmd, delete all content if cmd is empty.
+ """
+ update_method = params.get("update_method")
+ update_options = params.get("opt")
+ net_name = params.get("net_name")
+ error_msg = params.get("error_msg")
+
+ if update_method == "cmdline":
+ result = virsh.net_desc(net_name, extra=cmd+update_options, debug=True)
+ libvirt.check_exit_status(result, error_msg)
+
+ elif update_method == "edit_space":
+ send_cmd = r"virsh net-desc %s %s --edit %s" % (net_name, cmd, update_options)
+ test.log.debug("Send cmd: %s", send_cmd)
+ session = aexpect.ShellSession("sudo -s")
+ try:
+ session.sendline(send_cmd)
+ session.send('\x1b')
+ session.send('ZZ')
+ remote.handle_prompts(session, None, None, r"[\#\$]\s*$")
+ except Exception as e:
+ if not error_msg:
+ test.fail("Error occurred: %s when virsh net-desc --edit" % str(e))
+ session.close()
+
+
+def check_net_desc_result(test, params, get_cmd, expected_string, existed=True):
+ """
+ Get and check desc or title by virsh net-desc cmd.
+
+ :params: test, test object.
+ :params: params, params object.
+ :params: get_cmd, the get cmd for virsh net-desc.
+ :params: expected_string, expected result string.
+ :params: existed, the flag of expected result string exist or not.
+ """
+ net_name = params.get("net_name")
+ update_options = params.get("opt")
+
+ result = virsh.net_desc(net_name, extra=get_cmd+update_options, debug=True)
+ if existed:
+ if result.stdout.strip() != expected_string:
+ test.fail('Expect "%s" was existed.' % expected_string)
+ else:
+ if result.stderr.strip() != expected_string:
+ test.fail('Expect "%s" was not existed.' % expected_string)
+ test.log.debug("Check '%s' PASS in virsh net-desc", expected_string)
+
+
+def _confirm_existed(params, dumpxml_opt, check_remove):
+ """
+ Get existed value according to the scenario.
+
+ :params: params, params object.
+ :params: dumpxml_opt, virsh net-dumpxml option, '--inactive' or ''
+ :params: check_remove, check net-dumpxml after removing.
+ :return: expected xml was existed flag.
+ """
+ opt = params.get('opt')
+ network_states = params.get('network_states')
+
+ existed = True
+ if ((dumpxml_opt == " " and opt == ' --config' and network_states == 'active_net')
+ or (dumpxml_opt == " --inactive" and opt == " --live")
+ or (dumpxml_opt == " --inactive" and network_states == 'active_net' and opt in [' --current', ' '])
+ or check_remove):
+ existed = False
+
+ return existed
+
+
+def check_net_dumpxml(test, params, expected_xml, check_remove=False):
+ """
+ Check if expected xml in virsh net-dumpxml.
+
+ :params: test, test object.
+ :params: params, params object.
+ :params: expected_xml, expected xml string.
+ :params: check_remove, check net-dumpxml after removing, default False
+ """
+ net_name = params.get("net_name")
+
+ dumpxml_opt = [" ", " --inactive"]
+ for dump in dumpxml_opt:
+ existed = _confirm_existed(params, dump, check_remove)
+ result = virsh.net_dumpxml(net_name, dump, debug=True).stdout.strip()
+ if existed:
+ if not re.findall(expected_xml, result):
+ test.fail('Expect %s was existed' % expected_xml)
+ else:
+ if re.findall(expected_xml, result):
+ test.fail('Expect %s was not existed' % expected_xml)
+ test.log.debug("Checked '%s' PASS in active and inactive xml" % expected_xml)
+
+
+def run(test, params, env):
+ """
+ Test 'virsh net-desc' with different options to show or modify network description
+ or title.
+ """
+ def setup_test():
+ """
+ Prepare network status.
+ """
+ test.log.info("TEST_SETUP: Prepare network status.")
+ libvirt_network.ensure_default_network()
+ if network_states == "inactive_net":
+ virsh.net_destroy(net_name, **VIRSH_ARGS)
+
+ def run_test():
+ """
+ 1. Update and remove network description or title.
+ 2. Check description xml is existed or removed.
+ """
+ test.log.info("TEST_STEP1:Set a %s xml for network." % update_item)
+ virsh_net_desc(test, params, execute_cmd)
+ if error_msg:
+ return
+
+ test.log.info("TEST_STEP2: Check correct %s xml in network." % update_item)
+ check_net_desc_result(test, params, get_cmd, expected_str)
+ check_net_dumpxml(test, params, expected_xml)
+
+ test.log.info("TEST_STEP3-4: Modify and check %s xml in network." % update_item)
+ virsh_net_desc(test, params, execute_update_cmd)
+ check_net_desc_result(test, params, get_cmd, expected_update_str)
+ check_net_dumpxml(test, params, expected_update_xml)
+
+ test.log.info("TEST_STEP5-6: Remove and check %s xml in network." % update_item)
+ virsh_net_desc(test, params, remove_opt)
+ check_net_desc_result(test, params, get_cmd, removed_msg)
+ check_net_dumpxml(test, params, expected_update_xml, check_remove=True)
+
+ def teardown_test():
+ """
+ Clean data.
+ """
+ test.log.info("TEST_TEARDOWN: Clean up env.")
+ bk_net.sync()
+ bkxml.sync()
+ libvirt_network.ensure_default_network()
+
+ libvirt_version.is_libvirt_feature_supported(params)
+ vm_name = params.get('main_vm')
+ vmxml = vm_xml.VMXML.new_from_inactive_dumpxml(vm_name)
+ bkxml = vmxml.copy()
+ default_net = NetworkXML.new_from_net_dumpxml('default')
+ bk_net = default_net.copy()
+
+ net_name = params.get("net_name")
+ network_states = params.get("network_states")
+ update_item = params.get("update_item")
+ error_msg = params.get("error_msg")
+ remove_opt = params.get("remove_opt")
+ removed_msg = params.get("removed_msg")
+ get_cmd = params.get("get_cmd")
+ execute_cmd, execute_update_cmd = params.get('execute_cmd'), params.get("execute_update_cmd")
+ expected_str, expected_update_str = params.get("expected_str"), params.get("expected_update_str")
+ expected_xml, expected_update_xml = params.get("expected_xml"), params.get("expected_update_xml")
+
+ try:
+ setup_test()
+ run_test()
+
+ finally:
+ teardown_test()