Skip to content

Commit 7f26596

Browse files
authored
Merge pull request ceph#52218 from YiteGu/cephfs-get-statfs
mgr/dashboard: CephFS statfs REST API Reviewed-by: Avan Thakkar <[email protected]> Reviewed-by: Dhairya Parmar <[email protected]> Reviewed-by: Nizamudeen A <[email protected]> Reviewed-by: Pere Diaz Bou <[email protected]>
2 parents 6eb1276 + bcbf91d commit 7f26596

File tree

4 files changed

+289
-1
lines changed

4 files changed

+289
-1
lines changed

qa/tasks/mgr/dashboard/test_cephfs.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,26 @@ def new_quota_dir(self):
7474
yield 1
7575
self.rm_dir(self.QUOTA_PATH)
7676

77+
def write_to_file(self, path, buf):
78+
params = {'path': path, 'buf': buf}
79+
self._post(f"/api/cephfs/{self.get_fs_id()}/write_to_file",
80+
params=params)
81+
self.assertStatus(200)
82+
83+
def unlink(self, path, expectedStatus=200):
84+
params = {'path': path}
85+
self._delete(f"/api/cephfs/{self.get_fs_id()}/unlink",
86+
params=params)
87+
self.assertStatus(expectedStatus)
88+
89+
def statfs(self, path):
90+
params = {'path': path}
91+
data = self._get(f"/api/cephfs/{self.get_fs_id()}/statfs",
92+
params=params)
93+
self.assertStatus(200)
94+
self.assertIsInstance(data, dict)
95+
return data
96+
7797
@DashboardTestCase.RunAs('test', 'test', ['block-manager'])
7898
def test_access_permissions(self):
7999
fs_id = self.get_fs_id()
@@ -290,3 +310,40 @@ def test_listing_of_ui_api_ls_on_deeper_levels(self):
290310
ui_api_ls = self.ui_ls_dir('/pictures', 0)
291311
self.assertEqual(api_ls, ui_api_ls)
292312
self.rm_dir('/pictures')
313+
314+
def test_statfs(self):
315+
self.statfs('/')
316+
317+
self.mk_dirs('/animal')
318+
stats = self.statfs('/animal')
319+
self.assertEqual(stats['bytes'], 0)
320+
self.assertEqual(stats['files'], 0)
321+
self.assertEqual(stats['subdirs'], 1)
322+
323+
buf = 'a' * 512
324+
self.write_to_file('/animal/lion', buf)
325+
stats = self.statfs('/animal')
326+
self.assertEqual(stats['bytes'], 512)
327+
self.assertEqual(stats['files'], 1)
328+
self.assertEqual(stats['subdirs'], 1)
329+
330+
buf = 'b' * 512
331+
self.write_to_file('/animal/tiger', buf)
332+
stats = self.statfs('/animal')
333+
self.assertEqual(stats['bytes'], 1024)
334+
self.assertEqual(stats['files'], 2)
335+
self.assertEqual(stats['subdirs'], 1)
336+
337+
self.unlink('/animal/tiger')
338+
stats = self.statfs('/animal')
339+
self.assertEqual(stats['bytes'], 512)
340+
self.assertEqual(stats['files'], 1)
341+
self.assertEqual(stats['subdirs'], 1)
342+
343+
self.unlink('/animal/lion')
344+
stats = self.statfs('/animal')
345+
self.assertEqual(stats['bytes'], 0)
346+
self.assertEqual(stats['files'], 0)
347+
self.assertEqual(stats['subdirs'], 1)
348+
349+
self.rm_dir('/animal')

src/pybind/mgr/dashboard/controllers/cephfs.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@
1919
'max_bytes': (int, ''),
2020
'max_files': (int, '')
2121
}
22+
GET_STATFS_SCHEMA = {
23+
'bytes': (int, ''),
24+
'files': (int, ''),
25+
'subdirs': (int, '')
26+
}
2227

2328
logger = logging.getLogger("controllers.rgw")
2429

@@ -461,6 +466,47 @@ def get_quota(self, fs_id, path):
461466
cfs = self._cephfs_instance(fs_id)
462467
return cfs.get_quotas(path)
463468

