@@ -109,6 +109,68 @@ def test_linstor_sr_expand_disk(self, linstor_sr, provisioning_type, storage_poo
109
109
f"Expected SR size to increase but got old size: { sr_size } , new size: { new_sr_size } "
110
110
logging .info ("SR expansion completed" )
111
111
112
+ @pytest .mark .small_vm
113
+ def test_linstor_sr_expand_host (self , linstor_sr , vm_with_reboot_check , prepare_linstor_packages ,
114
+ join_host_to_pool , setup_lvm_on_host , host , hostB1 , storage_pool_name ,
115
+ provisioning_type ):
116
+ """
117
+ This test validates expansion of a LINSTOR SR by dynamically adding a new host with local storage to the pool.
118
+ A VM is started on the SR before expansion begins to ensure the SR is in active use during the process.
119
+
120
+ It performs the following steps:
121
+ - Installs LINSTOR packages on the new host (if missing).
122
+ - Detects and prepares raw disks using LVM commands.
123
+ - Joins the host (hostB1) to the existing pool and registers it with LINSTOR as a node.
124
+ - Creates a new LINSTOR storage pool on the added host (LVM or LVM-thin, based on provisioning type).
125
+ - Confirms SR expansion by verifying increased physical size.
126
+ - Ensures SR functionality by rebooting the VM running on the SR.
127
+
128
+ Finally, the test cleans up by deleting the LINSTOR node, ejecting the host from the pool,
129
+ and removing packages and LVM metadata.
130
+ """
131
+ sr = linstor_sr
132
+ sr_size = sr .pool .master .xe ('sr-param-get' , {'uuid' : sr .uuid , 'param-name' : 'physical-size' })
133
+ resized = False
134
+
135
+ # TODO: This section could be moved into a separate fixture for modularity.
136
+ # However, capturing the SR size before expansion is critical to the test logic,
137
+ # so it's intentionally kept inline to preserve control over the measurement point.
138
+
139
+ sr_group_name = "xcp-sr-" + storage_pool_name .replace ("/" , "_" )
140
+ hostname = hostB1 .xe ('host-param-get' , {'uuid' : hostB1 .uuid , 'param-name' : 'name-label' })
141
+ controller_option = "--controllers=" + "," .join ([m .hostname_or_ip for m in host .pool .hosts ])
142
+
143
+ logging .info ("Current list of linstor nodes:" )
144
+ logging .info (host .ssh_with_result (["linstor" , controller_option , "node" , "list" ]).stdout )
145
+
146
+ logging .info ("Creating linstor node" )
147
+ host .ssh (["linstor" , controller_option , "node" , "create" , "--node-type" , "combined" ,
148
+ "--communication-type" , "plain" , hostname , hostB1 .hostname_or_ip ])
149
+ hostB1 .ssh (['systemctl' , 'restart' , 'linstor-satellite.service' ])
150
+ time .sleep (45 )
151
+
152
+ logging .info ("New list of linstor nodes:" )
153
+ logging .info (host .ssh_with_result (["linstor" , controller_option , "node" , "list" ]).stdout )
154
+ logging .info ("Expanding with linstor node" )
155
+
156
+ if provisioning_type == "thin" :
157
+ hostB1 .ssh (['lvcreate' , '-l' , '+100%FREE' , '-T' , storage_pool_name ])
158
+ host .ssh (["linstor" , controller_option , "storage-pool" , "create" , "lvmthin" ,
159
+ hostname , sr_group_name , storage_pool_name ])
160
+ else :
161
+ host .ssh (["linstor" , controller_option , "storage-pool" , "create" , "lvm" ,
162
+ hostname , sr_group_name , storage_pool_name ])
163
+
164
+ sr .scan ()
165
+ resized = True
166
+ new_sr_size = sr .pool .master .xe ('sr-param-get' , {'uuid' : sr .uuid , 'param-name' : 'physical-size' })
167
+ assert int (new_sr_size ) > int (sr_size ) and resized is True , \
168
+ f"Expected SR size to increase but got old size: { sr_size } , new size: { new_sr_size } "
169
+ logging .info ("SR expansion completed from size %s to %s" , sr_size , new_sr_size )
170
+
171
+ # Cleanup
172
+ host .ssh (["linstor" , controller_option , "node" , "delete" , hostname ])
173
+
112
174
# *** tests with reboots (longer tests).
113
175
114
176
@pytest .mark .reboot
0 commit comments