diff --git a/synology_dsm/api/storage/storage.py b/synology_dsm/api/storage/storage.py index a5ba110c..a33ed8d0 100644 --- a/synology_dsm/api/storage/storage.py +++ b/synology_dsm/api/storage/storage.py @@ -144,8 +144,7 @@ def _get_disk(self, disk_id): def _get_disks_for_volume(self, volume_id): """Returns a list of disk for a specific volume.""" disks = [] - pools = self._data.get("storagePools", []) - for pool in pools: + for pool in self.storage_pools: if pool["deploy_path"] == volume_id: for disk_id in pool["disks"]: disks.append(self._get_disk(disk_id)) diff --git a/synology_dsm/synology_dsm.py b/synology_dsm/synology_dsm.py index 7aad8e0a..d6b1a4d1 100644 --- a/synology_dsm/synology_dsm.py +++ b/synology_dsm/synology_dsm.py @@ -28,6 +28,10 @@ class SynologyDSM(object): API_INFO = "SYNO.API.Info" API_AUTH = "SYNO.API.Auth" + DSM_5_WEIRD_URL_API = [ + SynoStorage.API_KEY, + ] + def __init__( self, dsm_ip, @@ -74,16 +78,22 @@ def _debuglog(self, message): if self._debugmode: print("DEBUG: " + message) - def _build_url(self, api): - if ( - api == SynoStorage.API_KEY + def _is_weird_api_url(self, api): + """Returns True if the API URL is not common (nas_base_url/webapi/path?params) [Only handles DSM 5 for now].""" + return ( + api in self.DSM_5_WEIRD_URL_API and self._information and self._information.version and int(self._information.version) < 7321 # < DSM 6 - ): - return ( - "%s/webman/modules/StorageManager/storagehandler.cgi?" % self._base_url - ) + ) + + def _build_url(self, api): + if self._is_weird_api_url(api): + if api == SynoStorage.API_KEY: + return ( + "%s/webman/modules/StorageManager/storagehandler.cgi?" + % self._base_url + ) return "%s/webapi/%s?" % (self._base_url, self.apis[api]["path"]) @@ -176,15 +186,18 @@ def _request( if not self._session_id and api not in [self.API_AUTH, self.API_INFO]: self.login() - # Check if API is available - if not self.apis.get(api): - raise SynologyDSMAPINotExistsException(api) - # Build request params if not params: params = {} params["api"] = api - params["version"] = self.apis[api]["maxVersion"] + params["version"] = 1 + + if not self._is_weird_api_url(api): + # Check if API is available + if not self.apis.get(api): + raise SynologyDSMAPINotExistsException(api) + params["version"] = self.apis[api]["maxVersion"] + params["method"] = method if api == SynoStorage.API_KEY: diff --git a/tests/__init__.py b/tests/__init__.py index bf09609a..fe4cb1ad 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -25,6 +25,37 @@ DSM_6_CORE_UTILIZATION, DSM_6_STORAGE_STORAGE, ) +from .api_data.dsm_5 import ( + DSM_5_API_INFO, + DSM_5_AUTH_LOGIN, + DSM_5_AUTH_LOGIN_2SA, + DSM_5_AUTH_LOGIN_2SA_OTP, + DSM_5_DSM_INFORMATION, + DSM_5_CORE_UTILIZATION, + DSM_5_STORAGE_STORAGE, +) + +API_SWITCHER = { + 5: { + "API_INFO": DSM_5_API_INFO, + "AUTH_LOGIN": DSM_5_AUTH_LOGIN, + "AUTH_LOGIN_2SA": DSM_5_AUTH_LOGIN_2SA, + "AUTH_LOGIN_2SA_OTP": DSM_5_AUTH_LOGIN_2SA_OTP, + "DSM_INFORMATION": DSM_5_DSM_INFORMATION, + "CORE_UTILIZATION": DSM_5_CORE_UTILIZATION, + "STORAGE_STORAGE": DSM_5_STORAGE_STORAGE, + }, + 6: { + "API_INFO": DSM_6_API_INFO, + "AUTH_LOGIN": DSM_6_AUTH_LOGIN, + "AUTH_LOGIN_2SA": DSM_6_AUTH_LOGIN_2SA, + "AUTH_LOGIN_2SA_OTP": DSM_6_AUTH_LOGIN_2SA_OTP, + "DSM_INFORMATION": DSM_6_DSM_INFORMATION, + "CORE_UTILIZATION": DSM_6_CORE_UTILIZATION, + "STORAGE_STORAGE": DSM_6_STORAGE_STORAGE, + }, +} + if six.PY2: from future.moves.urllib.parse import urlencode @@ -43,7 +74,7 @@ class SynologyDSMMock(SynologyDSM): """Mocked SynologyDSM.""" - API_URI = "entry.cgi" + API_URI = "api=" def __init__( self, @@ -66,8 +97,11 @@ def __init__( debugmode, ) + self.dsm_version = 6 + def _execute_request(self, method, url, **kwargs): url += urlencode(kwargs["params"]) + if "no_internet" in url: raise SynologyDSMRequestException( ConnError( @@ -98,38 +132,38 @@ def _execute_request(self, method, url, **kwargs): raise SynologyDSMRequestException(RequestException("Bad request")) if self.API_INFO in url: - return DSM_6_API_INFO + return API_SWITCHER[self.dsm_version]["API_INFO"] if self.API_AUTH in url: if VALID_USER_2SA in url and VALID_PASSWORD in url: if "otp_code" not in url and "device_id" not in url: - return DSM_6_AUTH_LOGIN_2SA + return API_SWITCHER[self.dsm_version]["AUTH_LOGIN_2SA"] if "device_id" in url and DEVICE_TOKEN in url: - return DSM_6_AUTH_LOGIN + return API_SWITCHER[self.dsm_version]["AUTH_LOGIN"] if "otp_code" in url: if VALID_OTP in url: - return DSM_6_AUTH_LOGIN_2SA_OTP + return API_SWITCHER[self.dsm_version]["AUTH_LOGIN_2SA_OTP"] return ERROR_AUTH_OTP_AUTHENTICATE_FAILED if VALID_USER in url and VALID_PASSWORD in url: - return DSM_6_AUTH_LOGIN + return API_SWITCHER[self.dsm_version]["AUTH_LOGIN"] return ERROR_AUTH_INVALID_CREDENTIALS if self.API_URI in url: - if not self._session_id or not self._syno_token: + if not self._session_id: return ERROR_INSUFFICIENT_USER_PRIVILEGE if SynoDSMInformation.API_KEY in url: - return DSM_6_DSM_INFORMATION + return API_SWITCHER[self.dsm_version]["DSM_INFORMATION"] if SynoCoreUtilization.API_KEY in url: - return DSM_6_CORE_UTILIZATION + return API_SWITCHER[self.dsm_version]["CORE_UTILIZATION"] if SynoStorage.API_KEY in url: - return DSM_6_STORAGE_STORAGE + return API_SWITCHER[self.dsm_version]["STORAGE_STORAGE"] if ( "SYNO.FileStation.Upload" in url diff --git a/tests/api_data/dsm_5/const_5_api_auth.py b/tests/api_data/dsm_5/const_5_api_auth.py index b1a3b0db..7f197e8e 100644 --- a/tests/api_data/dsm_5/const_5_api_auth.py +++ b/tests/api_data/dsm_5/const_5_api_auth.py @@ -1,12 +1,17 @@ """DSM 5 SYNO.API.Auth data.""" -from tests.const import ( # pylint: disable=unused-import +from tests.const import ( SESSION_ID, DEVICE_TOKEN, - SYNO_TOKEN, ERROR_AUTH_OTP_NOT_SPECIFIED, ) - -DSM_5_AUTH_LOGIN = {} +# No synotoken for an unknown reason +DSM_5_AUTH_LOGIN = { + "data": {"is_portal_port": False, "sid": SESSION_ID}, + "success": True, +} DSM_5_AUTH_LOGIN_2SA = ERROR_AUTH_OTP_NOT_SPECIFIED -DSM_5_AUTH_LOGIN_2SA_OTP = {} +DSM_5_AUTH_LOGIN_2SA_OTP = { + "data": {"did": DEVICE_TOKEN, "is_portal_port": False, "sid": SESSION_ID}, + "success": True, +} diff --git a/tests/api_data/dsm_5/storage/const_5_storage_storage.py b/tests/api_data/dsm_5/storage/const_5_storage_storage.py index 258cec1b..d01834da 100644 --- a/tests/api_data/dsm_5/storage/const_5_storage_storage.py +++ b/tests/api_data/dsm_5/storage/const_5_storage_storage.py @@ -3,265 +3,286 @@ from tests.const import UNIQUE_KEY DSM_5_STORAGE_STORAGE = { - "disks": [ - { - "container": { - "order": 0, - "str": "DS410j", - "supportPwrBtnDisable": False, - "type": "internal", + "data": { + "disks": [ + {"id": "test_disk"}, + { + "container": { + "order": 0, + "str": "DS410j", + "supportPwrBtnDisable": False, + "type": "internal", + }, + "device": "/dev/sdd", + "disable_secera": False, + "diskType": "SATA", + "erase_time": 374, + "firm": "SC60", + "has_system": True, + "id": "sdd", + "is4Kn": False, + "isSsd": False, + "is_erasing": False, + "longName": "Disk 4", + "model": "ST3000VN007-2E4166 ", + "name": "Disk 4", + "num_id": 4, + "order": 4, + "portType": "normal", + "serial": "Z73095S2", + "size_total": "3000592982016", + "smart_status": "safe", + "status": "normal", + "support": False, + "temp": 42, + "used_by": "volume_1", + "vendor": "Seagate", }, - "device": "/dev/sdd", - "disable_secera": False, - "diskType": "SATA", - "erase_time": 374, - "firm": "SC60", - "has_system": True, - "id": "sdd", - "is4Kn": False, - "isSsd": False, - "is_erasing": False, - "longName": "Disk 4", - "model": "ST3000VN007-2E4166 ", - "name": "Disk 4", - "num_id": 4, - "order": 4, - "portType": "normal", - "serial": "Z73095S2", - "size_total": "3000592982016", - "smart_status": "safe", - "status": "normal", - "support": False, - "temp": 42, - "used_by": "volume_1", - "vendor": "Seagate", - }, - { - "container": { - "order": 0, - "str": "DS410j", - "supportPwrBtnDisable": False, - "type": "internal", + { + "container": { + "order": 0, + "str": "DS410j", + "supportPwrBtnDisable": False, + "type": "internal", + }, + "device": "/dev/sdc", + "disable_secera": False, + "diskType": "SATA", + "erase_time": 410, + "firm": "80.00A80", + "has_system": True, + "id": "sdc", + "is4Kn": False, + "isSsd": False, + "is_erasing": False, + "longName": "Disk 3", + "model": "WD30EZRZ-00Z5HB0 ", + "name": "Disk 3", + "num_id": 3, + "order": 3, + "portType": "normal", + "serial": "WD-WCC4N0TEJ4F0", + "size_total": "3000592982016", + "smart_status": "safe", + "status": "normal", + "support": False, + "temp": 42, + "used_by": "volume_1", + "vendor": "WDC ", }, - "device": "/dev/sdc", - "disable_secera": False, - "diskType": "SATA", - "erase_time": 410, - "firm": "80.00A80", - "has_system": True, - "id": "sdc", - "is4Kn": False, - "isSsd": False, - "is_erasing": False, - "longName": "Disk 3", - "model": "WD30EZRZ-00Z5HB0 ", - "name": "Disk 3", - "num_id": 3, - "order": 3, - "portType": "normal", - "serial": "WD-WCC4N0TEJ4F0", - "size_total": "3000592982016", - "smart_status": "safe", - "status": "normal", - "support": False, - "temp": 42, - "used_by": "volume_1", - "vendor": "WDC ", - }, - { - "container": { - "order": 0, - "str": "DS410j", - "supportPwrBtnDisable": False, - "type": "internal", + { + "container": { + "order": 0, + "str": "DS410j", + "supportPwrBtnDisable": False, + "type": "internal", + }, + "device": "/dev/sdb", + "disable_secera": False, + "diskType": "SATA", + "erase_time": 408, + "firm": "82.00A82", + "has_system": True, + "id": "sdb", + "is4Kn": False, + "isSsd": False, + "is_erasing": False, + "longName": "Disk 2", + "model": "WD30EFRX-68EUZN0 ", + "name": "Disk 2", + "num_id": 2, + "order": 2, + "portType": "normal", + "serial": "WD-WCC4N6LSVCVX", + "size_total": "3000592982016", + "smart_status": "safe", + "status": "normal", + "support": False, + "temp": 43, + "used_by": "volume_1", + "vendor": "WDC ", }, - "device": "/dev/sdb", - "disable_secera": False, - "diskType": "SATA", - "erase_time": 408, - "firm": "82.00A82", - "has_system": True, - "id": "sdb", - "is4Kn": False, - "isSsd": False, - "is_erasing": False, - "longName": "Disk 2", - "model": "WD30EFRX-68EUZN0 ", - "name": "Disk 2", - "num_id": 2, - "order": 2, - "portType": "normal", - "serial": "WD-WCC4N6LSVCVX", - "size_total": "3000592982016", - "smart_status": "safe", - "status": "normal", - "support": False, - "temp": 43, - "used_by": "volume_1", - "vendor": "WDC ", - }, - { - "container": { - "order": 0, - "str": "DS410j", - "supportPwrBtnDisable": False, - "type": "internal", + { + "container": { + "order": 0, + "str": "DS410j", + "supportPwrBtnDisable": False, + "type": "internal", + }, + "device": "/dev/sda", + "disable_secera": False, + "diskType": "SATA", + "erase_time": 0, + "firm": "82.00A82", + "has_system": True, + "id": "sda", + "is4Kn": False, + "isSsd": False, + "is_erasing": False, + "longName": "Disk 1", + "model": "WD30EFRX-68N32N0 ", + "name": "Disk 1", + "num_id": 1, + "order": 1, + "portType": "normal", + "serial": "WD-WCC7K5YA5H40", + "size_total": "3000592982016", + "smart_status": "90%", + "status": "normal", + "support": False, + "temp": 44, + "used_by": "volume_1", + "vendor": "WDC ", }, - "device": "/dev/sda", - "disable_secera": False, - "diskType": "SATA", - "erase_time": 0, - "firm": "82.00A82", - "has_system": True, - "id": "sda", - "is4Kn": False, - "isSsd": False, - "is_erasing": False, - "longName": "Disk 1", - "model": "WD30EFRX-68N32N0 ", - "name": "Disk 1", - "num_id": 1, - "order": 1, - "portType": "normal", - "serial": "WD-WCC7K5YA5H40", - "size_total": "3000592982016", - "smart_status": "90%", - "status": "normal", - "support": False, - "temp": 44, - "used_by": "volume_1", - "vendor": "WDC ", + ], + "env": { + "batchtask": {"max_task": 64, "remain_task": 64}, + "bay_number": "4", + "ebox": [], + "fs_acting": False, + "is_space_actioning": False, + "isns": {"address": "", "enabled": False}, + "isns_server": "", + "max_fs_bytes": "17592181850112", + "max_fs_bytes_high_end": "219902325555200", + "model_name": "DS410j", + "ram_enough_for_fs_high_end": False, + "ram_size": 0, + "ram_size_required": 32, + "settingSwap": False, + "showpooltab": False, + "status": {"system_crashed": False, "system_need_repair": False}, + "support": {"ebox": False, "raid_cross": False, "sysdef": True}, + "unique_key": UNIQUE_KEY, }, - ], - "env": { - "batchtask": {"max_task": 64, "remain_task": 64}, - "bay_number": "4", - "ebox": [], - "fs_acting": False, - "is_space_actioning": False, - "isns": {"address": "", "enabled": False}, - "isns_server": "", - "max_fs_bytes": "17592181850112", - "max_fs_bytes_high_end": "219902325555200", - "model_name": "DS410j", - "ram_enough_for_fs_high_end": False, - "ram_size": 0, - "ram_size_required": 32, - "settingSwap": False, - "showpooltab": False, - "status": {"system_crashed": False, "system_need_repair": False}, - "support": {"ebox": False, "raid_cross": False, "sysdef": True}, - "unique_key": UNIQUE_KEY, - }, - "hotSpares": [], - "iscsiLuns": [ - { - "can_do": { - "data_scrubbing": True, - "delete": True, - "expand_by_disk": 1, - "migrate": {"to_raid5+spare": "1-1", "to_raid6": 1}, - }, - "id": "iscsilun_LUN-1", - "is_actioning": False, - "iscsi_lun": { - "blkNum": "19614744", - "device_type": "file", - "lid": 1, - "location": "volume_1", - "mapped_targets": [1], - "name": "LUN-1", - "restored_time": "0", - "rootpath": "/volume1", - "size": "10737418240", - "thin_provision": False, - "uuid": "fcf3a450-681c-06cb-fbb9-0400bdbe0780", - "vaai_extent_size": "0", - "vaai_support": False, - }, - "num_id": 1, - "progress": {"percent": "-1", "step": "none"}, - "status": "normal", - } - ], - "iscsiTargets": [ - { - "auth": {"mutual_username": "", "type": "none", "username": ""}, - "data_chksum": 0, - "enabled": True, - "hdr_chksum": 0, - "iqn": "iqn.2000-01.com.synology:DiskStation.name", - "mapped_logical_unit_number": [0], - "mapped_luns": [1], - "masking": [ - {"iqn": "iqn.2000-01.com.synology:default.acl", "permission": "rw"} - ], - "multi_sessions": False, - "name": "Target-1", - "num_id": 1, - "recv_seg_bytes": 262144, - "remote": [], - "send_seg_bytes": 4096, - "status": "online", - "tid": 1, - } - ], - "ports": [], - "storagePools": [], - "success": True, - "volumes": [ - { - "can_do": { - "data_scrubbing": True, - "delete": True, - "expand_by_disk": 1, - "migrate": {"to_raid5+spare": "1-1", "to_raid6": 1}, - }, - "container": "internal", - "device_type": "raid_5", - "disk_failure_number": 0, - "disks": ["sda", "sdb", "sdc", "sdd"], - "eppool_used": "10042748928", - "fs_type": "ext3", - "id": "volume_1", - "is_acting": False, - "is_actioning": False, - "is_inode_full": False, - "is_writable": True, - "max_fs_size": "17592181850112", - "maximal_disk_size": "0", - "minimal_disk_size": "2995767869440", - "num_id": 1, - "pool_path": "", - "progress": {"percent": "-1", "step": "none"}, - "size": { - "free_inode": "547237217", - "total": "8846249701376", - "total_device": "8987275100160", - "total_inode": "548544512", - "used": "5719795761152", - }, - "space_path": "/dev/md2", - "spares": [], - "ssd_trim": {"support": "not support"}, - "status": "normal", - "suggestions": [], - "timebackup": True, - "used_by_gluster": False, - "vol_path": "/volume1", - "vspace_can_do": { - "drbd": { - "resize": {"can_do": False, "errCode": 53504, "stopService": False} + "hotSpares": [], + "iscsiLuns": [ + { + "can_do": { + "data_scrubbing": True, + "delete": True, + "expand_by_disk": 1, + "migrate": {"to_raid5+spare": "1-1", "to_raid6": 1}, }, - "flashcache": { - "apply": {"can_do": False, "errCode": 53504, "stopService": False}, - "remove": {"can_do": False, "errCode": 53504, "stopService": False}, - "resize": {"can_do": False, "errCode": 53504, "stopService": False}, + "id": "iscsilun_LUN-1", + "is_actioning": False, + "iscsi_lun": { + "blkNum": "19614744", + "device_type": "file", + "lid": 1, + "location": "volume_1", + "mapped_targets": [1], + "name": "LUN-1", + "restored_time": "0", + "rootpath": "/volume1", + "size": "10737418240", + "thin_provision": False, + "uuid": "fcf3a450-681c-06cb-fbb9-0400bdbe0780", + "vaai_extent_size": "0", + "vaai_support": False, }, - "snapshot": { - "resize": {"can_do": True, "errCode": 0, "stopService": False} + "num_id": 1, + "progress": {"percent": "-1", "step": "none"}, + "status": "normal", + } + ], + "iscsiTargets": [ + { + "auth": {"mutual_username": "", "type": "none", "username": ""}, + "data_chksum": 0, + "enabled": True, + "hdr_chksum": 0, + "iqn": "iqn.2000-01.com.synology:DiskStation.name", + "mapped_logical_unit_number": [0], + "mapped_luns": [1], + "masking": [ + {"iqn": "iqn.2000-01.com.synology:default.acl", "permission": "rw"} + ], + "multi_sessions": False, + "name": "Target-1", + "num_id": 1, + "recv_seg_bytes": 262144, + "remote": [], + "send_seg_bytes": 4096, + "status": "online", + "tid": 1, + } + ], + "ports": [], + "storagePools": [], + "success": True, + "volumes": [ + {"id": "test_volume"}, + { + "can_do": { + "data_scrubbing": True, + "delete": True, + "expand_by_disk": 1, + "migrate": {"to_raid5+spare": "1-1", "to_raid6": 1}, + }, + "container": "internal", + "device_type": "raid_5", + "disk_failure_number": 0, + "disks": ["sda", "sdb", "sdc", "sdd"], + "eppool_used": "10042748928", + "fs_type": "ext3", + "id": "volume_1", + "is_acting": False, + "is_actioning": False, + "is_inode_full": False, + "is_writable": True, + "max_fs_size": "17592181850112", + "maximal_disk_size": "0", + "minimal_disk_size": "2995767869440", + "num_id": 1, + "pool_path": "", + "progress": {"percent": "-1", "step": "none"}, + "size": { + "free_inode": "547237217", + "total": "8846249701376", + "total_device": "8987275100160", + "total_inode": "548544512", + "used": "5719795761152", + }, + "space_path": "/dev/md2", + "spares": [], + "ssd_trim": {"support": "not support"}, + "status": "normal", + "suggestions": [], + "timebackup": True, + "used_by_gluster": False, + "vol_path": "/volume1", + "vspace_can_do": { + "drbd": { + "resize": { + "can_do": False, + "errCode": 53504, + "stopService": False, + } + }, + "flashcache": { + "apply": { + "can_do": False, + "errCode": 53504, + "stopService": False, + }, + "remove": { + "can_do": False, + "errCode": 53504, + "stopService": False, + }, + "resize": { + "can_do": False, + "errCode": 53504, + "stopService": False, + }, + }, + "snapshot": { + "resize": {"can_do": True, "errCode": 0, "stopService": False} + }, }, }, - } - ], + ], + }, + "success": True, } diff --git a/tests/api_data/dsm_6/core/const_6_core_utilization.py b/tests/api_data/dsm_6/core/const_6_core_utilization.py index b6ca9966..c452cf15 100644 --- a/tests/api_data/dsm_6/core/const_6_core_utilization.py +++ b/tests/api_data/dsm_6/core/const_6_core_utilization.py @@ -9,7 +9,7 @@ "5min_load": 33, "device": "System", "other_load": 3, - "system_load": 0, + "system_load": 2, "user_load": 4, }, "disk": { diff --git a/tests/test_synology_dsm.py b/tests/test_synology_dsm.py index cf295177..0fc32346 100644 --- a/tests/test_synology_dsm.py +++ b/tests/test_synology_dsm.py @@ -196,6 +196,7 @@ def test_information(self): assert self.api.information.temperature == 40 assert not self.api.information.temperature_warn assert self.api.information.uptime == 155084 + assert self.api.information.version == "24922" assert self.api.information.version_string == "DSM 6.2.2-24922 Update 4" def test_utilisation(self): @@ -207,7 +208,7 @@ def test_utilisation_cpu(self): assert self.api.utilisation.cpu assert self.api.utilisation.cpu_other_load assert self.api.utilisation.cpu_user_load - assert self.api.utilisation.cpu_system_load == 0 + assert self.api.utilisation.cpu_system_load assert self.api.utilisation.cpu_total_load assert self.api.utilisation.cpu_1min_load assert self.api.utilisation.cpu_5min_load diff --git a/tests/test_synology_dsm_5.py b/tests/test_synology_dsm_5.py new file mode 100644 index 00000000..9aef1dbc --- /dev/null +++ b/tests/test_synology_dsm_5.py @@ -0,0 +1,344 @@ +# -*- coding: utf-8 -*- +"""Synology DSM tests.""" +from unittest import TestCase + +from synology_dsm.exceptions import ( + SynologyDSMRequestException, + SynologyDSMAPINotExistsException, + SynologyDSMAPIErrorException, + SynologyDSMLoginInvalidException, + SynologyDSMLogin2SARequiredException, + SynologyDSMLogin2SAFailedException, +) + +from . import ( + SynologyDSMMock, + VALID_HOST, + VALID_PORT, + VALID_SSL, + VALID_OTP, + VALID_PASSWORD, + VALID_USER, + VALID_USER_2SA, +) +from .const import SESSION_ID, DEVICE_TOKEN + + +class TestSynologyDSM(TestCase): + """SynologyDSM test cases.""" + + api = None + + def setUp(self): + self.api = SynologyDSMMock( + VALID_HOST, VALID_PORT, VALID_USER, VALID_PASSWORD, VALID_SSL + ) + self.api.dsm_version = 5 + + def test_init(self): + """Test init.""" + assert self.api.username + assert self.api._base_url # pylint: disable=protected-access + assert not self.api.apis.get(SynologyDSMMock.API_AUTH) + assert not self.api._session_id # pylint: disable=protected-access + + def test_connection_failed(self): # pylint: disable=no-self-use + """Test failed connection.""" + api = SynologyDSMMock( + "no_internet", VALID_PORT, VALID_USER, VALID_PASSWORD, VALID_SSL + ) + api.dsm_version = 5 + with self.assertRaises(SynologyDSMRequestException): + assert not api.login() + assert not api.apis.get(SynologyDSMMock.API_AUTH) + assert not api._session_id # pylint: disable=protected-access + + api = SynologyDSMMock("host", VALID_PORT, VALID_USER, VALID_PASSWORD, VALID_SSL) + api.dsm_version = 5 + with self.assertRaises(SynologyDSMRequestException): + assert not api.login() + assert not api.apis.get(SynologyDSMMock.API_AUTH) + assert not api._session_id # pylint: disable=protected-access + + api = SynologyDSMMock(VALID_HOST, 0, VALID_USER, VALID_PASSWORD, VALID_SSL) + api.dsm_version = 5 + with self.assertRaises(SynologyDSMRequestException): + assert not api.login() + assert not api.apis.get(SynologyDSMMock.API_AUTH) + assert not api._session_id # pylint: disable=protected-access + + api = SynologyDSMMock(VALID_HOST, VALID_PORT, VALID_USER, VALID_PASSWORD, False) + api.dsm_version = 5 + with self.assertRaises(SynologyDSMRequestException): + assert not api.login() + assert not api.apis.get(SynologyDSMMock.API_AUTH) + assert not api._session_id # pylint: disable=protected-access + + def test_login(self): + """Test login.""" + assert self.api.login() + assert self.api.apis.get(SynologyDSMMock.API_AUTH) + assert self.api._session_id == SESSION_ID # pylint: disable=protected-access + assert self.api._syno_token is None # pylint: disable=protected-access + + def test_login_failed(self): # pylint: disable=no-self-use + """Test failed login.""" + api = SynologyDSMMock(VALID_HOST, VALID_PORT, "user", VALID_PASSWORD, VALID_SSL) + api.dsm_version = 5 + with self.assertRaises(SynologyDSMLoginInvalidException): + assert not api.login() + assert api.apis.get(SynologyDSMMock.API_AUTH) + assert not api._session_id # pylint: disable=protected-access + + api = SynologyDSMMock(VALID_HOST, VALID_PORT, VALID_USER, "pass", VALID_SSL) + api.dsm_version = 5 + with self.assertRaises(SynologyDSMLoginInvalidException): + assert not api.login() + assert api.apis.get(SynologyDSMMock.API_AUTH) + assert not api._session_id # pylint: disable=protected-access + + def test_login_2sa(self): + """Test login with 2SA.""" + api = SynologyDSMMock( + VALID_HOST, VALID_PORT, VALID_USER_2SA, VALID_PASSWORD, VALID_SSL + ) + api.dsm_version = 5 + with self.assertRaises(SynologyDSMLogin2SARequiredException): + api.login() + api.login(VALID_OTP) + + assert api._session_id == SESSION_ID # pylint: disable=protected-access + assert api._syno_token is None # pylint: disable=protected-access + assert api._device_token == DEVICE_TOKEN # pylint: disable=protected-access + assert api.device_token == DEVICE_TOKEN + + def test_login_2sa_new_session(self): # pylint: disable=no-self-use + """Test login with 2SA and a new session with granted device.""" + api = SynologyDSMMock( + VALID_HOST, + VALID_PORT, + VALID_USER_2SA, + VALID_PASSWORD, + VALID_SSL, + device_token=DEVICE_TOKEN, + ) + api.dsm_version = 5 + assert api.login() + + assert api._session_id == SESSION_ID # pylint: disable=protected-access + assert api._syno_token is None # pylint: disable=protected-access + assert api._device_token == DEVICE_TOKEN # pylint: disable=protected-access + assert api.device_token == DEVICE_TOKEN + + def test_login_2sa_failed(self): + """Test failed login with 2SA.""" + api = SynologyDSMMock( + VALID_HOST, VALID_PORT, VALID_USER_2SA, VALID_PASSWORD, VALID_SSL + ) + api.dsm_version = 5 + with self.assertRaises(SynologyDSMLogin2SARequiredException): + api.login() + with self.assertRaises(SynologyDSMLogin2SAFailedException): + api.login(888888) + + assert api._session_id is None # pylint: disable=protected-access + assert api._syno_token is None # pylint: disable=protected-access + assert api._device_token is None # pylint: disable=protected-access + + def test_request_get(self): + """Test get request.""" + assert self.api.get(SynologyDSMMock.API_INFO, "query") + assert self.api.get(SynologyDSMMock.API_AUTH, "login") + assert self.api.get("SYNO.DownloadStation2.Task", "list") + assert self.api.get(SynologyDSMMock.API_AUTH, "logout") + + def test_request_get_failed(self): + """Test failed get request.""" + with self.assertRaises(SynologyDSMAPINotExistsException): + assert self.api.get("SYNO.Virtualization.API.Task.Info", "list") + + def test_request_post(self): + """Test post request.""" + assert self.api.post( + "SYNO.FileStation.Upload", + "upload", + params={"dest_folder_path": "/upload/test", "create_parents": True}, + files={"file": "open('file.txt','rb')"}, + ) + + assert self.api.post( + "SYNO.DownloadStation2.Task", + "create", + params={ + "uri": "ftps://192.0.0.1:21/test/test.zip", + "username": "admin", + "password": "1234", + }, + ) + + def test_request_post_failed(self): + """Test failed post request.""" + with self.assertRaises(SynologyDSMAPIErrorException): + assert self.api.post( + "SYNO.FileStation.Upload", + "upload", + params={"dest_folder_path": "/upload/test", "create_parents": True}, + files={"file": "open('file_already_exists.txt','rb')"}, + ) + + with self.assertRaises(SynologyDSMAPIErrorException): + assert self.api.post( + "SYNO.DownloadStation2.Task", + "create", + params={ + "uri": "ftps://192.0.0.1:21/test/test_not_exists.zip", + "username": "admin", + "password": "1234", + }, + ) + + def test_information(self): + """Test information.""" + assert self.api.information + assert self.api.information.model == "DS3615xs" + assert self.api.information.ram == 6144 + assert self.api.information.serial == "B3J4N01003" + assert self.api.information.temperature == 40 + assert not self.api.information.temperature_warn + assert self.api.information.uptime == 3897 + assert self.api.information.version == "5967" + assert self.api.information.version_string == "DSM 5.2-5967 Update 9" + + def test_utilisation(self): + """Test utilization.""" + assert self.api.utilisation + + def test_utilisation_cpu(self): + """Test utilization CPU.""" + assert self.api.utilisation.cpu + assert self.api.utilisation.cpu_other_load + assert self.api.utilisation.cpu_user_load + assert self.api.utilisation.cpu_system_load + assert self.api.utilisation.cpu_total_load + assert self.api.utilisation.cpu_1min_load + assert self.api.utilisation.cpu_5min_load + assert self.api.utilisation.cpu_15min_load + + def test_utilisation_memory(self): + """Test utilization memory.""" + assert self.api.utilisation.memory + assert self.api.utilisation.memory_real_usage + assert self.api.utilisation.memory_size + assert self.api.utilisation.memory_available_swap + assert self.api.utilisation.memory_cached + assert self.api.utilisation.memory_available_real + assert self.api.utilisation.memory_total_real + assert self.api.utilisation.memory_total_swap + + def test_utilisation_network(self): + """Test utilization network.""" + assert self.api.utilisation.network + assert self.api.utilisation.network_up + assert self.api.utilisation.network_down + + def test_storage(self): + """Test storage roots.""" + assert self.api.storage + assert self.api.storage.disks + assert self.api.storage.env + assert self.api.storage.storage_pools == [] + assert self.api.storage.volumes + + def test_storage_volumes(self): + """Test storage volumes.""" + # Basics + assert self.api.storage.volumes_ids + for volume_id in self.api.storage.volumes_ids: + if volume_id == "test_volume": + continue + assert self.api.storage.volume_status(volume_id) + assert self.api.storage.volume_device_type(volume_id) + assert self.api.storage.volume_size_total(volume_id) + assert self.api.storage.volume_size_total(volume_id, False) + assert self.api.storage.volume_size_used(volume_id) + assert self.api.storage.volume_size_used(volume_id, False) + assert self.api.storage.volume_percentage_used(volume_id) + assert ( + self.api.storage.volume_disk_temp_avg(volume_id) is None + ) # because of empty storagePools + assert ( + self.api.storage.volume_disk_temp_max(volume_id) is None + ) # because of empty storagePools + + # Existing volume + assert self.api.storage.volume_status("volume_1") == "normal" + assert self.api.storage.volume_device_type("volume_1") == "raid_5" + assert self.api.storage.volume_size_total("volume_1") == "8.0Tb" + assert self.api.storage.volume_size_total("volume_1", False) == 8846249701376 + assert self.api.storage.volume_size_used("volume_1") == "5.2Tb" + assert self.api.storage.volume_size_used("volume_1", False) == 5719795761152 + assert self.api.storage.volume_percentage_used("volume_1") == 64.7 + assert ( + self.api.storage.volume_disk_temp_avg("volume_1") is None + ) # because of empty storagePools + assert ( + self.api.storage.volume_disk_temp_max("volume_1") is None + ) # because of empty storagePools + + # Non existing volume + assert not self.api.storage.volume_status("not_a_volume") + assert not self.api.storage.volume_device_type("not_a_volume") + assert not self.api.storage.volume_size_total("not_a_volume") + assert not self.api.storage.volume_size_total("not_a_volume", False) + assert not self.api.storage.volume_size_used("not_a_volume") + assert not self.api.storage.volume_size_used("not_a_volume", False) + assert not self.api.storage.volume_percentage_used("not_a_volume") + assert not self.api.storage.volume_disk_temp_avg("not_a_volume") + assert not self.api.storage.volume_disk_temp_max("not_a_volume") + + # Test volume + assert self.api.storage.volume_status("test_volume") is None + assert self.api.storage.volume_device_type("test_volume") is None + assert self.api.storage.volume_size_total("test_volume") is None + assert self.api.storage.volume_size_total("test_volume", False) is None + assert self.api.storage.volume_size_used("test_volume") is None + assert self.api.storage.volume_size_used("test_volume", False) is None + assert self.api.storage.volume_percentage_used("test_volume") is None + assert self.api.storage.volume_disk_temp_avg("test_volume") is None + assert self.api.storage.volume_disk_temp_max("test_volume") is None + + def test_storage_disks(self): + """Test storage disks.""" + # Basics + assert self.api.storage.disks_ids + for disk_id in self.api.storage.disks_ids: + if disk_id == "test_disk": + continue + assert "Disk" in self.api.storage.disk_name(disk_id) + assert "/dev/" in self.api.storage.disk_device(disk_id) + if disk_id == "sda": + assert self.api.storage.disk_smart_status(disk_id) == "90%" + else: + assert self.api.storage.disk_smart_status(disk_id) == "safe" + assert self.api.storage.disk_status(disk_id) == "normal" + assert not self.api.storage.disk_exceed_bad_sector_thr(disk_id) + assert not self.api.storage.disk_below_remain_life_thr(disk_id) + assert self.api.storage.disk_temp(disk_id) + + # Non existing disk + assert not self.api.storage.disk_name("not_a_disk") + assert not self.api.storage.disk_device("not_a_disk") + assert not self.api.storage.disk_smart_status("not_a_disk") + assert not self.api.storage.disk_status("not_a_disk") + assert not self.api.storage.disk_exceed_bad_sector_thr("not_a_disk") + assert not self.api.storage.disk_below_remain_life_thr("not_a_disk") + assert not self.api.storage.disk_temp("not_a_disk") + + # Test disk + assert self.api.storage.disk_name("test_disk") is None + assert self.api.storage.disk_device("test_disk") is None + assert self.api.storage.disk_smart_status("test_disk") is None + assert self.api.storage.disk_status("test_disk") is None + assert self.api.storage.disk_exceed_bad_sector_thr("test_disk") is None + assert self.api.storage.disk_below_remain_life_thr("test_disk") is None + assert self.api.storage.disk_temp("test_disk") is None