From 51b30303a041ce969e874d15d0e1569f524ee207 Mon Sep 17 00:00:00 2001 From: Andrii Sultanov Date: Mon, 18 May 2026 13:04:20 +0000 Subject: [PATCH 1/3] storage: Add tags to vdi_info struct This is the first step towards allowing SMAPIv1/v3 code to preserve tags on VDI migrations. Also modifies the tests by adding the new 'tags' field. Signed-off-by: Andrii Sultanov --- ocaml/xapi-idl/storage/storage_interface.ml | 1 + ocaml/xapi-storage-script/main.ml | 2 ++ ocaml/xapi-storage-script/python-self-test.t | 8 ++++---- .../test/volume/org.xen.xapi.storage.dummyv5/sr.py | 1 + .../test/volume/org.xen.xapi.storage.dummyv5/volume.py | 2 ++ ocaml/xapi-storage/rpc-light/SR.ls/response | 1 + ocaml/xapi-storage/rpc-light/Volume.clone/response | 1 + ocaml/xapi-storage/rpc-light/Volume.create/response | 1 + ocaml/xapi-storage/rpc-light/Volume.snapshot/response | 1 + ocaml/xapi/storage_smapiv1.ml | 1 + 10 files changed, 15 insertions(+), 4 deletions(-) diff --git a/ocaml/xapi-idl/storage/storage_interface.ml b/ocaml/xapi-idl/storage/storage_interface.ml index d47123e2b0f..d8c789c5a57 100644 --- a/ocaml/xapi-idl/storage/storage_interface.ml +++ b/ocaml/xapi-idl/storage/storage_interface.ml @@ -231,6 +231,7 @@ type vdi_info = { persistent: bool [@default true] ; sharable: bool [@default false] ; sm_config: (string * string) list [@default []] + ; tags: string list [@default []] } [@@deriving rpcty] diff --git a/ocaml/xapi-storage-script/main.ml b/ocaml/xapi-storage-script/main.ml index ca4661f2a88..f7921602b47 100644 --- a/ocaml/xapi-storage-script/main.ml +++ b/ocaml/xapi-storage-script/main.ml @@ -777,6 +777,7 @@ let vdi_of_volume x = x.Xapi_storage.Control.keys ; sharable= x.Xapi_storage.Control.sharable ; persistent= true + ; tags= [] } let choose_datapath ?(persistent = true) response = @@ -2152,6 +2153,7 @@ let self_test_plugin ~root_dir plugin = ; persistent= false ; sm_config= [] ; sharable= false + ; tags= [] } in Test.VDI.create rpc dbg sr vdi_info >>= fun vdi_info -> diff --git a/ocaml/xapi-storage-script/python-self-test.t b/ocaml/xapi-storage-script/python-self-test.t index 9ac59bed953..6b985a5b358 100644 --- a/ocaml/xapi-storage-script/python-self-test.t +++ b/ocaml/xapi-storage-script/python-self-test.t @@ -21,16 +21,16 @@ pids and uuids [INFO] $TESTCASE_ROOT/test/volume/org.xen.xapi.storage.dummyv5/SR.stat[PID] succeeded: {"sr": "file:///tmp/dummy", "name": "dummy SR plugin", "description": "Dummy v5 SR for unit tests.", "total_space": 0, "free_space": 0, "datasources": [], "clustered": false, "health": ["Healthy", ""]} [INFO] {"method":"Volume.create","params":[{"sharable":false,"size":0,"description":"vdi description","name":"vdi name","sr":"file:///tmp/dummy","dbg":"debug"}],"id":12} - [INFO] $TESTCASE_ROOT/test/volume/org.xen.xapi.storage.dummyv5/Volume.create[PID] succeeded: {"name": "vdi name", "description": "vdi description", "key": "UUID", "uuid": "UUID", "read_write": true, "sharable": false, "virtual_size": 0, "physical_utilisation": 0, "uri": ["raw+file:///tmp/disk.raw"], "keys": {}} + [INFO] $TESTCASE_ROOT/test/volume/org.xen.xapi.storage.dummyv5/Volume.create[PID] succeeded: {"name": "vdi name", "description": "vdi description", "key": "UUID", "uuid": "UUID", "read_write": true, "sharable": false, "virtual_size": 0, "physical_utilisation": 0, "uri": ["raw+file:///tmp/disk.raw"], "keys": {}, "tags": ["tag1"]} [INFO] {"method":"Volume.set","params":[{"v":"redolog","k":"vdi-type","key":"UUID","sr":"file:///tmp/dummy","dbg":"debug"}],"id":13} [INFO] $TESTCASE_ROOT/test/volume/org.xen.xapi.storage.dummyv5/Volume.set[PID] succeeded: null [INFO] {"method":"Volume.stat","params":[{"key":"UUID","sr":"file:///tmp/dummy","dbg":"debug"}],"id":15} - [INFO] $TESTCASE_ROOT/test/volume/org.xen.xapi.storage.dummyv5/Volume.stat[PID] succeeded: {"name": "dummy SR plugin", "description": "Dummy v5 SR for unit tests.", "key": "UUID", "uuid": "UUID", "read_write": true, "virtual_size": 0, "physical_utilisation": 0, "sharable": false, "uri": ["raw+file:///tmp/disk.raw"], "keys": {}} + [INFO] $TESTCASE_ROOT/test/volume/org.xen.xapi.storage.dummyv5/Volume.stat[PID] succeeded: {"name": "dummy SR plugin", "description": "Dummy v5 SR for unit tests.", "key": "UUID", "uuid": "UUID", "read_write": true, "virtual_size": 0, "physical_utilisation": 0, "sharable": false, "uri": ["raw+file:///tmp/disk.raw"], "keys": {}, "tags": ["tag1"]} [INFO] {"method":"Volume.stat","params":[{"key":"UUID","sr":"file:///tmp/dummy","dbg":"debug"}],"id":17} - [INFO] $TESTCASE_ROOT/test/volume/org.xen.xapi.storage.dummyv5/Volume.stat[PID] succeeded: {"name": "dummy SR plugin", "description": "Dummy v5 SR for unit tests.", "key": "UUID", "uuid": "UUID", "read_write": true, "virtual_size": 0, "physical_utilisation": 0, "sharable": false, "uri": ["raw+file:///tmp/disk.raw"], "keys": {}} + [INFO] $TESTCASE_ROOT/test/volume/org.xen.xapi.storage.dummyv5/Volume.stat[PID] succeeded: {"name": "dummy SR plugin", "description": "Dummy v5 SR for unit tests.", "key": "UUID", "uuid": "UUID", "read_write": true, "virtual_size": 0, "physical_utilisation": 0, "sharable": false, "uri": ["raw+file:///tmp/disk.raw"], "keys": {}, "tags": ["tag1"]} [INFO] {"method":"Volume.destroy","params":[{"key":"UUID","sr":"file:///tmp/dummy","dbg":"debug"}],"id":18} [INFO] $TESTCASE_ROOT/test/volume/org.xen.xapi.storage.dummyv5/Volume.destroy[PID] succeeded: null @@ -39,7 +39,7 @@ pids and uuids [INFO] $TESTCASE_ROOT/test/volume/org.xen.xapi.storage.dummyv5/SR.stat[PID] succeeded: {"sr": "file:///tmp/dummy", "name": "dummy SR plugin", "description": "Dummy v5 SR for unit tests.", "total_space": 0, "free_space": 0, "datasources": [], "clustered": false, "health": ["Healthy", ""]} [INFO] {"method":"SR.ls","params":[{"sr":"file:///tmp/dummy","dbg":"debug"}],"id":22} - [INFO] $TESTCASE_ROOT/test/volume/org.xen.xapi.storage.dummyv5/SR.ls[PID] succeeded: [{"name": "dummy SR plugin", "description": "Dummy v5 SR for unit tests.", "key": "file1", "uuid": "file1", "read_write": true, "virtual_size": 0, "physical_utilisation": 0, "sharable": false, "uri": ["raw+file:///tmp/disk.raw"], "keys": {}}] + [INFO] $TESTCASE_ROOT/test/volume/org.xen.xapi.storage.dummyv5/SR.ls[PID] succeeded: [{"name": "dummy SR plugin", "description": "Dummy v5 SR for unit tests.", "key": "file1", "uuid": "file1", "read_write": true, "virtual_size": 0, "physical_utilisation": 0, "sharable": false, "uri": ["raw+file:///tmp/disk.raw"], "keys": {}, "tags": ["tag1"]}] [INFO] {"method":"SR.probe","params":[{"configuration":{"uri":"file:///tmp/dummy"},"dbg":"debug"}],"id":24} [INFO] $TESTCASE_ROOT/test/volume/org.xen.xapi.storage.dummyv5/SR.probe[PID] succeeded: [{"configuration": {"uri": "file:///tmp/dummy"}, "complete": true, "extra_info": {}}, {"configuration": {"uri": "file:///tmp/dummy", "sr_uuid": "myuuid"}, "sr": {"sr": "file:///tmp/dummy", "name": "dummy SR plugin", "description": "Dummy v5 SR for unit tests.", "total_space": 0, "free_space": 0, "datasources": [], "clustered": false, "health": ["Healthy", ""]}, "complete": true, "extra_info": {}}] diff --git a/ocaml/xapi-storage-script/test/volume/org.xen.xapi.storage.dummyv5/sr.py b/ocaml/xapi-storage-script/test/volume/org.xen.xapi.storage.dummyv5/sr.py index 3c649423d15..9667a788690 100755 --- a/ocaml/xapi-storage-script/test/volume/org.xen.xapi.storage.dummyv5/sr.py +++ b/ocaml/xapi-storage-script/test/volume/org.xen.xapi.storage.dummyv5/sr.py @@ -39,6 +39,7 @@ def ls(self, dbg, sr): "sharable": False, "uri": ["raw+file:///tmp/disk.raw"], "keys": {}, + "tags": ["tag1"], }] def stat(self, dbg, sr): diff --git a/ocaml/xapi-storage-script/test/volume/org.xen.xapi.storage.dummyv5/volume.py b/ocaml/xapi-storage-script/test/volume/org.xen.xapi.storage.dummyv5/volume.py index fcf52ce3883..13f22794ad5 100755 --- a/ocaml/xapi-storage-script/test/volume/org.xen.xapi.storage.dummyv5/volume.py +++ b/ocaml/xapi-storage-script/test/volume/org.xen.xapi.storage.dummyv5/volume.py @@ -30,6 +30,7 @@ def create(self, dbg, sr, name, description, size, sharable): "physical_utilisation": 0, "uri": ["raw+file:///tmp/disk.raw"], "keys": {}, + "tags": ["tag1"], } def destroy(self, dbg, sr, key): @@ -50,6 +51,7 @@ def stat(self, dbg, sr, key): "sharable": False, "uri": ["raw+file:///tmp/disk.raw"], "keys": {}, + "tags": ["tag1"], } def set(self, dbg, sr, key, k, v): diff --git a/ocaml/xapi-storage/rpc-light/SR.ls/response b/ocaml/xapi-storage/rpc-light/SR.ls/response index 7f989e33066..b3ca5475f65 100644 --- a/ocaml/xapi-storage/rpc-light/SR.ls/response +++ b/ocaml/xapi-storage/rpc-light/SR.ls/response @@ -14,6 +14,7 @@ keys volume_typeData cbt_enabledfalse + tagstag1 diff --git a/ocaml/xapi-storage/rpc-light/Volume.clone/response b/ocaml/xapi-storage/rpc-light/Volume.clone/response index dc4036f599d..f6b23b83f93 100644 --- a/ocaml/xapi-storage/rpc-light/Volume.clone/response +++ b/ocaml/xapi-storage/rpc-light/Volume.clone/response @@ -13,6 +13,7 @@ keys volume_typeData cbt_enabledfalse + tagstag1 diff --git a/ocaml/xapi-storage/rpc-light/Volume.create/response b/ocaml/xapi-storage/rpc-light/Volume.create/response index dc4036f599d..f6b23b83f93 100644 --- a/ocaml/xapi-storage/rpc-light/Volume.create/response +++ b/ocaml/xapi-storage/rpc-light/Volume.create/response @@ -13,6 +13,7 @@ keys volume_typeData cbt_enabledfalse + tagstag1 diff --git a/ocaml/xapi-storage/rpc-light/Volume.snapshot/response b/ocaml/xapi-storage/rpc-light/Volume.snapshot/response index dc4036f599d..f6b23b83f93 100644 --- a/ocaml/xapi-storage/rpc-light/Volume.snapshot/response +++ b/ocaml/xapi-storage/rpc-light/Volume.snapshot/response @@ -13,6 +13,7 @@ keys volume_typeData cbt_enabledfalse + tagstag1 diff --git a/ocaml/xapi/storage_smapiv1.ml b/ocaml/xapi/storage_smapiv1.ml index dc1a48e809f..07c99a6800a 100644 --- a/ocaml/xapi/storage_smapiv1.ml +++ b/ocaml/xapi/storage_smapiv1.ml @@ -80,6 +80,7 @@ let vdi_info_of_vdi_rec __context vdi_rec = ; persistent= vdi_rec.API.vDI_on_boot = `persist ; sharable= vdi_rec.API.vDI_sharable ; sm_config= vdi_rec.API.vDI_sm_config + ; tags= vdi_rec.API.vDI_tags } let redirect _sr = From 42cb9bd2e06cc579eeb02af0e2c6ad455586b2ad Mon Sep 17 00:00:00 2001 From: Andrii Sultanov Date: Mon, 18 May 2026 13:13:05 +0000 Subject: [PATCH 2/3] storage: Add VDI.{add_tags,remove_tags} methods This allows for the storage backend to preserve tags after creating a destination VDI during migration. Signed-off-by: Andrii Sultanov --- ocaml/xapi-idl/storage/storage_interface.ml | 22 +++++++++++++ ocaml/xapi-idl/storage/storage_skeleton.ml | 6 ++++ ocaml/xapi-storage-script/main.ml | 35 ++++++++++++++++++++- ocaml/xapi/storage_mux.ml | 18 +++++++++++ ocaml/xapi/storage_smapiv1.ml | 22 +++++++++++++ ocaml/xapi/storage_smapiv1_wrapper.ml | 14 +++++++++ 6 files changed, 116 insertions(+), 1 deletion(-) diff --git a/ocaml/xapi-idl/storage/storage_interface.ml b/ocaml/xapi-idl/storage/storage_interface.ml index d8c789c5a57..25297028ebc 100644 --- a/ocaml/xapi-idl/storage/storage_interface.ml +++ b/ocaml/xapi-idl/storage/storage_interface.ml @@ -999,6 +999,16 @@ module StorageAPI (R : RPC) = struct declare "VDI.remove_from_sm_config" [] (dbg_p @-> sr_p @-> vdi_p @-> key_p @-> returning unit_p err) + (** [add_tags] task sr vdi key value] adds [key] to [vdi] tags *) + let add_tags = + declare "VDI.add_tags" [] + (dbg_p @-> sr_p @-> vdi_p @-> key_p @-> returning unit_p err) + + (** [remove_tags dbg sr vdi key] removes [key] from [vdi] tags *) + let remove_tags = + declare "VDI.remove_tags" [] + (dbg_p @-> sr_p @-> vdi_p @-> key_p @-> returning unit_p err) + (** [enable_cbt dbg sr vdi] enables changed block tracking for [vdi] *) let enable_cbt = declare "VDI.enable_cbt" [] @@ -1628,6 +1638,12 @@ module type Server_impl = sig val remove_from_sm_config : context -> dbg:debug_info -> sr:sr -> vdi:vdi -> key:string -> unit + val add_tags : + context -> dbg:debug_info -> sr:sr -> vdi:vdi -> key:string -> unit + + val remove_tags : + context -> dbg:debug_info -> sr:sr -> vdi:vdi -> key:string -> unit + val enable_cbt : context -> dbg:debug_info -> sr:sr -> vdi:vdi -> unit val disable_cbt : context -> dbg:debug_info -> sr:sr -> vdi:vdi -> unit @@ -1832,6 +1848,12 @@ module Server (Impl : Server_impl) () = struct S.VDI.remove_from_sm_config (fun dbg sr vdi key -> Impl.VDI.remove_from_sm_config () ~dbg ~sr ~vdi ~key ) ; + S.VDI.add_tags (fun dbg sr vdi key -> + Impl.VDI.add_tags () ~dbg ~sr ~vdi ~key + ) ; + S.VDI.remove_tags (fun dbg sr vdi key -> + Impl.VDI.remove_tags () ~dbg ~sr ~vdi ~key + ) ; S.VDI.enable_cbt (fun dbg sr vdi -> Impl.VDI.enable_cbt () ~dbg ~sr ~vdi) ; S.VDI.disable_cbt (fun dbg sr vdi -> Impl.VDI.disable_cbt () ~dbg ~sr ~vdi) ; S.VDI.data_destroy (fun dbg sr vdi -> Impl.VDI.data_destroy () ~dbg ~sr ~vdi) ; diff --git a/ocaml/xapi-idl/storage/storage_skeleton.ml b/ocaml/xapi-idl/storage/storage_skeleton.ml index a2d2d04ab08..44b971f2a8a 100644 --- a/ocaml/xapi-idl/storage/storage_skeleton.ml +++ b/ocaml/xapi-idl/storage/storage_skeleton.ml @@ -164,6 +164,12 @@ module VDI = struct let remove_from_sm_config ctx ~dbg ~sr ~vdi ~key = Storage_interface.unimplemented __FUNCTION__ + let add_tags ctx ~dbg ~sr ~vdi ~key = + Storage_interface.unimplemented __FUNCTION__ + + let remove_tags ctx ~dbg ~sr ~vdi ~key = + Storage_interface.unimplemented __FUNCTION__ + let enable_cbt ctx ~dbg ~sr ~vdi = Storage_interface.unimplemented __FUNCTION__ diff --git a/ocaml/xapi-storage-script/main.ml b/ocaml/xapi-storage-script/main.ml index f7921602b47..144f145147f 100644 --- a/ocaml/xapi-storage-script/main.ml +++ b/ocaml/xapi-storage-script/main.ml @@ -367,6 +367,8 @@ let _is_a_snapshot_key = "is_a_snapshot" let _snapshot_of_key = "snapshot_of" +let _vdi_tags_key = "tags" + module Script = struct (** We cache (lowercase script name -> original script name) mapping for the scripts in the root directory of every registered plugin. *) @@ -740,6 +742,19 @@ let vdi_of_volume x = v |> of_string in let find_string = find ~of_string:Fun.id in + let extract_prefixed_list prefix = + List.filter_map + (fun (k, _) -> + if String.starts_with ~prefix k then + Some + (String.sub k (String.length prefix) + (String.length k - String.length prefix) + ) + else + None + ) + x.Xapi_storage.Control.keys + in let open Storage_interface in { vdi= Vdi.of_string x.Xapi_storage.Control.key @@ -777,7 +792,7 @@ let vdi_of_volume x = x.Xapi_storage.Control.keys ; sharable= x.Xapi_storage.Control.sharable ; persistent= true - ; tags= [] + ; tags= extract_prefixed_list _vdi_tags_key } let choose_datapath ?(persistent = true) response = @@ -1742,6 +1757,22 @@ module VDIImpl (M : META) = struct let* () = unset ~dbg ~sr ~vdi ~key:(_sm_config_prefix_key ^ key) in return () + let vdi_add_tags_impl dbg sr vdi key = + wrap + @@ + let* sr = Attached_SRs.find sr in + let vdi = Storage_interface.Vdi.string_of vdi in + let* () = set ~dbg ~sr ~vdi ~key:(_vdi_tags_key ^ key) ~value:key in + return () + + let vdi_remove_tags_impl dbg sr vdi key = + wrap + @@ + let* sr = Attached_SRs.find sr in + let vdi = Storage_interface.Vdi.string_of vdi in + let* () = unset ~dbg ~sr ~vdi ~key:(_vdi_tags_key ^ key) in + return () + let similar_content_impl _dbg _sr _vdi = wrap @@ return [] end @@ -1946,6 +1977,8 @@ let bind ~volume_script_dir = S.VDI.set_content_id VDI.vdi_set_content_id_impl ; S.VDI.add_to_sm_config VDI.vdi_add_to_sm_config_impl ; S.VDI.remove_from_sm_config VDI.vdi_remove_from_sm_config_impl ; + S.VDI.add_tags VDI.vdi_add_tags_impl ; + S.VDI.remove_tags VDI.vdi_remove_tags_impl ; S.VDI.similar_content VDI.similar_content_impl ; let module DP = DPImpl (RuntimeMeta) in diff --git a/ocaml/xapi/storage_mux.ml b/ocaml/xapi/storage_mux.ml index e1d31f84844..c4abd1131fd 100644 --- a/ocaml/xapi/storage_mux.ml +++ b/ocaml/xapi/storage_mux.ml @@ -741,6 +741,24 @@ module Mux = struct end)) in C.VDI.remove_from_sm_config (Debug_info.to_string di) sr vdi key + let add_tags () ~dbg ~sr ~vdi ~key = + with_dbg ~name:"VDI.add_tags" ~dbg @@ fun di -> + info "VDI.add_tags dbg:%s sr:%s vdi:%s key:%s" dbg (s_of_sr sr) + (s_of_vdi vdi) key ; + let module C = StorageAPI (Idl.Exn.GenClient (struct + let rpc = of_sr sr + end)) in + C.VDI.add_tags (Debug_info.to_string di) sr vdi key + + let remove_tags () ~dbg ~sr ~vdi ~key = + with_dbg ~name:"VDI.remove_tags" ~dbg @@ fun di -> + info "VDI.remove_tags dbg:%s sr:%s vdi:%s key:%s" dbg (s_of_sr sr) + (s_of_vdi vdi) key ; + let module C = StorageAPI (Idl.Exn.GenClient (struct + let rpc = of_sr sr + end)) in + C.VDI.remove_tags (Debug_info.to_string di) sr vdi key + let get_url () ~dbg ~sr ~vdi = with_dbg ~name:"VDI.get_url" ~dbg @@ fun di -> info "VDI.get_url dbg:%s sr:%s vdi:%s" dbg (s_of_sr sr) (s_of_vdi vdi) ; diff --git a/ocaml/xapi/storage_smapiv1.ml b/ocaml/xapi/storage_smapiv1.ml index 07c99a6800a..b6ed8f127a8 100644 --- a/ocaml/xapi/storage_smapiv1.ml +++ b/ocaml/xapi/storage_smapiv1.ml @@ -1051,6 +1051,28 @@ module SMAPIv1 : Server_impl = struct Db.VDI.remove_from_sm_config ~__context ~self ~key ) + let add_tags _context ~dbg ~sr ~vdi ~key = + with_dbg ~name:"VDI.add_to_tags" ~dbg @@ fun di -> + info "VDI.add_tags dbg:%s sr:%s vdi:%s key:[%s]" di.log (s_of_sr sr) + (s_of_vdi vdi) key ; + let dbg = Debug_info.to_string di in + Server_helpers.exec_with_new_task "VDI.add_tags" + ~subtask_of:(Ref.of_string dbg) (fun __context -> + let vdi, _ = find_vdi ~__context sr vdi in + Db.VDI.add_tags ~__context ~self:vdi ~value:key + ) + + let remove_tags _context ~dbg ~sr ~vdi ~key = + with_dbg ~name:"VDI.remove_tags" ~dbg @@ fun di -> + info "VDI.remove_tags dbg:%s sr:%s vdi:%s key:%s" di.log (s_of_sr sr) + (s_of_vdi vdi) key ; + let dbg = Debug_info.to_string di in + Server_helpers.exec_with_new_task "VDI.remove_tags" + ~subtask_of:(Ref.of_string dbg) (fun __context -> + let self = find_vdi ~__context sr vdi |> fst in + Db.VDI.remove_tags ~__context ~self ~value:key + ) + let get_url _context ~dbg ~sr ~vdi = with_dbg ~name:"VDI.get_url" ~dbg @@ fun di -> info "VDI.get_url dbg:%s sr:%s vdi:%s" di.log (s_of_sr sr) (s_of_vdi vdi) ; diff --git a/ocaml/xapi/storage_smapiv1_wrapper.ml b/ocaml/xapi/storage_smapiv1_wrapper.ml index 71e11367b9f..197e5c84351 100644 --- a/ocaml/xapi/storage_smapiv1_wrapper.ml +++ b/ocaml/xapi/storage_smapiv1_wrapper.ml @@ -908,6 +908,20 @@ functor let dbg = Debug_info.to_string di in Impl.VDI.remove_from_sm_config context ~dbg ~sr ~vdi ~key + let add_tags context ~dbg ~sr ~vdi ~key = + with_dbg ~name:"VDI.add_tags" ~dbg @@ fun di -> + info "VDI.add_tags dbg:%s sr:%s vdi:%s key:%s" di.log (s_of_sr sr) + (s_of_vdi vdi) key ; + let dbg = Debug_info.to_string di in + Impl.VDI.add_tags context ~dbg ~sr ~vdi ~key + + let remove_tags context ~dbg ~sr ~vdi ~key = + with_dbg ~name:"VDI.remove_tags" ~dbg @@ fun di -> + info "VDI.remove_tags dbg:%s sr:%s vdi:%s key:%s" di.log (s_of_sr sr) + (s_of_vdi vdi) key ; + let dbg = Debug_info.to_string di in + Impl.VDI.remove_tags context ~dbg ~sr ~vdi ~key + let get_url context ~dbg ~sr ~vdi = with_dbg ~name:"VDI.get_url" ~dbg @@ fun di -> info "VDI.get_url dbg:%s sr:%s vdi:%s" di.log (s_of_sr sr) (s_of_vdi vdi) ; From 76c7e267da596ba4a527457c318ce81f19b38ef4 Mon Sep 17 00:00:00 2001 From: Andrii Sultanov Date: Mon, 18 May 2026 13:13:33 +0000 Subject: [PATCH 3/3] storage: Preserve VDI tags on SMAPIv1 migrate SMAPI.VDI.create ignores the tags from vdi_info, so set them after the call Signed-off-by: Andrii Sultanov --- ocaml/xapi-storage-script/main.ml | 8 ++++++++ ocaml/xapi/storage_smapiv1_migrate.ml | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/ocaml/xapi-storage-script/main.ml b/ocaml/xapi-storage-script/main.ml index 144f145147f..cd790afac48 100644 --- a/ocaml/xapi-storage-script/main.ml +++ b/ocaml/xapi-storage-script/main.ml @@ -1461,6 +1461,14 @@ module VDIImpl (M : META) = struct set ~dbg ~sr ~vdi:response.Xapi_storage.Control.key ~key:_vdi_type_key ~value:vdi_info.ty >>>= fun () -> + let rec f = function + | key :: x -> + let* _ = set ~dbg ~sr ~vdi ~key:(_vdi_tags_key ^ key) ~value:key in + f x + | [] -> + return () + in + f vdi_info.tags >>>= fun () -> let response = { (vdi_of_volume response) with diff --git a/ocaml/xapi/storage_smapiv1_migrate.ml b/ocaml/xapi/storage_smapiv1_migrate.ml index b4cc5c71b31..5857d56ed6f 100644 --- a/ocaml/xapi/storage_smapiv1_migrate.ml +++ b/ocaml/xapi/storage_smapiv1_migrate.ml @@ -631,6 +631,11 @@ module MIRROR : SMAPIv2_MIRROR = struct try let vdi_info = {vdi_info with sm_config= [("base_mirror", id)]} in let leaf = SMAPI.VDI.create dbg sr vdi_info in + + List.iter + (fun tag -> Local.VDI.add_tags dbg sr leaf.vdi tag) + vdi_info.tags ; + D.info "Created leaf VDI for mirror receive: %s" (string_of_vdi_info leaf) ; on_fail := (fun () -> SMAPI.VDI.destroy dbg sr leaf.vdi) :: !on_fail ; (* dummy VDI is created so that the leaf VDI becomes a differencing disk,