Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Added missing fields to the `Volume` class: `pseudo_path`, `mount_command`, `create_directory_command`, `filesystem_to_fstab_command`, `instances`, `contract`, `base_hourly_cost`, `monthly_price`, `currency`, `long_term`

## [1.23.1] - 2026-03-25

### Fixed
Expand Down
66 changes: 66 additions & 0 deletions tests/unit_tests/volumes/test_volumes.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,24 @@
'created_at': NVME_VOL_CREATED_AT,
'target': TARGET_VDA,
'ssh_key_ids': SSH_KEY_ID,
'pseudo_path': 'volume-nxC2tf9F',
'mount_command': 'mount -t nfs -o nconnect=16 nfs.fin-01.datacrunch.io:volume-nxC2tf9F /mnt/volume',
'create_directory_command': 'mkdir -p /mnt/volume',
'filesystem_to_fstab_command': "grep -qxF 'nfs.fin-01.datacrunch.io:volume-nxC2tf9F /mnt/volume nfs defaults 0 0' /etc/fstab || echo 'nfs.fin-01.datacrunch.io:volume-nxC2tf9F /mnt/volume nfs defaults 0 0' | sudo tee -a /etc/fstab",
'instances': [
{
'id': INSTANCE_ID,
'ip': '123.123.123.123',
'instance_type': '4A100.88V',
'status': 'running',
'hostname': 'hazy-star-swims-fin-01',
}
],
'contract': 'PAY_AS_YOU_GO',
'base_hourly_cost': 0.0273972602739726,
'monthly_price': 20,
'currency': 'eur',
'long_term': None,
}

HDD_VOLUME = {
Expand All @@ -64,6 +82,16 @@
'created_at': HDD_VOL_CREATED_AT,
'target': None,
'ssh_key_ids': [],
'pseudo_path': 'volume-iHdL4ysR',
'mount_command': 'mount -t nfs -o nconnect=16 nfs.fin-01.datacrunch.io:volume-iHdL4ysR /mnt/volume',
'create_directory_command': 'mkdir -p /mnt/volume',
'filesystem_to_fstab_command': "grep -qxF 'nfs.fin-01.datacrunch.io:volume-iHdL4ysR /mnt/volume nfs defaults 0 0' /etc/fstab || echo 'nfs.fin-01.datacrunch.io:volume-iHdL4ysR /mnt/volume nfs defaults 0 0' | sudo tee -a /etc/fstab",
'instances': [],
'contract': 'PAY_AS_YOU_GO',
'base_hourly_cost': 0.01,
'monthly_price': 10,
'currency': 'eur',
'long_term': None,
}

PAYLOAD = [NVME_VOLUME, HDD_VOLUME]
Expand Down Expand Up @@ -101,6 +129,34 @@ def test_initialize_a_volume(self):
assert volume.target is None
assert volume.ssh_key_ids == []

def test_create_from_dict_without_new_fields(self):
"""Test that create_from_dict handles API responses missing the new fields."""
minimal_dict = {
'id': RANDOM_VOL_ID,
'status': VolumeStatus.DETACHED,
'name': HDD_VOL_NAME,
'size': HDD_VOL_SIZE,
'type': HDD,
'is_os_volume': False,
'created_at': HDD_VOL_CREATED_AT,
'target': None,
'location': Locations.FIN_01,
'instance_id': None,
'ssh_key_ids': [],
}
volume = Volume.create_from_dict(minimal_dict)
assert volume.id == RANDOM_VOL_ID
assert volume.pseudo_path is None
assert volume.mount_command is None
assert volume.create_directory_command is None
assert volume.filesystem_to_fstab_command is None
assert volume.instances is None
assert volume.contract is None
assert volume.base_hourly_cost is None
assert volume.monthly_price is None
assert volume.currency is None
assert volume.long_term is None

def test_get_volumes(self, volumes_service, endpoint):
# arrange - add response mock
responses.add(responses.GET, endpoint, json=PAYLOAD, status=200)
Expand All @@ -126,6 +182,16 @@ def test_get_volumes(self, volumes_service, endpoint):
assert volume_nvme.created_at == NVME_VOL_CREATED_AT
assert volume_nvme.target == TARGET_VDA
assert volume_nvme.ssh_key_ids == SSH_KEY_ID
assert volume_nvme.pseudo_path == NVME_VOLUME['pseudo_path']
assert volume_nvme.mount_command == NVME_VOLUME['mount_command']
assert volume_nvme.create_directory_command == NVME_VOLUME['create_directory_command']
assert volume_nvme.filesystem_to_fstab_command == NVME_VOLUME['filesystem_to_fstab_command']
assert volume_nvme.instances == NVME_VOLUME['instances']
assert volume_nvme.contract == 'PAY_AS_YOU_GO'
assert volume_nvme.base_hourly_cost == NVME_VOLUME['base_hourly_cost']
assert volume_nvme.monthly_price == 20
assert volume_nvme.currency == 'eur'
assert volume_nvme.long_term is None

