From 8a809443a4c9446d4eaca03ab67dd711dde51506 Mon Sep 17 00:00:00 2001 From: catborise Date: Sun, 1 Nov 2026 22:20:21 +0300 Subject: [PATCH] feat(instances): a clone takes the size asked and starts if asked A VM made from a template came out at the template's size and shut off, and was then resized and started by hand. The Clone tab now asks for the clone's vCPUs and memory, filled in with the VM's, and whether to start it. After the copy, before the clone is recorded, it is resized with the Resize tab's own functions and started; a step that fails is a warning of the job, and the clone stays. The quota counts the larger of the size asked and the VM's, so a resize that fails leaves the clone at a size it allowed. When the job ends the page opens the clone's, not the cloned VM's again: a template's page, shut off, read as a clone that had not started. An outcome kept for a warning links to it. --- instances/jobs.py | 67 ++- instances/models.py | 5 + .../templates/instances/settings_tab.html | 18 + instances/tests/test_clone_job.py | 79 +++- instances/tests/test_pending_edits.py | 2 +- locale/tr/LC_MESSAGES/django.mo | Bin 157468 -> 157982 bytes locale/tr/LC_MESSAGES/django.po | 446 ++++++++++-------- operations/static/operations/job_panel.js | 16 +- operations/tests/js/job_panel.test.js | 23 +- 9 files changed, 423 insertions(+), 233 deletions(-) diff --git a/instances/jobs.py b/instances/jobs.py index 8e4d2282..e7f4a6ae 100644 --- a/instances/jobs.py +++ b/instances/jobs.py @@ -8,6 +8,7 @@ import time from django.db import transaction from django.core.exceptions import PermissionDenied from django.shortcuts import get_object_or_404 +from django.urls import reverse from django.template.loader import render_to_string from django.utils.translation import gettext_noop from django.utils.translation import gettext_noop as _ @@ -112,7 +113,7 @@ def _snapshot_free_space(): CLONE_POST_KEY_RE = re.compile(r"^(clone-net-mac-\d+|disk-[a-z0-9]+|meta-[a-z0-9]+)$") -CLONE_FIELDS = ("name", "clone-title", "clone-description") +CLONE_FIELDS = ("name", "clone-title", "clone-description", "clone-vcpu", "clone-memory", "clone-start") def clone_data(user, instance, fields): @@ -126,9 +127,19 @@ def clone_data(user, instance, fields): data["clone-title"] = fields.get("clone-title", "").strip() data["clone-description"] = fields.get("clone-description", "").strip() - # the clone copies the persistent definition - quota = {"cpus": instance.proxy.get_vcpu(config=True), "memory": instance.proxy.get_memory(config=True), - "disk": utils.disk_gib(instance.config_disks)} + # the clone copies the persistent definition, with the size the form gives + charged = {} + for key, source, label in (("vcpu", instance.config_vcpu, _("vCPUs")), + ("memory", instance.config_memory, _("memory (MiB)"))): + value = fields.get(f"clone-{key}") or str(source) + if not value.isdigit() or int(value) < 1: + raise util.OperationError(_("Enter a whole number of %(what)s for the clone") % {"what": label}) + # None: as the VM, nothing to change after the copy + data[key] = int(value) if int(value) != source else None + # the larger: a resize that fails after the copy leaves the clone at the VM's size + charged[key] = max(int(value), source) + data["start"] = fields.get("clone-start") == "true" + quota = {"cpus": charged["vcpu"], "memory": charged["memory"], "disk": utils.disk_gib(instance.config_disks)} quota_msg = utils.check_user_quota(user, 1, quota["cpus"], quota["memory"], quota["disk"]) data["disk_owner_uid"] = int(app_settings.INSTANCE_VOLUME_DEFAULT_OWNER_UID) @@ -188,6 +199,37 @@ def clone_data(user, instance, fields): return data, quota +def _finish_clone(op, clone, warning): + """The clone, defined but not recorded yet (the quota counts the job, at the + size it charged, until then): the size the form gave, and started if + asked. It is made either way: a step that fails is a warning of the job.""" + c = Compute.objects.get(pk=op.intent["compute"]) + proxy = wvmInstance(c.hostname, c.login, c.password, c.type, op.intent["name"], uuid=op.intent["uuid"]) + notes = [warning] if warning else [] + try: + if clone.get("vcpu"): + proxy.resize_cpu(str(clone["vcpu"]), str(clone["vcpu"])) + if clone.get("memory"): + proxy.resize_mem(clone["memory"], clone["memory"]) + if clone.get("start"): + proxy.start() + except libvirtError as err: + notes.append(_("The clone is made, but not as asked: %(error)s") % {"error": err}) + return {"warning": " ".join(notes)} if notes else {} + + +def _clone_made(op, result): + """Records the clone; the job's result, with the clone's page, which the + job panel opens: the page the job ran from is the VM cloned.""" + record_vm(op, owner=True) + return {**result, "open": _clone_page(op)} + + +def _clone_page(op): + clone = Instance.objects.get(compute_id=op.intent["compute"], uuid=op.intent["uuid"]) + return reverse("instances:instance", args=[clone.pk]) + + def vm_claims(compute, name): """A job that adds a VM: other VMs stay usable; the VM list reconcile waits.""" return {claims.compute_key(compute.pk): Claim.SHARED, claims.reconcile_key(compute.pk): Claim.EXCLUSIVE, @@ -494,8 +536,7 @@ class CloneInstance(Kind): ctx.record_intent(live=False) ctx.set_phase(gettext_noop("Copying disks")) proxy.clone_instance(clone, uuid) - record_vm(op, owner=True) - return {} + return _clone_made(op, _finish_clone(op, clone, "")) if status not in (1, 3): raise util.OperationError(CLONE_STATE_REFUSED) targets = proxy.live_clone_targets(clone, **_snapshot_free_space()) @@ -555,8 +596,7 @@ class CloneInstance(Kind): success, errmsg = result if success: proxy.define_clone(clone, uuid, {c["dev"]: c["path"] for c in copies}) - record_vm(op, owner=True) - return {"warning": warning} if warning else {} + return _clone_made(op, _finish_clone(op, clone, warning)) _delete_created(proxy, op) op.refresh_from_db(fields=["cancel_requested"]) if op.cancel_requested: @@ -578,7 +618,10 @@ class CloneInstance(Kind): def reconcile(self, op): if not op.intent.get("live"): disks = [v for k, v in op.intent["clone"].items() if k.startswith("disk-") and v] - return vm_outcome(op, owner=True, volumes=disks) + outcome = vm_outcome(op, owner=True, volumes=disks) + if outcome.state == Operation.SUCCEEDED: + outcome.result = {**outcome.result, "open": _clone_page(op)} + return outcome proxy = _recovery_proxy(op) if proxy is None: created = ", ".join(c["path"] for c in op.intent.get("created", [])) @@ -597,8 +640,7 @@ class CloneInstance(Kind): """The spec's recovery table: a defined clone first, then a volume still being created, since it comes before the copy.""" if _domain(op.intent["compute"], op.intent["uuid"]) is not None: - record_vm(op, owner=True) - return Outcome(Operation.SUCCEEDED) + return Outcome(Operation.SUCCEEDED, result=_clone_made(op, {})) created = op.intent.get("created", []) paths = [c["path"] for c in created] ended = Operation.CANCELLED if op.cancel_requested else Operation.FAILED @@ -637,8 +679,7 @@ class CloneInstance(Kind): _delete_created(proxy, op) return Outcome(ended, error=errmsg or "The copy failed") proxy.define_clone(op.intent["clone"], op.intent["uuid"], {c["dev"]: c["path"] for c in created}) - record_vm(op, owner=True) - return Outcome(Operation.SUCCEEDED) + return Outcome(Operation.SUCCEEDED, result=_clone_made(op, _finish_clone(op, op.intent["clone"], ""))) def log_entry(self, op, state): source = op.resource_label.rpartition("/")[2] diff --git a/instances/models.py b/instances/models.py index aaae924e..94db5012 100644 --- a/instances/models.py +++ b/instances/models.py @@ -187,6 +187,11 @@ class Instance(models.Model): def config_cur_vcpu(self) -> int: return self.proxy.get_cur_vcpu(config=True) + @cached_property + def config_memory(self) -> int: + """Memory (MiB) of the persistent definition, which a clone starts from.""" + return self.proxy.get_memory(config=True) + @cached_property def config_title(self) -> str: """Title and description of the persistent definition, which the diff --git a/instances/templates/instances/settings_tab.html b/instances/templates/instances/settings_tab.html index 4fb425ee..036cd3eb 100644 --- a/instances/templates/instances/settings_tab.html +++ b/instances/templates/instances/settings_tab.html @@ -834,6 +834,24 @@ {% endif %} +
+ {% trans "Size" %} +
+
+ + +
+
+ + +
+
+

{% trans "The VM's size to start from; the quota counts the larger of the two." %}

