mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 11:16:47 +00:00
feat(instances): a VM's boot settings as switches and its boot order as one list
The Boot pane shows Autostart and the boot menu as switches that save on change, and the boot order as one list of the VM's disks, CD-ROM drives and NICs: the devices in the order first, checked and numbered, the others below. Up and Down move the checked devices; Apply order saves. - The boot menu can be changed while the VM runs: it is in the persistent definition, so it applies at the next start, as the boot order does. - The boot order editor no longer needs the boot menu enabled. - A VM that boots by device type (<os><boot dev='hd'/>) names those types; checking devices replaces that order. - An order is refused while a device the list does not show (a host device, a floppy) has a boot place, so Apply does not drop it; the message points to the XML editor. - An empty order is refused with a message instead of being ignored. - get_bootorder gives a NIC's full MAC as its target. - The unused jQuery multiselect plugin is removed.
This commit is contained in:
parent
a8f445d580
commit
20cfc25367
10 changed files with 168 additions and 163 deletions
|
|
@ -201,6 +201,24 @@ class Instance(models.Model):
|
|||
def config_boot_order(self) -> dict:
|
||||
return self.proxy.get_bootorder(config=True)
|
||||
|
||||
@cached_property
|
||||
def config_boot_devices(self) -> list[dict]:
|
||||
"""The disks, CD-ROM drives and NICs the boot order editor lists, those
|
||||
in the boot order first in their position; value is as posted, as in
|
||||
"disk:vda"."""
|
||||
order = [f"{d['dev']}:{d['target']}" for position, d in sorted(self.config_boot_order.items())]
|
||||
devices = (
|
||||
[{"value": f"disk:{d['dev']}", "name": d["dev"], "kind": _("Disk"), "source": d["image"]}
|
||||
for d in self.config_disks]
|
||||
+ [{"value": f"cdrom:{d['dev']}", "name": d["dev"], "kind": _("CD-ROM"), "source": d["image"]}
|
||||
for d in self.config_media]
|
||||
+ [{"value": f"network:{n['mac']}", "name": n["mac"], "kind": _("NIC"), "source": n["nic"]}
|
||||
for n in self.config_networks]
|
||||
)
|
||||
for device in devices:
|
||||
device["checked"] = device["value"] in order
|
||||
return sorted(devices, key=lambda d: order.index(d["value"]) if d["checked"] else len(order))
|
||||
|
||||
@cached_property
|
||||
def config_video_model(self) -> str | None:
|
||||
"""Video model and console settings of the persistent definition,
|
||||
|
|
|
|||
|
|
@ -226,63 +226,39 @@
|
|||
});
|
||||
</script>
|
||||
<script>
|
||||
function set_orderlist(obj){
|
||||
var result = '';
|
||||
$('#b_order label input:checked').each(function () {
|
||||
if (result != '') result += ',';
|
||||
result += $(this).val();
|
||||
});
|
||||
obj.val(result);
|
||||
// the boot order: the checked devices, top to bottom, as "disk:vda,cdrom:sda"
|
||||
function set_orderlist() {
|
||||
var order = $('#b_order input:checked').map(function () { return this.value; }).get();
|
||||
$('#bootorder').val(order.join(','));
|
||||
}
|
||||
$(document).ready(function () {
|
||||
{# Boot Order Arragements #}
|
||||
jQuery.fn.multiselect = function() {
|
||||
$(this).each(function() {
|
||||
var checkboxes = $(this).find("input:checkbox");
|
||||
checkboxes.each(function() {
|
||||
var checkbox = $(this);
|
||||
// Highlight pre-selected checkboxes
|
||||
if (checkbox.prop("checked"))
|
||||
checkbox.parent().addClass("multiselect-on");
|
||||
// Highlight checkboxes that the user selects
|
||||
checkbox.click(function() {
|
||||
if (checkbox.prop("checked"))
|
||||
checkbox.parent().addClass("multiselect-on");
|
||||
else
|
||||
checkbox.parent().removeClass("multiselect-on");
|
||||
});
|
||||
$(function () {
|
||||
set_orderlist();
|
||||
$('#b_order').on('change', 'input', set_orderlist);
|
||||
|
||||
$('#boot_order_up').on('click', function () {
|
||||
$('#b_order label input:checked').each(function () {
|
||||
var label = $(this).parent();
|
||||
var newPos = label.index() - 1;
|
||||
if (newPos > -1) {
|
||||
$('#b_order label').eq(newPos).before(label);
|
||||
}
|
||||
});
|
||||
set_orderlist();
|
||||
});
|
||||
};
|
||||
$(function() {
|
||||
$(".multiselect").multiselect();
|
||||
});
|
||||
|
||||
$('#boot_order_up').bind('click', function() {
|
||||
$('#b_order label input:checked').each( function() {
|
||||
var label = $(this).parent();
|
||||
var newPos = label.index() - 1;
|
||||
if (newPos > -1) {
|
||||
// moved, not rebuilt: the checkbox keeps its handler
|
||||
$('#b_order label').eq(newPos).before(label);
|
||||
}
|
||||
$('#boot_order_down').on('click', function () {
|
||||
var countOptions = $('#b_order label').length;
|
||||
var countSelected = $('#b_order label input:checked').length;
|
||||
$('#b_order label input:checked').each(function () {
|
||||
var label = $(this).parent();
|
||||
var newPos = label.index() + countSelected;
|
||||
if (newPos < countOptions) {
|
||||
$('#b_order label').eq(newPos).after(label);
|
||||
}
|
||||
});
|
||||
set_orderlist();
|
||||
});
|
||||
set_orderlist($("#bootorder"));
|
||||
});
|
||||
|
||||
$('#boot_order_down').bind('click', function() {
|
||||
var countOptions = $('#b_order label').length;
|
||||
var countSelected = $('#b_order label input:checked').length;
|
||||
$('#b_order label input:checked').each( function() {
|
||||
var label = $(this).parent();
|
||||
var newPos = label.index() + countSelected;
|
||||
if (newPos < countOptions) {
|
||||
$('#b_order label').eq(newPos).after(label);
|
||||
}
|
||||
});
|
||||
set_orderlist($("#bootorder"));
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
$(function () {
|
||||
|
|
|
|||
|
|
@ -92,82 +92,57 @@
|
|||
{% endif %}
|
||||
{% if request.user.is_superuser %}
|
||||
<div role="tabpanel" class="tab-pane active" id="boot_opt">
|
||||
<p class="fw-bold">{% trans 'Autostart' %}</p>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 text-center">
|
||||
<p>{% trans "Autostart your instance when host server is power on " %}</p>
|
||||
{% if instance.autostart == 0 %}
|
||||
<form action="{% url 'instances:set_autostart' instance.id %}" method="post" class="d-inline">{% csrf_token %}
|
||||
<button type="submit" class="btn btn-primary">{% trans "Enable" %}</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<form action="{% url 'instances:unset_autostart' instance.id %}" method="post" class="d-inline">{% csrf_token %}
|
||||
<button type="submit" class="btn btn-danger">{% trans "Disable" %}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
<section class="mb-4" aria-labelledby="startup-heading">
|
||||
<h2 class="h6" id="startup-heading">{% trans "Startup settings" %}</h2>
|
||||
<div class="list-group">
|
||||
<div class="list-group-item py-3">
|
||||
<form action="{% if instance.autostart %}{% url 'instances:unset_autostart' instance.id %}{% else %}{% url 'instances:set_autostart' instance.id %}{% endif %}" method="post">{% csrf_token %}
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="autostart" aria-describedby="autostart-help"{% if instance.autostart %} checked{% endif %} onchange="this.form.submit()">
|
||||
<label class="form-check-label fw-semibold" for="autostart">{% trans "Autostart" %}</label>
|
||||
</div>
|
||||
<p class="small text-body-secondary mb-0 mt-1" id="autostart-help">{% trans "Start this VM when the host starts." %}</p>
|
||||
</form>
|
||||
</div>
|
||||
<div class="list-group-item py-3">
|
||||
<form action="{% if instance.config_bootmenu %}{% url 'instances:unset_bootmenu' instance.id %}{% else %}{% url 'instances:set_bootmenu' instance.id %}{% endif %}" method="post">{% csrf_token %}
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="bootmenu" aria-describedby="bootmenu-help"{% if instance.config_bootmenu %} checked{% endif %} onchange="this.form.submit()">
|
||||
<label class="form-check-label fw-semibold" for="bootmenu">{% trans "Boot menu" %}</label>
|
||||
</div>
|
||||
<p class="small text-body-secondary mb-0 mt-1" id="bootmenu-help">{% trans "Show a device chooser when the VM starts." %}</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<p class="fw-bold">{% trans 'Boot Order' %}</p>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 text-center">
|
||||
{% if instance.status == 5 %}
|
||||
<p>{% trans "Enable Boot Menu for your instance when it starts up " %}
|
||||
{% if instance.config_bootmenu == 0 %}
|
||||
<form action="{% url 'instances:set_bootmenu' instance.id %}" method="post" role="form" aria-label="Enable/disable instance boot order form">{% csrf_token %}
|
||||
<input type="submit" class="btn btn-primary" name="set_bootmenu" title="{% trans 'Show boot menu' %}" value="{% trans "Enable" %}">
|
||||
</form>
|
||||
{% else %}
|
||||
<form action="{% url 'instances:unset_bootmenu' instance.id %}" method="post" role="form" aria-label="Enable/disable instance boot order form">{% csrf_token %}
|
||||
<input type="submit" class="btn btn-danger" name="unset_bootmenu" title="{% trans 'Hide boot menu' %}" value="{% trans "Disable" %}">
|
||||
</form>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if instance.config_bootmenu == 0 %}
|
||||
<p>**** {% trans "Please shutdown instance to modify boot menu" %} ****</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if instance.config_bootmenu == 1 %}
|
||||
<div class="d-flex justify-content-center">
|
||||
<div class="col-sm-6 bg-light rounded shadow-sm">
|
||||
{% for idx, val in instance.config_boot_order.items %}
|
||||
<label>{{ idx|add:1 }}:{{ val.target }}, </label>
|
||||
</section>
|
||||
<section aria-labelledby="boot-order-heading">
|
||||
<h2 class="h6" id="boot-order-heading">{% trans "Boot order" %}</h2>
|
||||
<p class="small text-body-secondary" id="boot-order-help">{% trans "Checked devices boot from top to bottom. Up and Down move all checked devices." %}</p>
|
||||
{% if instance.config_boot_order and not instance.config_boot_devices.0.checked %}
|
||||
<p class="small text-body-secondary" id="boot-by-type">
|
||||
{% trans "The VM now boots by device type:" %}
|
||||
{% for b in instance.config_boot_order.values %}{{ b.target }}{% if not forloop.last %}, {% endif %}{% endfor %}.
|
||||
{% trans "Checking devices replaces this order." %}
|
||||
</p>
|
||||
{% endif %}
|
||||
<form action="{% url 'instances:set_bootorder' instance.id %}" method="post" aria-labelledby="boot-order-heading" aria-describedby="boot-order-help">{% csrf_token %}
|
||||
<input type="hidden" id="bootorder" name="bootorder">
|
||||
<div id="b_order" class="list-group boot-sequence mb-3">
|
||||
{% for device in instance.config_boot_devices %}
|
||||
<label class="list-group-item d-flex align-items-center gap-3">
|
||||
<input class="form-check-input flex-shrink-0 m-0" type="checkbox" value="{{ device.value }}"{% if device.checked %} checked{% endif %}>
|
||||
<span class="boot-rank small text-body-secondary" aria-hidden="true"></span>
|
||||
<span class="text-break"><strong>{{ device.name }}</strong><span class="d-block small text-body-secondary">{{ device.kind }} · {{ device.source|default:_("empty") }}</span></span>
|
||||
</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<form action="{% url 'instances:set_bootorder' instance.id %}" method="post" role="form" aria-label="Boot order edit form">{% csrf_token %}
|
||||
<input id="bootorder" name="bootorder" hidden>
|
||||
<div class="d-flex justify-content-center">
|
||||
<div id="b_order" class="list-group me-3">
|
||||
{% for disk in instance.config_disks %}
|
||||
<label class="list-group-item"><input class="form-check-input me-2" type="checkbox" name="disk:{{ disk.dev }}" value="disk:{{ disk.dev }}" onclick="set_orderlist($('#bootorder'))">{{ disk.dev }} - {{ disk.image }}</label>
|
||||
{% endfor %}
|
||||
{% for cd in instance.config_media %}
|
||||
<label class="list-group-item"><input class="form-check-input me-2" type="checkbox" name="cdrom:{{ cd.dev }}" value="cdrom:{{ cd.dev }}" onclick="set_orderlist($('#bootorder'))">{{ cd.dev }} - {{ cd.image }}</label>
|
||||
{% endfor %}
|
||||
{% for net in instance.config_networks %}
|
||||
<label class="list-group-item"><input class="form-check-input me-2" type="checkbox" name="network:{{ net.mac }}" value="network:{{ net.mac }}" onclick="set_orderlist($('#bootorder'))">NIC - {{ net.mac|slice:"9:" }}</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div>
|
||||
<div class="row mt-4">
|
||||
<a href="#" id="boot_order_up" class="btn btn-light shadow-sm"><span title="{% trans 'up: move selected devices' %}">{% bs_icon 'arrow-up' %} </span></a>
|
||||
</div>
|
||||
<div class="row mt-2">
|
||||
<a href="#" id="boot_order_down" class="btn btn-light shadow-sm"><span title="{% trans 'down: move selected devices' %}">{% bs_icon 'arrow-down' %} </span></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-center">
|
||||
<div class="d-grid col-sm-6">
|
||||
<input type="submit" class="btn btn-primary" name="set_bootorder" value="{% trans "Apply" %}">
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<button type="button" id="boot_order_up" class="btn btn-sm btn-secondary">{% bs_icon 'arrow-up' %} {% trans "Up" %}</button>
|
||||
<button type="button" id="boot_order_down" class="btn btn-sm btn-secondary">{% bs_icon 'arrow-down' %} {% trans "Down" %}</button>
|
||||
<button type="submit" class="btn btn-sm btn-primary ms-auto">{% trans "Apply order" %}</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
<div class="clearfix"></div>
|
||||
</section>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="disks">
|
||||
{# its CD-ROM drives, then its disks as cards; the forms are those of the actions #}
|
||||
|
|
|
|||
|
|
@ -493,7 +493,7 @@ class LiveDataLossTestCase(TestCase):
|
|||
|
||||
page = html.fromstring(self.client.get(reverse("instances:instance", args=[inst.id])).content)
|
||||
(boot,) = page.xpath("//form[@action=$a]", a=reverse("instances:set_bootorder", args=[inst.id]))
|
||||
self.assertEqual(boot.xpath(".//input[@type='checkbox']/@value"), ["disk:vda", "network:52:54:00:aa:05:21"])
|
||||
self.assertEqual(boot.xpath(".//input[@type='checkbox']/@value"), ["network:52:54:00:aa:05:21", "disk:vda"])
|
||||
self.assertEqual(page.xpath("//select[@name='video_model']/option[@selected]/@value"), [video])
|
||||
|
||||
self.post("set_bootorder", inst, {"bootorder": "network:52:54:00:aa:05:22,disk:vda"})
|
||||
|
|
|
|||
|
|
@ -147,13 +147,24 @@ class PendingEditsTestCase(TestCase):
|
|||
get_console_passwd=("live-pw", "pending-pw"),
|
||||
)
|
||||
(boot,) = doc.xpath("//form[@action=$a]", a=reverse("instances:set_bootorder", args=[self.instance.id]))
|
||||
self.assertEqual(boot.xpath(".//input[@type='checkbox']/@value"), ["disk:vda", "disk:vdb", "cdrom:sda"])
|
||||
self.assertIn("1:sda", doc.xpath("string(//div[label[contains(., ':')]])"))
|
||||
# the devices in the order first, checked
|
||||
self.assertEqual(boot.xpath(".//input[@type='checkbox']/@value"), ["cdrom:sda", "disk:vda", "disk:vdb"])
|
||||
self.assertEqual(boot.xpath(".//input[@checked]/@value"), ["cdrom:sda"])
|
||||
# the pending boot menu is on: its switch turns it off, also while the VM runs
|
||||
self.assertEqual(doc.xpath("//input[@id='bootmenu']/ancestor::form/@action"),
|
||||
[reverse("instances:unset_bootmenu", args=[self.instance.id])])
|
||||
self.assertEqual(doc.xpath("//select[@name='video_model']/option[@selected]/@value"), ["virtio"])
|
||||
self.assertEqual(doc.xpath("//select[@name='keymap']/option[@selected]/@value"), ["de"])
|
||||
self.assertEqual(doc.xpath("//select[@name='listen_on']/option[@selected]/@value"), ["0.0.0.0"])
|
||||
self.assertEqual(doc.xpath("//input[@name='password']/@value"), ["pending-pw"])
|
||||
|
||||
def test_a_boot_order_by_device_type_is_named_and_no_device_is_checked(self):
|
||||
doc = self.page(live=[disk("vda", "default", "")], config=[disk("vda", "default", "")],
|
||||
get_bootorder=({}, {0: {"type": "file", "dev": "hd", "target": "disk"},
|
||||
1: {"type": "file", "dev": "cdrom", "target": "cdrom"}}))
|
||||
self.assertIn("disk, cdrom.", " ".join(doc.xpath("string(//p[@id='boot-by-type'])").split()))
|
||||
self.assertEqual(doc.xpath("//div[@id='b_order']//input[@checked]"), [])
|
||||
|
||||
def test_a_boot_order_choice_not_in_the_definition_is_refused(self):
|
||||
with patch("instances.models.wvmInstance") as wvm:
|
||||
wvm.return_value.set_bootorder.side_effect = util.OperationError("Boot device vdz is not in the definition")
|
||||
|
|
@ -161,6 +172,12 @@ class PendingEditsTestCase(TestCase):
|
|||
{"bootorder": "disk:vda,disk:vdz"})
|
||||
self.assertEqual([str(m) for m in get_messages(response.wsgi_request)], ["Boot device vdz is not in the definition"])
|
||||
|
||||
def test_an_empty_boot_order_is_refused(self):
|
||||
with patch("instances.models.wvmInstance") as wvm:
|
||||
response = self.client.post(reverse("instances:set_bootorder", args=[self.instance.id]), {"bootorder": ""})
|
||||
wvm.return_value.set_bootorder.assert_not_called()
|
||||
self.assertEqual([str(m) for m in get_messages(response.wsgi_request)], ["Check at least one device to boot from."])
|
||||
|
||||
def test_an_active_vm_says_its_settings_apply_at_the_next_start(self):
|
||||
for status, shown in ((1, True), (3, True), (5, False)):
|
||||
with self.subTest(status=status):
|
||||
|
|
|
|||
|
|
@ -1658,28 +1658,24 @@ def unset_bootmenu(request, pk):
|
|||
def set_bootorder(request, pk):
|
||||
instance = get_instance(request.user, pk)
|
||||
bootorder = request.POST.get("bootorder", "")
|
||||
if bootorder:
|
||||
order_list = {}
|
||||
for idx, val in enumerate(bootorder.split(",")):
|
||||
dev_type, dev = val.split(":", 1)
|
||||
order_list[idx] = {"type": dev_type, "dev": dev}
|
||||
try:
|
||||
with audit_vm(request, instance, "change boot order"):
|
||||
instance.proxy.set_bootorder(order_list)
|
||||
except util.OperationError as err:
|
||||
messages.error(request, err)
|
||||
return _back(request, pk, "boot_opt")
|
||||
if not bootorder:
|
||||
messages.error(request, _("Check at least one device to boot from."))
|
||||
return _back(request, pk, "boot_opt")
|
||||
order_list = {}
|
||||
for idx, val in enumerate(bootorder.split(",")):
|
||||
dev_type, dev = val.split(":", 1)
|
||||
order_list[idx] = {"type": dev_type, "dev": dev}
|
||||
try:
|
||||
with audit_vm(request, instance, "change boot order"):
|
||||
instance.proxy.set_bootorder(order_list)
|
||||
except util.OperationError as err:
|
||||
messages.error(request, err)
|
||||
return _back(request, pk, "boot_opt")
|
||||
|
||||
if not instance.proxy.get_status() == 5:
|
||||
messages.success(
|
||||
request,
|
||||
_(
|
||||
"Boot menu changes applied. "
|
||||
+ "But it will be activated after shutdown"
|
||||
),
|
||||
)
|
||||
else:
|
||||
messages.success(request, _("Boot order changed successfully."))
|
||||
if instance.proxy.get_status() != 5:
|
||||
messages.success(request, _("Boot order changed. It applies at the next start."))
|
||||
else:
|
||||
messages.success(request, _("Boot order changed successfully."))
|
||||
return _back(request, pk, "boot_opt")
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -102,3 +102,25 @@ th[aria-sort="ascending"]:not([data-sort-method="none"])::after {
|
|||
.host-nav .breadcrumb-item + .breadcrumb-item::before {
|
||||
padding-right: var(--bs-breadcrumb-item-padding-x);
|
||||
}
|
||||
|
||||
/* the boot order editor numbers its checked devices, top to bottom */
|
||||
.boot-sequence {
|
||||
counter-reset: boot;
|
||||
}
|
||||
|
||||
.boot-sequence > label:has(> input:checked) {
|
||||
counter-increment: boot;
|
||||
}
|
||||
|
||||
.boot-rank {
|
||||
width: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.boot-rank::before {
|
||||
content: "—";
|
||||
}
|
||||
|
||||
.boot-sequence > label:has(> input:checked) .boot-rank::before {
|
||||
content: counter(boot);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1029,9 +1029,8 @@ class wvmInstance(wvmConnect):
|
|||
dev_target = dev.find("target").get("dev")
|
||||
|
||||
elif dev.tag == "interface": # network, bridge or direct
|
||||
dev_mac = dev.find("mac").get("address")
|
||||
dev_device = "network"
|
||||
dev_target = f"nic-{dev_mac[9:]}"
|
||||
dev_target = dev.find("mac").get("address")
|
||||
# pass dev_type usb
|
||||
boot_order[int(idx) - 1] = {
|
||||
"type": dev_type,
|
||||
|
|
@ -1045,21 +1044,13 @@ class wvmInstance(wvmConnect):
|
|||
if not devorder:
|
||||
return
|
||||
|
||||
def remove_bootorder():
|
||||
tree = etree.fromstring(self._XMLDesc(PERSISTENT_XML))
|
||||
os = tree.find("os")
|
||||
boot = os.findall("boot")
|
||||
# Remove old style boot order
|
||||
for b in boot:
|
||||
os.remove(b)
|
||||
# Remove rest of them
|
||||
for dev in tree.find("devices"):
|
||||
boot_dev = dev.find("boot")
|
||||
if boot_dev is not None:
|
||||
dev.remove(boot_dev)
|
||||
return tree
|
||||
|
||||
tree = remove_bootorder()
|
||||
tree = etree.fromstring(self._XMLDesc(PERSISTENT_XML))
|
||||
# the order lists disks, CD-ROM drives and NICs: it does not drop another device's place
|
||||
if tree.xpath("./devices/*[not(self::disk[@device='disk' or @device='cdrom'] or self::interface)]/boot"):
|
||||
raise util.OperationError("A device other than a disk or NIC is in the boot order: change it in the XML editor")
|
||||
# it replaces the order by device type and every device's place
|
||||
for boot in tree.xpath("./os/boot | ./devices/*/boot"):
|
||||
boot.getparent().remove(boot)
|
||||
|
||||
for idx, dev in devorder.items():
|
||||
order = etree.fromstring("<boot order='{}'/>".format(idx + 1))
|
||||
|
|
|
|||
|
|
@ -140,6 +140,16 @@ class DeviceEditsTestCase(unittest.TestCase):
|
|||
p.set_bootorder({0: {"type": "disk", "dev": "vda"}, 1: choice})
|
||||
p._defineXML.assert_not_called()
|
||||
|
||||
def test_a_boot_order_holding_a_device_the_list_does_not_show_is_not_replaced(self):
|
||||
for other in ("<hostdev mode='subsystem' type='pci'><boot order='2'/></hostdev>",
|
||||
"<disk type='file' device='floppy'><target dev='fda'/><boot order='2'/></disk>"):
|
||||
with self.subTest(other):
|
||||
p = proxy()
|
||||
p._XMLDesc = lambda flags, other=other: PERSISTENT.replace("<video>", other + "<video>")
|
||||
with self.assertRaises(util.OperationError):
|
||||
p.set_bootorder({0: {"type": "disk", "dev": "vda"}})
|
||||
p._defineXML.assert_not_called()
|
||||
|
||||
def test_the_default_nic_model_leaves_the_choice_to_libvirt(self):
|
||||
p = proxy()
|
||||
p.change_network("52:54:10:00:00:01", "52:54:10:00:00:01", "default", "net", "default", "")
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ class DeviceGettersTestCase(unittest.TestCase):
|
|||
order = proxy().get_bootorder()
|
||||
self.assertEqual(
|
||||
[order[i]["target"] for i in range(4)],
|
||||
["nic-00:00:02", "nic-00:00:03", "vda", "vdb"],
|
||||
["52:54:10:00:00:02", "52:54:10:00:00:03", "vda", "vdb"],
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue