Skip to content

Commit

Permalink
Add iscsi target controller , allow to boot from iscsi or attach (#2559)
Browse files Browse the repository at this point in the history
Create iqn class - represent iqn format
Create a reciever - a callback run the tatgetcli command on concrete device (ssh or local)

The isciTargetController gets from the user requested configuration to apply,
passing the configuration to a builder .
  • Loading branch information
bkopilov authored Nov 20, 2024
1 parent aaa9c7e commit b2aadcd
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 0 deletions.
1 change: 1 addition & 0 deletions Dockerfile.assisted-test-infra
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ RUN dnf -y install --enablerepo=crb \
libvirt-client \
guestfs-tools \
libvirt-devel \
targetcli \
libguestfs-tools \
libxslt \
xorriso \
Expand Down
1 change: 1 addition & 0 deletions skipper.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ volumes:
- /tmp:/tmp/
- /dev/:/dev
- /run/udev:/run/udev
- /run/dbus/system_bus_socket:/run/dbus/system_bus_socket

# podman - sharing the podman.socket between the host and the skipper container
- $XDG_RUNTIME_DIR/podman/podman.sock:/run/podman/podman.sock
Expand Down
2 changes: 2 additions & 0 deletions src/assisted_test_infra/test_infra/controllers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from .assisted_installer_infra_controller import AssistedInstallerInfraController
from .iptables import IpTableCommandOption, IptableRule
from .ipxe_controller.ipxe_controller import IPXEController
from .iscsi_target_controller import IscsiTargetController
from .nat_controller import NatController
from .node_controllers import NodeController
from .node_controllers.libvirt_controller import LibvirtController
Expand All @@ -20,6 +21,7 @@
"IptableRule",
"IpTableCommandOption",
"IPXEController",
"IscsiTargetController",
"Node",
"ProxyController",
"TangController",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
from assisted_test_infra.test_infra import utils
from service_client import log


class Iqn:

def __init__(self, unique_name: str, year_month: str = "2024-11", base_domain: str = "example.com"):
self.year_month = year_month
# base domain set in reverse order.
self.base_domain = ".".join(reversed(base_domain.split(".")))
# node name + disk index may be used test-infra-cluster-4a25cd51-master-0-disk-0
self.unique_name = unique_name

def __repr__(self):
return f"iqn.{self.year_month}.{self.base_domain}:{self.unique_name}"

def __str__(self):
return f"iqn.{self.year_month}.{self.base_domain}:{self.unique_name}"


class Receiver:

def __init__(self, fn_callback, **defaults_kwargs):
self.fn_callback = fn_callback
self.defaults_kwargs = defaults_kwargs

def execute(self, *args, **kwargs):
self.defaults_kwargs.update(kwargs)
log.info(f"iscsi_target exec: {str(args)} {str(kwargs)}")
self.fn_callback(*args, **kwargs)


class IscsiTargetConfig:
def __init__(
self, disk_name: str, disk_size_gb: int, iqn: Iqn, remote_iqn: Iqn, iso_disk_copy: str, servers: list[str]
):
self._disk_name = disk_name
self._disk_size = disk_size_gb
self._iqn = iqn
self._data_file = iso_disk_copy
self._remote_iqn = remote_iqn
self._servers = servers

@property
def disk_name(self):
return self._disk_name

@property
def disk_size(self):
return self._disk_size

@property
def iqn(self):
return self._iqn

@property
def data_file(self):
return self._data_file

@property
def remote_iqn(self):
return self._remote_iqn

@property
def servers(self):
return self._servers


class IscsiTargetController:

def __new__(cls):
# Make The controller singleton class because managed by hypervisor as a single service
if not hasattr(cls, "instance"):
log.info("Creating singleton IscsiTargetController instance")
cls.instance = super(IscsiTargetController, cls).__new__(cls)
return cls.instance

def __init__(self):
self.builder = IscsiBuilder(Receiver(utils.run_command, shell=True))
self._target_configs = []

def create_target(self, config: IscsiTargetConfig, clear_config=False) -> None:
if clear_config:
self.builder.clear_iscsi_target_config()
# append target configs to delete on cleanup
self._target_configs.append(config)

self.builder.remove_file_disk(config.disk_name)
self.builder.create_file_disk(config.disk_name, config.disk_size)
self.builder.create_file_io(config.disk_name, config.disk_size)
self.builder.create_iqn(config.iqn)
self.builder.create_lun(config.iqn, config.disk_name)
self.builder.create_portal(config.iqn, config.servers)
self.builder.create_access_list(config.iqn, config.remote_iqn)
self.builder.save_config()
self.builder.copy_to_created_disk(config.disk_name, config.data_file)

def clean_target(self) -> None:
for config in self._target_configs:
# Remove resource file created
self.builder.remove_file_disk(config.disk_name)
self.builder.clear_iscsi_target_config()


class IscsiBuilder:
"""iSCSI builder implements commands on the receiver (ssh runner).
The builder can run on any machine with targetcli installed
Iscsi configuration:
https://manpages.ubuntu.com/manpages/focal/man8/targetcli.8.html
"""

service_cmd = "targetcli"

def __init__(self, receiver):
self.receiver = receiver

def remove_file_disk(self, disk_name):
self.receiver.execute(f"rm -rf /tmp/{disk_name}")

def create_file_disk(self, disk_name: str, disk_size_gb: int, type_: str = "raw") -> None:
self.receiver.execute(f"qemu-img create -f {type_} /tmp/{disk_name} {str(disk_size_gb)}G")

def clear_iscsi_target_config(self) -> None:
cmd = f"{self.service_cmd} clearconfig confirm=True"
self.receiver.execute(cmd)

def create_file_io(self, disk_name: str, disk_size: int) -> None:
cmd = (
f"{self.service_cmd} backstores/fileio create name={disk_name}"
f" size={str(disk_size)}G file_or_dev=/tmp/{disk_name}"
)
self.receiver.execute(cmd)

def create_iqn(self, iqn: Iqn) -> None:
cmd = f"{self.service_cmd} /iscsi create {str(iqn)}"
self.receiver.execute(cmd)

def create_lun(self, iqn: Iqn, disk_name: str) -> None:
cmd = f"{self.service_cmd} /iscsi/{str(iqn)}/tpg1/luns create /backstores/fileio/{disk_name}"
self.receiver.execute(cmd)

def create_portal(self, iqn: Iqn, server_addresses: list[str], port: int = 3260) -> None:
# To enable multipath - set additional server addresses . by default 0.0.0.0 3260 exists.
cmd_delete = f"{self.service_cmd} /iscsi/{str(iqn)}/tpg1/portals delete 0.0.0.0 {port}"
self.receiver.execute(cmd_delete, raise_errors=False)
for server in server_addresses:
cmd = f"{self.service_cmd} /iscsi/{str(iqn)}/tpg1/portals create {server} {port}"
self.receiver.execute(cmd)

def create_access_list(self, iqn: Iqn, remote_iqn: Iqn) -> None:
cmd = f"{self.service_cmd} /iscsi/{str(iqn)}/tpg1/acls create {str(remote_iqn)}"
self.receiver.execute(cmd)

def save_config(self):
cmd = f"{self.service_cmd} / saveconfig"
self.receiver.execute(cmd)

def copy_to_created_disk(self, disk_name: str, data_disk_file: str) -> None:
cmd = f"dd conv=notrunc if={data_disk_file} of=/tmp/{disk_name}"
self.receiver.execute(cmd)
11 changes: 11 additions & 0 deletions src/tests/base_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
AssistedInstallerInfraController,
IptableRule,
IPXEController,
IscsiTargetController,
LibvirtController,
NatController,
Node,
Expand Down Expand Up @@ -821,6 +822,16 @@ def start_ipxe_server(**kwargs):
for server in ipxe_server_controllers:
server.remove()

@pytest.fixture()
def iscsi_target_controller(self) -> IscsiTargetController:
log.info("--- SETUP --- iscsi server controller")
_iscsi_target_controller = IscsiTargetController()

yield _iscsi_target_controller
if global_variables.test_teardown:
log.info("--- TEARDOWN --- iscsi controller")
_iscsi_target_controller.clean_target()

@staticmethod
def assert_http_error_code(api_call: Callable, status: int, reason: str, **kwargs) -> None:
with pytest.raises(ApiException) as response:
Expand Down

0 comments on commit b2aadcd

Please sign in to comment.