469+
@RESTController.Resource('POST', path='/write_to_file')
470+
@allow_empty_body
471+
def write_to_file(self, fs_id, path, buf) -> None:
472+
"""
473+
Write some data to the specified path.
474+
:param fs_id: The filesystem identifier.
475+
:param path: The path of the file to write.
476+
:param buf: The str to write to the buf.
477+
"""
478+
cfs = self._cephfs_instance(fs_id)
479+
cfs.write_to_file(path, buf)
480+
481+
@RESTController.Resource('DELETE', path='/unlink')
482+
def unlink(self, fs_id, path) -> None:
483+
"""
484+
Removes a file, link, or symbolic link.
485+
:param fs_id: The filesystem identifier.
486+
:param path: The path of the file or link to unlink.
487+
"""
488+
cfs = self._cephfs_instance(fs_id)
489+
cfs.unlink(path)
490+
491+
@RESTController.Resource('GET', path='/statfs')
492+
@EndpointDoc("Get Cephfs statfs of the specified path",
493+
parameters={
494+
'fs_id': (str, 'File System Identifier'),
495+
'path': (str, 'File System Path'),
496+
},
497+
responses={200: GET_STATFS_SCHEMA})
498+
def statfs(self, fs_id, path) -> dict:
499+
"""
500+
Get the statfs of the specified path.
501+
:param fs_id: The filesystem identifier.
502+
:param path: The path of the directory/file.
503+
:return: Returns a dictionary containing 'bytes',
504+
'files' and 'subdirs'.
505+
:rtype: dict
506+
"""
507+
cfs = self._cephfs_instance(fs_id)
508+
return cfs.statfs(path)
509+
464510
@RESTController.Resource('POST', path='/snapshot')
465511
@allow_empty_body
466512
def snapshot(self, fs_id, path, name=None):

src/pybind/mgr/dashboard/openapi.yaml

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2031,6 +2031,60 @@ paths:
20312031
- jwt: []
20322032
tags:
20332033
- Cephfs
2034+
/api/cephfs/{fs_id}/statfs:
2035+
get:
2036+
description: "\n Get the statfs of the specified path.\n :param\
2037+
\ fs_id: The filesystem identifier.\n :param path: The path of the\
2038+
\ directory/file.\n :return: Returns a dictionary containing 'bytes',\n\
2039+
\ 'files' and 'subdirs'.\n :rtype: dict\n "
2040+
parameters:
2041+
- description: File System Identifier
2042+
in: path
2043+
name: fs_id
2044+
required: true
2045+
schema:
2046+
type: string
2047+
- description: File System Path
2048+
in: query
2049+
name: path
2050+
required: true
2051+
schema:
2052+
type: string
2053+
responses:
2054+
'200':
2055+
content:
2056+
application/vnd.ceph.api.v1.0+json:
2057+
schema:
2058+
properties:
2059+
bytes:
2060+
description: ''
2061+
type: integer
2062+
files:
2063+
description: ''
2064+
type: integer
2065+
subdirs:
2066+
description: ''
2067+
type: integer
2068+
required:
2069+
- bytes
2070+
- files
2071+
- subdirs
2072+
type: object
2073+
description: OK
2074+
'400':
2075+
description: Operation exception. Please check the response body for details.
2076+
'401':
2077+
description: Unauthenticated access. Please login first.
2078+
'403':
2079+
description: Unauthorized access. Please check your permissions.
2080+
'500':
2081+
description: Unexpected error. Please check the response body for the stack
2082+
trace.
2083+
security:
2084+
- jwt: []
2085+
summary: Get Cephfs statfs of the specified path
2086+
tags:
2087+
- Cephfs
20342088
/api/cephfs/{fs_id}/tree:
20352089
delete:
20362090
description: "\n Remove a directory.\n :param fs_id: The filesystem\
@@ -2113,6 +2167,95 @@ paths:
21132167
- jwt: []
21142168
tags:
21152169
- Cephfs
2170+
/api/cephfs/{fs_id}/unlink:
2171+
delete:
2172+
description: "\n Removes a file, link, or symbolic link.\n :param\
2173+
\ fs_id: The filesystem identifier.\n :param path: The path of the\
2174+
\ file or link to unlink.\n "
2175+
parameters:
2176+
- in: path
2177+
name: fs_id
2178+
required: true
2179+
schema:
2180+
type: string
2181+
- in: query
2182+
name: path
2183+
required: true
2184+
schema:
2185+
type: string
2186+
responses:
2187+
'202':
2188+
content:
2189+
application/vnd.ceph.api.v1.0+json:
2190+
type: object
2191+
description: Operation is still executing. Please check the task queue.
2192+
'204':
2193+
content:
2194+
application/vnd.ceph.api.v1.0+json:
2195+
type: object
2196+
description: Resource deleted.
2197+
'400':
2198+
description: Operation exception. Please check the response body for details.
2199+
'401':
2200+
description: Unauthenticated access. Please login first.
2201+
'403':
2202+
description: Unauthorized access. Please check your permissions.
2203+
'500':
2204+
description: Unexpected error. Please check the response body for the stack
2205+
trace.
2206+
security:
2207+
- jwt: []
2208+
tags:
2209+
- Cephfs
2210+
/api/cephfs/{fs_id}/write_to_file:
2211+
post:
2212+
description: "\n Write some data to the specified path.\n :param\
2213+
\ fs_id: The filesystem identifier.\n :param path: The path of the\
2214+
\ file to write.\n :param buf: The str to write to the buf.\n \
2215+
\ "
2216+
parameters:
2217+
- in: path
2218+
name: fs_id
2219+
required: true
2220+
schema:
2221+
type: string
2222+
requestBody:
2223+
content:
2224+
application/json:
2225+
schema:
2226+
properties:
2227+
buf:
2228+
type: string
2229+
path:
2230+
type: string
2231+
required:
2232+
- path
2233+
- buf
2234+
type: object
2235+
responses:
2236+
'201':
2237+
content:
2238+
application/vnd.ceph.api.v1.0+json:
2239+
type: object
2240+
description: Resource created.
2241+
'202':
2242+
content:
2243+
application/vnd.ceph.api.v1.0+json:
2244+
type: object
2245+
description: Operation is still executing. Please check the task queue.
2246+
'400':
2247+
description: Operation exception. Please check the response body for details.
2248+
'401':
2249+
description: Unauthenticated access. Please login first.
2250+
'403':
2251+
description: Unauthorized access. Please check your permissions.
2252+
'500':
2253+
description: Unexpected error. Please check the response body for the stack
2254+
trace.
2255+
security:
2256+
- jwt: []
2257+
tags:
2258+
- Cephfs
21162259
/api/cluster:
21172260
get:
21182261
parameters: []

