44from pathlib import Path
55from typing import List
66
7- from ceph_devstack import config
7+ from ceph_devstack import config , DEFAULT_CONFIG_PATH
88from ceph_devstack .host import host
99from ceph_devstack .resources .container import Container
1010
@@ -154,7 +154,7 @@ class Pulpito(Container):
154154
155155
156156class TestNode (Container ):
157- cmd_vars : List [str ] = ["name" , "image" , "loop_dev_name" ]
157+ cmd_vars : List [str ] = ["name" , "image" ]
158158 capabilities = [
159159 "SYS_ADMIN" ,
160160 "NET_ADMIN" ,
@@ -170,99 +170,131 @@ class TestNode(Container):
170170 "AUDIT_WRITE" ,
171171 "AUDIT_CONTROL" ,
172172 ]
173- create_cmd = [
174- "podman" ,
175- "container" ,
176- "create" ,
177- "--rm" ,
178- "-i" ,
179- "--network" ,
180- "ceph-devstack" ,
181- "--systemd=always" ,
182- "--cgroupns=host" ,
183- "--secret" ,
184- "id_rsa.pub" ,
185- "-p" ,
186- "22" ,
187- "--cap-add" ,
188- "," .join (capabilities ),
189- "--security-opt" ,
190- "unmask=/sys/dev/block" ,
191- "-v" ,
192- "/sys/dev/block:/sys/dev/block" ,
193- "-v" ,
194- "/sys/fs/cgroup:/sys/fs/cgroup" ,
195- "-v" ,
196- "/dev/fuse:/dev/fuse" ,
197- "-v" ,
198- "/dev/disk:/dev/disk" ,
199- # cephadm tries to access these DMI-related files, and by default they
200- # have 600 permissions on the host. It appears to be ok if they are
201- # empty, though.
202- "-v" ,
203- "/dev/null:/sys/class/dmi/id/board_serial" ,
204- "-v" ,
205- "/dev/null:/sys/class/dmi/id/chassis_serial" ,
206- "-v" ,
207- "/dev/null:/sys/class/dmi/id/product_serial" ,
208- "--device" ,
209- "/dev/net/tun" ,
210- "--device" ,
211- "{loop_dev_name}" ,
212- "--name" ,
213- "{name}" ,
214- "{image}" ,
215- ]
216173 env_vars = {
217174 "SSH_PUBKEY" : "" ,
218175 "CEPH_VOLUME_ALLOW_LOOP_DEVICES" : "true" ,
219176 }
220177
221178 def __init__ (self , name : str = "" ):
222179 super ().__init__ (name = name )
223- self .loop_index = 0
224- self .loop_img_name = self .name
180+ self .index = 0
225181 if "_" in self .name :
226- self .loop_index = int (self .name .split ("_" )[- 1 ])
227- else :
228- self .loop_img_name += str (self .loop_index )
229- self .loop_dev_name = f"/dev/loop{ self .loop_index } "
182+ self .index = int (self .name .split ("_" )[- 1 ])
183+ self .loop_device_count = config ["containers" ]["testnode" ].get (
184+ "loop_device_count" , 1
185+ )
186+ self .devices = [self .device_name (i ) for i in range (self .loop_device_count )]
230187
231188 @property
232189 def loop_img_dir (self ):
233190 return (Path (config ["data_dir" ]) / "disk_images" ).expanduser ()
234191
192+ @property
193+ def create_cmd (self ):
194+ return [
195+ "podman" ,
196+ "container" ,
197+ "create" ,
198+ "--rm" ,
199+ "-i" ,
200+ "--network" ,
201+ "ceph-devstack" ,
202+ "--systemd=always" ,
203+ "--cgroupns=host" ,
204+ "--secret" ,
205+ "id_rsa.pub" ,
206+ "-p" ,
207+ "22" ,
208+ "--cap-add" ,
209+ "," .join (self .capabilities ),
210+ "--security-opt" ,
211+ "unmask=/sys/dev/block" ,
212+ "-v" ,
213+ "/sys/dev/block:/sys/dev/block" ,
214+ "-v" ,
215+ "/sys/fs/cgroup:/sys/fs/cgroup" ,
216+ "-v" ,
217+ "/dev/fuse:/dev/fuse" ,
218+ "-v" ,
219+ "/dev/disk:/dev/disk" ,
220+ # cephadm tries to access these DMI-related files, and by default they
221+ # have 600 permissions on the host. It appears to be ok if they are
222+ # empty, though.
223+ # The below was bizarrely causing this error message:
224+ # No such file or directory: OCI runtime attempted to invoke a command that was
225+ # not found
226+ # That was causing the container to fail to start up.
227+ "-v" ,
228+ "/dev/null:/sys/class/dmi/id/board_serial" ,
229+ "-v" ,
230+ "/dev/null:/sys/class/dmi/id/chassis_serial" ,
231+ "-v" ,
232+ "/dev/null:/sys/class/dmi/id/product_serial" ,
233+ * self .additional_volumes ,
234+ "--device" ,
235+ "/dev/net/tun" ,
236+ * [f"--device={ device } " for device in self .devices ],
237+ "--name" ,
238+ "{name}" ,
239+ "{image}" ,
240+ ]
241+
242+ @property
243+ def additional_volumes (self ):
244+ volumes = []
245+ if (
246+ sshd_config := DEFAULT_CONFIG_PATH .parent .joinpath (
247+ "sshd_config"
248+ ).expanduser ()
249+ ) and sshd_config .exists ():
250+ volumes .extend (
251+ [
252+ "-v" ,
253+ f"{ sshd_config } :/etc/ssh/sshd_config.d/teuthology.conf:z" ,
254+ ]
255+ )
256+ return volumes
257+
235258 async def create (self ):
236259 if not await self .exists ():
237- await self .create_loop_device ()
260+ await self .create_loop_devices ()
238261 await super ().create ()
239262
240263 async def remove (self ):
241264 await super ().remove ()
242- await self .remove_loop_device ()
265+ await self .remove_loop_devices ()
243266
244- async def create_loop_device (self ):
267+ async def create_loop_devices (self ):
268+ for device in self .devices :
269+ await self .create_loop_device (device )
270+
271+ async def remove_loop_devices (self ):
272+ for device in self .devices :
273+ await self .remove_loop_device (device )
274+
275+ async def create_loop_device (self , device : str ):
245276 size = config ["containers" ]["testnode" ]["loop_device_size" ]
246277 os .makedirs (self .loop_img_dir , exist_ok = True )
247278 proc = await self .cmd (["lsmod" , "|" , "grep" , "loop" ])
248279 if proc and await proc .wait () != 0 :
249280 await self .cmd (["sudo" , "modprobe" , "loop" ])
250- loop_img_name = os .path .join (self .loop_img_dir , self .loop_img_name )
251- await self .remove_loop_device ()
281+ loop_img_name = os .path .join (self .loop_img_dir , self .device_image (device ))
282+ await self .remove_loop_device (device )
283+ device_pos = device .removeprefix ("/dev/loop" )
252284 await self .cmd (
253285 [
254286 "sudo" ,
255287 "mknod" ,
256288 "-m700" ,
257- self . loop_dev_name ,
289+ device ,
258290 "b" ,
259291 "7" ,
260- str ( self . loop_index ) ,
292+ device_pos ,
261293 ],
262294 check = True ,
263295 )
264296 await self .cmd (
265- ["sudo" , "chown" , f"{ os .getuid ()} :{ os .getgid ()} " , self . loop_dev_name ],
297+ ["sudo" , "chown" , f"{ os .getuid ()} :{ os .getgid ()} " , device ],
266298 check = True ,
267299 )
268300 await self .cmd (
@@ -277,20 +309,25 @@ async def create_loop_device(self):
277309 ],
278310 check = True ,
279311 )
280- await self .cmd (
281- ["sudo" , "losetup" , self .loop_dev_name , loop_img_name ], check = True
282- )
312+ await self .cmd (["sudo" , "losetup" , device , loop_img_name ], check = True )
313+ await self .cmd (["chcon" , "-t" , "fixed_disk_device_t" , device ])
283314
284- async def remove_loop_device (self ):
285- loop_img_name = os .path .join (self .loop_img_dir , self .loop_img_name )
286- if os .path .ismount (self . loop_dev_name ):
287- await self .cmd (["umount" , self . loop_dev_name ], check = True )
288- if host .path_exists (self . loop_dev_name ):
289- await self .cmd (["sudo" , "losetup" , "-d" , self . loop_dev_name ])
290- await self .cmd (["sudo" , "rm" , "-f" , self . loop_dev_name ], check = True )
315+ async def remove_loop_device (self , device : str ):
316+ loop_img_name = os .path .join (self .loop_img_dir , self .device_image ( device ) )
317+ if os .path .ismount (device ):
318+ await self .cmd (["umount" , device ], check = True )
319+ if host .path_exists (device ):
320+ await self .cmd (["sudo" , "losetup" , "-d" , device ])
321+ await self .cmd (["sudo" , "rm" , "-f" , device ], check = True )
291322 if host .path_exists (loop_img_name ):
292323 os .remove (loop_img_name )
293324
325+ def device_name (self , index : int ):
326+ return f"/dev/loop{ self .loop_device_count * self .index + index } "
327+
328+ def device_image (self , device : str ):
329+ return f"{ self .name } -{ device .removeprefix ('/dev/loop' )} "
330+
294331
295332class Teuthology (Container ):
296333 cmd_vars : List [str ] = ["name" , "image" , "image_tag" , "archive_dir" ]
0 commit comments