assert volume_hdd.id == HDD_VOL_ID
assert volume_hdd.status == HDD_VOL_STATUS
Expand Down
140 changes: 140 additions & 0 deletions verda/volumes/_volumes.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ def __init__(
instance_id: str | None = None,
ssh_key_ids: list[str] = [],
deleted_at: str | None = None,
pseudo_path: str | None = None,
mount_command: str | None = None,
create_directory_command: str | None = None,
filesystem_to_fstab_command: str | None = None,
instances: list[dict] | None = None,
contract: str | None = None,
base_hourly_cost: float | None = None,
monthly_price: float | None = None,
currency: str | None = None,
long_term: dict | None = None,
) -> None:
"""Initialize the volume object.

Expand Down Expand Up @@ -48,6 +58,26 @@ def __init__(
:type ssh_key_ids: list[str]
:param deleted_at: the time the volume was deleted (UTC), defaults to None
:type deleted_at: str, optional
:param pseudo_path: volume pseudo path for NFS export, defaults to None
:type pseudo_path: str, optional
:param mount_command: ready-to-use NFS mount command, defaults to None
:type mount_command: str, optional
:param create_directory_command: mkdir command for mount point, defaults to None
:type create_directory_command: str, optional
:param filesystem_to_fstab_command: fstab entry command for persistent mounts, defaults to None
:type filesystem_to_fstab_command: str, optional
:param instances: list of attached instance details, defaults to None
:type instances: list[dict], optional
:param contract: volume contract type e.g. "LONG_TERM", "PAY_AS_YOU_GO", defaults to None
:type contract: str, optional
:param base_hourly_cost: volume base hourly cost, defaults to None
:type base_hourly_cost: float, optional
:param monthly_price: volume monthly price, defaults to None
:type monthly_price: float, optional
:param currency: volume currency e.g. "usd", "eur", defaults to None
:type currency: str, optional
:param long_term: long term contract details, defaults to None
:type long_term: dict, optional
"""
self._id = id
self._status = status
Expand All @@ -61,6 +91,16 @@ def __init__(
self._instance_id = instance_id
self._ssh_key_ids = ssh_key_ids
self._deleted_at = deleted_at
self._pseudo_path = pseudo_path
self._mount_command = mount_command
self._create_directory_command = create_directory_command
self._filesystem_to_fstab_command = filesystem_to_fstab_command
self._instances = instances
self._contract = contract
self._base_hourly_cost = base_hourly_cost
self._monthly_price = monthly_price
self._currency = currency
self._long_term = long_term

@property
def id(self) -> str:
Expand Down Expand Up @@ -170,6 +210,96 @@ def deleted_at(self) -> str | None:
"""
return self._deleted_at

@property
def pseudo_path(self) -> str | None:
"""Get the volume pseudo path for NFS export.

:return: volume pseudo path
:rtype: str, optional
"""
return self._pseudo_path

@property
def mount_command(self) -> str | None:
"""Get the ready-to-use NFS mount command.

:return: mount command
:rtype: str, optional
"""
return self._mount_command

@property
def create_directory_command(self) -> str | None:
"""Get the mkdir command for creating the mount point directory.

:return: create directory command
:rtype: str, optional
"""
return self._create_directory_command

@property
def filesystem_to_fstab_command(self) -> str | None:
"""Get the fstab entry command for persistent mounts.

:return: fstab command
:rtype: str, optional
"""
return self._filesystem_to_fstab_command

@property
def instances(self) -> list[dict] | None:
"""Get the list of attached instance details.

:return: list of instance details
:rtype: list[dict], optional
"""
return self._instances

@property
def contract(self) -> str | None:
"""Get the volume contract type.

:return: contract type e.g. "LONG_TERM", "PAY_AS_YOU_GO"
:rtype: str, optional
"""
return self._contract

@property
def base_hourly_cost(self) -> float | None:
"""Get the volume base hourly cost.

:return: base hourly cost
:rtype: float, optional
"""
return self._base_hourly_cost

@property
def monthly_price(self) -> float | None:
"""Get the volume monthly price.

:return: monthly price
:rtype: float, optional
"""
return self._monthly_price

@property
def currency(self) -> str | None:
"""Get the volume currency.

:return: currency e.g. "usd", "eur"
:rtype: str, optional
"""
return self._currency

@property
def long_term(self) -> dict | None:
"""Get the long term contract details.

:return: long term contract details
:rtype: dict, optional
"""
return self._long_term

@classmethod
def create_from_dict(cls: 'Volume', volume_dict: dict) -> 'Volume':
"""Create a Volume object from a dictionary.
Expand All @@ -192,6 +322,16 @@ def create_from_dict(cls: 'Volume', volume_dict: dict) -> 'Volume':
instance_id=volume_dict['instance_id'],
ssh_key_ids=volume_dict['ssh_key_ids'],
deleted_at=volume_dict.get('deleted_at'),
pseudo_path=volume_dict.get('pseudo_path'),
mount_command=volume_dict.get('mount_command'),
create_directory_command=volume_dict.get('create_directory_command'),
filesystem_to_fstab_command=volume_dict.get('filesystem_to_fstab_command'),
instances=volume_dict.get('instances'),
contract=volume_dict.get('contract'),
base_hourly_cost=volume_dict.get('base_hourly_cost'),
monthly_price=volume_dict.get('monthly_price'),
currency=volume_dict.get('currency'),
long_term=volume_dict.get('long_term'),
)

def __str__(self) -> str:
Expand Down
Loading