+
+ + +
+
{% trans "Optional details" %} diff --git a/instances/tests/test_clone_job.py b/instances/tests/test_clone_job.py index 723c60d9..7409cc4c 100644 --- a/instances/tests/test_clone_job.py +++ b/instances/tests/test_clone_job.py @@ -43,9 +43,11 @@ class CloneJobTestCase(TransactionTestCase): self.client.force_login(self.admin) self.compute = Compute.objects.create(name="clone-c", hostname="192.0.2.30", login="root", password="", type=1) self.source = Instance.objects.create(compute=self.compute, name="src", uuid="12121212-3434-5656-7878-909090909090") - patcher = patch("instances.models.wvmInstance", return_value=proxy()) - self.proxy = patcher.start().return_value - self.addCleanup(patcher.stop) + self.proxy = proxy() + for target_name in ("instances.models.wvmInstance", "instances.jobs.wvmInstance"): + patcher = patch(target_name, return_value=self.proxy) + patcher.start() + self.addCleanup(patcher.stop) def test_the_page_queues_the_clone_and_the_worker_makes_and_records_it(self): response = self.client.post(reverse("instances:clone", args=[self.source.pk]), @@ -69,6 +71,56 @@ class CloneJobTestCase(TransactionTestCase): clone = Instance.objects.get(uuid=op.intent["uuid"]) self.assertTrue(UserInstance.objects.filter(instance=clone, user=self.admin).exists()) + def clone_with(self, **fields): + self.client.post(reverse("instances:clone", args=[self.source.pk]), + {"name": "copy", "disk-vda": "copy.qcow2", "clone-net-mac-0": "52:54:00:00:00:01", + "submission_key": f"key-{uuid4().hex[:12]}", **fields}) + op = Operation.objects.filter(kind="instance.clone").first() + if op is None: + return None, None + acquire("wvc-test:1:a", "wvc-test") + Operation.objects.filter(pk=op.pk).update(state=Operation.RUNNING, worker="wvc-test:1:a") + op.refresh_from_db() + return op, CloneInstance().run(op, Context(op, SimpleNamespace(identity="wvc-test:1:a"))) + + def test_the_clone_takes_the_size_given_and_starts(self): + # before the clone is recorded: until then the quota counts the job, at the size charged + self.proxy.resize_cpu.side_effect = lambda *a: self.assertFalse(Instance.objects.filter(name="copy").exists()) + op, result = self.clone_with(**{"clone-vcpu": "4", "clone-memory": "2048", "clone-start": "true"}) + self.assertEqual((op.intent["quota"]["cpus"], op.intent["quota"]["memory"]), (4, 2048)) # what the quota counts + self.proxy.resize_cpu.assert_called_once_with("4", "4") + self.proxy.resize_mem.assert_called_once_with(2048, 2048) + self.proxy.start.assert_called_once_with() + # the job panel opens the clone's page + clone = Instance.objects.get(uuid=op.intent["uuid"]) + self.assertEqual(result, {"open": reverse("instances:instance", args=[clone.pk])}) + + def test_a_smaller_clone_is_charged_the_vm_s_size(self): + # a shrink that fails after the copy leaves the clone at the VM's size, which the quota allowed + op, _result = self.clone_with(**{"clone-vcpu": "1", "clone-memory": "256"}) + self.assertEqual((op.intent["quota"]["cpus"], op.intent["quota"]["memory"]), (1, 512)) + self.proxy.resize_mem.assert_called_once_with(256, 256) + + def test_the_vm_s_size_changes_nothing_after_the_copy(self): + _op, result = self.clone_with(**{"clone-vcpu": "1", "clone-memory": "512"}) + self.proxy.resize_cpu.assert_not_called() + self.proxy.resize_mem.assert_not_called() + self.proxy.start.assert_not_called() + self.assertEqual(list(result), ["open"]) + + def test_a_size_that_is_not_a_whole_number_is_refused(self): + for value in ("0", "abc", "-2"): + with self.subTest(value=value): + op, _result = self.clone_with(**{"clone-vcpu": value}) + self.assertIsNone(op) + self.proxy.clone_instance.assert_not_called() + + def test_a_clone_that_cannot_start_is_made_with_a_warning(self): + self.proxy.start.side_effect = libvirt.libvirtError("no memory") + op, result = self.clone_with(**{"clone-start": "true"}) + self.assertTrue(Instance.objects.filter(uuid=op.intent["uuid"]).exists()) + self.assertIn("The clone is made, but not as asked", result["warning"]) + def queued(self): self.client.post(reverse("instances:clone", args=[self.source.pk]), {"name": "copy", "disk-vda": "copy.qcow2", "clone-net-mac-0": "52:54:00:00:00:01", @@ -93,8 +145,10 @@ class CloneJobTestCase(TransactionTestCase): def test_recovery(self, conn): op = Operation(user=self.admin, intent={"compute": self.compute.pk, "uuid": "abababab-0000-1111-2222-333333333333", "name": "copy", "clone": {"name": "copy", "disk-vda": "copy.qcow2"}}) - self.assertEqual(CloneInstance().reconcile(op).state, Operation.SUCCEEDED) # the VM is defined - self.assertTrue(Instance.objects.filter(uuid=op.intent["uuid"]).exists()) + recovered = CloneInstance().reconcile(op) + self.assertEqual(recovered.state, Operation.SUCCEEDED) # the VM is defined + clone = Instance.objects.get(uuid=op.intent["uuid"]) + self.assertEqual(recovered.result["open"], reverse("instances:instance", args=[clone.pk])) missing = libvirt.libvirtError("no domain") missing.err = (libvirt.VIR_ERR_NO_DOMAIN, libvirt.VIR_FROM_QEMU, "no domain") conn.return_value.wvm.lookupByUUIDString.side_effect = missing @@ -248,7 +302,7 @@ class LiveCloneJobTestCase(TransactionTestCase): def test_the_live_run_records_each_step_before_it(self): op = self.op() - self.assertEqual(self.run_job(op), {}) + result = self.run_job(op) self.assertEqual(self.events, [ ("intent", "live", "targets"), ("intent", "planned"), ("create", "vda"), ("intent", "created"), @@ -269,6 +323,7 @@ class LiveCloneJobTestCase(TransactionTestCase): clone = Instance.objects.get(uuid=op.intent["uuid"]) self.assertTrue(UserInstance.objects.filter(instance=clone, user=self.admin).exists()) self.assertEqual(self.deleted(), []) + self.assertEqual(result, {"open": reverse("instances:instance", args=[clone.pk])}) def test_a_paused_vm_or_one_without_a_ready_agent_is_not_frozen(self): for status, agent in ((3, True), (1, False)): @@ -505,13 +560,23 @@ class LiveCloneJobTestCase(TransactionTestCase): Instance.objects.filter(uuid=op.intent["uuid"]).delete() op.delete() + def test_recovery_gives_the_clone_the_size_asked_and_starts_it(self): + clone = {"name": "copy", "disk-vda": "copy-vda.qcow2", "disk_owner_uid": 107, "disk_owner_gid": 107, + "vcpu": 2, "memory": None, "start": True} + outcome, _op = self.recovered(copy_result=[True, ""], clone=clone) + self.assertEqual(outcome.state, Operation.SUCCEEDED) + self.proxy.resize_cpu.assert_called_once_with("2", "2") + self.proxy.resize_mem.assert_not_called() + self.proxy.start.assert_called_once_with() + def test_recovery_of_a_defined_clone_succeeds(self): self.proxy.backup_result.return_value = None # libvirtd restarted after the clone was defined self.proxy.backup_targets.return_value = {"/other/x.qcow2"} # checked after it outcome, op = self.recovered(defined=True) self.assertEqual(outcome.state, Operation.SUCCEEDED) self.assertNotIn("define_clone", self.events) - self.assertTrue(Instance.objects.filter(uuid=op.intent["uuid"]).exists()) + clone = Instance.objects.get(uuid=op.intent["uuid"]) + self.assertEqual(outcome.result["open"], reverse("instances:instance", args=[clone.pk])) def test_recovery_of_a_failed_copy_deletes_what_was_created(self): outcome, _op = self.recovered(copy_result=[False, "No space left on device"]) diff --git a/instances/tests/test_pending_edits.py b/instances/tests/test_pending_edits.py index b4db1111..245e63fa 100644 --- a/instances/tests/test_pending_edits.py +++ b/instances/tests/test_pending_edits.py @@ -183,7 +183,7 @@ class PendingEditsTestCase(TestCase): # the DHCP entry of the clone's name holds one address: only the first NIC guesses it self.assertEqual(len(form.xpath(".//button[contains(@onclick, 'guess_mac_address')]")), 1) # a running or paused VM is copied by a backup, which does not preallocate - self.assertEqual(form.xpath(".//input[@type='checkbox']/@name"), metadata) + self.assertEqual(form.xpath(".//input[@type='checkbox']/@name"), [*metadata, "clone-start"]) def test_a_disk_with_a_pending_source_is_not_deleted(self): # running: vda is A, its definition for the next start uses B diff --git a/locale/tr/LC_MESSAGES/django.mo b/locale/tr/LC_MESSAGES/django.mo index afad99a47146573f1722fc10e6806e2ae2a59f4f..76e970da48c967ff98d009210d93de5d661f9a7b 100644 GIT binary patch delta 37547 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, 2020\n" "Language-Team: Turkish (https://www.transifex.com/catborise/teams/110663/" @@ -710,10 +710,10 @@ msgstr "Üyeler" #: instances/templates/instances/resize_tab.html:114 #: instances/templates/instances/settings_tab.html:416 #: instances/templates/instances/settings_tab.html:647 -#: instances/templates/instances/settings_tab.html:876 -#: instances/templates/instances/settings_tab.html:918 -#: instances/templates/instances/settings_tab.html:978 -#: instances/templates/instances/settings_tab.html:1008 +#: instances/templates/instances/settings_tab.html:894 +#: instances/templates/instances/settings_tab.html:936 +#: instances/templates/instances/settings_tab.html:996 +#: instances/templates/instances/settings_tab.html:1026 #: networks/templates/add_network_qos.html:36 #: networks/templates/network.html:335 networks/templates/network.html:375 #: networks/templates/network.html:402 networks/templates/network.html:441 @@ -877,7 +877,7 @@ msgid "Search removed VMs" msgstr "Kaldırılan sanal makinelerde ara" #: admin/templates/admin/tombstone_list.html:28 -#: instances/templates/instances/settings_tab.html:884 +#: instances/templates/instances/settings_tab.html:902 msgid "Template" msgstr "Şablon" @@ -1727,8 +1727,8 @@ msgid "Stored, leave empty to keep" msgstr "Kayıtlı, korumak için boş bırakın" #: computes/forms.py:28 computes/templates/computes/instances.html:45 -#: instances/templates/instances/settings_tab.html:841 -#: instances/templates/instances/settings_tab.html:873 +#: instances/templates/instances/settings_tab.html:859 +#: instances/templates/instances/settings_tab.html:891 #: instances/templates/instances/snapshots_tab.html:23 #: instances/templates/instances/snapshots_tab.html:67 msgid "Description" @@ -1798,7 +1798,9 @@ msgstr "taşıma adresi" msgid "" "IP address or host name the other hosts send migrating VMs to; empty: the " "address the panel reaches the host at" -msgstr "Diğer sunucuların taşınan sanal makineleri gönderdiği IP adresi veya ana bilgisayar adı; boş: panelin bu sunucuya ulaştığı adres" +msgstr "" +"Diğer sunucuların taşınan sanal makineleri gönderdiği IP adresi veya ana " +"bilgisayar adı; boş: panelin bu sunucuya ulaştığı adres" #: computes/models.py:33 computes/templates/computes/form.html:34 msgid "gstfsd key" @@ -2120,9 +2122,11 @@ msgid "of %(memory)s" msgstr "/ %(memory)s" #: computes/templates/computes/list.html:58 computes/templates/overview.html:58 -#: instances/forms.py:15 instances/templates/allinstances_index_grouped.html:16 +#: instances/forms.py:15 instances/jobs.py:131 +#: instances/templates/allinstances_index_grouped.html:16 #: instances/templates/allinstances_index_nongrouped.html:14 #: instances/templates/create_instance_w2.html:60 +#: instances/templates/instances/settings_tab.html:841 msgid "vCPUs" msgstr "vCPU" @@ -2130,14 +2134,14 @@ msgstr "vCPU" #: console/templates/console-vnc.html:13 #: console/templates/console-xterm.html:16 #: instances/templates/allinstances_index_grouped.html:36 -#: instances/templates/instances/settings_tab.html:995 +#: instances/templates/instances/settings_tab.html:1013 #: networks/templates/network.html:260 msgid "Connected" msgstr "Bağlı" #: computes/templates/computes/list.html:73 computes/templates/overview.html:21 #: instances/templates/allinstances_index_grouped.html:38 -#: instances/templates/instances/settings_tab.html:996 +#: instances/templates/instances/settings_tab.html:1014 msgid "Not connected" msgstr "Bağlı değil" @@ -2321,7 +2325,7 @@ msgstr "" "Sunucudaki /etc/default/webvirt-gstfsd içindeki GSTFSD_KEY değerinin 64 " "onaltılık karakterini girin" -#: computes/views.py:196 +#: computes/views.py:197 #, python-format msgid "" "Its VMs keep running on the host and leave the panel; their owners are kept " @@ -2578,6 +2582,7 @@ msgstr "" "VNC olarak değiştirin." #: instances/forms.py:14 instances/templates/create_instance_w2.html:66 +#: instances/templates/instances/settings_tab.html:845 msgid "Memory (MiB)" msgstr "Bellek (MiB)" @@ -2645,184 +2650,198 @@ msgstr "" msgid "The name of the virtual machine must not contain any special characters" msgstr "Sanal makine adı özel karakterler içeremez" -#: instances/jobs.py:149 +#: instances/jobs.py:132 +msgid "memory (MiB)" +msgstr "bellek (MiB)" + +#: instances/jobs.py:135 +#, python-format +msgid "Enter a whole number of %(what)s for the clone" +msgstr "Klon için %(what)s olarak bir tam sayı girin" + +#: instances/jobs.py:159 msgid "Choose the clone's name from the list of free names" msgstr "Klonun adını boş adlar listesinden seçin" -#: instances/jobs.py:176 +#: instances/jobs.py:186 #, python-format msgid "User '%(quota_msg)s' quota reached, cannot create '%(clone_name)s'!" msgstr "" "%(quota_msg)s' Kullanıcı kotası doldu, '%(clone_name)s' in CPU su yeniden " "boyutlandırılamıyor!" -#: instances/jobs.py:179 instances/jobs.py:485 +#: instances/jobs.py:189 instances/jobs.py:512 #, python-format msgid "Instance '%(clone_name)s' already exists!" msgstr "'%(clone_name)s' sanal makinesi zaten mevcut!" -#: instances/jobs.py:182 +#: instances/jobs.py:192 #, python-format msgid "Instance name '%(clone_name)s' contains invalid characters!" msgstr "Sanal makine adı '%(clone_name)s' geçersiz karakterler içermektedir!" -#: instances/jobs.py:184 +#: instances/jobs.py:194 #, python-format msgid "Instance MAC '%(clone_mac)s' invalid format!" msgstr "Sanal makine MAC adresi '%(clone_mac)s' geçersiz biçemde belirtilmiş!" -#: instances/jobs.py:187 +#: instances/jobs.py:197 #, python-format msgid "Disk name '%(disk_name)s' contains invalid characters!" msgstr "Disk adı '%(disk_name)s' geçersiz karakterler içeriyor!" -#: instances/jobs.py:246 +#: instances/jobs.py:214 +#, python-format +msgid "The clone is made, but not as asked: %(error)s" +msgstr "Klon oluşturuldu, ama istendiği gibi değil: %(error)s" + +#: instances/jobs.py:273 msgid "Delete snapshot" msgstr "Anlık görüntüyü sil" -#: instances/jobs.py:269 +#: instances/jobs.py:296 msgid "Merging the snapshot" msgstr "Anlık görüntü birleştiriliyor" -#: instances/jobs.py:291 +#: instances/jobs.py:318 msgid "libvirt is still merging the snapshot" msgstr "libvirt anlık görüntüyü hâlâ birleştiriyor" -#: instances/jobs.py:312 +#: instances/jobs.py:339 msgid "Take snapshot" msgstr "Anlık görüntü al" -#: instances/jobs.py:336 +#: instances/jobs.py:363 msgid "Taking the snapshot" msgstr "Anlık görüntü alınıyor" -#: instances/jobs.py:349 +#: instances/jobs.py:376 msgid "libvirt is still taking the snapshot" msgstr "libvirt anlık görüntüyü hâlâ alıyor" -#: instances/jobs.py:375 +#: instances/jobs.py:402 msgid "Revert snapshot" msgstr "Snapshota dön" -#: instances/jobs.py:404 +#: instances/jobs.py:431 msgid "Reverting the VM" msgstr "Sanal makine geri döndürülüyor" -#: instances/jobs.py:415 +#: instances/jobs.py:442 msgid "libvirt is still reverting the VM" msgstr "libvirt sanal makineyi hâlâ geri döndürüyor" -#: instances/jobs.py:456 +#: instances/jobs.py:483 msgid "Clone VM" msgstr "Sanal makineyi klonla" -#: instances/jobs.py:495 instances/jobs.py:503 instances/jobs.py:746 +#: instances/jobs.py:522 instances/jobs.py:530 instances/jobs.py:773 msgid "Copying disks" msgstr "Diskler kopyalanıyor" -#: instances/jobs.py:629 +#: instances/jobs.py:656 msgid "libvirt is still copying the disks" msgstr "libvirt diskleri hâlâ kopyalıyor" -#: instances/jobs.py:662 instances/templates/create_instance_w1.html:58 +#: instances/jobs.py:689 instances/templates/create_instance_w1.html:58 #: instances/templates/create_instance_w2.html:353 msgid "Create VM" msgstr "Sanal makine oluştur" -#: instances/jobs.py:685 instances/views.py:2442 +#: instances/jobs.py:712 instances/views.py:2465 msgid "A virtual machine with this name already exists" msgstr "Bu isimde bir sanal makine mevcut" -#: instances/jobs.py:687 +#: instances/jobs.py:714 msgid "There is an instance with same name. Remove it and try again!" msgstr "Aynı isimde başka bir sanal makine var. Onu silip yeniden deneyin!" -#: instances/jobs.py:690 +#: instances/jobs.py:717 msgid "First you need to create or select an image" msgstr "Öncelikle bir imaj oluşturun ya da mevcut bir imajı seçin" -#: instances/jobs.py:692 +#: instances/jobs.py:719 msgid "Choose one disk source: a new disk, existing volumes or a template" msgstr "" "Tek bir disk kaynağı seçin: yeni bir disk, mevcut birimler ya da bir şablon" -#: instances/jobs.py:694 +#: instances/jobs.py:721 msgid "Image has already exist. Please check volumes or change instance name" msgstr "" "İmaj zaten mevcut. Lütfen ya makine adını değişin ya da disk alanlarını " "kontrol edin" -#: instances/jobs.py:696 +#: instances/jobs.py:723 msgid "Invalid cache mode" msgstr "Geçersiz önbellek modu" -#: instances/jobs.py:698 +#: instances/jobs.py:725 #, python-format msgid "This host offers no CPU mode or model %(mode)s" msgstr "Bu sunucu %(mode)s CPU modunu veya modelini sunmuyor" -#: instances/jobs.py:700 +#: instances/jobs.py:727 #, python-format msgid "A CD-ROM cannot use the %(bus)s bus" msgstr "CD-ROM %(bus)s veri yolunu kullanamaz" -#: instances/jobs.py:703 +#: instances/jobs.py:730 msgid "The cloud-init seed is read from a CD-ROM: choose its bus" msgstr "cloud-init tohumu bir CD-ROM'dan okunur: veri yolunu seçin" -#: instances/jobs.py:706 +#: instances/jobs.py:733 msgid "The cloud-init seed needs a directory storage pool for the disk" msgstr "cloud-init tohumu, disk için bir dizin depolama havuzu gerektirir" -#: instances/jobs.py:712 +#: instances/jobs.py:739 #, python-format msgid "This host offers no firmware %(firmware)s" msgstr "Bu sunucu %(firmware)s firmware'ini sunmuyor" -#: instances/jobs.py:714 +#: instances/jobs.py:741 msgid "This host offers no TPM 2.0: install swtpm on it" msgstr "Bu sunucu TPM 2.0 sunmuyor: üzerine swtpm kurun" -#: instances/jobs.py:765 +#: instances/jobs.py:792 msgid "Writing the cloud-init seed" msgstr "cloud-init tohumu yazılıyor" -#: instances/jobs.py:772 +#: instances/jobs.py:799 msgid "Defining the VM" msgstr "Sanal makine tanımlanıyor" -#: instances/jobs.py:829 +#: instances/jobs.py:856 #, python-format msgid "The instance is already on %(compute)s" msgstr "Sanal makine zaten %(compute)s üzerinde" -#: instances/jobs.py:831 +#: instances/jobs.py:858 #, python-format msgid "%(compute)s is not reachable" msgstr "%(compute)s sunucusuna ulaşılamıyor" -#: instances/jobs.py:833 +#: instances/jobs.py:860 #, python-format msgid "%(compute)s is the VM's own host under another name" msgstr "%(compute)s, sanal makinenin kendi sunucusunun başka bir adı" -#: instances/jobs.py:851 +#: instances/jobs.py:878 msgid "Migrate VM" msgstr "Sanal makineyi taşı" -#: instances/jobs.py:895 +#: instances/jobs.py:922 msgid "Migrating the VM" msgstr "Sanal makine taşınıyor" -#: instances/jobs.py:929 +#: instances/jobs.py:956 msgid "libvirt is still migrating the VM" msgstr "libvirt sanal makineyi hâlâ taşıyor" -#: instances/jobs.py:970 +#: instances/jobs.py:997 msgid "Move disk" msgstr "Diski taşı" -#: instances/jobs.py:1001 +#: instances/jobs.py:1028 msgid "Copying the disk" msgstr "Disk kopyalanıyor" @@ -2858,67 +2877,67 @@ msgstr "oluşturuldu" msgid "drbd" msgstr "drbd" -#: instances/models.py:221 instances/templates/create_instance_w2.html:115 +#: instances/models.py:226 instances/templates/create_instance_w2.html:115 #: instances/templates/instances/resize_tab.html:20 #: instances/templates/instances/settings_tab.html:25 #: instances/templates/instances/stats_tab.html:49 msgid "Disk" msgstr "Disk" -#: instances/models.py:223 instances/templates/create_instance_w2.html:309 +#: instances/models.py:228 instances/templates/create_instance_w2.html:309 #: instances/templates/instances/settings_tab.html:165 msgid "CD-ROM" msgstr "CD-ROM" -#: instances/models.py:225 +#: instances/models.py:230 msgid "NIC" msgstr "NIC" -#: instances/models.py:390 +#: instances/models.py:395 msgid "Live" msgstr "Canlı" -#: instances/models.py:391 +#: instances/models.py:396 msgid "Offline" msgstr "Çevrimdışı" -#: instances/models.py:392 +#: instances/models.py:397 msgid "Auto Converge" msgstr "Otomatik yavaşlat" -#: instances/models.py:393 +#: instances/models.py:398 msgid "Compress" msgstr "Sıkıştır" -#: instances/models.py:394 +#: instances/models.py:399 msgid "Unsafe" msgstr "Güvenli olmayan" -#: instances/models.py:406 +#: instances/models.py:411 msgid "No Virtual Machine name has been entered" msgstr "Sanal makine adı girilmedi" -#: instances/models.py:412 +#: instances/models.py:417 msgid "No VCPU has been entered" msgstr "VCPU belirtilmedi" -#: instances/models.py:418 +#: instances/models.py:423 msgid "No RAM size has been entered" msgstr "RAM boyutu belirtilmedi" -#: instances/models.py:422 +#: instances/models.py:427 msgid "No Network pool has been choosen" msgstr "Ağ havuzu seçilmedi" -#: instances/models.py:430 +#: instances/models.py:435 msgid "Please select HDD cache mode" msgstr "Lütfen HDD ön bellek modunu seçin" -#: instances/models.py:442 +#: instances/models.py:447 msgid "Please select a video driver" msgstr "Lütfen bir video sürücüsü seçin" -#: instances/models.py:479 +#: instances/models.py:484 msgid "Can access console without password" msgstr "Parolasız konsol erişimi yapabilir" @@ -3061,7 +3080,7 @@ msgid "Disk source" msgstr "Disk kaynağı" #: instances/templates/add_instance_volume.html:21 -#: instances/templates/create_instance_w2.html:131 instances/views.py:2461 +#: instances/templates/create_instance_w2.html:131 instances/views.py:2484 msgid "New disk" msgstr "Yeni disk" @@ -3093,6 +3112,7 @@ msgid "Letters, digits, dot, dash and underscore." msgstr "Harf, rakam, nokta, tire ve alt çizgi." #: instances/templates/add_instance_volume.html:44 +#: instances/templates/instances/settings_tab.html:838 msgid "Size" msgstr "Boyut" @@ -3399,7 +3419,7 @@ msgid "Firmware" msgstr "Üretici yazılımı" #: instances/templates/create_instance_w2.html:83 -#: instances/templates/instances/settings_tab.html:952 +#: instances/templates/instances/settings_tab.html:970 msgid "TPM 2.0" msgstr "TPM 2.0" @@ -3424,7 +3444,7 @@ msgstr "Özel CPU modelleri" msgid "Size (GiB)" msgstr "Boyut (GiB)" -#: instances/templates/create_instance_w2.html:158 instances/views.py:2462 +#: instances/templates/create_instance_w2.html:158 instances/views.py:2485 msgid "Existing volumes" msgstr "Mevcut birimler" @@ -3459,7 +3479,7 @@ msgstr "Aygıt" msgid "Remove" msgstr "Kaldır" -#: instances/templates/create_instance_w2.html:191 instances/views.py:2463 +#: instances/templates/create_instance_w2.html:191 instances/views.py:2486 msgid "Clone from template" msgstr "Şablondan klonla" @@ -3684,8 +3704,8 @@ msgstr "Konuk ajanı yanıt vermedi" #: instances/templates/instance_actions.html:13 #: instances/templates/instances/settings_tab.html:53 -#: instances/templates/instances/settings_tab.html:851 -#: instances/templates/instances/settings_tab.html:853 +#: instances/templates/instances/settings_tab.html:869 +#: instances/templates/instances/settings_tab.html:871 #: nwfilters/templates/nwfilters.html:57 nwfilters/templates/nwfilters.html:94 #: storages/templates/storage.html:143 storages/templates/storage.html:203 msgid "Clone" @@ -3794,7 +3814,7 @@ msgstr "" msgid "Start the VM to open its console." msgstr "Konsolunu açmak için sanal makineyi başlatın." -#: instances/templates/instances/access_tab.html:41 instances/views.py:1077 +#: instances/templates/instances/access_tab.html:41 instances/views.py:1100 msgid "Reset root password" msgstr "Root parolasını sıfırla" @@ -4408,17 +4428,17 @@ msgstr "" "sağlar." #: instances/templates/instances/resize_tab.html:102 -#: instances/templates/instances/settings_tab.html:896 +#: instances/templates/instances/settings_tab.html:914 msgid "Shut the VM down to change this." msgstr "Bunu değiştirmek için sanal makineyi kapatın." #: instances/templates/instances/resize_tab.html:109 -#: instances/templates/instances/settings_tab.html:1003 +#: instances/templates/instances/settings_tab.html:1021 msgid "Enabled" msgstr "Etkin" #: instances/templates/instances/resize_tab.html:110 -#: instances/templates/instances/settings_tab.html:1004 +#: instances/templates/instances/settings_tab.html:1022 msgid "Disabled" msgstr "Devredışı" @@ -5153,16 +5173,24 @@ msgstr "qcow2 meta verisini önceden ayır" msgid "The VM has no disk to copy." msgstr "Sanal makinenin kopyalanacak diski yok." -#: instances/templates/instances/settings_tab.html:838 +#: instances/templates/instances/settings_tab.html:849 +msgid "The VM's size to start from; the quota counts the larger of the two." +msgstr "Başlangıç olarak sanal makinenin boyutu; kota ikisinden büyük olanı sayar." + +#: instances/templates/instances/settings_tab.html:852 +msgid "Start the clone when it is made" +msgstr "Klon oluşturulunca başlat" + +#: instances/templates/instances/settings_tab.html:856 msgid "Optional details" msgstr "İsteğe bağlı ayrıntılar" -#: instances/templates/instances/settings_tab.html:839 -#: instances/templates/instances/settings_tab.html:871 +#: instances/templates/instances/settings_tab.html:857 +#: instances/templates/instances/settings_tab.html:889 msgid "Title" msgstr "Başlık" -#: instances/templates/instances/settings_tab.html:846 +#: instances/templates/instances/settings_tab.html:864 msgid "" "The clone gets none of the VM's host devices: one VM holds a device of the " "host." @@ -5170,46 +5198,46 @@ msgstr "" "Klon, sanal makinenin sunucu aygıtlarını almaz: bir sunucu aygıtını tek bir " "sanal makine tutar." -#: instances/templates/instances/settings_tab.html:849 +#: instances/templates/instances/settings_tab.html:867 msgid "Cloning runs as a background job." msgstr "Klonlama arka planda bir iş olarak çalışır." -#: instances/templates/instances/settings_tab.html:867 +#: instances/templates/instances/settings_tab.html:885 msgid "VM details" msgstr "Sanal makine ayrıntıları" -#: instances/templates/instances/settings_tab.html:868 +#: instances/templates/instances/settings_tab.html:886 msgid "A title and description to identify the VM." msgstr "Sanal makineyi tanımlamak için bir başlık ve açıklama." -#: instances/templates/instances/settings_tab.html:885 +#: instances/templates/instances/settings_tab.html:903 msgid "" "A template is a VM to clone: it is not counted in quotas and cannot be " "started." msgstr "" "Şablon, klonlanacak bir sanal makinedir: kotalarda sayılmaz ve başlatılamaz." -#: instances/templates/instances/settings_tab.html:892 +#: instances/templates/instances/settings_tab.html:910 msgid "Use as a template" msgstr "Şablon olarak kullan" -#: instances/templates/instances/settings_tab.html:895 +#: instances/templates/instances/settings_tab.html:913 msgid "Only administrators and staff can change this." msgstr "Bunu yalnızca yöneticiler ve personel değiştirebilir." -#: instances/templates/instances/settings_tab.html:905 +#: instances/templates/instances/settings_tab.html:923 msgid "Primary video model" msgstr "Birincil video modeli" -#: instances/templates/instances/settings_tab.html:906 +#: instances/templates/instances/settings_tab.html:924 msgid "The graphics device the guest sees." msgstr "Konuk sistemin gördüğü grafik aygıtı." -#: instances/templates/instances/settings_tab.html:927 +#: instances/templates/instances/settings_tab.html:945 msgid "Random number generator" msgstr "Rastgele sayı üreteci" -#: instances/templates/instances/settings_tab.html:928 +#: instances/templates/instances/settings_tab.html:946 msgid "" "A virtio device that feeds the guest's entropy from the host: a new VM does " "not wait for it at boot. From the next start." @@ -5217,19 +5245,19 @@ msgstr "" "Konuk sistemin entropisini sunucudan besleyen bir virtio aygıtı: yeni bir " "sanal makine açılışta onu beklemez. Sonraki başlatmadan itibaren." -#: instances/templates/instances/settings_tab.html:934 +#: instances/templates/instances/settings_tab.html:952 msgid "Virtio RNG" msgstr "Virtio RNG" -#: instances/templates/instances/settings_tab.html:937 +#: instances/templates/instances/settings_tab.html:955 msgid "The VM has its own RNG device: change it in the XML editor." msgstr "Sanal makinenin kendi RNG aygıtı var: XML düzenleyicide değiştirin." -#: instances/templates/instances/settings_tab.html:945 +#: instances/templates/instances/settings_tab.html:963 msgid "TPM" msgstr "TPM" -#: instances/templates/instances/settings_tab.html:946 +#: instances/templates/instances/settings_tab.html:964 msgid "" "An emulated TPM 2.0, as Windows 11 needs with UEFI Secure Boot. Its state " "stays on the host until the VM is destroyed: a TPM added again has it back. " @@ -5239,19 +5267,19 @@ msgstr "" "2.0. Durumu sanal makine yok edilene kadar sunucuda kalır: yeniden eklenen " "TPM onu geri alır. Bir sonraki başlatmadan itibaren." -#: instances/templates/instances/settings_tab.html:955 +#: instances/templates/instances/settings_tab.html:973 msgid "The VM has its own TPM: change it in the XML editor." msgstr "Sanal makinenin kendi TPM'i var: XML düzenleyicide değiştirin." -#: instances/templates/instances/settings_tab.html:957 +#: instances/templates/instances/settings_tab.html:975 msgid "This host offers no TPM 2.0: install swtpm on it." msgstr "Bu sunucu TPM 2.0 sunmuyor: üzerine swtpm kurun." -#: instances/templates/instances/settings_tab.html:965 +#: instances/templates/instances/settings_tab.html:983 msgid "When the guest crashes" msgstr "Konuk sistem çöktüğünde" -#: instances/templates/instances/settings_tab.html:966 +#: instances/templates/instances/settings_tab.html:984 msgid "" "For a crash the guest reports, not a hang. Stop keeps a VM that crashes " "again and again down for a look. From the next start." @@ -5260,39 +5288,39 @@ msgstr "" "tekrar tekrar çöken bir sanal makineyi incelemek için kapalı tutar. Sonraki " "başlatmadan itibaren." -#: instances/templates/instances/settings_tab.html:973 +#: instances/templates/instances/settings_tab.html:991 msgid "Restart" msgstr "Yeniden başlat" -#: instances/templates/instances/settings_tab.html:974 +#: instances/templates/instances/settings_tab.html:992 #: interfaces/templates/interface.html:16 networks/templates/network.html:16 #: storages/templates/storage.html:18 msgid "Stop" msgstr "Durdur" -#: instances/templates/instances/settings_tab.html:982 +#: instances/templates/instances/settings_tab.html:1000 #, python-format msgid "%(action)s: change it in the XML editor." msgstr "%(action)s: XML düzenleyicide değiştirin." -#: instances/templates/instances/settings_tab.html:990 +#: instances/templates/instances/settings_tab.html:1008 msgid "QEMU guest agent" msgstr "QEMU konuk ajanı" -#: instances/templates/instances/settings_tab.html:991 +#: instances/templates/instances/settings_tab.html:1009 msgid "The channel to the agent in the guest: a running VM gets it at once." msgstr "" "Konuk sistemdeki ajana giden kanal: çalışan bir sanal makine onu hemen alır." -#: instances/templates/instances/settings_tab.html:992 +#: instances/templates/instances/settings_tab.html:1010 msgid "Agent" msgstr "Ajan" -#: instances/templates/instances/settings_tab.html:993 +#: instances/templates/instances/settings_tab.html:1011 msgid "VM not running" msgstr "Sanal makine çalışmıyor" -#: instances/templates/instances/settings_tab.html:994 +#: instances/templates/instances/settings_tab.html:1012 msgid "No channel" msgstr "Kanal yok" @@ -5567,51 +5595,51 @@ msgstr "Disk dosyaları %(host)s üzerinde denetlenemiyor: %(err)s" msgid "None available device name" msgstr "Hiçbiri uygun aygıt adı değil" -#: instances/utils.py:345 +#: instances/utils.py:364 #, python-format msgid "%(hostname)s via %(address)s" msgstr "%(address)s üzerinden %(hostname)s" -#: instances/utils.py:384 +#: instances/utils.py:403 msgid "Invalid MAC address" msgstr "Geçersiz MAC adresi" -#: instances/utils.py:386 +#: instances/utils.py:405 msgid "More MAC addresses than networks" msgstr "Ağlardan daha fazla MAC adresi" -#: instances/views.py:143 +#: instances/views.py:166 msgid "Enter an IP address, a network such as 192.0.2.0/24, or a MAC address." msgstr "Bir IP adresi, 192.0.2.0/24 gibi bir ağ ya da bir MAC adresi girin." -#: instances/views.py:149 instances/views.py:183 +#: instances/views.py:172 instances/views.py:206 msgid "Too many searches are still running; try again shortly." msgstr "Çok fazla arama hâlâ çalışıyor; birazdan yeniden deneyin." -#: instances/views.py:229 +#: instances/views.py:252 #, python-format msgid "The host %(host)s of '%(name)s' cannot be reached: %(error)s" msgstr "'%(name)s' sanal makinesinin sunucusu %(host)s erişilemiyor: %(error)s" -#: instances/views.py:235 +#: instances/views.py:258 #, python-format msgid "The host of '%(name)s' cannot be reached right now." msgstr "'%(name)s' sanal makinesinin sunucusuna şu anda erişilemiyor." -#: instances/views.py:236 +#: instances/views.py:259 msgid " Jobs on it can be followed under Jobs in the menu." msgstr " Üzerindeki işler menüdeki İşler bölümünden izlenebilir." -#: instances/views.py:335 +#: instances/views.py:358 msgid "There is no other host to migrate to." msgstr "Taşınacak başka bir sunucu yok." -#: instances/views.py:337 +#: instances/views.py:360 msgid "A VM migrates when it is running, paused or shut off." msgstr "" "Sanal makine çalışırken, duraklatılmışken ya da kapalıyken taşınabilir." -#: instances/views.py:339 +#: instances/views.py:362 msgid "" "A VM with snapshots cannot be migrated: its snapshots stay on this host. " "Delete them first." @@ -5619,7 +5647,7 @@ msgstr "" "Anlık görüntüsü olan bir sanal makine taşınamaz: anlık görüntüleri bu " "sunucuda kalır. Önce onları silin." -#: instances/views.py:341 +#: instances/views.py:364 msgid "" "A VM with host devices cannot be migrated: the destination may not have " "them. Remove them first." @@ -5627,11 +5655,11 @@ msgstr "" "Sunucu aygıtları olan bir sanal makine taşınamaz: hedefte bu aygıtlar " "olmayabilir. Önce onları kaldırın." -#: instances/views.py:607 +#: instances/views.py:630 msgid "The VM was moved meanwhile; try again." msgstr "Sanal makine bu arada taşındı; yeniden deneyin." -#: instances/views.py:638 +#: instances/views.py:661 msgid "" "The quota cannot be checked because a host of your virtual machines cannot " "be reached. Nothing was changed." @@ -5639,7 +5667,7 @@ msgstr "" "Sanal makinelerinizin bir sunucusuna erişilemediği için kota denetlenemiyor. " "Hiçbir şey değiştirilmedi." -#: instances/views.py:719 +#: instances/views.py:742 msgid "" "The running VM has no free PCI slot for another disk on this bus: choose the " "scsi bus if the VM has a SCSI controller, or shut the VM down and add the " @@ -5652,7 +5680,7 @@ msgstr "" "yuvaları ekle, sonraki başlatmadan itibaren ona boş yuvalar verir. Yeni bir " "birim havuzunda kalır, mevcut birim olarak eklenebilir." -#: instances/views.py:723 +#: instances/views.py:746 msgid "" "The running VM has no free PCI slot for the SCSI controller the disk needs: " "shut the VM down and add the disk then; Add PCIe slots on the Disks tab " @@ -5664,82 +5692,82 @@ msgstr "" "PCIe yuvaları ekle, sonraki başlatmadan itibaren ona boş yuvalar verir. Yeni " "bir birim havuzunda kalır, mevcut birim olarak eklenebilir." -#: instances/views.py:741 +#: instances/views.py:764 #, python-format msgid "Invalid disk options: %(options)s" msgstr "Geçersiz disk seçenekleri: %(options)s" -#: instances/views.py:751 instances/views.py:766 +#: instances/views.py:774 instances/views.py:789 msgid "Templates cannot be started." msgstr "Şablon makineler başlatılamaz." -#: instances/views.py:862 +#: instances/views.py:885 #, python-format msgid "Disk %(path)s was kept: a disk file that stays needs it" msgstr "%(path)s diski korundu: kalan bir disk dosyası ona ihtiyaç duyuyor" -#: instances/views.py:868 +#: instances/views.py:891 #, python-format msgid "The VM is removed, but disk %(path)s was not deleted: %(err)s" msgstr "Sanal makine kaldırıldı, ancak %(path)s diski silinmedi: %(err)s" -#: instances/views.py:877 +#: instances/views.py:900 #, python-format msgid "Disk %(path)s is used by another VM and was kept" msgstr "" "%(path)s diski başka bir sanal makine tarafından kullanılıyor ve korundu" -#: instances/views.py:999 +#: instances/views.py:1022 #, python-format msgid "Set the gstfsd key of %(compute)s first (Computes, edit the host)" msgstr "" "Önce %(compute)s sunucusunun gstfsd anahtarını ayarlayın (Sunucular, " "sunucuyu düzenle)" -#: instances/views.py:1005 instances/views.py:1019 instances/views.py:1028 +#: instances/views.py:1028 instances/views.py:1042 instances/views.py:1051 #, python-format msgid "gstfsd error: %(err)s" msgstr "gstfsd hatası: %(err)s" -#: instances/views.py:1029 +#: instances/views.py:1052 msgid "the reply is not signed with the key" msgstr "yanıt anahtarla imzalanmamış" -#: instances/views.py:1037 +#: instances/views.py:1060 msgid "Shut the VM down, or start its QEMU guest agent, and try again" msgstr "" "Sanal makineyi kapatın ya da QEMU konuk ajanını başlatın ve yeniden deneyin" -#: instances/views.py:1050 +#: instances/views.py:1073 msgid "The change may still be made when the agent answers again." msgstr "Ajan yeniden yanıt verdiğinde değişiklik yine de yapılabilir." -#: instances/views.py:1081 +#: instances/views.py:1104 msgid "Enter a password" msgstr "Bir parola girin" -#: instances/views.py:1095 +#: instances/views.py:1118 msgid "Select an SSH public key" msgstr "Bir SSH açık anahtarı seçin" -#: instances/views.py:1117 +#: instances/views.py:1140 #, python-format msgid "Installed new SSH public key %(keyname)s" msgstr "Yeni %(keyname)s SSH açık anahtarı yüklendi" -#: instances/views.py:1137 +#: instances/views.py:1160 msgid "Enter a whole number of vCPUs" msgstr "vCPU sayısı için bir tam sayı girin" -#: instances/views.py:1141 +#: instances/views.py:1164 msgid "Shut off the VM to change its vCPUs" msgstr "vCPU'larını değiştirmek için sanal makineyi kapatın" -#: instances/views.py:1144 +#: instances/views.py:1167 msgid "The current vCPUs cannot be more than the maximum" msgstr "Geçerli vCPU sayısı en fazla değerden büyük olamaz" -#: instances/views.py:1152 +#: instances/views.py:1175 #, python-format msgid "" "User %(quota_msg)s quota reached, cannot resize CPU of '%(instance_name)s'!" @@ -5747,20 +5775,20 @@ msgstr "" "%(quota_msg)s kullanıcısı kotası doldu, '%(instance_name)s' in CPU su " "yeniden boyutlandırılamıyor!" -#: instances/views.py:1163 +#: instances/views.py:1186 #, python-format msgid "vCPUs set to %(cur)s (maximum %(max)s)" msgstr "vCPU sayısı %(cur)s olarak ayarlandı (en fazla %(max)s)" -#: instances/views.py:1182 +#: instances/views.py:1205 msgid "Enter a whole number for memory" msgstr "Bellek için bir tam sayı girin" -#: instances/views.py:1185 +#: instances/views.py:1208 msgid "The current memory cannot be more than the maximum" msgstr "Geçerli bellek en fazla değerden büyük olamaz" -#: instances/views.py:1190 +#: instances/views.py:1213 #, python-format msgid "" "User %(quota_msg)s quota reached, cannot resize memory of " @@ -5769,25 +5797,25 @@ msgstr "" "%(quota_msg)s kullanıcı kotası doldu, '%(instance_name)s' in belleği yeniden " "boyutlandırılamıyor!" -#: instances/views.py:1199 +#: instances/views.py:1222 #, python-format msgid "Memory set to %(cur)s MiB (maximum %(max)s MiB)" msgstr "Bellek %(cur)s MiB olarak ayarlandı (en fazla %(max)s MiB)" -#: instances/views.py:1219 +#: instances/views.py:1242 msgid "Enter disk sizes as whole GiB" msgstr "Disk boyutlarını tam GiB olarak girin" -#: instances/views.py:1223 +#: instances/views.py:1246 #, python-format msgid "%(dev)s is %(size)s: a disk cannot shrink" msgstr "%(dev)s boyutu %(size)s: disk küçültülemez" -#: instances/views.py:1231 +#: instances/views.py:1254 msgid "No disks were selected or valid for resizing." msgstr "Boyutlandırma için seçilmiş ya da geçerli disk yok." -#: instances/views.py:1241 +#: instances/views.py:1264 #, python-format msgid "" "User %(quota_msg)s quota reached, cannot resize disks of '%(instance_name)s'!" @@ -5795,61 +5823,61 @@ msgstr "" "%(quota_msg)s kullanıcı kotası doldu, '%(instance_name)s' in diski yeniden " "boyutlandırılamıyor!" -#: instances/views.py:1256 +#: instances/views.py:1279 #, python-format msgid "The storage pool of %(dev)s cannot resize its volumes" msgstr "%(dev)s diskinin depolama havuzu birimlerini boyutlandıramaz" -#: instances/views.py:1258 +#: instances/views.py:1281 #, python-format msgid "Disk is resized: %(dev)s" msgstr "Disk boyutu değiştirildi: %(dev)s" -#: instances/views.py:1352 +#: instances/views.py:1375 msgid "The VM's machine has no PCIe slots to add" msgstr "Sanal makinenin makine tipinde eklenecek PCIe yuvası yok" -#: instances/views.py:1357 +#: instances/views.py:1380 #, python-format msgid "%(count)s PCIe slots added" msgstr "%(count)s PCIe yuvası eklendi" -#: instances/views.py:1359 +#: instances/views.py:1382 #, python-format msgid "%(count)s PCIe slots added: the VM gets them at its next start" msgstr "" "%(count)s PCIe yuvası eklendi: sanal makine bunları sonraki başlatmasında " "alır" -#: instances/views.py:1490 +#: instances/views.py:1513 msgid "Volume changes are applied. But it will be activated after shutdown" msgstr "" "Birim değişiklikleri uygulandı. Ama yeniden başlatıldıktan sonra uygulanacak" -#: instances/views.py:1495 +#: instances/views.py:1518 msgid "Volume is changed successfully." msgstr "Birim başarıyla değiştirildi." -#: instances/views.py:1513 +#: instances/views.py:1536 #, python-format msgid "Disk %(dev)s is not a storage volume of this instance" msgstr "%(dev)s diski bu sanal makinenin bir depolama birimi değil" -#: instances/views.py:1520 +#: instances/views.py:1543 #, python-format msgid "Disk %(dev)s has a pending change; delete it once the VM is shut down" msgstr "" "%(dev)s diskinin bekleyen bir değişikliği var; sanal makine kapatıldığında " "silin" -#: instances/views.py:1535 +#: instances/views.py:1558 #, python-format msgid "Volume %(vol)s is used by another VM; detach it instead" msgstr "" "%(vol)s birimi başka bir sanal makine tarafından kullanılıyor; onun yerine " "ayırın" -#: instances/views.py:1547 +#: instances/views.py:1570 #, python-format msgid "" "The guest has not released disk %(dev)s yet; volume %(vol)s was kept. Delete " @@ -5858,52 +5886,52 @@ msgstr "" "Konuk sistem %(dev)s diskini henüz bırakmadı; %(vol)s birimi korundu. Sanal " "makine kapatıldığında depolama havuzundan silin." -#: instances/views.py:1558 +#: instances/views.py:1581 #, python-format msgid "Disk %(dev)s was detached, but its volume was kept: %(err)s" msgstr "%(dev)s diski ayrıldı, ancak birimi korundu: %(err)s" -#: instances/views.py:1584 +#: instances/views.py:1607 #, python-format msgid "This VM cannot take a CD-ROM on the %(bus)s bus now" msgstr "Bu sanal makineye şu anda %(bus)s veri yolunda CD-ROM eklenemez" -#: instances/views.py:1587 +#: instances/views.py:1610 msgid "A USB CD-ROM needs an ISO image: libvirt has no empty USB drive" msgstr "USB CD-ROM bir ISO imajı ister: libvirt boş USB sürücü desteklemiyor" -#: instances/views.py:1680 +#: instances/views.py:1703 #, python-format msgid "Snapshot was not deleted: %(err)s" msgstr "Anlık görüntü silinmedi: %(err)s" -#: instances/views.py:1795 +#: instances/views.py:1818 msgid "Check at least one device to boot from." msgstr "Önyükleme için en az bir aygıt işaretleyin." -#: instances/views.py:1809 +#: instances/views.py:1832 msgid "Boot order changed. It applies at the next start." msgstr "Ön yükleme sırası değiştirildi. Sonraki başlatmada uygulanır." -#: instances/views.py:1811 +#: instances/views.py:1834 msgid "Boot order changed successfully." msgstr "Ön yükleme sırası başarıyla değiştirildi." -#: instances/views.py:1831 +#: instances/views.py:1854 msgid "The XML is empty" msgstr "XML boş" -#: instances/views.py:1835 instances/views.py:2452 +#: instances/views.py:1858 instances/views.py:2475 #, python-format msgid "The XML is not well-formed: %(error)s" msgstr "XML düzgün biçimlendirilmemiş: %(error)s" -#: instances/views.py:1837 +#: instances/views.py:1860 #, python-format msgid "The XML must keep the VM's UUID %(uuid)s." msgstr "XML, sanal makinenin %(uuid)s UUID'sini korumalıdır." -#: instances/views.py:1841 +#: instances/views.py:1864 msgid "" "The definition has changed since the editor was opened. Reload the page to " "edit the current one." @@ -5911,35 +5939,35 @@ msgstr "" "Düzenleyici açıldığından beri tanım değişti. Geçerli tanımı düzenlemek için " "sayfayı yeniden yükleyin." -#: instances/views.py:1853 +#: instances/views.py:1876 msgid "The XML definition was saved." msgstr "XML tanımı kaydedildi." -#: instances/views.py:1855 +#: instances/views.py:1878 msgid "The XML definition was saved; the VM uses it from its next start." msgstr "" "XML tanımı kaydedildi; sanal makine onu sonraki başlatmasından itibaren " "kullanır." -#: instances/views.py:1936 +#: instances/views.py:1959 msgid "Select one network interface to change" msgstr "Değiştirilecek bir ağ arabirimi seçin" -#: instances/views.py:1974 +#: instances/views.py:1997 msgid "Network Device Config is changed. Please shutdown instance to activate." msgstr "" "Ağ aygıtı yapılandırması değiştirildi. Aktifleştirmek için lütfen sanal " "makineyi kapatın." -#: instances/views.py:1976 +#: instances/views.py:1999 msgid "Network device changed" msgstr "Ağ aygıtı değiştirildi" -#: instances/views.py:1988 +#: instances/views.py:2011 msgid "No network selected" msgstr "Ağ seçilmedi" -#: instances/views.py:2008 +#: instances/views.py:2031 msgid "" "The running VM has no free PCI slot for another network device: shut it down " "and add the device then; Add PCIe slots on the Disks tab gives it free ones " @@ -5949,21 +5977,21 @@ msgstr "" "aygıtı sonra ekleyin; Diskler sekmesindeki PCIe yuvaları ekle, sonraki " "başlatmadan itibaren ona boş yuvalar verir." -#: instances/views.py:2045 instances/views.py:2051 +#: instances/views.py:2068 instances/views.py:2074 msgid "The VM has it" msgstr "Sanal makinede var" -#: instances/views.py:2082 +#: instances/views.py:2105 #, python-format msgid "The host has no USB device %(device)s" msgstr "Sunucuda %(device)s USB aygıtı yok" -#: instances/views.py:2103 +#: instances/views.py:2126 #, python-format msgid "The host has no PCI device %(device)s" msgstr "Sunucuda %(device)s PCI aygıtı yok" -#: instances/views.py:2121 +#: instances/views.py:2144 #, python-format msgid "" "Removal of PCI device %(address)s requested: the guest has not let it go " @@ -5973,20 +6001,20 @@ msgstr "" "bırakmadı; bırakana ya da sanal makine durana kadar çalışan sanal makinede " "kalır." -#: instances/views.py:2166 instances/views.py:2196 +#: instances/views.py:2189 instances/views.py:2219 msgid "Select a network interface and a QoS direction" msgstr "Bir ağ arabirimi ve bir QoS yönü seçin" -#: instances/views.py:2169 +#: instances/views.py:2192 msgid "QoS rates must be whole numbers" msgstr "QoS hızları tam sayı olmalıdır" -#: instances/views.py:2176 networks/views.py:229 +#: instances/views.py:2199 networks/views.py:229 #, python-format msgid "%(qos_dir)s QoS is set" msgstr "%(qos_dir)s QoS ayarlandı" -#: instances/views.py:2181 +#: instances/views.py:2204 #, python-format msgid "" "%(qos_dir)s QoS is set. A running VM gets it after it is powered off and on." @@ -5994,12 +6022,12 @@ msgstr "" "%(qos_dir)s QoS ayarlandı. Çalışan bir sanal makine, kapatılıp açıldıktan " "sonra bunu alır." -#: instances/views.py:2203 networks/views.py:255 +#: instances/views.py:2226 networks/views.py:255 #, python-format msgid "%(qos_dir)s QoS is deleted" msgstr "%(qos_dir)s QoS silindi" -#: instances/views.py:2208 +#: instances/views.py:2231 #, python-format msgid "" "%(qos_dir)s QoS is deleted. A running VM keeps the old limit until it is " @@ -6008,21 +6036,21 @@ msgstr "" "%(qos_dir)s QoS silindi. Çalışan bir sanal makine, kapatılıp açılana kadar " "eski sınırı korur." -#: instances/views.py:2236 +#: instances/views.py:2259 msgid "Only one owner is allowed and the one already added" msgstr "Sadece bir sahibe izin verilmiştir ve bir sahip zaten eklenmiş" -#: instances/views.py:2238 +#: instances/views.py:2261 #, python-format msgid "%(user)s is already an owner" msgstr "%(user)s zaten bir sahip" -#: instances/views.py:2282 +#: instances/views.py:2305 #, python-format msgid "Console settings were not saved: %(errors)s" msgstr "Konsol ayarları kaydedilmedi: %(errors)s" -#: instances/views.py:2304 +#: instances/views.py:2327 msgid "" "Error setting console password. You should check that your instance have an " "graphic device." @@ -6030,67 +6058,67 @@ msgstr "" "Konsol parolası ayarlanırken hata oluştu. Sanal makinenin bir grafik " "aygıtına sahip olduğundan emin olunuz." -#: instances/views.py:2351 +#: instances/views.py:2374 msgid "Shut the VM down to change whether it is a template." msgstr "Şablon olup olmadığını değiştirmek için sanal makineyi kapatın." -#: instances/views.py:2358 +#: instances/views.py:2381 #, python-format msgid "User %(quota_msg)s quota reached, '%(instance_name)s' stays a template!" msgstr "" "Kullanıcının %(quota_msg)s kotası doldu, '%(instance_name)s' şablon olarak " "kalıyor!" -#: instances/views.py:2440 +#: instances/views.py:2463 msgid "The XML has no name" msgstr "XML'de ad yok" -#: instances/views.py:2454 webvirtcloud/middleware.py:75 +#: instances/views.py:2477 webvirtcloud/middleware.py:75 #, python-format msgid "libvirt Error - %(exception)s" msgstr "libvirt Hatası - %(exception)s" -#: instances/views.py:2461 +#: instances/views.py:2484 msgid "An empty disk of the size you set" msgstr "Belirlediğiniz boyutta boş bir disk" -#: instances/views.py:2462 +#: instances/views.py:2485 msgid "Volumes already in a pool" msgstr "Bir havuzda zaten bulunan birimler" -#: instances/views.py:2463 +#: instances/views.py:2486 msgid "A copy of a template volume" msgstr "Bir şablon biriminin kopyası" -#: instances/views.py:2552 +#: instances/views.py:2575 msgid "You haven't defined any storage pools" msgstr "Hiç bir depolama havuzu tanımlamadınız" -#: instances/views.py:2554 +#: instances/views.py:2577 msgid "You haven't defined any network pools" msgstr "Hiç bir ağ havuzu tanımlamadınız" -#: instances/views.py:2617 +#: instances/views.py:2640 msgid "Flavor Created" msgstr "Flavor Oluşturuldu" -#: instances/views.py:2625 +#: instances/views.py:2648 msgid "Create Flavor" msgstr "Kullanıcı Oluştur" -#: instances/views.py:2640 +#: instances/views.py:2663 msgid "Flavor Updated" msgstr "Flavor Güncellendi" -#: instances/views.py:2646 +#: instances/views.py:2669 msgid "Update Flavor" msgstr "Flavor Güncelle" -#: instances/views.py:2658 +#: instances/views.py:2681 msgid "Flavor Deleted" msgstr "Flavor Silindi" -#: instances/views.py:2664 +#: instances/views.py:2687 msgid "Only the size template goes; VMs made from it keep their size." msgstr "" "Yalnızca boyut şablonu silinir; ondan oluşturulan sanal makineler " diff --git a/operations/static/operations/job_panel.js b/operations/static/operations/job_panel.js index 9a13e0b1..3353d33f 100644 --- a/operations/static/operations/job_panel.js +++ b/operations/static/operations/job_panel.js @@ -74,7 +74,8 @@ function jobPanel(element, urls, csrfToken, initialIds) { var note = job.result && job.result.warning ? '
' + esc(job.result.warning) + "
" : ""; var warn = note + (left.length ? '
Files not removed:
    ' + left.map(function (path) { return '
  • ' + esc(path) + "
  • "; }).join("") + "
" : ""); - var buttons = '' + var buttons = (opens(job) ? 'Open' : "") + + '' + ''; return alertHtml(job.state, header(job.label, job.state, "", buttons) + errorHtml(job) + warn); @@ -114,6 +115,11 @@ function jobPanel(element, urls, csrfToken, initialIds) { holding = busy; } + // the page of what the job made, a path of this site + function opens(job) { + return !!(job.result && /^\/[^\/]/.test(job.result.open || "")); + } + // a job that did what it was asked, with nothing more to read: the page shows its effect function quiet(job) { var result = job.result || {}; @@ -134,7 +140,13 @@ function jobPanel(element, urls, csrfToken, initialIds) { var others = Object.keys(outcomes).length + Object.keys(pending).length + Object.keys(shown).length; // the page in use (poll.js): its outcome waits in the panel, with Refresh if (quiet(job) && !pageInUse() && !others) { - window.location.reload(); // the job is not remembered again: no loop + // a job that made a VM opens its page (result.open); another reloads this one, + // where the job is not remembered again: no loop + if (opens(job)) { + window.location.assign(job.result.open); + } else { + window.location.reload(); + } return; } outcomes[id] = job; diff --git a/operations/tests/js/job_panel.test.js b/operations/tests/js/job_panel.test.js index f56e34fd..86de38b0 100644 --- a/operations/tests/js/job_panel.test.js +++ b/operations/tests/js/job_panel.test.js @@ -19,7 +19,8 @@ function advance(ms) { const realNow = Date.now; Date.now = () => 1700000000000 + now; global.document = { hidden: false, addEventListener() {}, activeElement: null, querySelector: () => null }; -global.window = { backgroundJobRunning: false, location: { reload() { window.reloaded = true; } } }; +global.window = { backgroundJobRunning: false, + location: { reload() { window.reloaded = true; }, assign(url) { window.opened = url; } } }; // requests settle only when the test says so; callbacks run in registration order let open = []; @@ -264,6 +265,26 @@ answer("/j/u", { ...running, id: "u", label: "Upload", state: "succeeded" }); assert.ok(window.reloaded, "a job that succeeds without a warning reloads the page: it shows the job's effect"); assert.ok(!el11.innerHTML.includes('succeeded'), "not shown as well"); +// a job that made a VM opens its page; a warning keeps the outcome, with Open; only a path of this site +open = []; timers = []; window.reloaded = false; window.opened = null; +const elv = { innerHTML: "", addEventListener() {} }; +jobPanel(elv, urls, "tok", ["v"]); +advance(0); +answer("/r", empty); +answer("/j/v", { ...running, id: "v", label: "Clone VM", state: "succeeded", result: { open: "/instances/9/" } }); +assert.ok(window.opened === "/instances/9/" && !window.reloaded, "opens the clone's page"); +for (const [result, shown] of [[{ open: "/instances/9/", warning: "not started" }, true], + [{ open: "//evil.example.com/" }, false], [{ open: "javascript:alert(1)" }, false]]) { + open = []; timers = []; window.reloaded = false; window.opened = null; + const elw = { innerHTML: "", addEventListener() {} }; + jobPanel(elw, urls, "tok", ["w"]); + advance(0); + answer("/r", empty); + answer("/j/w", { ...running, id: "w", state: "succeeded", result }); + assert.strictEqual(window.opened, null, JSON.stringify(result)); + assert.strictEqual(elw.innerHTML.includes('href="/instances/9/">Open'), shown, JSON.stringify(result)); +} + // a dialog open or a field in use: the outcome waits in the panel, with Refresh for (const [label, doc] of [["dialog", { querySelector: (s) => s === ".modal.show" ? {} : null }], ["field", { activeElement: { matches: (s) => s === "input, select, textarea" } }]]) {