src/pybind/mgr/dashboard/services/cephfs.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import datetime
44
import logging
55
import os
6-
from contextlib import contextmanager
6+
from contextlib import contextmanager, suppress
77

88
import cephfs
99

@@ -260,3 +260,45 @@ def set_quotas(self, path, max_bytes=None, max_files=None):
260260
if max_files is not None:
261261
self.cfs.setxattr(path, 'ceph.quota.max_files',
262262
str(max_files).encode(), 0)
263+
264+
def write_to_file(self, path, buf) -> None:
265+
"""
266+
Write some data to the specified path.
267+
:param path: The path of the file to write.
268+
:type path: str.
269+
:param buf: The str to write to the buf.
270+
:type buf: str.
271+
"""
272+
try:
273+
fd = self.cfs.open(path, 'w', 644)
274+
self.cfs.write(fd, buf.encode('utf-8'), 0)
275+
except cephfs.Error as e:
276+
logger.debug("EIO: %s", str(e))
277+
finally:
278+
if fd is not None:
279+
self.cfs.close(fd)
280+
281+
def unlink(self, path) -> None:
282+
"""
283+
Removes a file, link, or symbolic link.
284+
:param path: The path of the file or link to unlink.
285+
"""
286+
self.cfs.unlink(path)
287+
288+
def statfs(self, path) -> dict:
289+
"""
290+
Get the statfs of the specified path by xattr.
291+
:param path: The path of the directory/file.
292+
:type path: str
293+
:return: Returns a dictionary containing 'bytes',
294+
'files' and 'subdirs'.
295+
:rtype: dict
296+
"""
297+
rbytes = 0
298+
rfiles = 0
299+
rsubdirs = 0
300+
with suppress(cephfs.NoData):
301+
rbytes = int(self.cfs.getxattr(path, 'ceph.dir.rbytes'))
302+
rfiles = int(self.cfs.getxattr(path, 'ceph.dir.rfiles'))
303+
rsubdirs = int(self.cfs.getxattr(path, 'ceph.dir.rsubdirs'))
304+
return {'bytes': rbytes, 'files': rfiles, 'subdirs': rsubdirs}

0 commit comments

Comments
 (0)