From: Stephan Müller Date: Wed, 8 Jan 2020 17:02:29 +0000 (+0100) Subject: mgr/dashboard: Reload all CephFS directories X-Git-Tag: v15.1.1~322^2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=e753d987e5a1a5ccd9b4989801a00c4b4ad75fc2;p=ceph.git mgr/dashboard: Reload all CephFS directories Now angular tree is used instead of ng2-tree, as it provides a better way to dynamically load children and it provides a way to update all children without losing all track of everything. The loading icon will rotate now on any fetch. The tree will detect new directories and removed directories. It's also now possible to select the root directory of a CephFS in order to create snapshots of the whole FS. Fixes: https://tracker.ceph.com/issues/42617 Signed-off-by: Stephan Müller --- diff --git a/qa/tasks/mgr/dashboard/test_cephfs.py b/qa/tasks/mgr/dashboard/test_cephfs.py index bfd274025196b..e6594450f7141 100644 --- a/qa/tasks/mgr/dashboard/test_cephfs.py +++ b/qa/tasks/mgr/dashboard/test_cephfs.py @@ -31,11 +31,23 @@ class CephfsTest(DashboardTestCase): params={'path': path}) self.assertStatus(expectedStatus) + def get_root_directory(self, expectedStatus=200): + data = self._get("/api/cephfs/{}/get_root_directory".format(self.get_fs_id())) + self.assertStatus(expectedStatus) + self.assertIsInstance(data, dict) + return data + def ls_dir(self, path, expectedLength, depth = None): + return self._ls_dir(path, expectedLength, depth, "api") + + def ui_ls_dir(self, path, expectedLength, depth = None): + return self._ls_dir(path, expectedLength, depth, "ui-api") + + def _ls_dir(self, path, expectedLength, depth, baseApiPath): params = {'path': path} if depth is not None: params['depth'] = depth - data = self._get("/api/cephfs/{}/ls_dir".format(self.get_fs_id()), + data = self._get("/{}/cephfs/{}/ls_dir".format(baseApiPath, self.get_fs_id()), params=params) self.assertStatus(200) self.assertIsInstance(data, list) @@ -247,3 +259,16 @@ class CephfsTest(DashboardTestCase): self.setQuotas(0, 0) self.assertQuotas(0, 0) + def test_listing_of_root_dir(self): + self.ls_dir('/', 0) # Should not list root + ui_root = self.ui_ls_dir('/', 1)[0] # Should list root by default + root = self.get_root_directory() + self.assertEqual(ui_root, root) + + def test_listing_of_ui_api_ls_on_deeper_levels(self): + # The UI-API and API ls_dir methods should behave the same way on deeper levels + self.mk_dirs('/pictures') + api_ls = self.ls_dir('/pictures', 0) + ui_api_ls = self.ui_ls_dir('/pictures', 0) + self.assertEqual(api_ls, ui_api_ls) + self.rm_dir('/pictures') diff --git a/src/pybind/mgr/dashboard/controllers/cephfs.py b/src/pybind/mgr/dashboard/controllers/cephfs.py index 7678e45a50cd3..f4c024d619e9b 100644 --- a/src/pybind/mgr/dashboard/controllers/cephfs.py +++ b/src/pybind/mgr/dashboard/controllers/cephfs.py @@ -324,6 +324,30 @@ class CephFS(RESTController): raise cherrypy.HTTPError(404, "CephFS id {} not found".format(fs_id)) return CephFS_(fs_name) + @RESTController.Resource('GET') + def get_root_directory(self, fs_id): + """ + The root directory that can't be fetched using ls_dir (api). + :param fs_id: The filesystem identifier. + :return: The root directory + :rtype: dict + """ + try: + return self._get_root_directory(self._cephfs_instance(fs_id)) + except (cephfs.PermissionError, cephfs.ObjectNotFound): + return None + + def _get_root_directory(self, cfs): + """ + The root directory that can't be fetched using ls_dir (api). + It's used in ls_dir (ui-api) and in get_root_directory (api). + :param cfs: CephFS service instance + :type cfs: CephFS + :return: The root directory + :rtype: dict + """ + return cfs.get_directory(os.sep.encode()) + @RESTController.Resource('GET') def ls_dir(self, fs_id, path=None, depth=1): """ @@ -331,29 +355,35 @@ class CephFS(RESTController): :param fs_id: The filesystem identifier. :param path: The path where to start listing the directory content. Defaults to '/' if not set. + :type path: str | bytes + :param depth: The number of steps to go down the directory tree. + :type depth: int | str :return: The names of the directories below the specified path. :rtype: list """ - if path is None: - path = os.sep - else: - path = os.path.normpath(path) + path = self._set_ls_dir_path(path) try: cfs = self._cephfs_instance(fs_id) - paths = cfs.ls_dir(path, int(depth)) - # Convert (bytes => string), prettify paths (strip slashes) - # and append additional information. - paths = [{ - 'name': os.path.basename(p.decode()), - 'path': p.decode(), - 'parent': os.path.dirname(p.decode()), - 'snapshots': cfs.ls_snapshots(p.decode()), - 'quotas': cfs.get_quotas(p.decode()) - } for p in paths if p != path.encode()] + paths = cfs.ls_dir(path, depth) except (cephfs.PermissionError, cephfs.ObjectNotFound): paths = [] return paths + def _set_ls_dir_path(self, path): + """ + Transforms input path parameter of ls_dir methods (api and ui-api). + :param path: The path where to start listing the directory content. + Defaults to '/' if not set. + :type path: str | bytes + :return: Normalized path or root path + :return: str + """ + if path is None: + path = os.sep + else: + path = os.path.normpath(path) + return path + @RESTController.Resource('POST') def mk_dirs(self, fs_id, path): """ @@ -462,3 +492,29 @@ class CephFsUi(CephFS): data['clients'] = self._clients(fs_id) return data + + @RESTController.Resource('GET') + def ls_dir(self, fs_id, path=None, depth=1): + """ + The difference to the API version is that the root directory will be send when listing + the root directory. + To only do one request this endpoint was created. + :param fs_id: The filesystem identifier. + :type fs_id: int | str + :param path: The path where to start listing the directory content. + Defaults to '/' if not set. + :type path: str | bytes + :param depth: The number of steps to go down the directory tree. + :type depth: int | str + :return: The names of the directories below the specified path. + :rtype: list + """ + path = self._set_ls_dir_path(path) + try: + cfs = self._cephfs_instance(fs_id) + paths = cfs.ls_dir(path, depth) + if path == os.sep: + paths = [self._get_root_directory(cfs)] + paths + except (cephfs.PermissionError, cephfs.ObjectNotFound): + paths = [] + return paths diff --git a/src/pybind/mgr/dashboard/frontend/package-lock.json b/src/pybind/mgr/dashboard/frontend/package-lock.json index 9071fce742a7e..2a6ee4934587c 100644 --- a/src/pybind/mgr/dashboard/frontend/package-lock.json +++ b/src/pybind/mgr/dashboard/frontend/package-lock.json @@ -3236,6 +3236,17 @@ "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", "dev": true }, + "angular-tree-component": { + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/angular-tree-component/-/angular-tree-component-8.5.2.tgz", + "integrity": "sha512-3NwMB+vLq1+WHz2UVgsZA73E1LmIIWJlrrasCKXbLJ3S7NmY9O/wKcolji3Vp2W//5KQ33RXu1jiPXCOQdRzVA==", + "requires": { + "lodash": "^4.17.11", + "mobx": "^5.14.2", + "mobx-angular": "3.0.3", + "opencollective-postinstall": "^2.0.2" + } + }, "ansi-colors": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", @@ -11225,6 +11236,16 @@ } } }, + "mobx": { + "version": "5.15.2", + "resolved": "https://registry.npmjs.org/mobx/-/mobx-5.15.2.tgz", + "integrity": "sha512-eVmHGuSYd0ZU6x8gYMdgLEnCC9kfBJaf7/qJt+/yIxczVVUiVzHvTBjZH3xEa5FD5VJJSh1/Ba4SThE4ErEGjw==" + }, + "mobx-angular": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/mobx-angular/-/mobx-angular-3.0.3.tgz", + "integrity": "sha512-mZuuose70V+Sd0hMWDElpRe3mA6GhYjSQN3mHzqk2XWXRJ+eWQa/f3Lqhw+Me/Xd2etWsGR1hnRa1BfQ2ZDtpw==" + }, "moment": { "version": "2.24.0", "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", diff --git a/src/pybind/mgr/dashboard/frontend/package.json b/src/pybind/mgr/dashboard/frontend/package.json index 3afa944a65a5c..e6cec0bef30c1 100644 --- a/src/pybind/mgr/dashboard/frontend/package.json +++ b/src/pybind/mgr/dashboard/frontend/package.json @@ -90,6 +90,7 @@ "@auth0/angular-jwt": "2.1.1", "@ngx-translate/i18n-polyfill": "1.0.0", "@swimlane/ngx-datatable": "15.0.2", + "angular-tree-component": "8.5.2", "async-mutex": "0.1.4", "bootstrap": "4.3.1", "chart.js": "2.8.0", diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-directories/cephfs-directories.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-directories/cephfs-directories.component.html index 8a6e0f176fe71..7753d599bb6e0 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-directories/cephfs-directories.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-directories/cephfs-directories.component.html @@ -1,12 +1,33 @@
-
- - - - - +
+
+
+ +
+
+ + + + + + +
+