4378 lines
194 KiB
Markdown
4378 lines
194 KiB
Markdown
> 基于 Ceph 官方文档整理 | 文档版本:**Tentacle v20.2.4** | 抓取日期:2026-09-17 | 源文:ceph/ceph@v20.2.4 `doc/dev/developer_guide/`
|
||
> 官网入口:https://docs.ceph.com/en/tentacle/dev/developer_guide/
|
||
|
||
# 第 19 册 · 开发者贡献指南篇
|
||
|
||
---
|
||
|
||
## 本册导读
|
||
|
||
本册面向**想给 Ceph 提 PR、参与社区开发的人**。内容覆盖从「第一次 fork 仓库」到「PR 被 merge、backport、跑 teuthology 集成测试、用 gdb 调试」的完整贡献链路,并包含两块体量很大的专题:Ceph Dashboard 前后端开发(本册最大块,约占源文一半)与集成测试框架 teuthology。
|
||
|
||
**运维向读者可跳过本册。** 本册讲的是「如何改 Ceph 的代码」,不是「如何部署/调优 Ceph 集群」。如果你只是要装集群、扩 OSD、排障,请看第 3 册(集群运维与配置)、第 4 册(RADOS 排障)、第 5 册(cephadm 部署运维)。唯一可能对你有用的章节是第七章(用 gdb attach 到 `ceph-osd` 进程)与第八章(Windows CI 的产物怎么看),但那也是排障时才用得上。
|
||
|
||
### 本册覆盖的源文件(`doc/dev/developer_guide/`,共 18 个 .rst,约 214KB)
|
||
|
||
| 分组 | 文件 | 大小 | 对应章节 |
|
||
|---|---|---|---|
|
||
| 入口 | `index.rst` | 949B | 一 |
|
||
| 入门 | `intro.rst`、`essentials.rst` | 1.2KB / 11KB | 一 |
|
||
| 工作流 | `basic-workflow.rst` | 23KB | 二 |
|
||
| 任务管理 | `issue-tracker.rst` | 1.4KB | 三 |
|
||
| 合并策略 | `merging.rst` | 3.9KB | 四 |
|
||
| 测试 | `tests-unit-tests.rst`、`running-tests-locally.rst`、`tests-windows.rst` | 6.2KB / 7.5KB / 6KB | 五、八 |
|
||
| 集成测试 | `testing_integration_tests/`(index + intro + workflow + debugging-tips + kernel + sentry,共 6 个) | 约 49KB | 六 |
|
||
| 调试 | `debugging-gdb.rst` | 1.4KB | 七 |
|
||
| Dashboard | `dash-devel.rst` | 103KB | 十 |
|
||
| 追踪 | `jaegertracing.rst` | 2.4KB | 十一 |
|
||
|
||
### 阅读须知
|
||
|
||
1. **命令、工具名、目录名、git 规范术语、环境变量一律保留英文原文**,中文只做叙述。
|
||
2. **文档的年代痕迹要如实标注**。本册部分页面明显多年未更新:`merging.rst` 仍在用 hammer/infernalis/jewel/mimic 举例,`teuthology-intro.rst` 的平台列表写的是「as of September 2020」,Windows 章节的示例日志里有 `ceph_test_dokan.DokanTests`。这些内容不等于 Tentacle 的现状,遇到时本册会指出。
|
||
3. 凡官方文档没写的,一律写「**文档未明确**」;本册作者补充的内容一律以 `> 💡 补充:` 标注。
|
||
4. 每个章节末尾标注来源:`> 引自:[<页面标题>](URL)`。
|
||
|
||
---
|
||
|
||
# 一、入门:开发环境、社区与构建要点
|
||
|
||
## 1.1 索引页:本册的目录结构
|
||
|
||
`doc/dev/developer_guide/index.rst` 是整个开发者贡献指南的入口,作者署名 Loic Dachary 与 Nathan Cutler,许可为 CC-BY-SA-3.0。它的 toctree 如下(翻译保留原顺序):
|
||
|
||
| 英文条目 | 页面 |
|
||
|---|---|
|
||
| Introduction | `intro` |
|
||
| Essentials | `essentials` |
|
||
| What is Merged and When | `merging` |
|
||
| Issue tracker | `issue-tracker` |
|
||
| Basic workflow | `basic-workflow` |
|
||
| Tests: Unit Tests | `tests-unit-tests` |
|
||
| Tests: Integration Tests (Teuthology) | `testing_integration_tests/index` |
|
||
| Tests: Running Tests (Locally) | `running-tests-locally` |
|
||
| Tests: Windows | `tests-windows` |
|
||
| Ceph Dashboard Developer Documentation (formerly HACKING.rst) | `dash-devel` |
|
||
| Tracing Developer Documentation | `jaegertracing` |
|
||
| Cephadm Developer Documentation | `../cephadm/index`(不在本册范围) |
|
||
| Debugging with GDB | `debugging-gdb` |
|
||
|
||
页面顶部还有一条 note:你可能也对 `/dev/internals` 文档感兴趣。
|
||
|
||
> 引自:[Contributing to Ceph: A Guide for Developers](https://docs.ceph.com/en/tentacle/dev/developer_guide/)
|
||
|
||
## 1.2 Introduction:这份指南的两个目标
|
||
|
||
官方开篇写明本指南有两个目标:
|
||
|
||
1. **降低软件开发者参与 Ceph 项目的门槛**(lower the barrier to entry)。
|
||
2. **作为 Ceph 开发者的参考手册**(serve as a reference)。
|
||
|
||
它假设读者已经熟悉 Ceph 本身(分布式对象存储与文件系统,设计目标是提供出色的 performance、reliability 与 scalability)。如果不熟悉,官方给的三个入口是:
|
||
|
||
- 项目官网:https://ceph.com
|
||
- 论文列表:https://ceph.com/publications/
|
||
- YouTube 频道:https://www.youtube.com/c/CephStorage (有 Tech Talks、Code walk-throughs、Ceph Developer Monthly 录像)
|
||
|
||
由于读者被假定为有网络连接的开发者,本指南对「在别处已讲过的主题」采取**只给链接**的处理方式。如果发现链接失效或有更好的链接,官方请你到 http://tracker.ceph.com/projects/ceph/issues/new 报 bug。
|
||
|
||
> 引自:[Introduction](https://docs.ceph.com/en/tentacle/dev/developer_guide/intro/)
|
||
|
||
## 1.3 Essentials:每个 Ceph 开发者都必须知道的事
|
||
|
||
### 1.3.1 Leads(各组件负责人)
|
||
|
||
Ceph 由 Sage Weil 创建,现在由 **Ceph Leadership Team(CLT)** 领导。每个主要组件有自己的 lead。完整的 lead 列表及其 GitHub nick 见社区网站:https://ceph.io/en/community/team
|
||
|
||
列表里出现的 Ceph 专有缩写,官方说在 `/architecture` 文档里解释(不在本册范围)。
|
||
|
||
### 1.3.2 History
|
||
|
||
官方只给了一个外链:维基百科 Ceph 条目的 History 章节
|
||
https://en.wikipedia.org/wiki/Ceph_%28software%29#History
|
||
|
||
### 1.3.3 Licensing(许可证)
|
||
|
||
Ceph 是自由软件。除非另有说明,Ceph 源码按 **LGPL2.1 或 LGPL3.0** 分发。完整细节见源码树顶层目录的 `COPYING` 文件:
|
||
https://github.com/ceph/ceph/blob/master/COPYING
|
||
|
||
### 1.3.4 Source code repositories(代码仓库)
|
||
|
||
代码托管在 GitHub 的 Ceph organization(https://github.com/ceph)下的多个仓库里,但**本册只覆盖 `https://github.com/ceph/ceph`**。
|
||
|
||
官方强调:想做出有意义的贡献,**熟练掌握 git 是必需的**(A working knowledge of git is essential)。git 文档入口:https://git-scm.com/doc
|
||
|
||
### 1.3.5 Redmine issue tracker
|
||
|
||
代码在 GitHub,但 **issues(Bug、Feature、Backport、Documentation 等)在 Redmine**:http://tracker.ceph.com
|
||
|
||
- tracker 里有一个 Ceph 项目,下面有若干子项目,大致对应各个架构组件。
|
||
- **只要注册就能开新 issue 和评论**(权限自动授予)。注册地址:http://tracker.ceph.com/account/register
|
||
- 报 bug 或提 feature:跳到 http://tracker.ceph.com/projects/ceph 然后点 `New issue`。
|
||
|
||
### 1.3.6 Slack、邮件列表与 IRC
|
||
|
||
| 渠道 | 说明 |
|
||
|---|---|
|
||
| Slack | https://join.slack.com/t/ceph-storage/shared_invite/zt-32hkefbs5-f6qZDZLd5U8CYj7drBTHFw |
|
||
| `dev@ceph.io` | Ceph 本身的开发讨论、与其他技术的互操作性、项目运作。对所有人开放,订阅地址 https://lists.ceph.io/postorius/lists/dev.ceph.io |
|
||
| `ceph-devel@vger.kernel.org` | **Linux 内核 Ceph 客户端**组件的讨论与 patch review。注意:2018 年中之前,这个列表曾经是通用的 devel-ceph 归档列表,搜索历史归档时留意这一点。订阅走 https://subspace.kernel.org/vger.kernel.org.html |
|
||
| IRC | Ceph 社区在 **OFTC** 网络的 `#ceph` 频道实时交流。服务器 `irc.oftc.net`。建议注册账号,注册说明见 https://www.oftc.net/Services/#register-your-account |
|
||
|
||
IRC 频道清单(官方给出):`#ceph`、`#ceph-devel`、`#cephfs`、`#ceph-dashboard`、`#ceph-orchestrators`、`#sepia`。`#ceph` 的讨论有日志和在线归档。
|
||
|
||
IRC 的礼仪提醒(官方原话大意):频道里可能有很多人,但不一定都在键盘前;没人回应就多等一会儿。
|
||
|
||
### 1.3.7 Submitting patches
|
||
|
||
官方明确说:**提交 patch 的权威说明在源码树顶层目录的 `CONTRIBUTING.rst` 文件里**,并建议所有新人仔细阅读:
|
||
https://github.com/ceph/ceph/blob/main/CONTRIBUTING.rst
|
||
|
||
本册(developer_guide)与该文件之间可能有重叠。
|
||
|
||
### 1.3.8 Building from source(从源码构建)
|
||
|
||
官方只给了一个指向:`/install/build-ceph`(安装手册的构建章节,不在本册范围)。
|
||
|
||
### 1.3.9 用 ccache 加速本地构建
|
||
|
||
`ccache`(https://ccache.samba.org/)可以加快 Ceph 源码树的**重**构建。
|
||
|
||
**前提**:使用 ccache 之前,先确认源码树是干净的、不会产生构建失败。切分支时可能不知不觉引入旧的构建产物,导致构建失败。
|
||
|
||
**清理源码树**(两步):
|
||
|
||
```bash
|
||
ninja clean
|
||
```
|
||
|
||
```bash
|
||
git clean -fdx; git submodule foreach git clean -fdx
|
||
```
|
||
|
||
> **官方警告**:上面第二条命令会删除源码树中所有**不被 git 跟踪**的文件。跑之前务必备份你的日志文件和配置选项。
|
||
|
||
**开启 ccache 构建**:
|
||
|
||
```bash
|
||
cmake -DWITH_CCACHE=ON ..
|
||
```
|
||
|
||
`ccache` 在大多数发行版里都有包。更多关于 run modes 见 https://ccache.samba.org/manual.html#_run_modes ;默认设置用 `ccache -s` 查看。
|
||
|
||
**官方推荐**:覆盖 `max_size`(默认 10G),用更大的值,比如 **25G**。配置说明见 https://ccache.samba.org/manual.html#_configuration
|
||
|
||
**进一步提高缓存命中率**:在开发环境里把版本信息和构建时间戳固定下来,这样包含这些信息的二进制就不必每次重建。做法是在 `ccache.conf` 里加:
|
||
|
||
```text
|
||
sloppiness = time_macros
|
||
run_second_cpp = true
|
||
```
|
||
|
||
然后把环境变量 `SOURCE_DATE_EPOCH` 设成一个固定的 UNIX 时间戳,并在跑 `cmake` 时把 `ENABLE_GIT_VERSION` 设为 `OFF`:
|
||
|
||
```bash
|
||
export SOURCE_DATE_EPOCH=946684800
|
||
cmake -DWITH_CCACHE=ON -DENABLE_GIT_VERSION=OFF ..
|
||
```
|
||
|
||
> **官方警告**:用这些选项编出来的二进制**不适合生产或调试用途**,因为它们不包含正确的构建时间和 git 版本信息。
|
||
|
||
### 1.3.10 开发模式集群与 Kubernetes/Rook
|
||
|
||
- 开发模式集群:见 `/dev/quick_guide`(不在本册范围)。
|
||
- Kubernetes/Rook 开发集群:见 `kubernetes-dev` 引用(不在本册范围)。
|
||
|
||
### 1.3.11 Backporting(回移)
|
||
|
||
**所有 bugfix 都应先合到 `main` 分支,然后再 backport。**
|
||
|
||
给 bugfix 打 backport 标记的方法:
|
||
|
||
1. 确保它关联了一个 tracker issue;
|
||
2. 把该 issue 的 **`Backport` 字段**设为你认为需要回移的先前版本的**逗号分隔列表**(例如 `hammer,jewel`);
|
||
3. **你自己负责你提出的 PR 的 backport 工作**(You are responsible for the backporting of pull requests that you raise)。
|
||
|
||
### 1.3.12 Dependabot
|
||
|
||
Dependabot 是 GitHub 的机器人,扫描仓库依赖里的安全漏洞(CVE)。如果发现可用修复,它会自动开 PR 升级依赖,并给出**兼容度评分**(基于该修复在其他 GitHub 仓库引发的 CI 失败数量)。经过配置后它也可以做非安全更新(升到最新的 minor 或 patch 版本)。
|
||
|
||
支持的语言和包管理器见 GitHub 官方文档。**截至 2022 年 7 月**,Ceph 项目只接收来自 **pip**(基于 `requirements.txt`)和 **npm**(`package*.json`)的告警;可以扩展到 git submodules、Golang、Java;**C++ 包管理器(vcpkg、conan、C++20 modules)当时没有支持**。
|
||
|
||
官方提醒:Dependabot 发现的很多依赖,其实在 Ceph GitHub 仓库之外更新更合适(比如发行版软件包)。但Dependabot 生成的新增/现存漏洞清单依然有用。
|
||
|
||
示例 PR:https://github.com/ceph/ceph/pull/46998
|
||
|
||
### 1.3.13 cluster log 使用指引
|
||
|
||
如果你的 patch 会向 Ceph cluster log 输出消息,官方要求你先读 `/dev/logging`(不在本册范围)。
|
||
|
||
> 引自:[Essentials (tl;dr)](https://docs.ceph.com/en/tentacle/dev/developer_guide/essentials/)
|
||
|
||
## 1.4 代码库结构:`src/` 目录布局
|
||
|
||
> ⚠️ **文档未明确**:`doc/dev/developer_guide/` 下的 18 个页面**没有提供 `src/` 目录布局表**。本节下方表格中的路径,全部是本册 18 个源文里**实际出现过、可被确证**的路径;不属于「官方给出的目录布局说明」。完整的 `src/` 布局在官方文档中未明确(`/dev/internals` 可能有零散说明,但不在本册源文范围内)。
|
||
|
||
下表汇总本册源文里明确提到的路径及其内容:
|
||
|
||
| 路径 | 内容 | 出现于 |
|
||
|---|---|---|
|
||
| `src/vstart.sh` | vstart 脚本本体,在本机启动单节点 Ceph 集群。是 Ceph Dashboard 三种最常用开发环境的基础 | `dash-devel.rst` |
|
||
| `build/` | 构建目录。`vstart.sh` 通常从这里调用;`out/osd.0.pid` 在这里 | `debugging-gdb.rst`、`dash-devel.rst` |
|
||
| `build/bin/` | 编译产物二进制目录。`vstart_runner.py` 会在这里检查第一个参数对应的二进制是否存在 | `running-tests-locally.rst` |
|
||
| `build/out/` | 日志输出目录(`vstart` 提示 `see out/*`);单测客户端日志 `build/out/client.admin.<pid>.log` | `running-tests-locally.rst`、`tests-unit-tests.rst` |
|
||
| `build/vstart_enviroment.sh` | 用于 `source` 设置环境变量,以便直接跑 workunit(注意官方文件名拼写是 `enviroment`,少一个 n) | `running-tests-locally.rst` |
|
||
| `build/src/pybind/mgr/dashboard/node-env/` | Ceph 构建时创建的 nodeenv 虚拟环境,内含 `node`/`npm`,激活脚本在 `bin/activate` | `dash-devel.rst` |
|
||
| `build/src/pybind/mgr/dashboard/cypress/` | Cypress 二进制缓存目录(Ceph 改写了默认位置,避免污染 `~/.cache`) | `dash-devel.rst` |
|
||
| `src/CMakeLists.txt` | 单元测试在这里声明,使用 `add_ceph_test` / `add_ceph_unittest` | `tests-unit-tests.rst` |
|
||
| `cmake/modules/AddCephTest.cmake` | `add_ceph_test` / `add_ceph_unittest` 这两个 CMake 函数的定义处 | `tests-unit-tests.rst` |
|
||
| `src/test/cli/` | CLI 工具的测试文件,扩展名 `.t`,通过 `cram` 运行 | `tests-unit-tests.rst` |
|
||
| `src/test/run-cli-tests` | 运行 CLI 测试的 shell 脚本 | `tests-unit-tests.rst` |
|
||
| `src/test/dokan/dokan.cc` | Windows 端 ceph-dokan 测试源码(Windows CI 示例日志引用) | `tests-windows.rst` |
|
||
| `src/pybind/` | 大多数使用 tox 的 Python 模块所在地 | `tests-unit-tests.rst` |
|
||
| `src/pybind/mgr/` | Ceph Manager Python API,`tox.ini` 在 `./src/pybind/mgr/tox.ini` | `tests-unit-tests.rst` |
|
||
| `src/pybind/mgr/dashboard/` | Ceph Dashboard 模块根目录(前端在 `frontend/` 子目录),有 `tox.ini` | `dash-devel.rst` |
|
||
| `src/pybind/mgr/dashboard/frontend/` | Dashboard 前端(Angular)。`package.json`、`angular.json`、`proxy.conf.json.sample`、`cypress/` 都在这 | `dash-devel.rst` |
|
||
| `src/pybind/mgr/dashboard/frontend/src/app/shared/components` | 可在多个页面复用的前端组件声明处 | `dash-devel.rst` |
|
||
| `src/pybind/mgr/dashboard/frontend/src/app/core/error/` | 通用前端错误处理组件(`error.ts`、`DashboardError` 类) | `dash-devel.rst` |
|
||
| `src/pybind/mgr/dashboard/frontend/src/testing/` | 前端单测工具集,核心是 `unit-test-helper.ts` | `dash-devel.rst` |
|
||
| `src/pybind/mgr/dashboard/frontend/src/locale/messages.xlf` | `npm run i18n:extract` 生成的翻译模板(**不被 git 跟踪**) | `dash-devel.rst` |
|
||
| `src/pybind/mgr/dashboard/frontend/src/styles/defaults/_bootstrap-defaults.scss` | Dashboard 所有 UI 颜色的变量定义处 | `dash-devel.rst` |
|
||
| `src/pybind/mgr/dashboard/frontend/src/styles/vendor/_variables.scss` | 厂商定制:覆盖 SCSS 变量默认值 | `dash-devel.rst` |
|
||
| `src/pybind/mgr/dashboard/frontend/src/styles/vendor/_style-overrides.scss` | 厂商定制:覆盖或扩展默认 CSS | `dash-devel.rst` |
|
||
| `src/pybind/mgr/dashboard/frontend/src/app/shared/enum/icons.enum.ts` | 已使用图标清单(Fork Awesome 类名) | `dash-devel.rst` |
|
||
| `src/pybind/mgr/dashboard/frontend/cypress/integration/visualTests/` | 视觉回归测试,命名约定 `<component-name>.vrt-spec.ts` | `dash-devel.rst` |
|
||
| `src/pybind/mgr/dashboard/frontend/cypress/integration/a11y/` | 无障碍(accessibility)测试 | `dash-devel.rst` |
|
||
| `src/pybind/mgr/dashboard/frontend/cypress/screenshots/` | E2E 失败用例截图输出目录 | `dash-devel.rst` |
|
||
| `src/pybind/mgr/dashboard/frontend/.tx/config` | transifex 配置,每个 Ceph release 一个 resource | `dash-devel.rst` |
|
||
| `src/pybind/mgr/dashboard/controllers/` | Dashboard 后端控制器目录,OpenAPI 规范由这里动态生成 | `dash-devel.rst` |
|
||
| `src/pybind/mgr/dashboard/plugins/` | Dashboard 插件目录 | `dash-devel.rst` |
|
||
| `src/pybind/mgr/dashboard/tests/` | Dashboard 后端单测目录(如 `test_controllers.py`、`test_rgw_client.py`) | `dash-devel.rst`、`tests-unit-tests.rst` |
|
||
| `src/pybind/mgr/dashboard/openapi.yaml` | 版本化的 Ceph RESTful API OpenAPI 规范 | `dash-devel.rst` |
|
||
| `src/pybind/mgr/dashboard/settings.py` | `Options` 类所在文件,新增配置项在这里加 | `dash-devel.rst` |
|
||
| `src/pybind/mgr/dashboard/module.py` | Dashboard 模块入口,插件需要从这里 import(目前没有动态加载) | `dash-devel.rst` |
|
||
| `src/pybind/mgr/dashboard/run-backend-api-tests.sh` | 启动 vstart 集群并跑 teuthology API 测试 | `dash-devel.rst` |
|
||
| `src/pybind/mgr/dashboard/run-frontend-e2e-tests.sh` | 启动 vstart 集群并跑全量前端 E2E | `dash-devel.rst` |
|
||
| `src/pybind/mgr/dashboard/ci/cephadm/run-cephadm-e2e-tests.sh` | 基于 KCLI 的 cephadm E2E 测试 | `dash-devel.rst` |
|
||
| `src/pybind/mgr/dashboard/ci/cephadm/start-cluster.sh` | cephadm 开发模式集群启动脚本,支持 `--dev-mode`、`--expanded` | `dash-devel.rst` |
|
||
| `src/pybind/mgr/dashboard/requirements.txt` | Dashboard 后端 Python 依赖清单 | `dash-devel.rst` |
|
||
| `src/pybind/mgr/dashboard/grafana_dashboards.jsonnet` | Grafana 面板的 jsonnet 源(官方写的路径是 `monitoring/grafana/dashboards/jsonnet` 目录下的 `grafana_dashboards.jsonnet`) | `dash-devel.rst` |
|
||
| `src/python-common/` | Python common 模块,有 `tox.ini` | `tests-unit-tests.rst` |
|
||
| `src/cephadm/` | cephadm,有 `tox.ini` | `tests-unit-tests.rst` |
|
||
| `src/ceph-volume/` | ceph-volume,有多个 `tox.ini`(根目录、plugin/zfs、3 个 functional 测试目录) | `tests-unit-tests.rst` |
|
||
| `src/tools/cephfs/` | CephFS 工具,有 `tox.ini` | `tests-unit-tests.rst` |
|
||
| `src/script/` | 各类脚本:`run_tox.sh`、`ptl-tool.py`、`ceph-debug-docker.sh` | 多个文件 |
|
||
| `src/script/ptl-tool.py` | Patrick Donnelly 的 `ptl-tool`,用于生成 merge commit | `basic-workflow.rst` |
|
||
| `src/script/ceph-debug-docker.sh` | 用于分析 Teuthology 失败的调试脚本 | `debugging-gdb.rst` |
|
||
| `qa/` | 集成测试与 workunit 所在目录(`qa/suites`、`qa/tasks`、`qa/workunits`) | 多个文件 |
|
||
| `qa/suites/` | 集成测试的 yaml 定义(suite) | `teuthology-intro.rst` |
|
||
| `qa/tasks/` | 集成测试的 Python 实现(task) | `teuthology-intro.rst` |
|
||
| `qa/workunits/` | 可直接在 vstart 集群上跑的 shell 测试(如 `mon/osd.sh`) | `running-tests-locally.rst` |
|
||
| `qa/tasks/vstart_runner.py` | 在本机 vstart 集群上跑 `qa/tasks/` 下 Python 测试的驱动器 | `running-tests-locally.rst` |
|
||
| `qa/tasks/mgr/dashboard/` | Dashboard API 测试模块目录 | `dash-devel.rst` |
|
||
| `qa/suites/rados/mgr/tasks/dashboard.yaml` | 新增 Dashboard API 测试模块后,必须把模块路径加到这个文件的 `modules` 段 | `dash-devel.rst` |
|
||
| `qa/distros/supported/` | teuthology 支持的发行版列表(截至 2020 年 9 月:RHEL/CentOS 8、Ubuntu 18.04) | `teuthology-intro.rst` |
|
||
| `.githubmap`(仓库根) | GitHub 用户名 → 邮箱映射,用于查 reviewer 邮箱 | `basic-workflow.rst` |
|
||
| `win32_build.sh` / `win32_deps_build.sh` | Windows 构建脚本与依赖构建脚本 | `tests-windows.rst` |
|
||
|
||
> 💡 补充:如果你需要一份完整的 `src/` 子目录(如 `src/osd/`、`src/mon/`、`src/mds/`、`src/common/`、`src/msg/`、`src/crimson/`)逐条说明的布局表,官方 `developer_guide` 里没有;这类内容属于 `doc/dev/internals.rst` 与 `doc/dev/osd_internals/` 的范畴,可参考本手册第 16、17、18 册。
|
||
|
||
> 引自:[Essentials (tl;dr)](https://docs.ceph.com/en/tentacle/dev/developer_guide/essentials/)
|
||
|
||
## 1.5 构建要点小结(本册范围内的)
|
||
|
||
| 要点 | 做法 |
|
||
|---|---|
|
||
| 从源码构建 | 参见 `/install/build-ceph`,本册未展开 |
|
||
| 加速重建 | `cmake -DWITH_CCACHE=ON ..`,建议 `max_size` 调到 25G |
|
||
| 最大化缓存命中 | `ccache.conf` 加 `sloppiness = time_macros` + `run_second_cpp = true`;`export SOURCE_DATE_EPOCH=946684800`;`cmake -DENABLE_GIT_VERSION=OFF` |
|
||
| 构建失败先清树 | `ninja clean`,再 `git clean -fdx; git submodule foreach git clean -fdx`(**会删掉未跟踪文件,先备份**) |
|
||
| 带 Jaeger 构建 | `WITH_JAEGER=true ./install-deps.sh` 然后 `cmake -DWITH_JAEGER=ON ..`(见第十一章) |
|
||
| 跑单测 | 顶层目录 `./run-make-check.sh`(见第五章) |
|
||
|
||
---
|
||
|
||
# 二、基本工作流(fork → 分支 → 提交 → PR → 评审 → merge)
|
||
|
||
这一章来自 `basic-workflow.rst`(23KB,本册第二大的页面)。
|
||
|
||
## 2.1 官方给出的工作流图
|
||
|
||
官方用 ditaa 画了一张图,文字化如下:
|
||
|
||
```text
|
||
Upstream Code Your Local Environment
|
||
|
||
/----------\ git clone /-------------\
|
||
| Ceph | -------------------------> | ceph/main |
|
||
\----------/ \-------------/
|
||
^ |
|
||
| | git branch fix_1
|
||
| git merge |
|
||
| v
|
||
/----------------\ git commit --amend /-------------\
|
||
| ninja check |---------------------> | ceph/fix_1 |
|
||
| ceph--qa--suite| \-------------/
|
||
\----------------/ |
|
||
^ | fix changes
|
||
| | test changes
|
||
| review | git commit
|
||
| |
|
||
| v
|
||
/--------------\ /-------------\
|
||
| github |<---------------------- | ceph/fix_1 |
|
||
| pull request | git push \-------------/
|
||
\--------------/
|
||
```
|
||
|
||
官方还推荐了一个视频:*Getting Started with Ceph Development*(https://www.youtube.com/watch?v=t5UIehZ1oLs,1 小时 15 分),说它是本工作流的实操总结。
|
||
|
||
## 2.2 第 0 步:更新 tracker
|
||
|
||
**先找到你打算修的 bug 的 Redmine issue 号;如果没有,就创建一个。**
|
||
|
||
**唯一不需要建 tracker issue 的情况:minor documentation changes(文档的小改动)。**
|
||
|
||
| 文档改动类型 | 是否需要 tracker issue |
|
||
|---|---|
|
||
| 简单的文档清理(simple documentation cleanup) | **不需要** |
|
||
| 新增文档章节或文件 | 需要(属 major) |
|
||
| 对文档结构或内容做实质性改动 | 需要(属 major) |
|
||
|
||
写 ticket 标题时的自检问题(官方原话):**「如果两年后我要搜这个 ticket,我最可能搜哪些关键词?」**——然后把这些关键词放进标题。描述里要有详细的信息。
|
||
|
||
如果权限够,把 `Assignee` 设成自己;权限不够就在评论里写一句「I am working on this issue」。
|
||
|
||
## 2.3 Ceph 工作流总览:三个仓库
|
||
|
||
| # | 仓库 | 说明 |
|
||
|---|---|---|
|
||
| 1 | `ceph/ceph` | upstream repository(上游仓库) |
|
||
| 2 | `your_github_id/ceph` | 你的 fork |
|
||
| 3 | 本地工作副本 | 你工作站上的 clone |
|
||
|
||
完整步骤:
|
||
|
||
1. **配置本地环境**
|
||
1. 创建 fork
|
||
2. clone fork 到本地
|
||
2. **修 bug**
|
||
1. 同步本地 main 与 upstream main
|
||
2. 在本地工作副本创建 bugfix 分支
|
||
3. 改代码
|
||
4. push 到你的 fork
|
||
3. **开 PR**:请求把改动合入 upstream Ceph
|
||
|
||
## 2.4 准备本地工作副本(仅首次需要)
|
||
|
||
### 2.4.1 创建 fork
|
||
|
||
GitHub 文档:https://help.github.com/articles/fork-a-repo/#platform-linux
|
||
|
||
简言之:如果你的 GitHub 用户名是 `mygithubaccount`,你的 fork 就在 `https://github.com/mygithubaccount/ceph`。
|
||
|
||
### 2.4.2 Clone 你的 fork
|
||
|
||
```bash
|
||
git clone https://github.com/mygithubaccount/ceph
|
||
```
|
||
|
||
> **必须先 fork 再 clone。** 不 fork 就没法开 GitHub pull request。
|
||
|
||
### 2.4.3 配置本地环境(生成 Signed-off-by 的必要步骤)
|
||
|
||
这一节的命令是为了让本地 git 生成 `Signed-off-by:` 标签,并让本地环境能与 upstream 同步。**只在初次搭建时跑一次**,但不跑就没法正常工作。
|
||
|
||
**1. 配置用户名和邮箱**(必须在 clone 出来的 `ceph/` 目录里跑):
|
||
|
||
```bash
|
||
git config user.name "FIRST_NAME LAST_NAME"
|
||
git config user.email "MY_NAME@example.com"
|
||
```
|
||
|
||
**2. 把 upstream 仓库加为 remote 并 fetch:**
|
||
|
||
```bash
|
||
git remote add ceph https://github.com/ceph/ceph.git
|
||
git fetch ceph
|
||
```
|
||
|
||
这两条会把 `ceph/ceph.git` 的所有分支和提交拉到本地 git 仓库,存为 `remotes/ceph/$BRANCH_NAME`,在本地命令里可以引用为 `ceph/$BRANCH_NAME`。
|
||
|
||
## 2.5 修 bug
|
||
|
||
### 2.5.1 同步本地 main 与 upstream main
|
||
|
||
本地工作副本里 `remotes/origin/main` 是 main 分支的一个副本,叫「local main」。**它在你 clone 的那一刻就「冻结」了**,而 upstream 还在被别人持续更新,fork 会越漂越远。所以要经常同步:
|
||
|
||
```bash
|
||
git fetch ceph
|
||
git checkout main
|
||
git reset --hard ceph/main
|
||
git push -u origin main
|
||
```
|
||
|
||
如果 `git status` 出现「Untracked files」这一行,官方让你去看 `the procedure on updating submodules`(更新子模块的步骤,本册源文未展开,**文档未明确**)。
|
||
|
||
### 2.5.2 创建 bugfix 分支
|
||
|
||
```bash
|
||
git checkout main
|
||
git checkout -b fix_1
|
||
git push -u origin fix_1
|
||
```
|
||
|
||
三条命令的含义:
|
||
|
||
| 命令 | 作用 |
|
||
|---|---|
|
||
| `git checkout main` | 确保 bugfix 分支 `fix_1` 是从 upstream main 的最新状态创建的 |
|
||
| `git checkout -b fix_1` | 在本地工作副本创建名为 `fix_1` 的 bugfix 分支,修 bug 的改动都提交到这个分支 |
|
||
| `git push -u origin fix_1` | 把本地的 bugfix 分支推到你的 fork |
|
||
|
||
### 2.5.3 在本地改代码
|
||
|
||
1. **更新 tracker**:把 issue 状态改成 `In progress`,告诉别人你在做了,避免重复劳动。没权限就评论一句。
|
||
2. **修 bug 本身**:官方坦白说「本指南没法告诉你怎么修你选中的 bug」。多数情况下要经历多轮测试,过程是迭代的,需要 trial、error、skill 和 patience。可用的验证工具见第五章、第六章。
|
||
|
||
### 2.5.4 提交并推送到你的 fork
|
||
|
||
**提交(注意 `-as`,其中 `s` 就是 `--signoff`):**
|
||
|
||
```bash
|
||
git commit -as
|
||
```
|
||
|
||
**推送:**
|
||
|
||
```bash
|
||
git push origin fix_1
|
||
```
|
||
|
||
关于 `origin` 的说明:
|
||
|
||
```bash
|
||
$ git remote -v
|
||
ceph https://github.com/ceph/ceph.git (fetch)
|
||
ceph https://github.com/ceph/ceph.git (push)
|
||
origin git@github.com:username/ceph.git (fetch)
|
||
origin git@github.com:username/ceph.git (push)
|
||
```
|
||
|
||
上面输出里 `origin` 那两行说明 `origin` 是你的 fork。**`origin` 不一定是你的 fork 的名字**——用 `git remote -v` 确认。
|
||
|
||
## 2.6 开 GitHub Pull Request
|
||
|
||
push 之后开 PR,让你的改动被 Ceph 贡献者社区看到、评审、可能追加测试、可能要求你改。
|
||
|
||
官方建议读:
|
||
|
||
| 资源 | 链接 |
|
||
|---|---|
|
||
| GitHub PR 教程 | https://help.github.com/articles/using-pull-requests/ |
|
||
| Git Commit Good Practice(OpenStack Wiki) | https://wiki.openstack.org/wiki/GitCommitMessages |
|
||
| Ceph 自己的 Submitting Patches | https://github.com/ceph/ceph/blob/main/SubmittingPatches.rst |
|
||
|
||
**开完 PR 后,去 tracker issue 里加一条评论指向你的 PR**,可以简单到只有一行:
|
||
|
||
```text
|
||
*PR*: https://github.com/ceph/ceph/pull/$NUMBER_OF_YOUR_PULL_REQUEST
|
||
```
|
||
|
||
## 2.7 Commit message 规范(本册源文能确证的部分)
|
||
|
||
> ⚠️ 关于 `Fixes:` 等更细的 trailer 规范:**文档未明确**。本册源文(basic-workflow + essentials)只明确要求了下面表格里的这些;更完整的规范官方指向仓库里的 `CONTRIBUTING.rst` 与 `SubmittingPatches.rst`,本册未展开。
|
||
|
||
| 规范项 | 官方要求 | 出处 |
|
||
|---|---|---|
|
||
| `Signed-off-by:` | **必须有**。通过 `git commit -as`(`-s` = `--signoff`)自动追加。这也是 CI 的一个检查项(a test to check that the commits are properly signed) | `basic-workflow.rst` |
|
||
| 提交前配置 | `git config user.name` / `git config user.email`,否则生成不出 `Signed-off-by` | `basic-workflow.rst` |
|
||
| 前置 tracker issue | 除 minor 文档改动外,先有 Redmine issue;PR 开出来后在 issue 里留 `*PR*: <url>` 评论 | `basic-workflow.rst` |
|
||
| Backport 标记 | 在 tracker issue 的 `Backport` 字段填逗号分隔的先前版本列表(如 `hammer,jewel`),**提交者自己负责 backport** | `essentials.rst` |
|
||
| Backport 前提 | 所有 bugfix 先合到 `main`,再 backport | `essentials.rst` |
|
||
| Merge commit 标题 | `doc/component: title of the commit` | `basic-workflow.rst` |
|
||
| Merge commit 身体 | `Reviewed-by: Reviewer Name <rname@example.com>`(邮箱用尖括号包起来) | `basic-workflow.rst` |
|
||
| PR 只含一个 commit | **最佳实践**:一个 PR 只包含一个 commit,便于保持干净的 history、简化评审、便于 merge 与 revert | `basic-workflow.rst` |
|
||
| Git commit 好实践 | 见 OpenStack Wiki 的 GitCommitMessages | `basic-workflow.rst` |
|
||
|
||
**最基本的 merge commit 形式(官方原文):**
|
||
|
||
```text
|
||
doc/component: title of the commit
|
||
|
||
Reviewed-by: Reviewer Name <rname@example.com>
|
||
```
|
||
|
||
两部分:被 merge 的 commit 的标题 + 评审者的姓名和邮箱。
|
||
|
||
> 💡 补充:Ceph 社区实际使用的 `Fixes: https://tracker.ceph.com/issues/NNNNN` trailer 在 `SubmittingPatches.rst` 里有规定,但该文件不在本册源文范围内,因此本册不声称它是本册所讲内容。
|
||
|
||
## 2.8 Merge commit 的三条生成路径
|
||
|
||
### 2.8.1 用浏览器扩展自动填充
|
||
|
||
官方推荐 **Ceph GitHub Helper Extension**(https://github.com/tspmelo/ceph-github-helper):
|
||
|
||
- Chrome:https://chrome.google.com/webstore/detail/ceph-github-helper/ikpfebikkeabmdnccbimlomheocpgkmn
|
||
- Firefox:https://addons.mozilla.org/en-US/firefox/addon/ceph-github-helper/
|
||
|
||
启用后,打开 GitHub PR 页面,右上角会出现一条竖直的 helper;点用户剪影按钮,merge message 输入框会被自动填充。
|
||
|
||
### 2.8.2 查 reviewer 邮箱的两种方法
|
||
|
||
| 方法 | 做法 |
|
||
|---|---|
|
||
| `.githubmap` 文件 | 仓库里 `/ceph/.githubmap`,查 GitHub 用户名对应的邮箱 |
|
||
| `git log` | 在待评审分支上跑 `git log`,评审者之前大概率提交过东西,日志里会有其邮箱 |
|
||
|
||
### 2.8.3 用 ptl-tool 生成
|
||
|
||
Patrick Donnelly 的 `ptl-tool` 位于 `/ceph/src/script/ptl-tool.py`。它生成的 merge commit 形如:
|
||
|
||
```text
|
||
Merge PR #36257 into main
|
||
* refs/pull/36257/head:
|
||
client: move client_lock to _unmount()
|
||
client: add timer_lock support
|
||
Reviewed-by: Patrick Donnelly <pdonnell@redhat.com>
|
||
```
|
||
|
||
## 2.9 理解自动化的 PR 校验(CI)
|
||
|
||
创建或更新 PR 时,Ceph 的 CI 基础设施会自动测试。**官方列出的自动化检查:**
|
||
|
||
1. 检查 commit 是否正确签名(signed)
|
||
2. 检查文档能否构建(that the documentation builds)
|
||
3. 检查 submodules 未被修改(that the submodules are unmodified)
|
||
4. 检查 API 是否正常(that the API is in order)
|
||
5. **`make check` 测试**
|
||
|
||
此外,根据 PR 修改了哪些文件,**可能还会跑更多测试**。
|
||
|
||
`make check` 会构建 PR 并跑一整套测试,跑在 Ceph CI 团队运维的服务器上,结果直接显示在 GitHub 的 PR 页面里。
|
||
|
||
**官方明确要求:开 PR 之前先自己测。**
|
||
|
||
### make check 的注意事项
|
||
|
||
- GitHub 的 `make check` 由 **Jenkins 实例**驱动。
|
||
- **Jenkins 会先把你的 PR 分支 merge 进 base 分支的最新版本再开始测试**——所以你不需要为了拿到最新修复而 rebase。
|
||
- 你可以随时通过在 PR 里**发评论**触发测试,评论里要包含字符串 `test this please`。由于订阅了 PR 的人可能误以为你在请求他/她去测,官方要求**直接称呼 Jenkins**,例如写 `jenkins retest this please`。
|
||
- 只跑某一个测试可以用类似 `jenkins test signed` 的命令。这些命令列表会自动附加在每个新 PR 描述的末尾,去那里查你需要的那一条。
|
||
- 出构建失败又不确定原因,就去看 `make check` 日志:点 PR 里 `make check` 旁边的 **details** 进 Jenkins web GUI,然后点左侧的 **Console Output**。
|
||
- Jenkins 配了「已知与历史 make check 失败相关的字符串」的搜索,但**不保证这些字符串与你的失败有关**,还是得自己读日志。
|
||
|
||
## 2.10 集成测试(ceph-qa-suite)
|
||
|
||
有时候需要在物理或虚拟硬件上的真实 Ceph 集群上测试你的修复。这类测试在 `ceph/qa` 子目录(https://github.com/ceph/ceph/tree/main/qa/)里,通过 **teuthology 框架**(https://github.com/ceph/teuthology)运行。
|
||
|
||
Ceph 社区有 **Sepia lab**(https://wiki.sepia.ceph.com/doku.php)可以在物理硬件上跑集成测试。申请访问权限从 https://wiki.sepia.ceph.com/doku.php?id=vpnaccess 开始。
|
||
|
||
**关于 `needs-qa` 与 `needs-upgrade-testing` 标签:**
|
||
|
||
| 标签 | 含义 |
|
||
|---|---|
|
||
| `needs-qa` | 其他贡献者可能给你的 PR 加这个标签。这样可以把多个 PR 合到一条分支上一起测。teuthology suite 可能要跑几小时甚至几天,批量跑能减少资源争用、节省时间 |
|
||
| `needs-upgrade-testing` | **如果你的改动会影响升级,自己加上这个标签**,表示应该安排一个升级测试 suite |
|
||
|
||
## 2.11 Code review
|
||
|
||
bugfix 经过充分测试后(有时测试期间就会开始),会接受其他开发者的代码评审。形式通常是 PR 里的评论,也可能辅以 IRC、Slack 或邮件列表上的讨论。
|
||
|
||
## 2.12 修改(amend)你的 PR
|
||
|
||
测试与评审期间,你随时可以修改本地分支里的文件。改动在本地(`fix_1` 分支)提交后,**必须 push 到 GitHub 才会出现在 PR 里**。
|
||
|
||
修改 PR 的做法:往 `fix_1` 分支加 commit,通常再 rebase 来整理 git history(rebase 入门见 https://www.atlassian.com/git/tutorials/rewriting-history)。改完之后需要 **force push**:
|
||
|
||
```bash
|
||
git push --force origin fix_1
|
||
```
|
||
|
||
**为什么要这么麻烦,而不是直接往 PR 里加 commit?** 官方给了三条理由:
|
||
|
||
1. **一个 PR 只含一个 commit 是最佳实践**,能保持干净的 history;
|
||
2. 简化 peer review;
|
||
3. 让 merge 更容易;万一(in the unlikely event)PR 需要被 revert,只有一个 commit 的 PR 回退起来也更容易。
|
||
|
||
## 2.13 Merging
|
||
|
||
流程在项目 lead 合并你的 PR 时结束。
|
||
|
||
此时你(或合并 PR 的 lead)应该把 tracker 状态改成 **Resolved**。如果 issue 被标记了 backport,状态应改成 **Pending Backport**。
|
||
|
||
## 2.14 杂项 git 技巧
|
||
|
||
### `--set-upstream`
|
||
|
||
忘记加 `--set-upstream origin x` 会看到:
|
||
|
||
```text
|
||
fatal: The current branch {x} has no upstream branch.
|
||
To push the current branch and set the remote as upstream, use
|
||
git push --set-upstream origin {x}
|
||
```
|
||
|
||
想让 git 自动创建对应的 upstream 分支(不用每次都加参数),在 `ceph/` 目录里跑一次:
|
||
|
||
```bash
|
||
git config --global push.autoSetupRemote true
|
||
```
|
||
|
||
### 删除本地分支
|
||
|
||
```bash
|
||
git branch -d localBranchName
|
||
```
|
||
|
||
### 删除远程分支
|
||
|
||
```bash
|
||
git push origin --delete remoteBranchName
|
||
```
|
||
|
||
(这里的「远程」指你的 fork。)
|
||
|
||
### 纵向搜索某个文件里的某个字符串是被哪次 commit 引入的
|
||
|
||
```bash
|
||
git log -S 'foo' file.rst
|
||
```
|
||
|
||
`-S <string>` 会找出引入/移除该字符串的 commit。
|
||
|
||
> 引自:[Basic Workflow](https://docs.ceph.com/en/tentacle/dev/developer_guide/basic-workflow/)
|
||
|
||
---
|
||
|
||
# 三、Issue Tracker 与任务管理
|
||
|
||
Ceph 开发者用 issue tracker 做两件事:
|
||
|
||
1. **跟踪 issue**——bug、fix request、feature request、backport request 等;
|
||
2. **与其他开发者沟通**,让大家知道 issue 的进展。
|
||
|
||
## 3.1 开始工作时的约定
|
||
|
||
当你开始处理某个已有 issue 时,**最好让别人知道**,避免重复劳动。典型做法:
|
||
|
||
1. 把 `Assignee` 字段改成你自己;
|
||
2. 把 `Status` 改成 **In progress**。
|
||
|
||
> **注意**:Ceph 社区的新人通常**没有足够权限**修改这些字段。这时只需在 issue 里加一条简短说明即可。
|
||
|
||
## 3.2 常用状态的含义
|
||
|
||
| Status | Meaning |
|
||
|---|---|
|
||
| **New** | 初始状态 |
|
||
| **In Progress** | 有人在做了 |
|
||
| **Need Review** | 修复的 pull request 已开 |
|
||
| **Pending Backport** | 修复已 merge,backport 待办 |
|
||
| **Resolved** | 修复以及(如果有)backport 都已 merge |
|
||
|
||
> 引自:[Issue Tracker](https://docs.ceph.com/en/tentacle/dev/developer_guide/issue-tracker/)
|
||
|
||
---
|
||
|
||
# 四、代码合并流程(`merging.rst`)
|
||
|
||
本章回答三个问题:**什么**(What)、**合到哪**(Where)、**什么时候**(When),以及**分支之间的 merge 关系**。
|
||
|
||
> ⚠️ 本章的举例版本(hammer / infernalis / jewel / luminous / mimic / nautilus / pacific / octopus)是文档原有内容,用于说明规则,不代表 Tentacle 当前正在用的分支名。
|
||
|
||
## 4.1 开发发布阶段(Development releases,即 x.0.z)
|
||
|
||
**What(合什么)**:Features(新特性)+ Bug fixes(缺陷修复)。
|
||
|
||
**Where(合到哪)**:
|
||
|
||
- Features 合到 **main** 分支。
|
||
- Bug fixes 应合到对应的 named branch,例如 14.0.z 对应 *nautilus*,16.0.z 对应 *pacific*。
|
||
- **但这不是强制的**——bug fix 和文档改进也可以合到 main,因为在开发发布阶段 main 本身也会偶尔被 merge 到 named branch。
|
||
- 无论哪种情况,如果某个 bug fix 很重要,它也可以被标记 backport 到一个或多个先前的 stable release。
|
||
|
||
**When(什么时候)**:每次 stable release 之后,先前 release 的候选分支进入 phase 2。举例:*jewel* named branch 是在 *infernalis* release candidates 进入 phase 2 时创建的。从那时起,main 就不再与 infernalis 关联。下一个 stable release 的 named branch 创建后,main 会偶尔被 merge 进去。
|
||
|
||
**Branch merges(分支合并规则)**:
|
||
|
||
- 最新的 stable release 分支被**周期性**地 merge 进 main。
|
||
- main 分支被**周期性**地 merge 进 stable release 的分支。
|
||
- 每次开发版(x.0.z)发布后,main 会**立即** merge 进 stable release 分支。
|
||
|
||
## 4.2 Stable release candidates(x.1.z)阶段 1
|
||
|
||
**What**:**只有 bug fixes**。
|
||
|
||
**Where**:stable release 分支(如 10.0.z 对应 *jewel*,12.0.z 对应 *luminous*)**或 main**。
|
||
|
||
在此阶段,**所有合到 main 的 commit 都会被 merge 到 named branch,反之亦然**。换句话说,合到 named branch 还是 main **没有区别**——反正都会进下一个 release candidate。
|
||
|
||
**When**:第一个 stable release candidate 发布之后,即在 release 分支上打了 x.1.0 tag 之后。
|
||
|
||
**Branch merges**:
|
||
|
||
- stable release 分支被周期性地 merge 进 main;
|
||
- main 被周期性地 merge 进 stable release 分支;
|
||
- 每次 x.1.z release candidate 之后,main 立即 merge 进 stable release 分支。
|
||
|
||
## 4.3 Stable release candidates(x.1.z)阶段 2
|
||
|
||
**What**:**只有 bug fixes**。
|
||
|
||
**Where**:stable release 分支(如 13.0.z 对应 *mimic*,15.0.z 对应 *octopus*)。
|
||
|
||
在此阶段,**所有合到 named branch 的 commit 都会被 merge 进 main**。而**往 named branch 的 cherry-pick 是手工做的**,因为正式的 backport 流程要等 release 被宣布为 "stable" 之后才开始。
|
||
|
||
**When**:CLT 宣布进入 phase 2 之后。
|
||
|
||
**Branch merges**:stable release 分支**偶尔** merge 进 main。
|
||
|
||
## 4.4 Stable releases(x.2.z)
|
||
|
||
**What**:
|
||
|
||
- Bug fixes;
|
||
- Features **有时**会被接受;
|
||
- **尽可能从 main cherry-pick** commit;
|
||
- 不是从 main cherry-pick 的 commit,**必须**是针对该 stable release 独有 bug 的修复;
|
||
- 另见 backport HOWTO:http://tracker.ceph.com/projects/ceph-releases/wiki/HOWTO#HOWTO
|
||
|
||
**Where**:stable release 分支(0.94.x 对应 *hammer*,9.2.x 对应 *infernalis* 等)。
|
||
|
||
**When**:stable release 发布之后,即在 release 分支上打了 "vx.2.0" tag 之后。
|
||
|
||
**Branch merges**:**Never(从不)**。
|
||
|
||
## 4.5 四个阶段对照表
|
||
|
||
| 阶段 | 版本形态 | 合什么 | 合到哪 | 何时开始 | 分支 merge 关系 |
|
||
|---|---|---|---|---|---|
|
||
| Development releases | x.0.z | Features + Bug fixes | Features → main;Bug fixes → named branch(也可 main) | 每次 stable release 之后,上一批候选分支进入 phase 2 | stable ↔ main 周期性互 merge;x.0.z 发布后 main 立即合入 stable |
|
||
| Stable RC phase 1 | x.1.z | **仅** Bug fixes | stable release 分支 **或** main(**等价**) | 打了 x.1.0 tag 之后 | stable ↔ main 周期性互 merge;每个 RC 后 main 立即合入 stable |
|
||
| Stable RC phase 2 | x.1.z | **仅** Bug fixes | stable release 分支 | CLT 宣布进入 phase 2 | stable **偶尔** merge 进 main;反向靠手工 cherry-pick |
|
||
| Stable releases | x.2.z | Bug fixes;Features 有时接受;**尽量 cherry-pick 自 main** | stable release 分支 | 打了 vx.2.0 tag 之后 | **Never** |
|
||
|
||
> 💡 补充:把四个阶段串起来的直觉是——越接近发布,收得越紧:从「什么都能合」到「只收 bugfix」到「只收 bugfix 且主方向只剩单向」,最后 stable 分支完全不再与其他分支互相 merge,只靠 backport(cherry-pick)单点进补丁。
|
||
|
||
> 引自:[Commit merging: scope and cadence](https://docs.ceph.com/en/tentacle/dev/developer_guide/merging/)
|
||
|
||
---
|
||
|
||
# 五、本地运行测试(单元测试与 workunit)
|
||
|
||
## 5.1 Ceph 的两类测试
|
||
|
||
Ceph GitHub 仓库有两类测试:
|
||
|
||
| 类型 | 也称 | 特征 |
|
||
|---|---|---|
|
||
| **Unit tests** | `make check` tests | 从源码编译完 Ceph 后,能在**单台构建机器**上轻松跑起来的测试 |
|
||
| **Integration tests** | teuthology tests | 需要**安装软件包**、需要**多机集群**才能跑的测试 |
|
||
|
||
官方特意说明:**严格来说 `make check` 测试并不是「单元测试」**,只是因为它们能在单机构建环境下跑,才被归为一类。
|
||
|
||
## 5.2 「make check」是什么意思
|
||
|
||
编译完 Ceph 后,代码可以跑一整套测试。由于历史原因,这套测试常被称为 `make check`,**尽管实际用来跑测试的命令现在是 `ctest`**。
|
||
|
||
**一个测试要被纳入这一类,必须满足:**
|
||
|
||
- 绑定的端口不与其他测试冲突;
|
||
- 不需要 root 权限;
|
||
- 不需要多于一机器就能跑;
|
||
- 在几分钟内完成。
|
||
|
||
为简化表述,这类测试被称为 "make check tests" 或 "unit tests",用来和更复杂的、通过 teuthology 框架运行的 "integration tests" 区分开。
|
||
|
||
虽然可以直接跑 `ctest`,但**正确设置环境比较麻烦**。官方提供了一个脚本,从 Ceph 源码树顶层目录调用即可:
|
||
|
||
```bash
|
||
./run-make-check.sh
|
||
```
|
||
|
||
**资源要求(官方给出)**:x86_64 架构上至少需要 **8GB RAM 和 32GB 可用磁盘空间**;其他架构要求可能不同。根据硬件不同,**耗时从 20 分钟到 3 小时不等**。
|
||
|
||
## 5.3 单元测试是怎么声明的
|
||
|
||
单元测试在 **`./src` 目录的 `CMakeLists.txt`** 里声明,使用两个 CMake 函数:
|
||
|
||
| 函数 | 用途 |
|
||
|---|---|
|
||
| `add_ceph_test` | 声明**脚本形式**的单元测试 |
|
||
| `add_ceph_unittest` | 声明**二进制形式**的单元测试(编译期生成的可执行文件) |
|
||
|
||
这两个函数本身定义在 **`./cmake/modules/AddCephTest.cmake`**。
|
||
|
||
## 5.4 CLI 工具的单元测试
|
||
|
||
部分 CLI 工具用扩展名为 **`.t`** 的特殊文件测试,存放在 **`./src/test/cli`** 下。这些测试通过 **`cram`**(https://bitheap.org/cram/)运行,驱动脚本是 **`./src/test/run-cli-tests`**。
|
||
|
||
不适合放进 `make check` 的 cram 测试,也可以由 teuthology 通过 **cram task**(https://github.com/ceph/ceph/blob/master/qa/tasks/cram.py)来跑。
|
||
|
||
## 5.5 基于 Tox 的 Python 模块测试
|
||
|
||
Ceph 的部分 Python 模块使用 **tox**(https://tox.readthedocs.io/en/latest/)跑单元测试,这些模块大多在 **`./src/pybind/`** 下。
|
||
|
||
**官方列出的(截至 2020 年 12 月)使用 tox 的模块清单:**
|
||
|
||
| 模块 | tox.ini 位置 |
|
||
|---|---|
|
||
| Cephadm | `./src/cephadm/tox.ini` |
|
||
| Ceph Manager Python API | `./src/pybind/mgr/tox.ini` |
|
||
| Dashboard | `./src/pybind/mgr/dashboard/tox.ini` |
|
||
| (mgr 下另有) | `./src/pybind/tox.ini` |
|
||
| Python common | `./src/python-common/tox.ini` |
|
||
| CephFS | `./src/tools/cephfs/tox.ini` |
|
||
| ceph-volume | `./src/ceph-volume/tox.ini` |
|
||
| ceph-volume zfs plugin | `./src/ceph-volume/plugin/zfs/tox.ini` |
|
||
| ceph-volume batch functional | `./src/ceph-volume/ceph_volume/tests/functional/batch/tox.ini` |
|
||
| ceph-volume simple functional | `./src/ceph-volume/ceph_volume/tests/functional/simple/tox.ini` |
|
||
| ceph-volume lvm functional | `./src/ceph-volume/ceph_volume/tests/functional/lvm/tox.ini` |
|
||
|
||
### 5.5.1 查看 tox 的环境与任务列表
|
||
|
||
大多数 tox 配置支持多个 environment 和 task,清单在 `tox.ini` 的 **`envlist`** 里。例如 `./src/cephadm/tox.ini` 的前三行:
|
||
|
||
```ini
|
||
[tox]
|
||
envlist = py3, mypy
|
||
skipsdist=true
|
||
```
|
||
|
||
这个例子指定了 `Python 3` 和 `mypy` 两个环境。
|
||
|
||
列出环境:
|
||
|
||
```bash
|
||
tox --list
|
||
```
|
||
|
||
或
|
||
|
||
```bash
|
||
tox -l
|
||
```
|
||
|
||
### 5.5.2 运行 tox
|
||
|
||
在包含 `tox.ini` 的目录里直接执行 `tox` 即可。如果不指定环境(例如 `-e $env1,$env2`),**tox 会跑所有环境**。Jenkins 通过执行 **`./src/script/run_tox.sh`** 来跑 tox。
|
||
|
||
Ceph Dashboard 的示例:
|
||
|
||
```bash
|
||
## Run Python 2+3 tests+lint commands:
|
||
tox -e py27,py3,lint,check
|
||
|
||
## Run Python 3 tests+lint commands:
|
||
tox -e py3,lint,check
|
||
|
||
## To run it as Jenkins would:
|
||
../../../script/run_tox.sh --tox-env py3,lint,check
|
||
```
|
||
|
||
### 5.5.3 Manager core 单元测试
|
||
|
||
目前**只跑 `mgr_util.py` 里的 doctests**(https://docs.python.org/3/library/doctest.html)。
|
||
|
||
要往 manager core 里加更多被测文件,打开 `tox.ini`,把文件加到**包含 `mgr_util.py` 的那一行末尾**。
|
||
|
||
## 5.6 单元测试的两个注意事项(caveats)
|
||
|
||
1. **内存分配器**:与各个 Ceph 守护进程和 `ceph-fuse` 不同,单元测试**默认链接到系统默认的内存分配器(glibc)**,除非显式链接到别的。这样做的目的是**让 valgrind 这类工具能在测试中使用**。
|
||
2. **Google Test 会隐藏客户端输出**。要调试客户端,先设好 debug level(例如 `ceph config set client debug_rbd 20`),然后去 **`build/out/client.admin.<pid>.log`** 找日志文件。这在查看 teuthology 里失败的单元测试 job 时也很有用——可以在相关的 yaml 文件里设置 job 的 debug level。
|
||
|
||
> 引自:[Testing - unit tests](https://docs.ceph.com/en/tentacle/dev/developer_guide/tests-unit-tests/)
|
||
|
||
## 5.7 在本地跑 s3-tests(RGW)
|
||
|
||
RGW 代码可以这样测:本地从源码构建 Ceph → 起一个 vstart 集群 → 跑 "s3-tests" suite。以下步骤在 jewel 及以上应该可用。
|
||
|
||
**Step 1 - 构建 Ceph**:参见 `/install/build-ceph`。(Step 2 可以在构建期间并行做。)
|
||
|
||
**Step 2 - vstart**。构建完成后,仍在 git clone 的顶层目录里:
|
||
|
||
```bash
|
||
cd build/
|
||
RGW=1 ../src/vstart.sh -n
|
||
```
|
||
|
||
会输出很多内容,最后应看到类似:
|
||
|
||
```text
|
||
started. stop.sh to stop. see out/* (e.g. 'tail -f out/????') for debug output.
|
||
```
|
||
|
||
表示集群已跑起来。
|
||
|
||
**Step 3 - 跑 s3-tests**:
|
||
|
||
```bash
|
||
../qa/workunits/rgw/run-s3tests.sh
|
||
```
|
||
|
||
## 5.8 用 `vstart_runner.py` 跑测试(CephFS / Mgr)
|
||
|
||
CephFS 和 Ceph Manager 的代码用 **`vstart_runner.py`** 测试。
|
||
|
||
### 5.8.1 安装 teuthology(前置)
|
||
|
||
Ceph 仓库里的 Python 测试要在本机执行,需要装 `teuthology`:
|
||
|
||
```bash
|
||
git clone https://github.com/ceph/teuthology
|
||
cd teuthology
|
||
./bootstrap install
|
||
```
|
||
|
||
这会在 teuthology 仓库根目录创建一个名为 `virtualenv` 的虚拟环境并安装 teuthology。
|
||
|
||
也可以用 `pip` 装到自定义虚拟环境(同时用 git clone 一份 teuthology 仓库):
|
||
|
||
```bash
|
||
virtualenv --python=python3 venv
|
||
source venv/bin/activate
|
||
pip install 'setuptools >= 12'
|
||
pip install teuthology[test]@git+https://github.com/ceph/teuthology
|
||
deactivate
|
||
```
|
||
|
||
如果上面两种方式都不行(比如 bootstrap 脚本有 bug 或暂时下载不到),可以从本机已有的 teuthology 仓库副本手动安装:
|
||
|
||
```bash
|
||
cd teuthology
|
||
virtualenv -p python3 venv
|
||
source venv/bin/activate
|
||
pip install -r requirements.txt
|
||
pip install .
|
||
deactivate
|
||
```
|
||
|
||
### 5.8.2 跑你的第一个测试
|
||
|
||
先成功构建 Ceph,然后:
|
||
|
||
```bash
|
||
cd build
|
||
../src/vstart.sh -n -d -l
|
||
source ~/path/to/teuthology/venv/bin/activate
|
||
```
|
||
|
||
跑一个具体的测试(例如 `qa/tasks/cephfs/test_client_recovery` 里 `TestClientRecovery` 类的 `test_reconnect_timeout`):
|
||
|
||
```bash
|
||
python ../qa/tasks/vstart_runner.py tasks.cephfs.test_client_recovery.TestClientRecovery.test_reconnect_timeout
|
||
```
|
||
|
||
也可以按组跑:
|
||
|
||
```bash
|
||
$ # run all tests in class TestClientRecovery
|
||
$ python ../qa/tasks/vstart_runner.py tasks.cephfs.test_client_recovery.TestClientRecovery
|
||
$ # run all tests in test_client_recovery.py
|
||
$ python ../qa/tasks/vstart_runner.py tasks.cephfs.test_client_recovery
|
||
```
|
||
|
||
`vstart_runner.py` 根据传入的参数收集测试并像跑单个测试一样执行它们。
|
||
|
||
### 5.8.3 `vstart_runner.py` 的命令行选项
|
||
|
||
| 选项 | 说明 |
|
||
|---|---|
|
||
| `--clear-old-log` | 跑测试前删除旧日志文件 |
|
||
| `--create` | 跑测试前创建 Ceph 集群 |
|
||
| `--create-cluster-only` | 只创建集群然后退出,测试可以稍后再发 |
|
||
| `--interactive` | 测试失败时进入 Python shell |
|
||
| `--log-ps-output` | 记录 ps 输出,调试时有用 |
|
||
| `--teardown` | 测试跑完后拆掉 Ceph 集群 |
|
||
| `--kclient` | 用内核 cephfs 客户端而不是 FUSE |
|
||
| `--brxnet=<net/mask>` | 为 mount 客户端的网络命名空间容器指定新的 net/mask(默认 `192.168.0.0/16`) |
|
||
|
||
三条官方 note:
|
||
|
||
- 用 FUSE 客户端时,确保系统装了并启用了 fuse 包,且 `/etc/fuse.conf` 里加了 `user_allow_other`。
|
||
- 用内核客户端时,用户必须能**免密 sudo**。
|
||
- 内核客户端上的失败**可能导致宿主机崩溃**,建议在虚拟机里用这个功能。
|
||
|
||
### 5.8.4 `vstart_runner.py` 的内部工作机制
|
||
|
||
它主要做三件事:
|
||
|
||
1. **收集并运行测试**:setup/teardown 集群,收集并运行测试。由 `scan_tests()`、`load_tests()`、`exec_test()` 三个方法实现。所有命令行选项以及「记录日志」「把 traceback 复制到日志底部」等特性都在这里实现。
|
||
2. **提供下发与测试 shell 命令的接口**:测试是假设集群在远程机器上写的。`vstart_runner.py` 提供接口,让同样的测试能跑在本机的集群上。这靠 class `LocalRemote` 实现;`LocalRemoteProcess` 管理执行 `LocalRemote` 命令的进程,`LocalDaemon` 提供操作 Ceph 守护进程的接口,`LocalFuseMount` 创建并管理 FUSE mount。
|
||
3. **提供操作 Ceph 集群的接口**:`LocalCephManager` 提供带/不带 admin socket 执行 Ceph 集群命令的方法;`LocalCephCluster` 提供设置或清除 `ceph.conf` 的方法。
|
||
|
||
三条实现细节 note:
|
||
|
||
- `vstart_runner.py` **无条件**从命令参数里删除 `adjust-ulimits` 和 `ceph-coverage`,因为它们在开发者本机上不适用。
|
||
- 对于 `passwd` 和 `chown` 命令,`omit_sudo` 会被**无条件重置为 False**。
|
||
- 会检查 `<ceph-repo-root>/build/bin/` 下是否存在与第一个参数同名的二进制文件;如果存在,第一个参数会被替换为该二进制文件的路径。
|
||
|
||
## 5.9 用 `vstart_enviroment.sh` 跑 workunit
|
||
|
||
和 s3-tests 类似,其他 workunit 也可以配置好环境后直接对着 vstart 集群跑。
|
||
|
||
**设置环境:**
|
||
|
||
```bash
|
||
. ./build/vstart_enviroment.sh
|
||
```
|
||
|
||
> ⚠️ 注意官方文件名是 `vstart_enviroment.sh`(**enviroment**,不是 environment),这是官方文档里就有的拼写。
|
||
|
||
**跑一个 workunit(例如 `mon/osd.sh`):**
|
||
|
||
```bash
|
||
./qa/workunits/mon/osd.sh
|
||
```
|
||
|
||
> 引自:[Running Unit Tests](https://docs.ceph.com/en/tentacle/dev/developer_guide/running-tests-locally/)
|
||
|
||
## 5.10 三类本地测试对比
|
||
|
||
| 测试方式 | 适用代码 | 前置条件 | 命令 |
|
||
|---|---|---|---|
|
||
| `make check` / `ctest` | 通用 C++ 单元测试 | 编译完 Ceph;8GB RAM / 32GB 磁盘 | `./run-make-check.sh`(或手工 `ctest`) |
|
||
| `cram`(`.t` 文件) | CLI 工具 | `./src/test/cli` 下的 `.t` 文件 | `./src/test/run-cli-tests` |
|
||
| `tox` | Python 模块(cephadm / mgr / dashboard / python-common / cephfs / ceph-volume) | 装 `tox`、`coverage` | `tox -e py3,lint,check` 或 `../../../script/run_tox.sh --tox-env py3,lint,check` |
|
||
| s3-tests | RGW | 编译 + `RGW=1 ../src/vstart.sh -n` | `../qa/workunits/rgw/run-s3tests.sh` |
|
||
| `vstart_runner.py` | CephFS、Mgr Python 测试 | 装 teuthology + `../src/vstart.sh -n -d -l` | `python ../qa/tasks/vstart_runner.py tasks.cephfs.<module>.<Class>.<test>` |
|
||
| workunit | 通用 shell 测试 | `. ./build/vstart_enviroment.sh` | `./qa/workunits/<path>.sh` |
|
||
|
||
---
|
||
|
||
# 六、集成测试与 teuthology
|
||
|
||
`testing_integration_tests/` 目录下共 6 个文件:`index.rst`、`tests-integration-testing-teuthology-intro.rst`(约 27KB,本册第三大)、`-workflow.rst`(18KB)、`-debugging-tips.rst`、`-kernel.rst`、`tests-sentry-developers-guide.rst`。
|
||
|
||
## 6.1 什么是集成测试
|
||
|
||
Ceph 有两类测试:`make check` 测试和集成测试。**当一个测试需要多台机器、需要 root 权限、或者持续时间很长(例如要模拟真实的 Ceph 工作负载),它就被认定为集成测试。**
|
||
|
||
集成测试被组织成 **suites**,定义在 `ceph/qa` 子目录(https://github.com/ceph/ceph/tree/master/qa),用 **`teuthology-suite`** 命令运行。该命令是 teuthology 框架(https://github.com/ceph/teuthology)的一部分。
|
||
|
||
### 6.1.1 关键认知:Teuthology 消费的是**软件包**
|
||
|
||
官方特别强调这一点「需要点时间才能理解其意义,但它极其重要」:**这意味着自动化测试可以在多个平台上使用与任意运行这些平台的机器所能安装的相同软件包(RPM、DEB)来进行。**
|
||
|
||
teuthology 有一份支持的平台清单(https://github.com/ceph/ceph/tree/master/qa/distros/supported),**截至 2020 年 9 月,清单是「RHEL/CentOS 8」和「Ubuntu 18.04」**。它期望拿到这些平台的**预构建 Ceph 软件包**,然后按测试要求把这些平台部署到机器上(裸金属或云供给的),装包,部署 Ceph 集群。
|
||
|
||
### 6.1.2 The Nightlies
|
||
|
||
大量集成测试会**定期**在 Sepia lab(https://wiki.sepia.ceph.com/doku.php)里针对 Ceph 官方仓库(`master` 开发分支和 stable 分支)跑。传统上叫 "the nightlies"(夜间测试),因为 Ceph 核心开发者过去住在同一时区,从他们的视角看测试是在 overnight 跑的。
|
||
|
||
结果发布在 http://pulpito.ceph.com/ 的 `teuthology` 用户下。开发者 nick 出现在测试结果 URL 里以及 Pulpito dashboard 的第一列。结果也会报到 ceph-qa 邮件列表(https://ceph.com/irc/)。
|
||
|
||
## 6.2 测试优先级(Testing Priority):务必设置 `-p <N>` 且 N < 500
|
||
|
||
`teuthology-suite` 有 `-p <N>` 选项,指定提交到队列的 job 优先级。**`N` 越小,优先级越高。**
|
||
|
||
**`N` 的默认值是 1000。** 由 `teuthology@teuthology.front.sepia.ceph.com` 自动调度的大多数 nightly 测试都以 `N >= 500` 跑。一些关键的 nightly 测试会给更高优先级,比如 smoke 测试或即将发布的重大版本的 QA 跑。
|
||
|
||
> 官方 note:nightly 期间要跑的测试量经常大到**在 allotted 时间内跑不完全部 nightly 测试**。
|
||
|
||
**如果不把 `N` 设到 500 以下,你的测试优先级不会高于 nightly 测试——这意味着它们可能永远跑不起来。**
|
||
|
||
**优先级取值指南(官方表格):**
|
||
|
||
| 优先级 | 适用场景 |
|
||
|---|---|
|
||
| **N < 10** | 天塌下来了,某组测试必须立刻跑 |
|
||
| **10 <= N < 50** | 你的测试很紧急,正阻塞其他重要开发 |
|
||
| **50 <= N < 75** | 你在测某个 feature/fix,且跑的 job 少于约 25 个;也用于紧急的 release 测试 |
|
||
| **75 <= N < 100** | Tech Leads 常规用来针对 master 验证 PR 的优先级 |
|
||
| **100 <= N < 150** | 点发布(point release)的 QE 验证 |
|
||
| **150 <= N < 200** | 测某个 feature/fix,100 个 job 或更少;约 24 小时出结果 |
|
||
| **200 <= N < 500** | 大规模测试跑;约一周出结果 |
|
||
|
||
**两个配套选项:**
|
||
|
||
| 选项 | 用途 |
|
||
|---|---|
|
||
| `--dry-run` | 先看这条命令会触发多少 job。数量满意后,去掉 `--dry-run` 并加上 `-p <合适的数字>` 再跑一次 |
|
||
| `--force-priority` | 跳过优先级检查。官方要求:**体谅其他开发者也要跑测试,只在紧急情况下用** |
|
||
|
||
## 6.3 Suites Inventory(suite 清单)
|
||
|
||
`ceph/qa` 子目录的 `suites` 目录包含所有 Ceph 组件的集成测试。
|
||
|
||
| Suite | 功能 |
|
||
|---|---|
|
||
| `ceph-deploy` | 用 `ceph-deploy` 安装 Ceph 集群 |
|
||
| `dummy` | 拿一台机器,什么都不做,返回成功。常用来验证集成测试基础设施本身是否正常 |
|
||
| `fs` | 测试用 kernel 和 FUSE 客户端挂载的 CephFS,也测多 MDS |
|
||
| `krbd` | 测试 RBD 内核模块 |
|
||
| `powercycle` | 验证机器断电再上电时 Ceph 集群的行为 |
|
||
| `rados` | 在各种压力条件下运行含 OSD 和 MON 的 Ceph 集群 |
|
||
| `rbd` | 用真实 Ceph 集群跑 RBD 测试,带 qemu 和不带 qemu 都有 |
|
||
| `rgw` | 用真实 Ceph 集群跑 RGW 测试 |
|
||
| `smoke` | 用真实 Ceph 集群跑调用 Ceph API 的测试 |
|
||
| `teuthology` | 验证 teuthology 能跑集成测试,带 OpenStack 和不带都有 |
|
||
| `upgrade` | 对各个版本的 Ceph,验证升级能在不中断进行中工作负载的前提下完成 |
|
||
|
||
## 6.4 teuthology-describe
|
||
|
||
`teuthology-describe` 被加入 teuthology 框架,是为了**便于文档化集成测试并增进理解**。
|
||
|
||
做法是:在定义测试的 yaml 文件里嵌入 **`meta:` 注解**。查看效果:https://gist.github.com/jdurgin/09711d5923b583f60afc
|
||
|
||
由于这是个新特性,**很多 yaml 文件还没被注解过**。官方鼓励开发者去提高文档的覆盖率和质量。
|
||
|
||
## 6.5 怎么跑集成测试
|
||
|
||
- 通常用 **Sepia lab** 跑。但作为 Ceph 新开发者,你**多半没有 Sepia lab 访问权限**;不过你可能能在 Sepia lab 之外的环境里跑一部分集成测试。**去问相关团队的成员**怎么弄。
|
||
- 一种方式是在**裸金属上搭 teuthology 集群**,这是件复杂的活儿。入门笔记:https://docs.ceph.com/projects/teuthology/en/latest/LAB_SETUP.html
|
||
- 每个 teuthology 集群(无论裸金属还是云供给)都有一台所谓的 **teuthology machine**,从那台机器上用 `teuthology-suite` 命令触发测试 suite。
|
||
- 在该机器上跑下面的命令可拿到每个 `teuthology-suite` 选项的最新详尽说明:
|
||
|
||
```bash
|
||
teuthology-suite --help
|
||
```
|
||
|
||
`teuthology-suite` 在线文档:https://docs.ceph.com/projects/teuthology/en/latest/commands/teuthology-suite.html
|
||
|
||
**为什么要跑并把结果发出去**:在你贡献的代码上跑集成测试并发布结果,能让评审者验证改动没有引入回归,也能在测试失败时让评审者分析失败原因。
|
||
|
||
## 6.6 集成测试是怎么定义的
|
||
|
||
集成测试由 `ceph/qa` 子目录下 **`suites` 子目录里的 yaml 文件**定义,由 **`tasks` 子目录里的 python 代码**实现。
|
||
|
||
- 有些测试("standalone tests")定义在**单个 yaml 文件**里;
|
||
- 其他测试由**一棵含若干 yaml 文件的目录树**定义,运行时这些 yaml 会被组合成一个更大的 yaml 文件。
|
||
|
||
## 6.7 读一个 standalone test(单例)
|
||
|
||
以 `rados/singleton/all/admin-socket.yaml` 为例:
|
||
|
||
```yaml
|
||
roles:
|
||
- - mon.a
|
||
- osd.0
|
||
- osd.1
|
||
tasks:
|
||
- install:
|
||
- ceph:
|
||
- admin_socket:
|
||
osd.0:
|
||
version:
|
||
git_version:
|
||
help:
|
||
config show:
|
||
config set filestore_dump_file /tmp/foo:
|
||
perf dump:
|
||
perf schema:
|
||
```
|
||
|
||
**`roles` 数组**:决定这个测试运行在什么样的集群构成上(几个 MON、几个 OSD 等),以及这些 role 如何分布到测试集群的机器上。
|
||
|
||
- 顶层数组只有一个元素 → 只给这个测试分配**一台机器**;
|
||
- 嵌套数组声明这台机器要跑一个 id 为 `a` 的 MON(`mon.a`)和两个 OSD(`osd.0`、`osd.1`)。
|
||
|
||
**`tasks` 数组**是测试主体:每个元素**按顺序求值**,导致 `tasks` 子目录(teuthology 仓库的,或 `ceph/qa` 子目录的)里对应的 python 文件被运行。"运行" 在这里指的是**调用该文件里定义的 `task()` 函数**。
|
||
|
||
| 本例的 task | 作用 |
|
||
|---|---|
|
||
| `install` | 在每台机器上安装 Ceph 软件包(按 `roles` 数组定义)。完整描述见 https://github.com/ceph/teuthology/blob/master/teuthology/task/install/__init__.py(搜 "def task") |
|
||
| `ceph` | 按 `roles` 数组要求启动 OSD 和 MON(可能还有 MDS),源码见 https://github.com/ceph/ceph/blob/master/qa/tasks/ceph.py(搜 "def task")。本例会启动一个 MON(`mon.a`)和两个 OSD(`osd.0`、`osd.1`),都在同一台机器上。**当 Ceph 集群达到 `HEALTH_OK` 状态时,控制流才转到下一个 task** |
|
||
| `admin_socket` | 源码 https://github.com/ceph/ceph/blob/master/qa/tasks/admin_socket.py。它的参数(任何 task 的参数都一样)是一个结构,解释方式由该 task 文档化。本例的参数是发给 `osd.0` 的 admin socket 的一组命令;task 会验证**每条命令都返回成功(exit code 为零)** |
|
||
|
||
**运行这个测试:**
|
||
|
||
```bash
|
||
teuthology-suite --machine-type smithi --suite rados/singleton/all/admin-socket.yaml fs/ext4.yaml
|
||
```
|
||
|
||
## 6.8 Test descriptions(测试描述)
|
||
|
||
每个测试有一个 "test description",**类似目录路径,但不完全是**。
|
||
|
||
- **standalone test** 的描述:等同于定义它的 yaml 文件**从 `suites/` 目录开始的相对路径**。
|
||
- **更常见的情况**:测试不是由单个 yaml 文件定义,而是由**一棵 yaml 文件目录树**定义。运行时遍历这棵树,所有 yaml 文件(**facets**)被组合成更大的 yaml "programs" 来定义测试。**每个测试日志开头都会包含定义该测试的完整 yaml 清单。**
|
||
|
||
这类测试的描述 = `suites/` 下包含 yaml facets 的子目录 + 一个**花括号 `{}` 表达式**,里面是按拼接顺序排列的 yaml facet 列表。例如:
|
||
|
||
```text
|
||
ceph-deploy/basic/{distros/centos_7.0.yaml tasks/ceph-deploy.yaml}
|
||
```
|
||
|
||
表示两个文件按顺序拼接:
|
||
|
||
1. `ceph-deploy/basic/distros/centos_7.0.yaml`
|
||
2. `ceph-deploy/basic/tasks/ceph-deploy.yaml`
|
||
|
||
## 6.9 测试是怎么从目录构建出来的:三个特殊文件操作符
|
||
|
||
一组给定 `suites/` 子目录所定义的所有测试,称为一个 "integration test suite",或 "teuthology suite"。
|
||
|
||
**yaml facets 的组合由放在目录树里的特殊文件控制,可以看作操作符:**
|
||
|
||
| 特殊文件 | 名字 | 含义 |
|
||
|---|---|---|
|
||
| `%` | **convolution(卷积)** 操作符 | 从该目录下各子目录里的 yaml facets 构造测试矩阵 |
|
||
| `+` | **concatenation(拼接)** | 把一个目录内的文件拼接在一起 |
|
||
| `$` | **random selection(随机选择)** | 提示 teuthology 随机包含一个 YAML fragment |
|
||
|
||
### 6.9.1 卷积操作符 `%`
|
||
|
||
`%` 是一个(通常为空的)文件,告诉 teuthology 从**包含该操作符的目录下面的各子目录**里的 YAML facets 构造测试矩阵。
|
||
|
||
以 `ceph-deploy` suite 为例,目录结构:
|
||
|
||
```text
|
||
qa/suites/ceph-deploy
|
||
├── %
|
||
├── distros
|
||
│ ├── centos_latest.yaml
|
||
│ └── ubuntu_latest.yaml
|
||
└── tasks
|
||
├── ceph-admin-commands.yaml
|
||
└── rbd_import_export.yaml
|
||
```
|
||
|
||
被解释为一个 2x1 矩阵,产生两个测试:
|
||
|
||
1. `ceph-deploy/basic/{distros/centos_7.0.yaml tasks/ceph-deploy.yaml}`
|
||
2. `ceph-deploy/basic/{distros/ubuntu_16.04.yaml tasks/ceph-deploy.yaml}`
|
||
|
||
人话:**`ceph-deploy.yaml` 里的 task 打算在 CentOS 7.0 和 Ubuntu 16.04 上都跑一遍。**
|
||
|
||
**如果没有 `%` 文件**,`ceph-deploy` 树会被解释成三个 standalone 测试:
|
||
|
||
- `ceph-deploy/basic/distros/centos_7.0.yaml`
|
||
- `ceph-deploy/basic/distros/ubuntu_16.04.yaml`
|
||
- `ceph-deploy/basic/tasks/ceph-deploy.yaml`
|
||
|
||
(这当然是错的。)
|
||
|
||
> **symlink 技巧**:`suites/ceph-deploy/basic/distros/` 里的 `centos_7.0.yaml` 和 `ubuntu_16.04.yaml` 是**符号链接**。用 symlink 而不是复制,可以让一个文件出现在多个 suite 里,降低整个测试框架的维护成本。
|
||
|
||
跑整个 ceph-deploy suite:
|
||
|
||
```bash
|
||
teuthology-suite --machine-type smithi --suite ceph-deploy
|
||
```
|
||
|
||
跑其中一个测试(加 `--filter`):
|
||
|
||
```bash
|
||
teuthology-suite \
|
||
--machine-type smithi \
|
||
--suite ceph-deploy/basic \
|
||
--filter 'ceph-deploy/basic/{distros/ubuntu_16.04.yaml tasks/ceph-deploy.yaml}'
|
||
```
|
||
|
||
> 官方 note:跑 standalone test **只用 `--suite` 就够了**。但如果想跑一个「由目录树定义的 suite」里的单个测试,**`--suite` 必须配合 `--filter`**,因为 `--suite` 选项只认 POSIX 相对路径。
|
||
|
||
### 6.9.2 Nested Subsets(嵌套子集)
|
||
|
||
suite 会因为 yaml 配置的组合爆炸而变得非常大:**写作时 `rados` suite 有超过 100,000 个 job**。因此调度时常用 `--subset` 只跑一部分 job。但这**只在被跑 suite 的顶层生效**(例如 `fs`),这可能**顺带放大了**某些较大子 suite(如 `fs:workload`)相对某些较小但关键的子 suite(如 `fs:volumes`)的 job 占比。
|
||
|
||
因此,对那些**永远不会被完整跑**的子 suite 自动做子集化是很有吸引力的。做法:**给 `%` 卷积操作符文件里写一个整数除数**,而不是留空。该除数会自动对生成的矩阵做子集化。例如 `%` 文件里内容是 `2`,矩阵就会用与 `--subset` 相同的逻辑被一分为二。
|
||
|
||
注意:**分子不像 `--subset` 选项那样指定**,因为在可能有多层嵌套的情况下没法有意义地表达。取而代之的是**随机选一个子集**(上例就是 2 选 1)。选择基于调度时用的随机种子(**`--seed`**)。**记住种子会被保存在结果里**,这样对失败测试做 `--rerun` 时仍能保持正确的分子(subset of subsets)。
|
||
|
||
可以用 **`--no-nested-subset`** 参数禁用嵌套子集。
|
||
|
||
### 6.9.3 拼接操作符 `+`
|
||
|
||
为了在 suite 之间共享 yaml 文件时获得更大的灵活性,可以在一个目录里放特殊文件 `+`,把该目录内的文件拼接起来。
|
||
|
||
以 `suites/rbd/thrash` 为例:
|
||
|
||
```text
|
||
qa/suites/rbd/thrash
|
||
├── %
|
||
├── clusters
|
||
│ ├── +
|
||
│ ├── fixed-2.yaml
|
||
│ └── openstack.yaml
|
||
└── workloads
|
||
├── rbd_api_tests_copy_on_read.yaml
|
||
├── rbd_api_tests.yaml
|
||
└── rbd_fsx_rate_limit.yaml
|
||
```
|
||
|
||
这产生两个测试:
|
||
|
||
- `rbd/thrash/{clusters/fixed-2.yaml clusters/openstack.yaml workloads/rbd_api_tests_copy_on_read.yaml}`
|
||
- `rbd/thrash/{clusters/fixed-2.yaml clusters/openstack.yaml workloads/rbd_api_tests.yaml}`
|
||
|
||
因为 `clusters/` 子目录里有特殊文件 `+`,该目录里其他所有文件(`fixed-2.yaml` 和 `openstack.yaml`)被**拼接成一个文件**对待。
|
||
|
||
**如果没有 `+`**,它们会和 workloads 目录里的文件做卷积,产生 2x2 矩阵四个测试:
|
||
|
||
- `rbd/thrash/{clusters/openstack.yaml workloads/rbd_api_tests_copy_on_read.yaml}`
|
||
- `rbd/thrash/{clusters/openstack.yaml workloads/rbd_api_tests.yaml}`
|
||
- `rbd/thrash/{clusters/fixed-2.yaml workloads/rbd_api_tests_copy_on_read.yaml}`
|
||
- `rbd/thrash/{clusters/fixed-2.yaml workloads/rbd_api_tests.yaml}`
|
||
|
||
`clusters/fixed-2.yaml` 被很多 suite 共享,用来定义如下 `roles`:
|
||
|
||
```yaml
|
||
roles:
|
||
- [mon.a, mon.c, osd.0, osd.1, osd.2, client.0]
|
||
- [mon.b, osd.3, osd.4, osd.5, client.1]
|
||
```
|
||
|
||
跑整个 `rbd/thrash` suite:
|
||
|
||
```bash
|
||
teuthology-suite --machine-type smithi --suite rbd/thrash
|
||
```
|
||
|
||
跑其中一个测试:
|
||
|
||
```bash
|
||
teuthology-suite \
|
||
--machine-type smithi \
|
||
--suite rbd/thrash \
|
||
--filter 'rbd/thrash/{clusters/fixed-2.yaml clusters/openstack.yaml workloads/rbd_api_tests_copy_on_read.yaml}'
|
||
```
|
||
|
||
### 6.9.4 Upgrade Testing(升级测试)
|
||
|
||
用 upgrade suite 可以验证:**从较早版本升级能够成功完成,且不中断任何进行中的工作负载**。
|
||
|
||
**每个 release 分支的升级目录包含 2-x 升级测试**,也就是说能测**从当前版本往前 2 个版本**升级到当前版本。升级过程与其他给定工作负载**并行**进行(用 https://github.com/ceph/teuthology/blob/main/teuthology/task/parallel.py)。
|
||
|
||
以 Quincy release 分支的升级测试目录为例:
|
||
|
||
```text
|
||
.
|
||
├── octopus-x
|
||
└── pacific-x
|
||
```
|
||
|
||
可以测从 Octopus(2-x)或从 Pacific(1-x)升到 Quincy(x)。
|
||
|
||
一个简单的升级测试由以下部分组成:
|
||
|
||
```text
|
||
├── 0-start.yaml
|
||
├── 1-tasks.yaml
|
||
├── upgrade-sequence.yaml
|
||
└── workload
|
||
```
|
||
|
||
先用旧版本启动集群,然后开始**并行**跑给定的 `workload` 和 `upgrade-sequence`:
|
||
|
||
```yaml
|
||
- print: "**** done start parallel"
|
||
- parallel:
|
||
- workload
|
||
- upgrade-sequence
|
||
- print: "**** done end parallel"
|
||
```
|
||
|
||
`workload` 目录就是普通的 yaml 文件(和其他 suite 一样),而 `upgrade-sequence` 负责执行升级并等待其完成:
|
||
|
||
```yaml
|
||
- print: "**** done start upgrade, wait"
|
||
...
|
||
mon.a:
|
||
- ceph orch upgrade start --image quay.ceph.io/ceph-ci/ceph:$sha1
|
||
- while ceph orch upgrade status | jq '.in_progress' | grep true ; do ceph orch ps ; ceph versions ; sleep 30 ; done
|
||
...
|
||
- print: "**** done end upgrade, wait..."
|
||
```
|
||
|
||
**也可以分阶段升级,中间穿插跑工作负载:**
|
||
|
||
```text
|
||
├── %
|
||
├── 0-cluster
|
||
├── 1-ceph-install
|
||
├── 2-partial-upgrade
|
||
├── 3-thrash
|
||
├── 4-workload
|
||
├── 5-finish-upgrade.yaml
|
||
├── 6-quincy.yaml
|
||
└── 8-final-workload
|
||
```
|
||
|
||
流程:启动集群 → 只升级集群的 2/3(`2-partial-upgrade`)→ 跑 thrash 测试和给定工作负载测试 → 继续升级剩下的部分(`5-finish-upgrade.yaml`)→ 最后要求升级后的 release(`ceph require-osd-release quincy`、`ceph osd set-require-min-compat-client quincy`)并跑 `final-workload`。
|
||
|
||
### 6.9.5 随机选择操作符 `$`
|
||
|
||
存在名为 **`$`** 的文件,会提示 teuthology **随机包含其中一个 YAML fragment**。这种场景典型地出现在:需要在若干 flavor 的 feature/option 里**随机挑一个**来测的时候。
|
||
|
||
## 6.10 Position Independent Linking(位置无关链接)
|
||
|
||
`qa/suites` 目录下**每个目录里都有 `.qa` 符号链接**,每个链接都**递归地指向 `../.qa/`**,最终终止链接在 `qa/` 目录本身:`qa/.qa -> .`。
|
||
|
||
这种符号链接布局让一个 suite **可以轻松被复制或移动而不破坏大量符号链接**。例如:
|
||
|
||
```text
|
||
qa/suites/fs/upgrade/nofs/centos_latest.yaml -> .qa/distros/supported/centos_latest.yaml
|
||
```
|
||
|
||
如果你把 `nofs` suite 复制到别处、在其上加一层父目录、或者把 `centos_latest.yaml` fragment 挪进一个子目录,**这个链接都不会断**。
|
||
|
||
对比传统写法:
|
||
|
||
```text
|
||
qa/suites/fs/upgrade/nofs/centos_latest.yaml -> ../../../../distros/supported/centos_latest.yaml
|
||
```
|
||
|
||
一旦移动,很可能会断,因为到达 `distros` 目录所需的父目录层数可能变化。
|
||
|
||
**官方建议:新增目录或 suite 时,记得一并加上 `.qa` 符号链接。** 一条 find 命令就能搞定:
|
||
|
||
```bash
|
||
find qa/suites/ -type d -execdir ln -sfT ../.qa/ {}/.qa \;
|
||
```
|
||
|
||
## 6.11 按描述过滤测试
|
||
|
||
当少数 job 失败需要重跑时,可以用 `--filter` 选出描述匹配的测试。例如 `rados` suite 里的 `all/peer.yaml` 失败了,下面这条只跑包含这个文件的测试:
|
||
|
||
```bash
|
||
teuthology-suite --machine-type smithi --suite rados --filter all/peer.yaml
|
||
```
|
||
|
||
| 选项 | 作用 |
|
||
|---|---|
|
||
| `--filter` | 只跑描述**包含**给定字符串的测试 |
|
||
| `--filter-out` | 反过来,匹配**不包含**给定字符串的测试;可以与 `--filter` 组合使用 |
|
||
|
||
两者都接受**逗号分隔的字符串列表**(这意味着 `ceph/qa` 子目录里的文件名**隐式禁止出现逗号字符**)。例如:
|
||
|
||
```bash
|
||
teuthology-suite --machine-type smithi --suite rados --filter all/peer.yaml,all/rest-api.yaml
|
||
```
|
||
|
||
会跑包含 `all/peer.yaml` **或** `all/rest-api.yaml` 的测试。
|
||
|
||
> **重要**:每个字符串是在 test description 的**任意位置**查找,且**必须是精确匹配——它们不是正则表达式**。
|
||
|
||
## 6.12 减少测试数量
|
||
|
||
`rados` suite 从几百个文件里生成数万甚至数十万个测试,这是因为 teuthology 在**每遇到一个名为 `%` 的文件**时就从子目录构造测试矩阵。例如 `rados/basic` suite 里所有测试都会用不同的 messenger 类型(`simple`、`async`、`random`)跑,因为它们通过特殊文件 `%` 与 `msgr` 目录组合。
|
||
|
||
**所有集成测试在 Ceph 发布之前都必须跑一遍。** 但如果只是要验证一个贡献能否合入而不冒引入低级回归的风险,**跑一个子集就够了**。
|
||
|
||
| 选项 | 作用 | 备注 |
|
||
|---|---|---|
|
||
| `--subset` | 减少被触发的测试数量。例如 `--subset 0/4000` 会跑尽可能少的测试 | 权衡是**并非所有测试变体的组合都会被一起跑到**;但**无论比例给多小,teuthology 仍会保证 suite 里的每个文件至少出现在一个测试中**。理解背后真正的逻辑需要读 teuthology 源码 |
|
||
| `--no-nested-subset` | 禁用嵌套子集(某些 suite 现在用了这个自动对一组精心挑选的 YAML 配置做子集化的特性) | 想做自定义过滤时可用 |
|
||
| `--limit` | 只跑 suite 里**前 N 个**测试 | **很少有用**,因为没法控制哪个测试排在第一个 |
|
||
|
||
> 引自:[Testing - Integration Tests - Introduction](https://docs.ceph.com/en/tentacle/dev/developer_guide/testing_integration_tests/tests-integration-testing-teuthology-intro/)
|
||
|
||
## 6.13 Teuthology 工作流:基础设施组件
|
||
|
||
一次 teuthology **run** 包含多个 **job**,每个 job 在相互隔离的环境里、在不同的 test node 集合上运行。
|
||
|
||
| # | 组件 | 说明 |
|
||
|---|---|---|
|
||
| 1 | `ceph-ci` | Ceph 主仓库的 clone,用于触发 Jenkins 开发构建 |
|
||
| 2 | Ceph Jenkins | 负责触发构建、把包上传到 Chacra、把构建更新推送到 Shaman |
|
||
| 3 | `Shaman` | UI 界面,用来查看构建状态。后端是一个查询和存储构建信息的 REST API |
|
||
| 4 | `Chacra` | 包上传目的地服务。上传到这里二进制任何人都能下载使用 |
|
||
| 5 | Teuthology CLI | 开发者用各种 Teuthology 命令调度和管理测试运行 |
|
||
| 6 | Teuthology | 负责把测试 job 推到 Beanstalk 队列和 Paddles;也负责从队列取 job 并跑测试 |
|
||
| 7 | Beanstalk queue | 包含所有排队 job 的优先队列。开发者通常不需要直接交互 |
|
||
| 8 | Paddles | 存储所有测试运行信息的后端服务。开发者通常不需要直接交互 |
|
||
| 9 | `Pulpito` | UI 界面(数据来自 Paddles),让开发者查看已调度测试的详细信息,包括状态和结果 |
|
||
| 10 | Testnodes | 用于跑测试的各种机器组成的集群。开发者通常把测试调度到 **`smithi`** 机器上——这是 Teuthology 集成测试的专用测试节点 |
|
||
|
||
**工作流总览(文字版)**:
|
||
|
||
1. 把带改动的分支推到 `ceph-ci` 仓库 → 自动触发 Jenkins 构建 Ceph 二进制,构建状态在 `Shaman` 上观察;
|
||
2. 构建好的包上传到 `Chacra`;
|
||
3. 要针对这个新构建调度 teuthology 集成测试,需要 **Sepia lab 访问权限**;
|
||
4. 登录后做一次性的初始 teuthology 设置,之后用 `teuthology-suite` 调度,用 `-c <ceph-ci branch name>` 指定你的构建;
|
||
5. 结果在 `Pulpito` 上看;
|
||
6. 登录 **developer playground machine** 查看 teuthology run 的归档日志。
|
||
|
||
## 6.14 拿到二进制:构建 Ceph
|
||
|
||
1. **把分支推到 `ceph-ci` 仓库**,触发 Jenkins CI 上的二进制构建流程。
|
||
2. **确认构建流程已启动**:分支名出现在 `Shaman` 的 "Latest Builds Available" 列表里。不久之后,测试基础设施会往列表里加其他名字相近的构建——它们的名字里会包含各种 Linux 发行版的名字,用于在这些发行版上测你的构建。
|
||
3. **等待包构建完成并上传到 `Chacra`、等待提供这些包的 repository 创建完成**。`Shaman` 上 "Latest Builds Available" 列表里对应分支名的条目会**变绿**,表示包已上传到 Chacra 且 repository 已创建。**等到每个条目都变绿**。通常**需要 2 到 3 小时**,取决于机器可用性。
|
||
|
||
Chacra URL 可以从 https://shaman.ceph.com/api/search/?status=ready&project=ceph 查询。
|
||
|
||
两条 note:
|
||
|
||
- 推到 ceph-ci 的分支**可以是任意分支**,不必是 PR 分支。
|
||
- 如果你打算推 master 或其他标准分支,**先查一下 Shaman**,因为可能已经有构建好的了。
|
||
|
||
### 6.14.1 推到 ceph-ci 仓库
|
||
|
||
**1. 把 ceph-ci 仓库加为本地 Ceph clone 的 remote:**
|
||
|
||
```bash
|
||
git remote add ceph-ci git@github.com:ceph/ceph-ci.git
|
||
|
||
$ git remote -v
|
||
origin git@github.com:ceph/ceph.git (fetch)
|
||
origin git@github.com:ceph/ceph.git (push)
|
||
ceph-ci git@github.com:ceph/ceph-ci.git (fetch)
|
||
ceph-ci git@github.com:ceph/ceph-ci.git (push)
|
||
```
|
||
|
||
**2. 推你的分支:**
|
||
|
||
```bash
|
||
$ git push ceph-ci wip-yourname-feature-x
|
||
```
|
||
|
||
### 6.14.2 ceph-ci 分支命名规则
|
||
|
||
**推到 ceph-ci 前,给分支名加上你自己的名字前缀。** 例如分支 `feature-x` 应该命名为 `wip-$yourname-feature-x`。这样你的分支在 Shaman 和 Pulpito 上容易被找到。
|
||
|
||
**如果你用的是某个 stable 分支(quincy、pacific 等),把该 stable 分支名也放进 ceph-ci 分支名里。** 例如 `feature-x` PR 分支应命名为 `wip-feature-x-nautilus`。
|
||
|
||
> 官方强调:**这不只是约定。这确保你的分支在正确的环境里被构建。**
|
||
|
||
不用了就把分支从 ceph-ci 删掉。登录 GitHub 后,你在 ceph-ci 上的所有分支可以在这里看到:https://github.com/ceph/ceph-ci/branches
|
||
|
||
> note:你可以只触发 CentOS 9.Stream 构建(排除 ubuntu 等其他发行版),方法是在 ceph-ci 分支名末尾加 `"centos9-only"`,例如 `wip-$yourname-feature-centos9-only`。这能在你不需要其他发行版二进制时**更快拿到构建并节省资源**。
|
||
|
||
## 6.15 调度测试运行
|
||
|
||
### 6.15.1 触发 teuthology 测试
|
||
|
||
**1. 登录 teuthology 机器:**
|
||
|
||
```bash
|
||
ssh <username>@teuthology.front.sepia.ceph.com
|
||
```
|
||
|
||
(需要 Sepia lab 访问权限。申请:https://ceph.github.io/sepia/adding_users/)
|
||
|
||
**2. 初次设置**:按 teuthology 安装指南(https://docs.ceph.com/projects/teuthology/en/latest/INSTALL.html#installation-and-setup)在你的用户下配置 teuthology,之后你才能跑 teuthology 命令。
|
||
|
||
**3. 跑 `teuthology-suite` 命令:**
|
||
|
||
```bash
|
||
teuthology-suite -v \
|
||
-m smithi \
|
||
-c wip-devname-feature-x \
|
||
-s fs \
|
||
-p 110 \
|
||
--filter "cephfs-shell" \
|
||
-e foo@gmail.com
|
||
```
|
||
|
||
| 选项 | 含义 |
|
||
|---|---|
|
||
| `-v` | verbose |
|
||
| `-m` | machine name(机器名) |
|
||
| `-c` | 推到 ceph-ci 上的分支名 |
|
||
| `-s` | test-suite 名字 |
|
||
| `-p` | **数字越大,job 优先级越低** |
|
||
| `--filter` | 过滤给定 suite 里的测试,参数指定你想跑哪些测试 |
|
||
| `-e <email>` | 测试完成或超时时发邮件到指定地址。也可以在 `~/.teuthology.yaml` 里用 `results_email` 指定 |
|
||
|
||
两条 note:
|
||
|
||
- 上面命令里的优先级数字**只是占位符,不要在你自己的测试里用**。见「Testing Priority」的推荐取值。
|
||
- **不要不带优先级数字就发命令**。默认值是 1000,大到你的 job 很可能永远不跑。
|
||
|
||
跑 `teuthology-suite --help` 读这些和其他可用选项的说明。
|
||
|
||
**4. 等测试跑。** `teuthology-suite` 会打印一个 `Pulpito` 链接,可以看结果。
|
||
|
||
### 6.15.2 其他常用选项
|
||
|
||
| 选项 | 用途 |
|
||
|---|---|
|
||
| `--dry-run` | 试跑,不真的调度。用来检查一条命令会调度多少 job、哪些 job |
|
||
| `-d` / `--distro` | 发行版 |
|
||
| `--distro-version` | 发行版版本 |
|
||
| `--filter-out` | 反向过滤 |
|
||
| `--timeout` | 超时 |
|
||
| `flavor` | flavor |
|
||
| `--rerun` | 重跑 |
|
||
| `--limit` | 限制 job 数量 |
|
||
| `-N` | job 跑多少次 |
|
||
| `--subset` | 减少被触发的测试数量 |
|
||
|
||
### 6.15.3 只改了 `qa/` 目录:不用重新构建二进制
|
||
|
||
**如果你只在 `qa/` 目录里做改动,重跑测试前不必重新构建二进制**,可以用为 ceph-ci 分支构建的二进制直接重跑,只需告诉 `teuthology-suite` 用一个单独的分支来跑测试。
|
||
|
||
具体做法:给 `teuthology-suite` 传两个额外参数——
|
||
|
||
1. `--suite-repo`:指定你的 ceph 仓库;
|
||
2. `--suite-branch`:指定你的分支名。
|
||
|
||
例如,你要在测完 `branch-x`(对应 ceph-ci 分支 `wip-username-branch-x`)之后改 `qa/`:
|
||
|
||
```bash
|
||
teuthology-suite -v \
|
||
-m smithi \
|
||
-c wip-username-branch-x \
|
||
-s fs \
|
||
-p 50 \
|
||
--filter cephfs-shell
|
||
```
|
||
|
||
然后本地做修改、更新 PR 分支,再从你的 PR 分支触发测试:
|
||
|
||
```bash
|
||
teuthology-suite -v \
|
||
-m smithi \
|
||
-c wip-username-branch-x \
|
||
-s fs -p 50 \
|
||
--filter cephfs-shell \
|
||
--suite-repo https://github.com/$username/ceph \
|
||
--suite-branch branch-x
|
||
```
|
||
|
||
**如何确认测试确实用了这个分支跑**:看 teuthology job 开头打印的 job config 里的 `suite_branch`、`suite_repo` 和 `suite_sha1` 三个键的值。
|
||
|
||
> note:如果改动**不在 `qa/` 目录里**,就必须走标准流程:触发构建 → 等构建完成 → 触发测试 → 等结果。
|
||
|
||
### 6.15.4 过滤测试:关键词从哪找
|
||
|
||
Suite 里包含大量 yaml 文件的组合,可能调度出海量的 job,因此需要过滤。
|
||
|
||
**过滤关键词的位置**:`qa/suites/<suite-name>/<subsuite-name>/tasks`。该目录里**每个 YAML 文件都能触发测试**;把**文件名去掉扩展名**作为 `--filter` 的参数,就能触发那些测试。
|
||
|
||
例如上面用的 `cephfs-shell`,是因为 `qa/suites/fs/basic_functional/tasks/` 下有一个 `cephfs-shell.yaml`。
|
||
|
||
**如果文件名看不出它触发什么测试**,就去搜文件内容里的 **`modules` 属性**。`cephfs-shell.yaml` 的 `modules` 属性是 `tasks.cephfs.test_cephfs_shell`,意味着它触发 `qa/tasks/cephfs/test_cephfs_shell.py` 里的所有测试。
|
||
|
||
## 6.16 查看测试结果
|
||
|
||
### 6.16.1 Pulpito Dashboard
|
||
|
||
https://pulpito.ceph.com/
|
||
|
||
### 6.16.2 Teuthology Archives(查日志)
|
||
|
||
测试跑完后,在 Pulpito 的 run 页面点 **job ID** 可以拿到日志。**官方建议下载日志再看,而不是在浏览器里看**,因为这些日志**很容易达到 1GB**。
|
||
|
||
也可以 ssh 到 developer playground machine(https://wiki.sepia.ceph.com/doku.php?id=devplayground)访问这个路径:
|
||
|
||
```text
|
||
/teuthology/<run-name>/<job-id>/teuthology.log
|
||
```
|
||
|
||
例如:
|
||
|
||
```text
|
||
/teuthology/teuthology-2019-12-10_05:00:03-smoke-master-testing-basic-smithi/4588482/teuthology.log
|
||
```
|
||
|
||
这比通过浏览器看快。
|
||
|
||
**看 ceph 日志**(cephadm、ceph monitors、ceph-mgr 等)或**系统日志**:在浏览器里把 job 的 teuthology log url 末尾的 `teuthology.log` 去掉,然后进入 `remote/<machine>/log/`;系统日志在 `remote/<machine>/syslog/`。在 developer playground 机器上对应路径是 `/teuthology/<test-id>/<job-id>/remote/<machine>/`。
|
||
|
||
**其他为调试提供的文件:**
|
||
|
||
| 文件 | 内容 |
|
||
|---|---|
|
||
| `unit_test_summary.yaml` | 所有单元测试失败的摘要。**可选生成**——只有当 job 的 YAML 文件里用了 `unit_test_scan` 配置项时才会生成 |
|
||
| `valgrind.yaml` | 汇总可能发生的 Valgrind 错误 |
|
||
|
||
> 便捷访问:`/a/` 已经符号链接到 `/teuthology/`。例如上面的例子可以写成:
|
||
> `/a/teuthology-2019-12-10_05:00:03-smoke-master-testing-basic-smithi/4588482/teuthology.log`
|
||
|
||
## 6.17 杀掉测试
|
||
|
||
`teuthology-kill` 用来杀掉那些**意外跑了几个小时**的 job,或者开发者想提前终止测试时。
|
||
|
||
```bash
|
||
teuthology-kill -p -r teuthology-2019-12-10_05:00:03-smoke-master-testing-basic-smithi -m smithi -o scheduled_teuthology@teuthology
|
||
```
|
||
|
||
传给 `-r` 的是 **run name**,可以在你触发的测试的 Pulpito 页面链接里轻松找到。例如:
|
||
http://pulpito.front.sepia.ceph.com/teuthology-2019-12-10_05:00:03-smoke-master-testing-basic-smithi/
|
||
|
||
## 6.18 重跑测试
|
||
|
||
`teuthology-suite` 的 `-r`(`--rerun`)选项允许重跑测试,在测试失败或变成 dead 时很方便。**`--rerun` 接受一个 teuthology run 的名字作为参数。** 可以配合 `-R`(`--rerun-statuses`)**选择从这次 run 里挑出哪些状态的测试**重跑。例如只重跑上次变成 dead 的测试:
|
||
|
||
```bash
|
||
teuthology-suite -v \
|
||
-m smithi \
|
||
-c wip-rishabh-fs-test_cephfs_shell-fix \
|
||
-p 50 \
|
||
--r teuthology-2019-12-10_05:00:03-smoke-master-testing-basic-smithi \
|
||
-R fail,dead,queued \
|
||
-e $CEPH_QA_MAIL
|
||
```
|
||
|
||
| 选项 | 含义 |
|
||
|---|---|
|
||
| `-r`, `--rerun` | 尝试重新调度一次 run,只挑选状态被 `--rerun-status` 提及的那些 job |
|
||
| `-R`, `--rerun-statuses` | 与 `--rerun` 配合使用的逗号分隔状态列表。支持的状态:`dead`、`fail`、`pass`、`queued`、`running`、`waiting`。**默认值:`fail,dead`** |
|
||
|
||
> 引自:[Integration Tests using Teuthology Workflow](https://docs.ceph.com/en/tentacle/dev/developer_guide/testing_integration_tests/tests-integration-testing-teuthology-workflow/)
|
||
|
||
## 6.19 分析与调试一个 Teuthology Job
|
||
|
||
### 6.19.1 查看结果
|
||
|
||
run 成功完成后,用 pulpito dashboard 看结果(https://pulpito.ceph.com):
|
||
|
||
```text
|
||
http://pulpito.front.sepia.ceph.com/<job-name>/<job-id>/
|
||
```
|
||
|
||
或 ssh 进 teuthology server:
|
||
|
||
```bash
|
||
ssh <username>@teuthology.front.sepia.ceph.com
|
||
```
|
||
|
||
然后访问 teuthology archives,例如:
|
||
|
||
```bash
|
||
nano /a/teuthology-2021-01-06_07:01:02-rados-master-distro-basic-smithi/
|
||
```
|
||
|
||
(需要 Sepia lab 访问权限,申请见 https://ceph.github.io/sepia/adding_users/)
|
||
|
||
### 6.19.2 识别失败的 job
|
||
|
||
**在 pulpito 上,红色表示一个 failed job 或一个 dead job。**
|
||
|
||
一个 job 是 `qa/suites` 里 yaml fragments 定义的一组守护进程与配置的组合。Teuthology 使用这些配置并运行 `qa/tasks` 里列出的 tasks——这些 task 是搭建测试环境并测试 Ceph 各组件的命令。这些 task 覆盖了很大一部分用例,有助于暴露 `make check` 测试暴露不出的 bug。
|
||
|
||
**一个 job 失败可能由以下一个或多个原因导致:**
|
||
|
||
- **环境搭建**(在多样的系统上测试,见 https://github.com/ceph/ceph/tree/master/qa/distros/supported):测试与受支持版本的 stable release 的兼容性。
|
||
- **配置值的排列组合**:例如 `qa/suites/rados/thrash` 确保我们在压力工作负载下对 Ceph 跑 thrashing 测试,从而抓到 corner-case bug。
|
||
|
||
**最终用于测试的 setup config yaml 文件**可以在这里拿到:
|
||
|
||
```text
|
||
/a/<job-name>/<job-id>/orig.config.yaml
|
||
```
|
||
|
||
更多关于 config.yaml 的细节见 https://docs.ceph.com/projects/teuthology/en/latest/detailed_test_config.html
|
||
|
||
### 6.19.3 定位失败原因(Triaging)
|
||
|
||
job 失败时,需要读它的 teuthology log 来定位原因。用 pulpito 上的 job name 和 id 找到失败 job 的日志:
|
||
|
||
```text
|
||
http://qa-proxy.ceph.com/<job-name>/<job-id>/teuthology.log
|
||
```
|
||
|
||
打开文件:
|
||
|
||
```text
|
||
/a/<job-name>/<job-id>/teuthology.log
|
||
```
|
||
|
||
例如:
|
||
|
||
```bash
|
||
nano /a/teuthology-2021-01-06_07:01:02-rados-master-distro-basic-smithi/5759282/teuthology.log
|
||
```
|
||
|
||
**每一次 job 失败都会以 Traceback 的形式记录在 teuthology log 里,并被加进 job summary。**
|
||
|
||
**做法:找 `Traceback` 关键字,搜索调用栈和日志里导致失败的问题。通常 traceback 里会包含失败的那条命令。**
|
||
|
||
> note:teuthology 日志会被定期删除。如果上面示例的链接访问不了,就从 http://pulpito.front.sepia.ceph.com/ 随便找另一个 case 用。
|
||
|
||
### 6.19.4 上报 issue
|
||
|
||
**原则:先确认你的 job 失败是不是已知 issue 导致的;如果不是,就提 tracker ticket。**
|
||
|
||
流程:
|
||
|
||
1. 定位完失败原因,确认**不是你的代码改动导致的**——这可能说明你遇到了上游分支的已知失败(本节例子里上游分支是 "octopus")。
|
||
2. 去 https://tracker.ceph.com 用你在失败中看到的关键词搜索相关 issue。
|
||
3. **找到相似 issue**:在上面留言,按你的理解解释这次失败,**务必附上你最近一次 test run 的链接**。
|
||
4. **没找到**:为这个 issue 新建一个 tracker ticket,尽可能详尽地解释 job 失败的原因。
|
||
5. **不确定失败原因**:找团队成员求助。
|
||
|
||
### 6.19.5 用 `interactive-on-error` 调试
|
||
|
||
遇到 job 失败时应该尝试复现它。这就是 `--interactive-on-error` 的用武之地。
|
||
|
||
确认 job 失败后,用 teuthology 再跑一次同一个 job,但加上 `interactive-on-error` flag:
|
||
|
||
```bash
|
||
ideepika@teuthology:~/teuthology$ ./virtualenv/bin/teuthology -v --lock --block $<your-config-yaml> --interactive-on-error
|
||
```
|
||
|
||
可以用自定义 `config.yaml`,也可以用失败 job 的 yaml 文件。如果用失败 job 的,先把 `orig.config.yaml` 拷到本地目录:
|
||
|
||
```bash
|
||
ideepika@teuthology:~/teuthology$ cp /a/teuthology-2021-01-06_07:01:02-rados-master-distro-basic-smithi/5759282/orig.config.yaml test.yaml
|
||
ideepika@teuthology:~/teuthology$ ./virtualenv/bin/teuthology -v --lock --block test.yaml --interactive-on-error
|
||
```
|
||
|
||
**它做了什么**:如果用了 `interactive-on-error` 后 job 失败,teuthology 会**锁住 `config.yaml` 需要的机器**,**停住**这些测试机并把它们**保持在失败发生那一刻的状态**,然后把你放进一个**交互式 Python session**。从那里你可以 ssh 进系统排查失败原因。
|
||
|
||
排查完后**直接终止 session** 即可,teuthology 会清理 session 并解锁机器。
|
||
|
||
### 6.19.6 官方推荐的学习资源
|
||
|
||
| 资源 | 链接 |
|
||
|---|---|
|
||
| Testing Ceph: Pains & Pleasures | https://www.youtube.com/watch?v=gj1OXrKdSrs |
|
||
| Teuthology Training(播放列表) | https://www.youtube.com/playlist?list=PLrBUGiINAakNsOwHaIM27OBGKezQbUdM- |
|
||
| Intro to Teuthology | https://www.youtube.com/watch?v=WiEUzoS6Nc4 |
|
||
|
||
> 引自:[Analyzing and Debugging A Teuthology Job](https://docs.ceph.com/en/tentacle/dev/developer_guide/testing_integration_tests/tests-integration-testing-teuthology-debugging-tips/)
|
||
|
||
## 6.20 Kernel 开发的集成测试
|
||
|
||
### 6.20.1 CephFS
|
||
|
||
`fs` suite 会跑各种 kernel,由 **kernel YAML fragments** 描述。这些 fragments 被 `fs` suite 下的其他子 suite **符号链接**引用。
|
||
|
||
这个 fragment 矩阵允许测试以下配置:
|
||
|
||
- **RHEL 8 上的 "stock" kernel**(即随发行版自带的那个内核);
|
||
- 内核开发团队的 **`testing` 分支**,代表正在积极测试中的补丁。这些补丁**可能进入也可能不进入**下一个上游内核发布,包含 CephFS 或 kRBD 改动的混合。对 testing kernel,我们用**子 suite 指定的发行版**来测。例如 `fs:functional` 子 suite 使用 **supported random distros 的随机选择**。
|
||
|
||
### 6.20.2 测试自定义内核
|
||
|
||
如果你在 `ceph-client.git` 上有一个 kernel 分支,并且已经用 shaman 构建过,那么可以通过给 `teuthology-suite` 传一个 YAML fragment 来**覆盖 kernel**,轻松测它:
|
||
|
||
```yaml
|
||
$ cat custom-kernel.yaml
|
||
overrides:
|
||
kernel:
|
||
branch: for-linus
|
||
```
|
||
|
||
这为 suite 矩阵里指定的 kernel 分支提供了 override。你也可以把 override 指定为 **`kernel` task 的一个 tag 或 SHA1**。
|
||
|
||
> **重要提醒**:覆盖 kernel 时,你**应该减少 job 的选择范围**,因为矩阵会包含很多你不关心的 kernel 配置(见上文 CephFS 一节);override YAML 会**应用到 kernel 的所有配置**,从而产生**重复的测试**。
|
||
|
||
运行命令形如:
|
||
|
||
```bash
|
||
teuthology-suite ... --suite fs --filter k-testing custom-kernel.yaml
|
||
```
|
||
|
||
其中 `...` 是平时跑 `teuthology-suite` 时的其他常规选项。**关键的过滤器 `--filter k-testing`** 会把 job 选择范围**限制在使用 kernel 的 `testing` 分支的那些**(见 `k-testing.yaml` 文件),所以你只会选到用 `testing` 分支 kernel client 的 job。然后你的自定义 YAML 文件 `custom-kernel.yaml` 会进一步把 `testing` 分支覆盖成你指定的任何东西。
|
||
|
||
> 引自:[Integration Tests for Kernel Development](https://docs.ceph.com/en/tentacle/dev/developer_guide/testing_integration_tests/tests-integration-testing-teuthology-kernel/)
|
||
|
||
## 6.21 Sentry
|
||
|
||
`tests-sentry-developers-guide.rst` 的完整内容只有两行:
|
||
|
||
```text
|
||
Sentry Notes
|
||
============
|
||
|
||
To be updated. Feel free to contribute.
|
||
```
|
||
|
||
即:**待补充,欢迎贡献。** 官方文档本身没有写任何 Sentry 相关内容。
|
||
|
||
> 引自:[Sentry Notes](https://docs.ceph.com/en/tentacle/dev/developer_guide/testing_integration_tests/tests-sentry-developers-guide/)
|
||
|
||
## 6.22 子目录索引
|
||
|
||
`testing_integration_tests/index.rst` 的 toctree(用 `:glob:` + `:titlesonly:`):
|
||
|
||
| 条目 | 页面 |
|
||
|---|---|
|
||
| Introduction | `tests-integration-testing-teuthology-intro` |
|
||
| Workflow | `tests-integration-testing-teuthology-workflow` |
|
||
| Debugging Tips | `tests-integration-testing-teuthology-debugging-tips` |
|
||
| Kernel Development | `tests-integration-testing-teuthology-kernel` |
|
||
| Sentry Notes | `tests-sentry-developers-guide` |
|
||
|
||
> 引自:[Teuthology User Guide](https://docs.ceph.com/en/tentacle/dev/developer_guide/testing_integration_tests/)
|
||
|
||
---
|
||
|
||
# 七、用 GDB 调试 Ceph
|
||
|
||
**GNU Project Debugger (GDB)**(https://www.sourceware.org/gdb)是一个强大的工具,可以分析进程的执行流。GDB 能帮你找 bug、发现 crash 错误、或者在开发集群运行时跟踪源码。**它也可以用来调试 Teuthology 测试运行。**
|
||
|
||
## 7.1 起步
|
||
|
||
基础用法与示例:https://geeksforgeeks.org/gdb-command-in-linux-with-examples
|
||
|
||
**GDB 可以 attach 到运行中的进程。** 例如部署一个开发集群后,`ceph-osd` 守护进程的 PID 可以在这里找到:
|
||
|
||
```bash
|
||
$ cd build
|
||
$ cat out/osd.0.pid
|
||
```
|
||
|
||
**attach gdb 到该进程:**
|
||
|
||
```bash
|
||
$ gdb ./bin/ceph-osd -p <pid>
|
||
```
|
||
|
||
## 7.2 两条重要建议
|
||
|
||
**1)编译时不要优化。** 官方建议**编译时不带任何优化(`-O0` gcc flag)**,以避免中间值被消除。
|
||
|
||
**2)调大的超时时间。** 调试时因断点停下来可能导致超时,官方建议设置以下配置项:
|
||
|
||
```ini
|
||
[osd]
|
||
osd_op_thread_timeout = 1500
|
||
osd_op_thread_suicide_timeout = 1500
|
||
```
|
||
|
||
## 7.3 调试 Teuthology 测试
|
||
|
||
**`src/script/ceph-debug-docker.sh`** 可以用来分析 Teuthology 失败:
|
||
|
||
```bash
|
||
$ ./ceph-debug-docker.sh <branch-name>
|
||
```
|
||
|
||
更多信息参见该脚本的头部注释。
|
||
|
||
## 7.4 常用 GDB 命令速查
|
||
|
||
> ⚠️ **文档未明确**:官方 `debugging-gdb.rst` **没有列出任何具体的 GDB 子命令**(如 `bt`、`info threads`、`thread apply all bt` 等),只给了一个外部链接。上面的 `gdb ./bin/ceph-osd -p <pid>` 是官方给出的唯一一条完整命令。
|
||
|
||
> 💡 补充:本册源文能确证的 gdb 相关命令只有:
|
||
|
||
```bash
|
||
# 1) 找到 ceph-osd 的 PID
|
||
cd build
|
||
cat out/osd.0.pid
|
||
|
||
# 2) attach
|
||
gdb ./bin/ceph-osd -p <pid>
|
||
|
||
# 3) 分析 teuthology 失败
|
||
./ceph-debug-docker.sh <branch-name>
|
||
```
|
||
|
||
> 💡 补充:调试时若需要看 Ceph 自己的日志,注意 Google Test 与守护进程的客户端日志位置不同(见 5.6 节):单测客户端日志在 `build/out/client.admin.<pid>.log`;vstart 集群日志在 `build/out/` 下,可用 `tail -f out/????` 观察。
|
||
|
||
> 引自:[GDB - The GNU Project Debugger](https://docs.ceph.com/en/tentacle/dev/developer_guide/debugging-gdb/)
|
||
|
||
---
|
||
|
||
# 八、Windows 测试
|
||
|
||
## 8.1 背景
|
||
|
||
**从 Pacific 起,Ceph 客户端工具和库可以在 Windows 上原生使用**,这样 Windows 节点消费 Ceph 时不需要额外加 iSCSI gateway 或 SMB share 之类的层。
|
||
|
||
为了保证这些组件在 Windows 上持续正常工作,**大量单元测试和集成测试被移植**过来。
|
||
|
||
## 8.2 Windows CI Job 做了什么
|
||
|
||
`Windows CI job`(https://github.com/ceph/ceph-build/blob/main/ceph-windows-pull-requests/config/definitions/ceph-windows-pull-requests.yml)对**每个 GitHub pull request** 执行以下步骤:
|
||
|
||
1. 起一台 Linux VM,在里面构建服务端(Linux)Ceph 二进制,并交叉编译 Windows(客户端)二进制;
|
||
2. 重建 Linux VM 并启动一个 Ceph **vstart** 集群;
|
||
3. 启动一台 Windows VM,在上面跑 Ceph 测试。
|
||
|
||
一个**小型 PowerShell 框架**(https://github.com/ceph/ceph-win32-tests/)负责并行化测试、聚合结果,并隔离或跳过已知的 flaky 测试。
|
||
|
||
**控制台输出**里可能包含编译错误和失败测试的名字。要拿到失败测试的控制台输出以及 Ceph 和操作系统日志,**去看 Jenkins "Status" 页面的 build artifacts**。
|
||
|
||
Windows CI artifacts 可以下载为 zip 归档,也可以在浏览器里直接看。点 "artifacts" 按钮查看内容。
|
||
|
||
## 8.3 Artifact 目录结构
|
||
|
||
| 路径 | 内容 |
|
||
|---|---|
|
||
| `client/` | Ceph 客户端侧日志(Windows) |
|
||
| `client/eventlog/` | Windows 系统日志 |
|
||
| `client/logs/` | Ceph 日志 |
|
||
| `client/*-windows.conf` | Ceph 配置文件 |
|
||
| `cluster/` | Ceph 服务端日志(Linux) |
|
||
| `cluster/ceph_logs/` | Ceph 日志 |
|
||
| `cluster/journal` | journal |
|
||
| `test_results/` | 测试结果 |
|
||
| `test_results/out/` | 按测试可执行文件分组的 raw 与 xml 测试输出 |
|
||
| `test_results/test_results.html` | 聚合测试报告(html) |
|
||
| `test_results/test_results.txt` | 聚合测试报告(纯文本) |
|
||
|
||
测试结果用 **`subunit`** 格式(https://github.com/testing-cabal/subunit)及相关工具聚合,**在并行跑大量测试时特别好用**。
|
||
|
||
## 8.4 怎么读聚合测试报告
|
||
|
||
聚合测试报告能很好地概览失败的测试。**跳到文件末尾**可以看到实际错误:
|
||
|
||
```text
|
||
{0} unittest_mempool.mempool.bufferlist_reassign [0.000000s] ... ok
|
||
{0} unittest_mempool.mempool.bufferlist_c_str [0.006000s] ... ok
|
||
{0} unittest_mempool.mempool.btree_map_test [0.000000s] ... ok
|
||
{0} ceph_test_dokan.DokanTests.test_mount [9.203000s] ... FAILED
|
||
|
||
Captured details:
|
||
~~~~~~~~~~~~~~~~~
|
||
b'/home/ubuntu/ceph/src/test/dokan/dokan.cc:136'
|
||
b'Expected equality of these values:'
|
||
b' wait_for_mount(mountpoint)'
|
||
b' Which is: -138'
|
||
b' 0'
|
||
b''
|
||
b'/home/ubuntu/ceph/src/test/dokan/dokan.cc:208'
|
||
b'Expected equality of these values:'
|
||
b' ret'
|
||
b' Which is: "ceph-dokan: exit status: -22"'
|
||
b' ""'
|
||
b'Failed unmapping: Y:\\'
|
||
{0} ceph_test_dokan.DokanTests.test_mount_read_only [9.140000s] ... FAILED
|
||
```
|
||
|
||
HTML 报告按测试 suite(测试二进制)分组,**出于安全原因默认不渲染**,但可以下载到本地看。
|
||
|
||
**排查技巧:**
|
||
|
||
- **超时和缺失的测试结果往往是进程崩溃的信号**。
|
||
- 注意 **ceph status 会在跑测试前后打印到控制台**,这有助于识别崩溃的服务。
|
||
- 也可以查服务日志(客户端和服务端都要看)。
|
||
- 另外注意:**Windows "application" 事件日志**里会有 Windows 进程崩溃的条目。
|
||
|
||
## 8.5 FAQ
|
||
|
||
### Q1:为什么我的 PR 只有 Windows CI job 失败?
|
||
|
||
Ceph 集成测试通常是通过 **Teuthology** 在 Ceph Lab 基础设施上做的。这些测试由 Ceph QA 团队**按需触发**,**不会为每个提交的 PR 自动跑**。
|
||
|
||
而 Windows CI job **只聚焦客户端侧的 Ceph 组件**,因此**能对每个 GitHub PR 及时跑各种集成测试**。**换句话说,它跑的是各种 librados、librbd 和 libcephfs 测试——这些是 `make check` 等其他检查所不跑的。**
|
||
|
||
因此 Windows CI 经常能抓到其他检查漏掉的回归,而这些回归**往往不是平台特有的,也影响 Linux**。
|
||
|
||
**Windows CI 失败时,强烈建议按上面的方法查测试结果。**
|
||
|
||
另外要注意:`Windows build script`(https://github.com/ceph/ceph/blob/main/win32_build.sh)可能使用**不同的编译 flag 和传给 CMake 的 `-D` 选项**。例如它**默认是 `Release` 模式而不是 `Debug` 模式**;同时它用的是不同的 toolchain(**`mingw-llvm`**)和一套独立的 **dependencies**(https://github.com/ceph/ceph/blob/main/win32_deps_build.sh),**需要时要记得升级版本**。
|
||
|
||
### Q2:为什么 Windows CI job 是强制的(mandatory)?
|
||
|
||
这个测试 job **最初是可选的**,结果**回归被引入得非常频繁**。
|
||
|
||
后来 Windows 支持成熟到足以让这个 CI job 变成**强制的**。这**显著减少了处理回归所需的工作量**,也向 Ceph 用户保证了 Windows 支持的持续性。
|
||
|
||
另一个巨大好处(如上所述):**它跑的集成测试能快速抓到往往同样影响 Linux 构建的回归**,让开发者不必等完整的 Teuthology 结果。
|
||
|
||
> 引自:[Testing - Windows](https://docs.ceph.com/en/tentacle/dev/developer_guide/tests-windows/)
|
||
|
||
---
|
||
|
||
# 九、文档贡献
|
||
|
||
> ⚠️ **范围说明**:`documenting.rst` **不在 `doc/dev/developer_guide/` 目录下**,它的实际位置是 `doc/dev/documenting.rst`,属于本手册**第 18 册**(内部实现篇·组件工具与 Crimson)的覆盖范围。按本册的取材范围,这里只讲**与贡献流程直接相关**的部分,完整内容见第 18 册。
|
||
|
||
## 9.1 从贡献流程角度看文档改动
|
||
|
||
| 场景 | 是否需要 Redmine tracker issue |
|
||
|---|---|
|
||
| Simple documentation cleanup(简单文档清理) | **不需要** |
|
||
| 新增文档章节或文件 | 需要 |
|
||
| 对文档结构或内容做实质性改动 | 需要 |
|
||
|
||
> 引自:[Basic Workflow](https://docs.ceph.com/en/tentacle/dev/developer_guide/basic-workflow/)
|
||
|
||
## 9.2 所有 PR 都会跑的一个 CI 检查:文档能否构建
|
||
|
||
官方列出的第 2 项自动化检查就是 **a test to check that the documentation builds**。也就是说,**你的 rst 写坏了,PR 会红**。
|
||
|
||
> 引自:[Basic Workflow](https://docs.ceph.com/en/tentacle/dev/developer_guide/basic-workflow/)
|
||
|
||
## 9.3 `doc/dev/documenting.rst` 要点(摘要,详见第 18 册)
|
||
|
||
- docs.ceph.com 上的文档由 Ceph git 仓库 **`/doc/` 目录里的 reStructuredText 源文件**生成。
|
||
- 除非你是在 **`/doc/dev/`**(开发者专区)里做增补,否则**改动必须面向软件的最终用户来写**。
|
||
- **所有修改面向用户功能的 PR 都必须包含对应的文档更新**,详见 `SubmittingPatches`。
|
||
- 检查 rst 语法:在 GitHub 上看 diff 时用 **"View" 按钮**;或者用 **`admin/build-doc`** 脚本本地构建。
|
||
- C/C++ 代码文档用 **Doxygen**(配合 **Breathe** 支持的 Doxygen 标记子集),注释写在**声明函数的头文件**里,函数应按逻辑分组。生成 HTML:
|
||
```bash
|
||
cmake --build . --target doxygen
|
||
```
|
||
输出在 `build-doc/doxygen/html`。
|
||
- 画图工具:**Graphviz**(推荐把 DOT 源放单独文件,用 `.. graphviz:: myfile.dot`)、**Ditaa**、**Blockdiag**(如需要可集成)、**Inkscape**(应同时提交 SVG 和导出的 PNG,rst 里引用 PNG)。
|
||
|
||
> 💡 补充:本节 9.3 的内容来自 `doc/dev/documenting.rst`,**不属于本册的源文范围**(本册源文是 `doc/dev/developer_guide/`),此处仅作为与「贡献流程」相关的必要引述列出,完整讲解见第 18 册。
|
||
|
||
---
|
||
|
||
# 十、Ceph Dashboard 开发(`dash-devel.rst`,本册最大块)
|
||
|
||
这是本册最大的一章,源文 103KB、2747 行,官方标题是 *Ceph Dashboard Developer Documentation (formerly HACKING.rst)*——即它前身是 Dashboard 的 `HACKING.rst`。
|
||
|
||
## 10.1 Feature Design(特性设计)
|
||
|
||
为了促进在 Ceph Dashboard 新特性上的协作,**第一步是定义一份设计文档**。这些文档随后构成实现范围的基础,并让更多人能参与到 Ceph Dashboard UI 的演进中。
|
||
|
||
设计文档的 toctree 里目前挂着一个条目:
|
||
|
||
- UI Design Goals → `../dashboard/ui_goals`
|
||
|
||
## 10.2 Preliminary Steps:搭建开发环境
|
||
|
||
后续各章都假设你有一个**运行中的 Ceph 集群**,且至少跑着 `dashboard` manager module(少数例外)。本章讲如何为开发搭建这样一个系统,**无需搭建完整的生产环境**。
|
||
|
||
> **重要前提**:**每个 `vstart` 环境都需要 Ceph 从 GitHub 仓库编译**(https://docs.ceph.com/docs/master/install/build-ceph/),不过 Docker 环境通过提供一个包含这些指令的 shell 脚本简化了这一步。
|
||
>
|
||
> **一个例外**:`ceph-dev` 的 **`build-free`** 能力(见下文)——它通过**用 RPM 系统包安装 Ceph** 来做到免编译,同时你仍然能像往常一样使用本地 GitHub 仓库。
|
||
|
||
### 10.2.1 vstart 是什么
|
||
|
||
"vstart" 其实是 Ceph 仓库 `src/` 目录里的一个 shell 脚本(**`src/vstart.sh`**),用来**在执行它的机器上启动一个单节点 Ceph 集群**。用它启动集群时,若干必需的以及一些可选的 Ceph 内部服务会自动启动。
|
||
|
||
**vstart 是 Ceph Dashboard 三种最常用开发环境的基础。**
|
||
|
||
更多见 `Deploying a development cluster`,开发者额外信息见 Developer Guide(https://docs.ceph.com/docs/master/dev/quick_guide/)。
|
||
|
||
### 10.2.2 基于宿主机 vs 基于 Docker 的开发环境
|
||
|
||
本册介绍三种都基于 vstart 的开发环境:
|
||
|
||
1. **vstart 跑在你的宿主机系统上**
|
||
2. **vstart 跑在 Docker 环境里**
|
||
- `ceph-dev-docker`(https://github.com/ricardoasmarques/ceph-dev-docker)
|
||
- `ceph-dev`(https://github.com/rhcs-dashboard/ceph-dev)
|
||
|
||
两个 Docker 项目除了各自独立的开发分支和略有差异的做法,底层操作系统也不同:
|
||
|
||
| Release | ceph-dev-docker | ceph-dev |
|
||
|---|---|---|
|
||
| Mimic | openSUSE Leap 15 | CentOS 7 |
|
||
| Nautilus | openSUSE Leap 15 | CentOS 7 |
|
||
| Octopus | openSUSE Leap 15.2 | CentOS 8 |
|
||
| Master | openSUSE Tumbleweed | CentOS 8 |
|
||
|
||
> **重要 note**:**无论你选哪个环境,都需要在该环境里编译 Ceph。** 如果你在宿主机上编译过 Ceph,切到 Docker 方案就得在 Docker 里重新编译;反之亦然(也可以另开一个独立仓库编译)。
|
||
>
|
||
> `ceph-dev` 是**唯一的例外**:它提供 `build-free` 选项,通过 RPM 系统包安装 Ceph 实现免编译,同时你仍然可以用本地 GitHub 仓库工作。
|
||
|
||
**基于宿主机的开发环境:**
|
||
|
||
- 不需要学 Docker、不需要 Docker 经验,可以直接上手;
|
||
- 用于自动化(如 Ceph 编译)的脚本数量有限;
|
||
- 没有预配置好、开箱即用的服务(Prometheus、Grafana 等);
|
||
- 支持的宿主操作系统有限,取决于要用哪个 Ceph 版本;
|
||
- 依赖需要装在你的宿主机上;
|
||
- 你可能被迫升级宿主操作系统(例如因为编译 Ceph 用的 GCC 版本变了)。
|
||
|
||
**基于 Docker 的开发环境:**
|
||
|
||
- 如果你还不熟悉 Docker,有一点学习开销;
|
||
- 两个 Docker 项目都提供了帮你上手和自动化重复任务的脚本;
|
||
- **都自带部分预配置的外部服务**,可以用来对接或补充 Ceph Dashboard 特性,包括:
|
||
- Prometheus
|
||
- Grafana
|
||
- Node-Exporter
|
||
- Shibboleth
|
||
- HAProxy
|
||
- **与你宿主机用的操作系统无关**。
|
||
|
||
### 10.2.3 在宿主机上跑 vstart
|
||
|
||
vstart 脚本通常从你的 `build/` 目录这样调用:
|
||
|
||
```bash
|
||
../src/vstart.sh -n -d
|
||
```
|
||
|
||
- **`-n`**:确保创建一个新的 vstart 集群,不会复用之前可能已创建的集群;
|
||
- **`-d`**:在日志文件里开启 debug 消息。
|
||
|
||
还有更多选项,用 `--help` 参数可以列出。
|
||
|
||
**vstart 输出末尾应该有 dashboard 及其 URL 的信息:**
|
||
|
||
```text
|
||
vstart cluster complete. Use stop.sh to stop. See out/* (e.g. 'tail -f out/????') for debug output.
|
||
|
||
dashboard urls: https://192.168.178.84:41259, https://192.168.178.84:43259, https://192.168.178.84:45259
|
||
w/ user/pass: admin / admin
|
||
```
|
||
|
||
**开发过程中(尤其是后端开发),你也会想偶尔检查 dashboard manager module 是否还在跑。** 可以手工调用 `./bin/ceph mgr services`,它会列出所有成功启用的服务的 URL。**只有可通过 HTTP(S) 访问的服务才会列在那里**,Ceph Dashboard 就是其中之一。应该类似这样:
|
||
|
||
```bash
|
||
$ ./bin/ceph mgr services
|
||
{
|
||
"dashboard": "https://home:41931/"
|
||
}
|
||
```
|
||
|
||
> **默认这个环境给 Ceph Dashboard 用随机端口**,你需要用上面这条命令查出来是哪个。
|
||
|
||
### 10.2.4 Docker
|
||
|
||
Docker 开发环境通常自带很多有用的脚本。例如 `ceph-dev-docker` 里有一个 `start-ceph.sh`,它会:清理日志文件、总是启动一个 Rados Gateway 服务、设置一些 Ceph Dashboard 配置选项、并自动运行一个 frontend proxy——全都在启动 vstart 集群之前或之后完成。
|
||
|
||
使用说明在各自仓库的 README 里(见 10.2.2 的两个链接)。
|
||
|
||
## 10.3 Frontend Development(前端开发)
|
||
|
||
在开发环境里启动 dashboard 之前,你需要先生成前端代码,然后**要么**使用一个已编译且运行的 Ceph 集群(例如由 `vstart.sh` 启动的),**要么**使用 standalone development web server。
|
||
|
||
构建过程基于 **Node.js**(https://nodejs.org/),需要安装 **npm**(https://www.npmjs.com/)。
|
||
|
||
### 10.3.1 前置条件与工具链
|
||
|
||
| 项 | 要求 / 说明 |
|
||
|---|---|
|
||
| Node | **20.13.1 或更高** |
|
||
| NPM | **10.5.2 或更高** |
|
||
| nodeenv | Ceph 构建时会创建一个装了 `node` 和 `npm` 的 virtualenv,可替代系统安装的 node/npm。用它之前先激活:`. build/src/pybind/mgr/dashboard/node-env/bin/activate`;用完 `deactivate` 退出 |
|
||
| Angular CLI | 如果你**没有全局安装** Angular CLI(https://github.com/angular/angular-cli),那么执行 `ng` 命令时需要前面加一个 `npm run` |
|
||
|
||
**技术栈小结**(官方明确提到的):**Angular + Angular CLI + Node.js/npm + TypeScript + SCSS + HTML + Bootstrap(CSS 框架)+ Jest(单元测试)+ Cypress(E2E)+ Applitools Eyes(视觉回归)+ ngx-toastr(通知)+ Fork Awesome(图标)+ compodoc(代码文档)**。
|
||
|
||
### 10.3.2 安装依赖
|
||
|
||
在 `src/pybind/mgr/dashboard/frontend` 目录里跑:
|
||
|
||
```bash
|
||
npm ci
|
||
```
|
||
|
||
### 10.3.3 增加或更新依赖包
|
||
|
||
```bash
|
||
npm install <PACKAGE_NAME>
|
||
npm ci
|
||
```
|
||
|
||
### 10.3.4 起开发服务器
|
||
|
||
1. 基于 `proxy.conf.json.sample` 创建 **`proxy.conf.json`** 文件;
|
||
2. 跑 `npm start` 起 dev server;
|
||
3. 浏览器打开 **http://localhost:4200/**。**改动任何源文件后应用会自动 reload。**
|
||
|
||
### 10.3.5 代码脚手架
|
||
|
||
```bash
|
||
ng generate component component-name
|
||
```
|
||
|
||
也可以生成其他类型:
|
||
|
||
```bash
|
||
ng generate directive|pipe|service|class|guard|interface|enum|module
|
||
```
|
||
|
||
没装全局 Angular CLI 时,前面加 `npm run`。
|
||
|
||
### 10.3.6 构建项目
|
||
|
||
```bash
|
||
npm run build
|
||
```
|
||
|
||
构建产物存放在 **`dist/`** 目录。用 `--prod` flag 做生产构建:
|
||
|
||
```bash
|
||
npm run build -- --prod
|
||
```
|
||
|
||
然后访问 `https://localhost:8443`。
|
||
|
||
### 10.3.7 构建代码文档
|
||
|
||
```bash
|
||
npm run doc-build
|
||
```
|
||
|
||
会在 **`documentation/`** 目录生成代码文档。要在本地用浏览器访问:
|
||
|
||
```bash
|
||
npm run doc-serve
|
||
```
|
||
|
||
然后打开 **http://localhost:8444**。用 `npm run compodoc -- <opts>` 可以**完全配置**它(https://compodoc.app/guides/usage.html)。
|
||
|
||
### 10.3.8 代码 lint 与格式化
|
||
|
||
官方用于所有 TS、SCSS 和 HTML 文件的工具:
|
||
|
||
| 工具 | 链接 |
|
||
|---|---|
|
||
| codelyzer | http://codelyzer.com/ |
|
||
| html-linter | https://github.com/chinchiheather/html-linter |
|
||
| htmllint-cli | https://github.com/htmllint/htmllint-cli |
|
||
| Prettier | https://prettier.io/ |
|
||
| ESLint | https://eslint.org/ |
|
||
| stylelint | https://stylelint.io/ |
|
||
|
||
官方加了两个 npm script:
|
||
|
||
```bash
|
||
npm run lint # 用所有 linter 检查前端文件
|
||
npm run fix # 尝试修复所有检测到的 linting 错误
|
||
```
|
||
|
||
### 10.3.9 Ceph Dashboard 与 Bootstrap
|
||
|
||
目前 Ceph Dashboard 用 **Bootstrap** 作为 CSS 框架,这意味着大部分 SCSS 和 HTML 代码可以利用 Bootstrap 提供的所有 utility 和其他优势。
|
||
|
||
**历史问题**:过去经常使用自定义样式,导致越来越多的「只用过一次的变量」和「重复定义的变量」——有些忘了删,有些导致样式不一致(有人忘了改颜色或调整某个自定义 SCSS class)。
|
||
|
||
**查当前 Bootstrap 版本**:看 `package.json`,搜:
|
||
|
||
- `bootstrap`:Bootstrap 版本;
|
||
- `@ng-bootstrap`:使用的 Angular bindings 版本。
|
||
|
||
**官方明确要求(今后访问组件时请照做):**
|
||
|
||
1. **这段 HTML/SCSS 代码用了自定义代码吗?** 如果是:**有必要吗?** → **先清理干净**再改你想修/想改的东西。
|
||
2. **如果你在创建新组件**:**尽可能合理地使用 Bootstrap!别重复造轮子。**
|
||
3. **如果可能**:查一下 Bootstrap 有没有「如何正确扩展它」的指南来实现你想要的效果。
|
||
|
||
官方给的理由:代码越像 Bootstrap,就越容易 theme、越容易维护、bug 越少;Bootstrap 本身考虑了可用性和用户体验,能同时提升这两点;**最大好处是我们要维护的代码更少**——对新手更好读,对已熟悉代码的人更容易。
|
||
|
||
## 10.4 编写前端单元测试
|
||
|
||
为高效编写单元测试,官方提供了一小套在 test suite 里使用的工具,位于:
|
||
|
||
```text
|
||
src/pybind/mgr/dashboard/frontend/src/testing/
|
||
```
|
||
|
||
**特别要看 `unit-test-helper.ts`。** 你能找到:
|
||
|
||
| 工具 | 说明 |
|
||
|---|---|
|
||
| `configureTestBed` | 取代初始的 `TestBed` 方法。**接受与 `TestBed.configureTestingModule` 相同的参数。** 用它**在开发时跑测试会快很多**,因为它不会每次测试都从零重建一切。**要用默认行为,传 `true` 作为第二个参数** |
|
||
| `PermissionHelper` | 帮助判断**基于当前权限和列表中的选择,是否显示了正确的操作** |
|
||
| `FormHelper` | 用几个简单方法让**测试表单**容易得多。可以设置单个或多个 control、断言某个 control 是否有效或有错误,或者用一个方法同时做这两件事。此外还能断言某个 template element 或多个 elements 在渲染后的模板里可见 |
|
||
|
||
## 10.5 运行前端单元测试
|
||
|
||
```bash
|
||
npm run test
|
||
```
|
||
|
||
通过 **Jest**(https://facebook.github.io/jest/)执行单元测试。
|
||
|
||
**如果所有测试都报错**,可能是因为 Jest 或其他东西被更新了。可以尝试这样解决:
|
||
|
||
```bash
|
||
rm -rf dist node_modules
|
||
npm install
|
||
```
|
||
|
||
或清 Jest 缓存:
|
||
|
||
```bash
|
||
npx jest --clearCache
|
||
```
|
||
|
||
## 10.6 运行端到端(E2E)测试
|
||
|
||
Ceph Dashboard 用 **Cypress**(https://www.cypress.io/)跑前端 E2E 测试。
|
||
|
||
### 10.6.1 E2E 前置条件
|
||
|
||
- **需要先构建前端**。
|
||
- 在某些环境下,取决于你的用户权限和 `CYPRESS_CACHE_FOLDER`,可能需要用 `--unsafe-perm` flag 跑 `npm ci`。
|
||
- 可能需要安装额外的包才能跑 Cypress。跑 **`npx cypress verify`** 来验证。
|
||
|
||
### 10.6.2 `run-frontend-e2e-tests.sh`(全量 E2E 首选)
|
||
|
||
官方的 `run-frontend-e2e-tests.sh` 脚本是**做全规模 e2e 跑时的首选方案**。它会:验证所需组件是否都装了 → 起一个新的 vstart 集群 → 跑完整测试套件。
|
||
|
||
```bash
|
||
$ cd src/pybind/mgr/dashboard
|
||
$ ./run-frontend-e2e-tests.sh
|
||
```
|
||
|
||
| 方面 | 说明 |
|
||
|---|---|
|
||
| **报告** | 可以在终端跟踪 e2e 报告;失败用例的截图在 `src/pybind/mgr/dashboard/frontend/cypress/screenshots/` |
|
||
| **Device** | 用 `-d` flag 强制指定设备:`<chrome\|chromium\|electron\|docker>`,例如 `./run-frontend-e2e-tests.sh -d <chrome>` |
|
||
| **Remote** | 默认脚本会停掉并重启一个新的 vstart 集群。想在 ceph 环境外跑测试,需要用 `-r` 手动指定 dashboard url,可选地用 `-u`、`-p` 指定凭据:`./run-frontend-e2e-tests.sh -r <DASHBOARD_URL> -u <E2E_LOGIN_USER> -p <E2E_LOGIN_PWD>` |
|
||
| **Note** | 用 docker 作为 device 时,可能需要 sudo 权限跑脚本 |
|
||
|
||
### 10.6.3 `run-cephadm-e2e-tests.sh`(cephadm 场景)
|
||
|
||
`run-cephadm-e2e-tests.sh` 跑 **E2E 测试的一个子集**,用来验证 Dashboard 与作为 Orchestrator 后端的 **cephadm** 行为是否正确。
|
||
|
||
**前置条件**:本机需要安装 **KCLI**(https://kcli.readthedocs.io/en/latest/)和 Node.js。
|
||
|
||
**配置 KCLI plan:**
|
||
|
||
```bash
|
||
$ sudo chown -R $(id -un) /var/lib/libvirt/images
|
||
$ mkdir -p /var/lib/libvirt/images/ceph-dashboard
|
||
$ kcli create pool -p /var/lib/libvirt/images/ceph-dashboard ceph-dashboard
|
||
$ kcli create network -c 192.168.100.0/24 ceph-dashboard
|
||
```
|
||
|
||
> Note:这个脚本**目标是作为 jenkins job 跑的,所以只在 jenkins 环境里触发清理。在本地,用户需要自己决定何时关掉集群(例如调试完之后)。**
|
||
|
||
**启动 E2E 测试:**
|
||
|
||
```bash
|
||
$ cd <your/ceph/repo/dir>
|
||
$ sudo chown -R $(id -un) src/pybind/mgr/dashboard/frontend/{dist,node_modules,src/environments}
|
||
$ ./src/pybind/mgr/dashboard/ci/cephadm/run-cephadm-e2e-tests.sh
|
||
```
|
||
|
||
> Note(Fedora 35):挂载 shared_folders 时可能出现权限错误,可以修:
|
||
> ```bash
|
||
> $ sudo setfacl -R -m u:qemu:rwx <abs-path-to-your-user-home>
|
||
> ```
|
||
> 或者给你的 `$HOME` 目录设置合适的权限。
|
||
|
||
**开发模式启动集群**(前端构建进入 watch mode,你只需刷新页面就能看到改动):
|
||
|
||
```bash
|
||
$ ./src/pybind/mgr/dashboard/ci/cephadm/start-cluster.sh --dev-mode
|
||
```
|
||
|
||
> Note:如果需要一个**可直接部署服务**的集群(monitor 守护进程分布在多台主机上且有足够 OSD),加 **`--expanded`**。
|
||
|
||
**用改动测一遍:**
|
||
|
||
```bash
|
||
$ ./src/pybind/mgr/dashboard/ci/cephadm/run-cephadm-e2e-tests.sh
|
||
```
|
||
|
||
**关闭集群:**
|
||
|
||
```bash
|
||
$ kcli delete plan -y ceph
|
||
$ # 在 development mode 下,还要杀掉 npm build watch 进程(例如 pkill -f "ng build")
|
||
```
|
||
|
||
### 10.6.4 其他运行方式(活跃开发期推荐)
|
||
|
||
**在活跃开发期间,官方不建议跑上面那个脚本**,因为它没有为「文件持续变动」做准备。改用下面这些命令:
|
||
|
||
| 命令 | 说明 |
|
||
|---|---|
|
||
| `npm run e2e` | 跑 `ng serve` 并打开 Cypress Test Runner |
|
||
| `npm run e2e:ci` | 跑 `ng serve` 并把 Cypress Test Runner 跑一次 |
|
||
| `npx cypress run` | 直接调 cypress 跑 Cypress Test Runner。**需要有一个正在运行的前端服务器** |
|
||
| `npx cypress open` | 直接调 cypress 打开 Cypress Test Runner。**需要有一个正在运行的前端服务器** |
|
||
|
||
**直接调 Cypress 的好处**:可以使用任何可用的 flags(https://docs.cypress.io/guides/guides/command-line.html#cypress-run)来定制测试运行,而且**不必每次都启动前端服务器**。
|
||
|
||
用 `open` 类命令会打开一个 cypress 应用,里面能看到所有测试文件并逐个运行。**这会以 watch mode 运行**,所以改动测试文件会重新触发测试跑。**这在 docker 里没法用,因为它需要 X11 环境才能打开。**
|
||
|
||
**默认 Cypress 会在 `https://localhost:4200/` 找页面。** 如果你用别的 URL 提供服务,需要通过导出环境变量 **`CYPRESS_BASE_URL`** 来配置,例如:
|
||
|
||
```bash
|
||
CYPRESS_BASE_URL=https://localhost:41076/ npx cypress open
|
||
```
|
||
|
||
### 10.6.5 `CYPRESS_CACHE_FOLDER`
|
||
|
||
通过 npm 安装 cypress 时,cypress app 的二进制也会被下载并存在一个缓存目录里,这样**每次跑 `npm ci` 甚至在其他项目用 cypress 时都不必重新下载**。
|
||
|
||
- Cypress **默认用 `~/.cache`** 存二进制。
|
||
- 为避免改动用户 home 目录,Ceph 把这个目录改成了 **`/ceph/build/src/pybind/mgr/dashboard/cypress`**——所以当你构建 ceph 或跑 `run-frontend-e2e-tests.sh` 时,Cypress 会用这个目录。
|
||
- **当你用任何其他命令安装或运行 cypress 时,它会回到默认目录。**
|
||
- **官方建议**:导出 `CYPRESS_CACHE_FOLDER` 环境变量指向一个固定目录,这样无论用哪个命令都用同一个目录。
|
||
|
||
## 10.7 编写 E2E 测试
|
||
|
||
### 10.7.1 `PageHelper` 类
|
||
|
||
`PageHelper` 类**用于各种页面或 suite 都能用的通用代码**。
|
||
|
||
示例方法:
|
||
|
||
- `navigateTo()` —— 导航到特定页面并等待加载完成;
|
||
- `getFirstTableCell()` —— 返回第一个表格单元格。也可以传一个期望内容的字符串,它会返回**第一个包含该内容的单元格**;
|
||
- `getTabsCount()` —— 返回 tab 的数量。
|
||
|
||
**凡是可能在多个页面上有用的方法都放这里。** 另外,**增强 `PageHelper` 派生类的方法**也属于这里。一个好例子是 **`restrictTo()` 装饰器**:它确保 `PageHelper` 子类里实现的某个方法是在正确的页面上被调用的;如果不是,它会显示一条**对开发者友好的警告**。
|
||
|
||
### 10.7.2 `PageHelper` 的子类
|
||
|
||
**Helper Methods**:为了让针对某个特定 suite 的代码可复用,确保把它放在 `PageHelper` 的**派生类**里。例如对 pool suite,这样的方法就是 `create()`、`exist()` 和 `delete()`——它们对 pool 是特有的,但对其他 suite 也有用。
|
||
|
||
返回**只能在特定页面上找到的 HTML 元素**的方法,应该实现在 `PageHelper` 子类的 helper methods 里,或者作为 `PageHelper` 子类自己的方法。
|
||
|
||
**Using PageHelpers**:在任何 suite 里,应该**实例化并直接调用**具体 `Helper` 类的实例:
|
||
|
||
```typescript
|
||
const pools = new PoolPageHelper();
|
||
|
||
it('should create a pool', () => {
|
||
pools.exist(poolName, false);
|
||
pools.navigateTo('create');
|
||
pools.create(poolName, 8);
|
||
pools.exist(poolName, true);
|
||
});
|
||
```
|
||
|
||
### 10.7.3 代码风格:`describe()` vs `it()`
|
||
|
||
官方请读者参考 Cypress Core Concepts(https://docs.cypress.io/guides/core-concepts/introduction-to-cypress.html#Cypress-Can-Be-Simple-Sometimes)来更好地理解如何编写和组织测试。
|
||
|
||
**`describe()` vs `it()`:**
|
||
|
||
两者都是函数块,意味着测试需要的任何可执行代码都可以放在任一块里。但 **Typescript 的作用域规则仍然适用**,因此在 `describe` 里声明的任何变量对其中 `it()` 块都是可用的。
|
||
|
||
**`describe()`** 通常是测试的**容器**,让你把测试拆成多个部分。同样,测试运行前必须做的任何 setup 都可以在 `describe()` 块里初始化。示例:
|
||
|
||
```typescript
|
||
describe('create, edit & delete image test', () => {
|
||
const poolName = 'e2e_images_pool';
|
||
|
||
before(() => {
|
||
cy.login();
|
||
pools.navigateTo('create');
|
||
pools.create(poolName, 8, 'rbd');
|
||
pools.exist(poolName, true);
|
||
});
|
||
|
||
beforeEach(() => {
|
||
cy.login();
|
||
images.navigateTo();
|
||
});
|
||
|
||
//...
|
||
|
||
});
|
||
```
|
||
|
||
如上所示,我们可以在 `describe` 里初始化变量 `poolName`,也可以在测试套件开始前跑命令(创建 pool)。**`describe()` 块的消息应该说明这个测试套件是什么。**
|
||
|
||
**`it()`** 块通常是** overarching test 的一部分**,包含测试套件的功能,各自扮演独立角色。示例:
|
||
|
||
```typescript
|
||
describe('create, edit & delete image test', () => {
|
||
//...
|
||
|
||
it('should create image', () => {
|
||
images.createImage(imageName, poolName, '1');
|
||
images.getFirstTableCell(imageName).should('exist');
|
||
});
|
||
|
||
it('should edit image', () => {
|
||
images.editImage(imageName, poolName, newImageName, '2');
|
||
images.getFirstTableCell(newImageName).should('exist');
|
||
});
|
||
|
||
//...
|
||
});
|
||
```
|
||
|
||
**`it()` 的命名规范(明确要求)**:**每个 `it()` 块的消息都应该小写,并且写成能让 "it" 作为前缀的形式。**
|
||
|
||
- 好:`it('edits the test image' () => ...)` —— 用 `it()` 作前缀语法通顺;
|
||
- 不好:`it('image edit test' () => ...)` —— 不通顺。
|
||
|
||
**`it()` 应该描述这个单独的测试在做什么、期望发生什么。**
|
||
|
||
## 10.8 视觉回归测试(Visual Regression Testing)
|
||
|
||
Ceph Dashboard 用 **Applitools Eyes**(https://applitools.com/products-eyes/)——一个 AI 驱动的自动化视觉回归测试工具。Applitools 与现有的 Cypress E2E 测试集成。
|
||
|
||
测试位置:`ceph/src/pybind/mgr/dashboard/frontend/cypress/integration/visualTests`,命名约定:**`<component-name>.vrt-spec.ts`**。
|
||
|
||
### 10.8.1 本地运行
|
||
|
||
需要 **Applitools API key**(没有可以注册免费账号)。拿到后导出为环境变量 **`APPLITOOLS_API_KEY`**。
|
||
|
||
然后就可以像普通 cypress E2E 测试一样跑:用 `npx cypress open`,或 headless 模式 `npx cypress run`。
|
||
|
||
### 10.8.2 截图(Capturing Screenshots)
|
||
|
||
**Baseline screenshots(基线截图)** 是 checkpoint screenshots(或者说你 feature 分支上的截图)用来比对的对象。
|
||
|
||
做法:**先对 master 分支跑测试**捕获 baseline,然后**切到你的 feature 分支再跑一次**捕获 checkpoint。
|
||
|
||
然后登录 applitools.com,落地页会看到 applitools eyes test runner,里面能看到你所有的截图。**如果 baseline 和 checkpoint 之间有任何视觉回归或差异(diff),会用一层遮罩高亮出来。**
|
||
|
||
### 10.8.3 写更多视觉回归测试
|
||
|
||
参考 Applitools 官方 cypress sdk 文档:https://www.npmjs.com/package/@applitools/eyes-cypress#usage
|
||
|
||
### 10.8.4 Jenkins 中的视觉回归测试
|
||
|
||
目前所有视觉回归测试在 **ceph dashboard tests**(https://jenkins.ceph.com/job/ceph-dashboard-pull-requests)这个 GitHub check 下的 Jenkins job 里跑。
|
||
|
||
### 10.8.5 接受或拒绝差异
|
||
|
||
目前**只有 ceph dashboard 团队**有 applitools test runner 的读写权限。如果测试报告了差异:
|
||
|
||
- 想**接受**它们并更新 baseline 截图,或
|
||
- 差异是**真实的回归**,想**判为失败**,
|
||
|
||
按这个指南操作:https://applitools.com/docs/topics/test-manager/pages/page-test-results/tm-accepting-and-rejecting-steps.html
|
||
|
||
### 10.8.6 调试回归
|
||
|
||
本地跑测试时如果报告了回归,可以利用 **Applitools 的 Root Cause Analysis 特性**(https://applitools.com/docs/topics/test-manager/viewers/root-cause-analysis.html)找回归原因。
|
||
|
||
## 10.9 前端单元测试 vs E2E 测试(FAQ)
|
||
|
||
### 10.9.1 它们各自是为什么设计的?
|
||
|
||
**E2E 测试**:需要一个**功能完整的系统**,测试应用所有组件的交互(Ceph、back-end、front-end)。E2E 测试被设计成**模仿用户与应用交互时的行为**——例如创建/编辑/删除一个条目这样的工作流。测试还应验证**某些条目按用户点击 UI 时会看到的那样被显示**(例如一个菜单项,或者一个在测试中被创建的 pool 及其属性应显示在表格里)。
|
||
|
||
**Angular 单元测试**:顾名思义,是**更小代码单元**的测试。这些测试用于测试各种 Angular 组件(如 services、pipes 等)。**它们不需要连接后端,因此独立于后端。** 后端预期的数据在前端被 mock,用这些数据就能测试前端功能而不需要来自后端的真实数据。简单情况下是「静态输入 + 函数调用 + 期望的静态输出」;更复杂的例子包括组件的 state(component class 的属性)如何根据给定输入改变输出。
|
||
|
||
### 10.9.2 什么样的测试算「有效」?
|
||
|
||
这不好回答,但**用与已有 dashboard 测试相同方式写的新测试,一般应视为有效**。
|
||
|
||
- 单元测试应**聚焦在被测试的组件**上(Angular component、directive、service、pipe 等)。
|
||
- E2E 测试应聚焦测试**整个应用的功能**。**整体 E2E 测试中大约三分之一应该验证用户可见元素的正确性。**
|
||
|
||
### 10.9.3 一个测试应该长什么样?
|
||
|
||
- **单元测试**应聚焦所述目的,**不应试图在同一个 `it` 块里测其他东西**。
|
||
- **E2E 测试**应包含一段描述,要么验证**用户可见元素的正确性**,要么验证一个**完整流程**(例如 pool 的创建/验证/删除)。
|
||
|
||
### 10.9.4 应该覆盖什么?
|
||
|
||
- **E2E 测试**应**主要(但不排他)覆盖与后端的交互**。这样与后端的交互就被用来写成集成测试。
|
||
- **单元测试**应主要覆盖组件的**关键或复杂功能**(Angular Components、Services、Pipes、Directives 等)。
|
||
|
||
### 10.9.5 不应该覆盖什么?
|
||
|
||
- **避免重复测试**:不要为已经被前端单元测试覆盖的东西写 E2E 测试,反之亦然。(可能无法完全避免重叠。)
|
||
- **单元测试不应**被用来大量点击组件;**E2E 测试不应**被用来大量测试单个 Angular 组件。
|
||
|
||
### 10.9.6 最佳实践 / 指导方针
|
||
|
||
作为通用指导,官方遵循 **70/20/10 方法**:
|
||
|
||
| 类型 | 占比 |
|
||
|---|---|
|
||
| Unit tests | **70%** |
|
||
| Integration tests | **20%** |
|
||
| End-to-end tests | **10%** |
|
||
|
||
进一步阅读:https://testing.googleblog.com/2015/04/just-say-no-to-more-end-to-end-tests.html 及其中提到的 "Testing Pyramid"。
|
||
|
||
## 10.10 更多帮助与 Generator 示例
|
||
|
||
用 `ng help` 获取更多 Angular CLI 帮助,或看 Angular CLI README(https://github.com/angular/angular-cli/blob/master/README.md)。
|
||
|
||
**Generator 示例(官方原文):**
|
||
|
||
```bash
|
||
# Create module 'Core'
|
||
src/app> ng generate module core -m=app --routing
|
||
|
||
# Create module 'Auth' under module 'Core'
|
||
src/app/core> ng generate module auth -m=core --routing
|
||
or, alternatively:
|
||
src/app> ng generate module core/auth -m=core --routing
|
||
|
||
# Create component 'Login' under module 'Auth'
|
||
src/app/core/auth> ng generate component login -m=core/auth
|
||
or, alternatively:
|
||
src/app> ng generate component core/auth/login -m=core/auth
|
||
```
|
||
|
||
## 10.11 前端 TypeScript 代码风格指南(推荐)
|
||
|
||
**按来源对 import 分组,组之间用空行分隔。** 来源分组可以是 **Angular**、**external** 或 **internal**。
|
||
|
||
```typescript
|
||
import { Component } from '@angular/core';
|
||
import { Router } from '@angular/router';
|
||
|
||
import { ToastrManager } from 'ngx-toastr';
|
||
|
||
import { Credentials } from '../../../shared/models/credentials.model';
|
||
import { HostService } from './services/host.service';
|
||
```
|
||
|
||
## 10.12 可复用前端组件
|
||
|
||
有多个组件可以在不同页面上复用,这些组件声明在 components module 里:
|
||
|
||
```text
|
||
src/pybind/mgr/dashboard/frontend/src/app/shared/components
|
||
```
|
||
|
||
### `cd-helper` 组件
|
||
|
||
**这个组件应该用来给用户提供补充信息。**
|
||
|
||
```html
|
||
<cd-helper>
|
||
Some <strong>helper</strong> html text
|
||
</cd-helper>
|
||
```
|
||
|
||
## 10.13 术语与措辞(Terminology and wording)
|
||
|
||
**不要使用 Ceph 组件名,官方建议用逻辑/通用名称**:用 **Block** 而不是 RBD,用 **Filesystem** 而不是 CephFS,用 **Object** 而不是 RGW。不过由于 Ceph-Dashboard 无法完全隐藏 Ceph 内部,一些 Ceph 专有名称可能仍然可见。
|
||
|
||
**动作标签和其他文本元素(表单标题、按钮等)的措辞**,官方选择遵循这些指南:https://www.patternfly.org/styles/terminology-and-wording/#terminology-and-wording-for-action-labels
|
||
|
||
**经验法则**:**大多数表单用 'Create' 和 'Delete'**,而不是 'Add' 和 'Remove'——**除非**某个已创建的条目被**加入**或**移出**一组条目(例如:给用户 'Add permission' vs 'Create (new) permission')。
|
||
|
||
**为了强制使用这套措辞,创建了一个 `ActionLabelsI18n` service**,为 UI 元素提供翻译好的标签。
|
||
|
||
## 10.14 前端品牌定制(Frontend branding)
|
||
|
||
每个厂商都可以按自己的需要定制 'Ceph dashboard'。无论是 logo、HTML 模板还是 TypeScript,**frontend 文件夹里的每个文件都可以被替换。**
|
||
|
||
**替换方法**:打开 `./frontend/angular.json`,滚到 production 配置里的 **`fileReplacements`** 段。在这里添加你想定制的文件。
|
||
|
||
**官方命名建议**:把定制版文件放在与原文件**同一个目录**,并在**文件名里、紧挨着扩展名之前**加一个 **`.brand`**。一个 `fileReplacement` 例如:
|
||
|
||
```json
|
||
{
|
||
"replace": "src/app/core/auth/login/login.component.html",
|
||
"with": "src/app/core/auth/login/login.component.brand.html"
|
||
}
|
||
```
|
||
|
||
**serve 或 build 定制后的 UI:**
|
||
|
||
```bash
|
||
$ npm run start -- --prod
|
||
```
|
||
|
||
或
|
||
|
||
```bash
|
||
$ npm run build -- --prod
|
||
```
|
||
|
||
**两个已知限制:**
|
||
|
||
1. **serve 或 build UI 时目前无法同时使用多个 configuration**。也就是说,只为 branding 的 `fileReplacements` 单开一个 configuration 不可行,因为你反正要用 production configuration(https://github.com/angular/angular-cli/issues/10612)。
|
||
2. **`fileReplacements` 目前不支持 glob 表达式**。在该特性实现之前,你必须手工把 file replacements 加到 `angular.json`(https://github.com/angular/angular-cli/issues/12354)。
|
||
|
||
> 但**你仍应坚持建议的命名方案**,因为一旦未来支持 glob,你就容易迁移。
|
||
|
||
**改变量默认值或加自己的变量**:覆盖 `./frontend/src/styles/vendor/_variables.scss`,直接重新赋值你想改的变量,例如:
|
||
|
||
```scss
|
||
$color-primary: teal;
|
||
```
|
||
|
||
**覆盖或扩展默认 CSS**:把你的样式加到 `./frontend/src/styles/vendor/_style-overrides.scss`。
|
||
|
||
## 10.15 UI Style Guide
|
||
|
||
样式指南的创建是为了记录 Ceph Dashboard 的标准,并在整个项目中保持一致性,让贡献者更容易设计 mockup 和 Dashboard 设计。
|
||
|
||
**Ceph Dashboard 的开发环境启用了 live reloading**,UI 里的改动会反映在打开的浏览器窗口中。Ceph Dashboard 使用 **Bootstrap** 作为主要的第三方 CSS 库。
|
||
|
||
**避免重复代码。** 尽量复用已有的 SCSS 声明,与现有 UI 保持一致。
|
||
|
||
**总是先检查是否已有类似你想写的代码。** 应尽量保持与现有代码相同的 look-and-feel。
|
||
|
||
### 10.15.1 Colors
|
||
|
||
Ceph Dashboard UI 中用到的所有颜色都列在 **`frontend/src/styles/defaults/_bootstrap-defaults.scss`**。
|
||
|
||
- **使用新颜色时,总是在 `_bootstrap-defaults.scss` 里定义颜色变量,并使用变量而不是硬编码颜色值**,这样颜色的改动会反映到相似的 UI 元素上。
|
||
- Ceph Dashboard 的**主色是 `$primary`**,用于导航组件,以及作为表单 input 组件的 `$border-color`。
|
||
- **次要色是 `$secondary`**,是 Ceph Dashboard 的背景色。
|
||
|
||
### 10.15.2 Buttons
|
||
|
||
按钮用于执行动作,如 "Submit"、"Edit"、"Create"、"Update"。
|
||
|
||
| 场景 | 主按钮 | 次按钮 |
|
||
|---|---|---|
|
||
| **Forms** | 用 **`cd-submit-button`** 组件;文字**应与表单标题相同**且**遵循 title case** | 用 **`cd-back-button`** 组件;文字应为 **`Cancel`** |
|
||
| **Modals** | 用 **`cd-submit-button`** 组件;文字遵循 title case 且**对应要执行的动作** | 用 **`cd-back-button`** 组件;文字应为 **`Close`** |
|
||
|
||
其他约定:
|
||
|
||
- **位置**:`Perform action` 按钮**始终在右**,`Cancel` 按钮**始终在左**。
|
||
- **Disclosure Button**:用于让用户显示/隐藏界面中的附加内容。
|
||
- **Action Button**:用于执行编辑或更新组件等动作。**所有 action button 都应有与其动作对应的图标**,按钮文字遵循 title case。按钮颜色应与表单主按钮颜色一致。
|
||
- **Drop Down Buttons**:用于显示预定义的动作列表。**所有 dropdown 按钮都有与其动作对应的图标。**
|
||
|
||
### 10.15.3 Links
|
||
|
||
用文本超链接作为导航,引导用户到应用中的新页面,或锚定到页面内的某个 section。**超链接的颜色应为 `$primary`。**
|
||
|
||
### 10.15.4 Forms
|
||
|
||
- **用红色描边标记无效表单字段**并显示**有意义的错误消息**。消息用红色字体,**尽可能具体**。
|
||
- **必填字段的错误消息必须 exactly 是**:`This field is required.`
|
||
- **用绿色描边和表单末尾的绿色对勾标记有效表单**。
|
||
- **Section 的标题不应比父级更大。**
|
||
|
||
### 10.15.5 Modals
|
||
|
||
- **模糊化背景中的界面元素**,让 modal 内容成为焦点。
|
||
- **Modal 的标题应反映它能执行的动作**,并在 modal 顶部清楚写明。
|
||
- **在 footer 用 `cd-back-button` 组件关闭 modal。**
|
||
|
||
### 10.15.6 Icons
|
||
|
||
- 使用 **Fork Awesome**(https://forkaweso.me/Fork-Awesome/)的 class 作为图标。
|
||
- 已使用图标清单在 **`src/app/shared/enum/icons.enum.ts`**,**HTML 里应引用这些**,便于日后修改。
|
||
- **图标与文字相邻时,水平居中对齐。图标堆叠时,也应垂直居中对齐。**
|
||
- **按钮配小图标。通知配大图标。**
|
||
|
||
### 10.15.7 Navigation
|
||
|
||
- **局部导航用 tabs。**
|
||
- **整体导航用可展开的垂直导航**,按需折叠和展开条目。
|
||
|
||
### 10.15.8 Alerts and notifications
|
||
|
||
| 类型 | 颜色 class |
|
||
|---|---|
|
||
| 默认通知 | `text-info` |
|
||
| 成功通知 | `text-success` |
|
||
| 失败通知 | `text-danger` |
|
||
|
||
## 10.16 前端错误处理
|
||
|
||
处理前端错误有一个**通用 Error Component**,位于:
|
||
|
||
```text
|
||
./src/pybind/mgr/dashboard/frontend/src/app/core/error
|
||
```
|
||
|
||
**上报一个新错误**:只需**扩展 `error.ts` 文件里的 `DashboardError` 类**,并为新错误添加特定的 header 和 message。
|
||
|
||
**已经有一些通用错误类就位**,可以在不同场景调用和复用,例如 **`DashboardNotFoundError`** 和 **`DashboardForbiddenError`**。
|
||
|
||
用法示例:
|
||
|
||
```typescript
|
||
throw new DashboardNotFoundError();
|
||
```
|
||
|
||
## 10.17 国际化(Internationalization, i18n)
|
||
|
||
### 10.17.1 从源码提取消息
|
||
|
||
在 `src/pybind/mgr/dashboard/frontend` 里跑:
|
||
|
||
```bash
|
||
$ npm run i18n:extract
|
||
```
|
||
|
||
这会**先从 HTML 模板里提取所有被标记的消息**,然后**把 TypeScript 文件里所有被标记的字符串加到翻译模板中**。
|
||
|
||
由于 **Angular 本身仍不支持从 TypeScript 文件提取**,官方使用 **`ngx-translator`**(https://github.com/ngx-translate/i18n-polyfill)的 extractor 来解析 TypeScript 文件。
|
||
|
||
命令成功跑完后,应该已经创建或更新了文件 **`src/locale/messages.xlf`**。
|
||
|
||
> **该文件不被 git 跟踪**,你可以用它来离线开始翻译,或用于添加/更新 transifex 上的资源文件。
|
||
|
||
### 10.17.2 支持的语言
|
||
|
||
**所有支持的语言都应注册在 `supported-languages.enum.ts` 的两个 export 里**,并在 **`language-selector.component.spec.ts`** 里有对应的测试。
|
||
|
||
**`SupportedLanguages` enum 提供默认语言选择的列表。**
|
||
|
||
### 10.17.3 翻译流程
|
||
|
||
为了便利 Dashboard 的翻译流程,官方使用一个叫 **transifex**(https://www.transifex.com/)的 web 工具。
|
||
|
||
想帮忙翻译任何语言,只需去 transifex 项目页(https://www.transifex.com/ceph/ceph-dashboard/),加入项目,就可以立即开始翻译。
|
||
|
||
**所有翻译会被 review,之后推送到 upstream。**
|
||
|
||
### 10.17.4 更新已翻译的消息
|
||
|
||
1. 下载 **transifex CLI tool**(https://github.com/transifex/cli)
|
||
2. 创建 API token(https://www.transifex.com/user/settings/api/)
|
||
3. **推送翻译**:
|
||
|
||
```bash
|
||
$ tx push -s
|
||
```
|
||
|
||
这会把源文件推到 transifex。
|
||
|
||
4. **拉取翻译**:
|
||
|
||
```bash
|
||
$ tx pull -r ceph-dashboard.<resource_slug> -f
|
||
```
|
||
|
||
例如 `tx pull -r ceph-dashboard.main`。这会拉取该 resource 的所有翻译。
|
||
|
||
### 10.17.5 给 transifex 加一个新的 release resource
|
||
|
||
为了组织翻译,官方**为每一个 Ceph release 创建一个 transifex resource**。这意味着**一旦发布了新版本,release 分支上的 `src/pybind/mgr/dashboard/frontend/.tx/config` 就需要更新**。
|
||
|
||
把:
|
||
|
||
```text
|
||
resource_name = Main
|
||
```
|
||
|
||
替换成:
|
||
|
||
```text
|
||
resource_name = <Release-name>
|
||
```
|
||
|
||
例如 Tentacle release 的 resource 定义:
|
||
|
||
```text
|
||
resource_name = Tentacle
|
||
```
|
||
|
||
并把:
|
||
|
||
```text
|
||
[o:ceph:p:ceph-dashboard:r:main]
|
||
```
|
||
|
||
替换成:
|
||
|
||
```text
|
||
[o:ceph:p:ceph-dashboard:r:<release-name>]
|
||
```
|
||
|
||
例如 Tentacle release:
|
||
|
||
```text
|
||
[o:ceph:p:ceph-dashboard:r:tentacle]
|
||
```
|
||
|
||
完成后推送翻译:
|
||
|
||
```bash
|
||
$ tx push -s
|
||
```
|
||
|
||
> **Note**:只有 `<Release-name>` 首字母大写。
|
||
|
||
### 10.17.6 i18n 书写建议(官方给了 4 组 avoid/recommended)
|
||
|
||
**1)字符串应与元素在同一行开始和结束:**
|
||
|
||
```html
|
||
<!-- avoid -->
|
||
<span i18n>
|
||
Foo
|
||
</span>
|
||
|
||
<!-- recommended -->
|
||
<span i18n>Foo</span>
|
||
|
||
|
||
<!-- avoid -->
|
||
<span i18n>
|
||
Foo bar baz.
|
||
Foo bar baz.
|
||
</span>
|
||
|
||
<!-- recommended -->
|
||
<span i18n>Foo bar baz.
|
||
Foo bar baz.</span>
|
||
```
|
||
|
||
**2)孤立的插值不应被翻译:**
|
||
|
||
```html
|
||
<!-- avoid -->
|
||
<span i18n>{{ foo }}</span>
|
||
|
||
<!-- recommended -->
|
||
<span>{{ foo }}</span>
|
||
```
|
||
|
||
**3)用在句子里的插值应保留在翻译中:**
|
||
|
||
```html
|
||
<!-- recommended -->
|
||
<span i18n>There are {{ x }} OSDs.</span>
|
||
```
|
||
|
||
**4)移除翻译上下文之外的元素:**
|
||
|
||
```html
|
||
<!-- avoid -->
|
||
<label i18n>
|
||
Profile
|
||
<span class="required"></span>
|
||
</label>
|
||
|
||
<!-- recommended -->
|
||
<label>
|
||
<ng-container i18n>Profile<ng-container>
|
||
<span class="required"></span>
|
||
</label>
|
||
```
|
||
|
||
**5)保留影响句子的元素:**
|
||
|
||
```html
|
||
<!-- recommended -->
|
||
<span i18n>Profile <b>foo</b> will be removed.</span>
|
||
```
|
||
|
||
## 10.18 无障碍(Accessibility)
|
||
|
||
Ceph Dashboard 的很多部分以 **WCAG 2.1**(Web Content Accessibility Guidelines 2.1,https://www.w3.org/TR/WCAG21/)**level A** 无障碍一致性指南为模型。通过实施无障碍最佳实践,你在改善 Ceph Dashboard 对盲人和视障用户的可用性。
|
||
|
||
### 10.18.1 提交改动前的 5 项自检
|
||
|
||
1. 给**可操作的 HTML 元素**加 **ARIA labels 和 descriptions**(https://www.w3.org/TR/wai-aria/)。
|
||
2. **别忘了**给 ARIA labels/descriptions 或任何用户可读文本**打上翻译标记**(`i18n-title`、`i18n-aria-label`...)。
|
||
3. 给**行为与其本意不同**(例如 `<a>` 标签当 `<button>` 用)或**提供扩展行为**(roles)的 HTML 元素加 **ARIA roles**(https://www.w3.org/TR/wai-aria/#usage_intro)。
|
||
4. **避免糟糕的色彩对比选择**(前景-背景)。见 10.18.3 的工具。
|
||
5. **测试菜单或下拉框时,确保在展开和收起两种状态下都用无障碍检查器扫一遍。** 有时问题在菜单收起时被隐藏。
|
||
|
||
### 10.18.2 无障碍检查器
|
||
|
||
| 工具 | 链接 |
|
||
|---|---|
|
||
| Accessibility insights plugin | https://accessibilityinsights.io/downloads/ |
|
||
| Site Improve plugin | https://www.siteimprove.com/integrations/browser-extensions/ |
|
||
| Axe devtools | https://www.deque.com/axe/devtools/ |
|
||
|
||
> **用两个或更多工具测试可以大幅提升无障碍违规的检出率。**
|
||
|
||
### 10.18.3 色彩对比检查器
|
||
|
||
| 工具 | 链接 |
|
||
|---|---|
|
||
| Accessible web color-contrast checker | https://accessibleweb.com/color-contrast-checker/ |
|
||
| Colorsafe generator | https://colorsafe.co/ |
|
||
|
||
### 10.18.4 无障碍 linter
|
||
|
||
如果你用 **VSCode**,可以装 **axe accessibility linter**(https://marketplace.visualstudio.com/items?itemName=deque-systems.vscode-axe-linter),能在开发时帮你发现并修复潜在问题。
|
||
|
||
### 10.18.5 无障碍测试
|
||
|
||
基于 Cypress 的 e2e 测试套件**支持加入无障碍测试**,使用 **axe-core**(https://github.com/dequelabs/axe-core)和 **cypress-axe**(https://github.com/component-driven/cypress-axe)。**也可以直接使用自定义 Cypress 命令 `cy.checkAccessibility`**。这是防止高影响组件出现无障碍回归的好办法。
|
||
|
||
测试位于 dashboard 的 a11y 目录:`./src/pybind/mgr/dashboard/frontend/cypress/integration/a11y`。示例:
|
||
|
||
```typescript
|
||
describe('Navigation accessibility', { retries: 0 }, () => {
|
||
const shared = new NavigationPageHelper();
|
||
|
||
beforeEach(() => {
|
||
cy.login();
|
||
shared.navigateTo();
|
||
});
|
||
|
||
it('top-nav should have no accessibility violations', () => {
|
||
cy.injectAxe();
|
||
cy.checkAccessibility('.cd-navbar-top');
|
||
});
|
||
|
||
it('sidebar should have no accessibility violations', () => {
|
||
cy.injectAxe();
|
||
cy.checkAccessibility('nav[id=sidebar]');
|
||
});
|
||
|
||
});
|
||
```
|
||
|
||
### 10.18.6 额外指南
|
||
|
||
如果你不确定该遵循哪种 UI 模式来实施无障碍修复,可以使用 **patternfly** 的指南:https://www.patternfly.org/v4/accessibility/accessibility-fundamentals
|
||
|
||
## 10.19 Backend Development(后端开发)
|
||
|
||
Dashboard 模块的 Python 后端代码需要若干 Python 模块,列在 **`requirements.txt`** 里。用 pip(https://pypi.python.org/pypi/pip)安装:
|
||
|
||
```bash
|
||
pip install -r requirements.txt
|
||
```
|
||
|
||
在 **`src/pybind/mgr/dashboard`** 目录里执行。
|
||
|
||
如果你用 **ceph-dev-docker** 开发环境,只需从顶层目录跑 `./install_deps.sh` 即可安装它们。
|
||
|
||
## 10.20 后端单元测试
|
||
|
||
Dashboard 里有**两种**后端测试:
|
||
|
||
1. **基于 `tox` 的单元测试**
|
||
2. **基于 Teuthology 的 API 测试**
|
||
|
||
### 10.20.1 基于 tox 的单元测试
|
||
|
||
官方提供了一个 `tox` 配置文件,会在 Python 3 下跑单元测试,并运行 linting 工具来保证代码统一性。
|
||
|
||
**前置**:需要安装 `tox` 和 `coverage`。
|
||
|
||
```bash
|
||
dnf install python-tox python-coverage # Fedora Linux
|
||
```
|
||
|
||
或:
|
||
|
||
```bash
|
||
$ pip install tox
|
||
$ pip install coverage
|
||
```
|
||
|
||
**跑测试**:在 dashboard 目录(即 `tox.ini` 所在目录)跑 `src/script/run_tox.sh`:
|
||
|
||
```bash
|
||
## Run Python 3 tests+lint commands:
|
||
$ ../../../script/run_tox.sh --tox-env py3,lint,check
|
||
|
||
## Run Python 3 arbitrary command (e.g. 1 single test):
|
||
$ ../../../script/run_tox.sh --tox-env py3 "" tests/test_rgw_client.py::RgwClientTest::test_ssl_verify
|
||
```
|
||
|
||
**也可以直接跑 tox 而不是 `run_tox.sh`**:
|
||
|
||
```bash
|
||
## Run Python 3 tests command:
|
||
$ tox -e py3
|
||
|
||
## Run Python 3 arbitrary command (e.g. 1 single test):
|
||
$ tox -e py3 tests/test_rgw_client.py::RgwClientTest::test_ssl_verify
|
||
```
|
||
|
||
**自动按 PEP8 修复和格式化 Python 文件:**
|
||
|
||
```bash
|
||
run_tox.sh --tox-env fix
|
||
# 或
|
||
tox -e fix
|
||
```
|
||
|
||
**覆盖率**:跑测试时会收集后端代码的 coverage 信息。可以看 tox 输出的 coverage 信息,或在 tox 成功跑完后执行:
|
||
|
||
```bash
|
||
$ coverage html
|
||
```
|
||
|
||
这会创建一个 **`htmlcov`** 目录,包含后端代码覆盖率的 HTML 表示。
|
||
|
||
### 10.20.2 基于 Teuthology 的 API 测试
|
||
|
||
**怎么跑已有的 API 测试**:为了在真实 Ceph 集群上跑 API 测试,官方借助 **Teuthology 框架**。好处是**能抓到因 Ceph 内部代码变更而产生的 bug**。
|
||
|
||
官方的 **`run-backend-api-tests.sh`** 脚本会**在跑 Teuthology 测试之前启动一个 `vstart` Ceph 集群,跑完后再停掉集群**。(当然这要求你已经构建/编译过 Ceph。)
|
||
|
||
```bash
|
||
$ ./run-backend-api-tests.sh
|
||
```
|
||
|
||
或者指定测试名跑一个或多个特定测试:
|
||
|
||
```bash
|
||
$ ./run-backend-api-tests.sh tasks.mgr.dashboard.test_pool.PoolTest
|
||
```
|
||
|
||
或者 `source` 该脚本手工跑:
|
||
|
||
```bash
|
||
$ source run-backend-api-tests.sh
|
||
$ run_teuthology_tests [tests]...
|
||
$ cleanup_teuthology
|
||
```
|
||
|
||
**怎么写自己的测试**:两种方式。
|
||
|
||
1. **扩展 `qa/tasks/mgr/dashboard` 目录里已有的某个测试类**。
|
||
2. **添加你自己的 API 测试模块**(例如你在创建一个新 controller 时)。做法:把包含新测试类的文件加到 `qa/tasks/mgr/dashboard` 目录,并在那里实现所有测试。
|
||
|
||
> **别忘了**把新模块的路径加到 **`qa/suites/rados/mgr/tasks/dashboard.yaml`** 的 **`modules`** 段。
|
||
|
||
**短示例**:假设你创建了一个新 controller `my_new_controller.py` 和相关的测试模块 `test_my_new_controller.py`,你需要把 `tasks.mgr.dashboard.test_my_new_controller` 加到 `dashboard.yaml` 文件的 `modules` 段。
|
||
|
||
> **另外,如果你在删除测试模块,记住同时删掉相关的段,否则 Teuthology 测试运行会失败。**
|
||
|
||
**合入前的要求**:
|
||
|
||
- 提交 PR 前,请**在你的开发环境里跑过 API 测试**(如上所述)。
|
||
- 还要确保**一次完整的 Teuthology/sepia lab QA run(基于你的改动)已成功完成**,然后才能被 merge。
|
||
- **你不需要自己调度 QA run**——只要你觉得可以合了(例如 make check 成功、PR 已 approved、所有评论都已处理),就给 PR 加上 **`'needs-qa'`** 标签。**有 Teuthology/sepia lab 访问权限的开发者之一会处理并把结果反馈给你。**
|
||
|
||
## 10.21 如何新增一个 controller?
|
||
|
||
一个 **controller** 是一个继承自 **`BaseController`** 类的 Python class,并用 **`@Controller`**、**`@ApiController`** 或 **`@UiApiController`** 之一装饰。这个 Python class **必须存放在 `controllers` 目录下的一个 Python 文件里**。Dashboard 模块会在启动时**自动加载**你的新 controller。
|
||
|
||
**三个装饰器的关系与用途:**
|
||
|
||
| 装饰器 | 用途 | URL 前缀 |
|
||
|---|---|---|
|
||
| `@Controller` | 其他任何类型的 controller | 见下 |
|
||
| `@ApiController` | 提供 API 风格 REST 接口的 controller(是 `@Controller` 的特化) | `/api` |
|
||
| `@UiApiController` | **被 UI 消费但不属于 'public' API** 的端点(也是 `@Controller` 的特化) | `/ui-api` |
|
||
|
||
**一个 controller 有一个 URL prefix path**,在 controller 装饰器里指定,该 controller 暴露的所有端点都共享这个前缀。
|
||
|
||
**一个 controller 的端点通过在 controller class 上实现一个用 `@Endpoint` 装饰的方法来暴露。**
|
||
|
||
**示例**:在 `controllers` 目录创建 `ping.py`:
|
||
|
||
```python
|
||
from ..tools import Controller, ApiController, UiApiController, BaseController, Endpoint
|
||
|
||
@Controller('/ping')
|
||
class Ping(BaseController):
|
||
@Endpoint()
|
||
def hello(self):
|
||
return {'msg': "Hello"}
|
||
|
||
@ApiController('/ping')
|
||
class ApiPing(BaseController):
|
||
@Endpoint()
|
||
def hello(self):
|
||
return {'msg': "Hello"}
|
||
|
||
@UiApiController('/ping')
|
||
class UiApiPing(BaseController):
|
||
@Endpoint()
|
||
def hello(self):
|
||
return {'msg': "Hello"}
|
||
```
|
||
|
||
**URL 生成规则:**
|
||
|
||
| Controller | hello 端点的 URL | 规则 |
|
||
|---|---|---|
|
||
| `Ping` | `https://mgr_hostname:8443/ping/hello`(HTTP GET) | controller URL path `/ping` 拼接方法名 `hello` |
|
||
| `ApiPing` | `https://mgr_hostname:8443/api/ping/hello`(HTTP GET) | API controller URL path `/ping` **被 `/api` 前缀修饰**,再拼方法名 |
|
||
| `UiApiPing` | `https://mgr_hostname:8443/ui-api/ping/hello` | 被 `/ui-api` 前缀修饰 |
|
||
|
||
内部实现上:
|
||
|
||
```python
|
||
@ApiController('/ping') <=> @Controller('/ping', base_url="/api")
|
||
@UiApiController('/ping') <=> @Controller('/ping', base_url="/ui-api")
|
||
```
|
||
|
||
### 10.21.1 `@Endpoint` 装饰器支持的参数
|
||
|
||
| 参数 | 默认值 | 说明 |
|
||
|---|---|---|
|
||
| `method="GET"` | `GET` | 允许访问该端点的 HTTP 方法 |
|
||
| `path="/<method_name>"` | 方法名 | 端点的 URL path,**不含** controller URL path 前缀 |
|
||
| `path_params=[]` | `[]` | 对应 URL path 参数的方法参数名列表。**只能在 `method in ['POST', 'PUT']` 时使用** |
|
||
| `query_params=[]` | `[]` | 对应 URL query 参数的方法参数名列表 |
|
||
| `json_response=True` | `True` | 端点响应是否应序列化为 JSON 格式 |
|
||
| `proxy=False` | `False` | 该端点是否应作为代理使用 |
|
||
|
||
### 10.21.2 方法参数如何映射为 path / query / body 参数
|
||
|
||
| HTTP 方法 | 默认行为 | 覆盖方式 |
|
||
|---|---|---|
|
||
| **GET / DELETE** | **非可选参数**默认视为 **path 参数**;**可选参数**视为 **query 参数** | 在 endpoint 装饰器里指定 `query_parameters`,可以让一个非可选参数变成 query 参数 |
|
||
| **POST / PUT** | **所有**方法参数默认视为 **body 参数** | 用 `path_params` 和 `query_params` 指定哪些方法参数是 path、哪些是 query |
|
||
|
||
**Body 参数**从请求 body 解码,可以是 form 格式,也可以是 JSON 格式的 dictionary。
|
||
|
||
### 10.21.3 自定义端点的完整示例
|
||
|
||
```python
|
||
from ..tools import Controller, BaseController, Endpoint
|
||
|
||
@Controller('/ping')
|
||
class Ping(BaseController):
|
||
|
||
# URL: /ping/{key}?opt1=...&opt2=...
|
||
@Endpoint(path="/", query_params=['opt1'])
|
||
def index(self, key, opt1, opt2=None):
|
||
"""..."""
|
||
|
||
# URL: /ping/{key}?opt1=...&opt2=...
|
||
@Endpoint(query_params=['opt1'])
|
||
def __call__(self, key, opt1, opt2=None):
|
||
"""..."""
|
||
|
||
# URL: /ping/post/{key1}/{key2}
|
||
@Endpoint('POST', path_params=['key1', 'key2'])
|
||
def post(self, key1, key2, data1, data2=None):
|
||
"""..."""
|
||
```
|
||
|
||
要点:
|
||
|
||
- **`path` 选项可以覆盖生成的端点 URL,从而不在 URL 里使用方法名。** 在 `index` 方法里把 `path` 设为 `"/"`,生成的端点就可以通过 controller 的**根 URL** 访问。
|
||
- **另一种**生成「只通过 controller 的 path URL 访问」的端点的方式是使用 **`__call__`** 方法(如上例)。
|
||
- 从第三个方法可以看到:**path 参数是从 URL 里解析「URL path 之后由 `/` 分隔的值列表」得到的**——对 `index` 方法是 `/ping` 之后,对 `post` 方法是 `/ping/post` 之后。
|
||
|
||
### 10.21.4 显式声明 URL 中的 path 参数
|
||
|
||
用 Python 方法参数来定义端点 URL 里的 path 参数很容易,但**在 URL 结构中这些参数的位置上仍稍显死板**。有时我们想显式定义一个**把 path 参数与 URL 静态部分混合**的 URL scheme。controller 基础设施**支持在 controller 级别和方法级别都声明带显式 path 参数的 URL path**。
|
||
|
||
```python
|
||
from ..tools import Controller, BaseController, Endpoint
|
||
|
||
@Controller('/ping/{node}/stats')
|
||
class Ping(BaseController):
|
||
|
||
# URL: /ping/{node}/stats/{date}/latency?unit=...
|
||
@Endpoint(path="/{date}/latency")
|
||
def latency(self, node, date, unit="ms"):
|
||
""" ..."""
|
||
```
|
||
|
||
这里在 controller URL path 里显式声明了 path 参数 `{node}`,在 `latency` 方法里声明了 path 参数 `{date}`。`latency` 方法的端点可通过 `https://mgr_hostname:8443/ping/{node}/stats/{date}/latency` 访问。
|
||
|
||
> **想看 `@Endpoint` 装饰器的完整示例集,请查看单元测试文件 `tests/test_controllers.py`。** 那里有很多如何自定义 endpoint 方法的例子。
|
||
|
||
## 10.22 实现 Proxy Controller
|
||
|
||
有时你可能需要把一些请求从 Dashboard 前端**直接转发给外部服务**。为此官方提供了 **`@Proxy`** 装饰器。(具体例子可以看 `controllers/rgw.py`,官方在那里实现了一个 RGW Admin Ops proxy。)
|
||
|
||
**`@Proxy` 装饰器是 `@Endpoint` 装饰器的包装**,已经把端点定制成作为代理工作。
|
||
|
||
**代理端点的工作方式**:捕获**跟在 controller URL 前缀路径之后的 URL path**,并且**不对请求体做任何解码**。
|
||
|
||
```python
|
||
from ..tools import Controller, BaseController, Proxy
|
||
|
||
@Controller('/foo/proxy')
|
||
class FooServiceProxy(BaseController):
|
||
|
||
@Proxy()
|
||
def proxy(self, path, **params):
|
||
"""
|
||
if requested URL is "/foo/proxy/access/service?opt=1"
|
||
then path is "access/service" and params is {'opt': '1'}
|
||
"""
|
||
```
|
||
|
||
## 10.23 `RESTController` 是怎么工作的
|
||
|
||
官方还提供了一个用 **`RESTController`** class 创建 REST 风格 controller 的简单机制。**任何继承 `RESTController` 的 class 默认都会返回 JSON。**
|
||
|
||
`RESTController` 基本上是一个额外的抽象层,用来简化并统一对 **collection** 的操作。一个 collection 就是**一组特定类型的对象组成的数组**。`RESTController` 启用了一些**请求类型 + 给定参数 → 特定方法名**的默认映射。
|
||
|
||
```python
|
||
import cherrypy
|
||
from ..tools import ApiController, RESTController
|
||
|
||
@ApiController('ping')
|
||
class Ping(RESTController):
|
||
def list(self):
|
||
return {"msg": "Hello"}
|
||
|
||
def get(self, id):
|
||
return self.objects[id]
|
||
```
|
||
|
||
上例中,`list` 方法被自动用于**所有不带额外参数、请求类型为 `GET`** 的 `api/ping` 请求。如果请求**带了一个额外参数**(本例是 ID),它就不再映射到 `list`,而是映射到 `get`,返回给定 ID 的元素(假设 `self.objects` 之前已被填充)。
|
||
|
||
**完整的默认映射表(官方表格):**
|
||
|
||
| Request type | Arguments | Method | Status Code |
|
||
|---|---|---|---|
|
||
| GET | No | `list` | 200 |
|
||
| PUT | No | `bulk_set` | 200 |
|
||
| POST | No | `create` | 201 |
|
||
| DELETE | No | `bulk_delete` | 204 |
|
||
| GET | Yes | `get` | 200 |
|
||
| PUT | Yes | `set` | 200 |
|
||
| DELETE | Yes | `delete` | 204 |
|
||
|
||
**要为上述方法使用自定义端点,可以用 `@RESTController.MethodMap`:**
|
||
|
||
```python
|
||
import cherrypy
|
||
from ..tools import ApiController, RESTController
|
||
|
||
@RESTController.MethodMap(version='0.1')
|
||
def create(self):
|
||
return {"msg": "Hello"}
|
||
```
|
||
|
||
**`MethodMap` 装饰器支持三个自定义参数:**
|
||
|
||
| 参数 | 说明 |
|
||
|---|---|
|
||
| `resource"` | resource id(官方原文如此,含一个多余引号) |
|
||
| `status=200` | 设置 HTTP 状态响应码 |
|
||
| `version` | version |
|
||
|
||
## 10.24 如何在 `RESTController` 里使用自定义 API 端点?
|
||
|
||
**如果你没有任何访问限制,可以用 `@Endpoint`。**
|
||
|
||
**如果你给端点设置了 permission scope 来限制访问,`@Endpoint` 会失败**,因为它不知道该用哪个 permission property。
|
||
|
||
**在一个受限制的 `RESTController` 里使用自定义端点,要用 `@RESTController.Collection`。** 如果你在 `RESTController` class 里设置了 `RESOURCE_ID`,也可以选择 `@RESTController.Resource`。
|
||
|
||
```python
|
||
import cherrypy
|
||
from ..tools import ApiController, RESTController
|
||
|
||
@ApiController('ping', Scope.Ping)
|
||
class Ping(RESTController):
|
||
RESOURCE_ID = 'ping'
|
||
|
||
@RESTController.Resource('GET')
|
||
def some_get_endpoint(self):
|
||
return {"msg": "Hello"}
|
||
|
||
@RESTController.Collection('POST')
|
||
def some_post_endpoint(self, **data):
|
||
return {"msg": data}
|
||
```
|
||
|
||
**这两个装饰器都支持五个自定义参数:**
|
||
|
||
| 参数 | 说明 |
|
||
|---|---|
|
||
| `method="GET"` | 允许访问该端点的 HTTP 方法 |
|
||
| `path="/<method_name>"` | 端点的 URL path,不含 controller URL path 前缀 |
|
||
| `status=200` | 设置 HTTP 状态响应码 |
|
||
| `query_params=[]` | 对应 URL query 参数的方法参数名列表 |
|
||
| `version` | version |
|
||
|
||
## 10.25 如何限制 controller 的访问?
|
||
|
||
**所有 controller 默认都需要认证。**
|
||
|
||
如果你要求某个 controller **可以免认证访问**,可以给 controller 装饰器加参数 **`secure=False`**:
|
||
|
||
```python
|
||
import cherrypy
|
||
from . import ApiController, RESTController
|
||
|
||
|
||
@ApiController('ping', secure=False)
|
||
class Ping(RESTController):
|
||
def list(self):
|
||
return {"msg": "Hello"}
|
||
```
|
||
|
||
## 10.26 如何创建一个使用 'public' API 的专用 UI 端点?
|
||
|
||
有时我们想把**多次调用合并成一次**,以节省带宽或其他性能原因。
|
||
|
||
做法:首先创建一个 **`@UiApiController`**(用于被 UI 消费但不属于 'public' API 的端点),**让这个 ui class 继承自那个 REST controller class**,这样你就能使用 api controller 的所有方法。
|
||
|
||
```python
|
||
import cherrypy
|
||
from . import UiApiController, ApiController, RESTController
|
||
|
||
|
||
@ApiController('ping', secure=False) # /api/ping
|
||
class Ping(RESTController):
|
||
def list(self):
|
||
return self._list()
|
||
|
||
def _list(self): # To not get in conflict with the JSON wrapper
|
||
return [1,2,3]
|
||
|
||
|
||
@UiApiController('ping', secure=False) # /ui-api/ping
|
||
class PingUi(Ping):
|
||
def list(self):
|
||
return self._list() + [4, 5, 6]
|
||
```
|
||
|
||
> 注意官方注释:`_list()` 加下划线前缀是**为了不与 JSON wrapper 冲突**。
|
||
|
||
## 10.27 如何从 controller 里访问 manager module 实例?
|
||
|
||
官方**把 manager module 实例作为一个全局变量提供**,可以在任何模块里 import。
|
||
|
||
```python
|
||
import logging
|
||
import cherrypy
|
||
from .. import mgr
|
||
from ..tools import ApiController, RESTController
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
@ApiController('servers')
|
||
class Servers(RESTController):
|
||
def list(self):
|
||
logger.debug('Listing available servers')
|
||
return {'servers': mgr.list_servers()}
|
||
```
|
||
|
||
## 10.28 如何为 controller 写单元测试?
|
||
|
||
官方提供了名为 **`ControllerTestCase`** 的测试辅助类,方便为 controller 创建单元测试。
|
||
|
||
如果要给上面的 `Ping` controller 写单测,在 `tests` 目录创建 `test_ping.py`:
|
||
|
||
```python
|
||
from .helper import ControllerTestCase
|
||
from .controllers.ping import Ping
|
||
|
||
|
||
class PingTest(ControllerTestCase):
|
||
@classmethod
|
||
def setup_test(cls):
|
||
cp_config = {'tools.authenticate.on': True}
|
||
cls.setup_controllers([Ping], cp_config=cp_config)
|
||
|
||
def test_ping(self):
|
||
self._get("/api/ping")
|
||
self.assertStatus(200)
|
||
self.assertJsonBody({'msg': 'Hello'})
|
||
```
|
||
|
||
**`ControllerTestCase` class 的工作方式**:先初始化一个 **CherryPy webserver**,然后调用 `setup_test()` 类方法,在那里我们可以**显式加载想测试的 controller**(上例只加载了 `Ping`)。我们也可以提供 `cp_config` 来更新 controller 的 cherrypy config(如示例中启用认证)。
|
||
|
||
## 10.29 如何更新或创建新的 Grafana dashboard?
|
||
|
||
官方用 **`jsonnet`** 和 **`grafonnet-lib`** 来写 grafana dashboard 的代码。所有 dashboard 都写在 `monitoring/grafana/dashboards/jsonnet` 目录下的 **`grafana_dashboards.jsonnet`** 文件里。
|
||
|
||
**生成 dashboard json 文件**:在 grafana/dashboards 目录跑:
|
||
|
||
```bash
|
||
jsonnet -m . jsonnet/grafana_dashboards.jsonnet
|
||
```
|
||
|
||
(要让上面命令成功,需要装 **`jsonnet`** 包并在本机 clone 了 **`grafonnet-lib`** 目录。有问题参考 https://grafana.github.io/grafonnet-lib/getting-started/)
|
||
|
||
**要更新已有的 grafana dashboard 或创建新的**:更新 `grafana_dashboards.jsonnet` 文件,然后用上面的命令生成新的/更新后的 json 文件。不熟悉 grafonnet 或 jsonnet 的可以看 https://grafana.github.io/grafonnet-lib/ 。
|
||
|
||
**jsonnet 格式的 dashboard 示例:**
|
||
|
||
指定 grafana dashboard 属性(title、uid 等)可以创建一个 local function:
|
||
|
||
```text
|
||
local dashboardSchema(title, uid, time_from, refresh, schemaVersion, tags,timezone, timepicker)
|
||
```
|
||
|
||
加一个 graph panel 可以在 local function 里指定 graph schema:
|
||
|
||
```text
|
||
local graphPanelSchema(title, nullPointMode, stack, formatY1, formatY2, labelY1, labelY2, min, fill, datasource)
|
||
```
|
||
|
||
然后在 dashboard 定义里用这些 function:
|
||
|
||
```text
|
||
{
|
||
radosgw-sync-overview.json: //json file name to be generated
|
||
|
||
dashboardSchema(
|
||
'RGW Sync Overview', 'rgw-sync-overview', 'now-1h', '15s', .., .., ..
|
||
)
|
||
|
||
.addPanels([
|
||
graphPanelSchema(
|
||
'Replication (throughput) from Source Zone', 'Bps', null, .., .., ..)
|
||
])
|
||
}
|
||
```
|
||
|
||
合法的 grafonnet-lib 属性见 https://grafana.github.io/grafonnet-lib/api-docs/
|
||
|
||
## 10.30 如何在 controller 里监听 manager 通知?
|
||
|
||
manager 会就若干类型的集群事件通知各模块,例如集群日志事件等。
|
||
|
||
**每个模块有一个叫 `notify` 的 "global" handler 函数**,manager 调用它来通知模块。**但这个 handler 函数不得阻塞或花太多时间处理事件通知。**
|
||
|
||
为此官方提供了一个 **notification queue**,controller 可以向它注册自己以接收集群通知。
|
||
|
||
下面是一个实现了一个非常简单的 live log viewer 页面的 controller:
|
||
|
||
```python
|
||
import collections
|
||
|
||
import cherrypy
|
||
|
||
from ..tools import ApiController, BaseController, NotificationQueue
|
||
|
||
|
||
@ApiController('livelog')
|
||
class LiveLog(BaseController):
|
||
log_buffer = collections.deque(maxlen=1000)
|
||
|
||
def __init__(self):
|
||
super(LiveLog, self).__init__()
|
||
NotificationQueue.register(self.log, 'clog')
|
||
|
||
def log(self, log_struct):
|
||
self.log_buffer.appendleft(log_struct)
|
||
|
||
@cherrypy.expose
|
||
def default(self):
|
||
ret = '<html><meta http-equiv="refresh" content="2" /><body>'
|
||
for l in self.log_buffer:
|
||
ret += "{}<br>".format(l)
|
||
ret += "</body></html>"
|
||
return ret
|
||
```
|
||
|
||
**`NotificationQueue` class 提供 `register` 方法**:
|
||
|
||
- 第一个参数是**函数**;
|
||
- 第二个参数是 **"notification type"(通知类型)**;
|
||
- **可以省略第二个参数**,那样你就注册为**监听所有类型的所有通知**。
|
||
|
||
**可用的通知类型清单(官方注明这些未来可能变化):**
|
||
|
||
| 类型 | 含义 |
|
||
|---|---|
|
||
| `clog` | 集群日志通知 |
|
||
| `command` | 由 `MgrModule.send_command` 发出的命令完成时的通知 |
|
||
| `perf_schema_update` | perf counters schema 更新 |
|
||
| `mon_map` | monitor map 更新 |
|
||
| `fs_map` | cephfs map 更新 |
|
||
| `osd_map` | OSD map 更新 |
|
||
| `service_map` | services(RGW、RBD-Mirror 等)map 更新 |
|
||
| `mon_status` | monitor status 定期更新 |
|
||
| `health` | health status 定期更新 |
|
||
| `pg_summary` | PG 状态信息的定期更新 |
|
||
|
||
## 10.31 当 controller 访问某个 Ceph 模块时,单元测试怎么写?
|
||
|
||
考虑下面这个获取 `rbd` pool 里 RBD image 列表的 controller:
|
||
|
||
```python
|
||
import rbd
|
||
from .. import mgr
|
||
from ..tools import ApiController, RESTController
|
||
|
||
|
||
@ApiController('rbdimages')
|
||
class RbdImages(RESTController):
|
||
def __init__(self):
|
||
self.ioctx = mgr.rados.open_ioctx('rbd')
|
||
self.rbd = rbd.RBD()
|
||
|
||
def list(self):
|
||
return [{'name': n} for n in self.rbd.list(self.ioctx)]
|
||
```
|
||
|
||
上例中我们想 **mock `rbd.list` 函数的返回值**,以便测试 controller 的 JSON 响应。
|
||
|
||
单测代码:
|
||
|
||
```python
|
||
import mock
|
||
from .helper import ControllerTestCase
|
||
|
||
|
||
class RbdImagesTest(ControllerTestCase):
|
||
@mock.patch('rbd.RBD.list')
|
||
def test_list(self, rbd_list_mock):
|
||
rbd_list_mock.return_value = ['img1', 'img2']
|
||
self._get('/api/rbdimages')
|
||
self.assertJsonBody([{'name': 'img1'}, {'name': 'img2'}])
|
||
```
|
||
|
||
## 10.32 如何新增一个配置项?
|
||
|
||
如果你需要为新特性存储某个配置设置,官方已经提供了一个简单的机制来指定/使用新的 config setting。
|
||
|
||
例如,想加一个保存 dashboard admin 邮箱地址的配置项,只需**在 `settings.py` 文件的 `Options` class 里加一个 setting 名作为 class attribute**:
|
||
|
||
```python
|
||
# ...
|
||
class Options(object):
|
||
# ...
|
||
|
||
ADMIN_EMAIL_ADDRESS = ('admin@admin.com', str)
|
||
```
|
||
|
||
**class attribute 的值是一个 pair,由该 setting 的默认值和该值的 python type 组成。**
|
||
|
||
**声明了 `ADMIN_EMAIL_ADDRESS` class attribute 后,重启 dashboard 模块时你会自动多出两条 CLI 命令**来 get 和 set 该 setting:
|
||
|
||
```bash
|
||
$ ceph dashboard get-admin-email-address
|
||
$ ceph dashboard set-admin-email-address <value>
|
||
```
|
||
|
||
**要在 Python 代码里访问或修改该配置值**(无论是在 controller 里还是别处),只需 import `Settings` class 并这样访问:
|
||
|
||
```python
|
||
from settings import Settings
|
||
|
||
# ...
|
||
tmp_var = Settings.ADMIN_EMAIL_ADDRESS
|
||
|
||
# ....
|
||
Settings.ADMIN_EMAIL_ADDRESS = 'myemail@admin.com'
|
||
```
|
||
|
||
**settings 管理实现会保证**:如果你从 Python 代码里改了一个 setting 值,从 CLI 访问该 setting 时会看到这个改动,反之亦然。
|
||
|
||
## 10.33 如何异步执行 controller 的读写操作?
|
||
|
||
有些 controller 需要执行**会改变 Ceph 集群状态**的操作。这些操作可能需要一些时间执行,而为了在 Web UI 里保持良好的用户体验,我们需要**异步执行**这些操作,并**立即**返回一些信息给前端,说明操作正在后台运行。
|
||
|
||
为此官方加入了对**异步任务(asynchronous tasks)**的支持。触发一个异步任务必须用 `TaskManager` class 的下面这个类方法:
|
||
|
||
```python
|
||
from ..tools import TaskManager
|
||
# ...
|
||
TaskManager.run(name, metadata, func, args, kwargs)
|
||
```
|
||
|
||
| 参数 | 说明 |
|
||
|---|---|
|
||
| `name` | 一个可用于**给任务分组**的字符串。例如 RBD image 创建任务可以指定 `"rbd/create"`,RBD image 删除任务 `"rbd/remove"` |
|
||
| `metadata` | 一个 dictionary,存放**刻画该任务的 key-value 对**。例如创建 RBD image 的任务可以传 `{'pool_name': "rbd", image_name': "test-img"}` |
|
||
| `func` | 实现操作代码的 python 函数,将被异步执行 |
|
||
| `args` / `kwargs` | 任务管理器开始执行时传给 `func` 的位置参数和命名参数 |
|
||
|
||
**`TaskManager.run` 方法触发 `func` 的异步执行并返回一个 `Task` 对象。**
|
||
|
||
**`Task` 提供公开方法 `Task.wait(timeout)`**,可以等待任务完成,最多等 `timeout` 秒(以参数形式提供)。**如果不提供参数,`wait` 方法会一直阻塞到任务完成。**
|
||
|
||
`Task.wait` 对「通常执行很快、但有时可能跑很久」的任务非常有用。
|
||
|
||
**`Task.wait` 的返回值是一个 pair `(state, value)`:**
|
||
|
||
| `state` 取值 | 含义 |
|
||
|---|---|
|
||
| `VALUE_DONE = "done"` | 已完成 |
|
||
| `VALUE_EXECUTING = "executing"` | 执行中 |
|
||
|
||
- 如果 `state == VALUE_DONE`,`value` 存放函数 `func` 的执行结果;
|
||
- 如果 `state == VALUE_EXECUTING`,则 `value == None`。
|
||
|
||
**幂等性规则**:**`(name, metadata)` 这一对应能唯一标识正在运行的任务**。这意味着**如果你试图触发一个新任务,其 `(name, metadata)` 与当前正在运行的任务相同,那么新任务不会被创建,你会拿到当前正在运行任务的 task 对象。**
|
||
|
||
```python
|
||
task1 = TaskManager.run("dummy/task", {'attr': 2}, func)
|
||
task2 = TaskManager.run("dummy/task", {'attr': 2}, func)
|
||
```
|
||
|
||
如果第二次 `TaskManager.run` 调用时第一个任务仍在执行,它会返回**同一个 task 对象**:`assert task1 == task2`。
|
||
|
||
## 10.34 如何获取执行中和已完成的异步任务列表?
|
||
|
||
执行中和已完成任务的列表**包含在 `Summary` controller 里,dashboard 前端每 5 秒轮询一次它**。此外官方还提供了一个专门的 controller 来获取同样的列表。
|
||
|
||
**`Task` controller 暴露 `/api/task` 端点**,返回执行中和已完成的任务列表。**该端点接受 `name` 参数,其值可以是一个 glob 表达式。** 例如 HTTP GET 请求 `/api/task?name=rbd/*` 会返回所有**名字以 `rbd/` 开头**的执行中和已完成任务。
|
||
|
||
**防止已完成任务列表无限增长的措施:**
|
||
|
||
- **始终只保留最近 10 个已完成的任务**;
|
||
- 其余较旧的已完成任务在**达到 1 分钟 TTL 时被删除**。TTL 用任务完成执行时的时间戳计算;
|
||
- 一分钟后,当已完成任务信息被检索时(无论是通过 summary controller 还是 task controller),它会**自动从列表中删除**,不会再包含在后续的任务查询中。
|
||
|
||
**每个执行中的任务用如下 dictionary 表示:**
|
||
|
||
```python
|
||
{
|
||
'name': "name", # str
|
||
'metadata': { }, # dict
|
||
'begin_time': "2018-03-14T15:31:38.423605Z", # str (ISO 8601 format)
|
||
'progress': 0 # int (percentage)
|
||
}
|
||
```
|
||
|
||
**每个已完成的任务用如下 dictionary 表示:**
|
||
|
||
```python
|
||
{
|
||
'name': "name", # str
|
||
'metadata': { }, # dict
|
||
'begin_time': "2018-03-14T15:31:38.423605Z", # str (ISO 8601 format)
|
||
'end_time': "2018-03-14T15:31:39.423605Z", # str (ISO 8601 format)
|
||
'duration': 0.0, # float
|
||
'progress': 0 # int (percentage)
|
||
'success': True, # bool
|
||
'ret_value': None, # object, populated only if 'success' == True
|
||
'exception': None, # str, populated only if 'success' == False
|
||
}
|
||
```
|
||
|
||
## 10.35 如何在异步任务里使用异步 API?
|
||
|
||
前面描述的 `TaskManager.run` 方法**很适合调用阻塞型函数**,因为它在一个新建的线程里跑该函数。但**有时我们想调用某个本身就是异步的 API 的函数**。
|
||
|
||
对于这些情况,我们**不想为了跑一个非阻塞函数而新建一个线程**,而是想**利用该函数的异步特性**。`TaskManager.run` 已经为此做好了准备:通过传一个 **`TaskExecutor`** 类型的对象作为额外参数 **`executor`**。
|
||
|
||
**完整方法签名:**
|
||
|
||
```python
|
||
TaskManager.run(name, metadata, func, args=None, kwargs=None, executor=None)
|
||
```
|
||
|
||
**`TaskExecutor` class 负责执行给定任务函数的代码,并定义了三个可被子类覆盖的方法:**
|
||
|
||
```python
|
||
def init(self, task)
|
||
def start(self)
|
||
def finish(self, ret_value, exception)
|
||
```
|
||
|
||
| 方法 | 调用时机 / 作用 |
|
||
|---|---|
|
||
| `init(self, task)` | 在跑任务函数**之前**调用,接收 task 对象(class `Task`) |
|
||
| `start(self)` | 跑任务函数。**默认实现是在当前线程上下文里跑任务函数** |
|
||
| `finish(self, ret_value, exception)` | 当任务函数结束时应被调用,参数要么是填充了执行结果的 `ret_value`,要么(在执行抛异常的情况下)是一个 exception 对象 |
|
||
|
||
**要利用非阻塞函数的异步特性,开发者应通过创建 `TaskExecutor` class 的子类来实现一个自定义 executor,并把该自定义 executor class 的实例作为 `TaskManager.run` 的 `executor` 参数传入。**
|
||
|
||
**完整示例:用自定义 executor 执行 `MgrModule.send_command` 这个异步函数**
|
||
|
||
```python
|
||
import json
|
||
from mgr_module import CommandResult
|
||
from .. import mgr
|
||
from ..tools import ApiController, RESTController, NotificationQueue, \
|
||
TaskManager, TaskExecutor
|
||
|
||
|
||
class SendCommandExecutor(TaskExecutor):
|
||
def __init__(self):
|
||
super(SendCommandExecutor, self).__init__()
|
||
self.tag = None
|
||
self.result = None
|
||
|
||
def init(self, task):
|
||
super(SendCommandExecutor, self).init(task)
|
||
|
||
# we need to listen for 'command' events to know when the command
|
||
# finishes
|
||
NotificationQueue.register(self._handler, 'command')
|
||
|
||
# store the CommandResult object to retrieve the results
|
||
self.result = self.task.fn_args[0]
|
||
if len(self.task.fn_args) > 4:
|
||
# the user specified a tag for the command, so let's use it
|
||
self.tag = self.task.fn_args[4]
|
||
else:
|
||
# let's generate a unique tag for the command
|
||
self.tag = 'send_command_{}'.format(id(self))
|
||
self.task.fn_args.append(self.tag)
|
||
|
||
def _handler(self, data):
|
||
if data == self.tag:
|
||
# the command has finished, notifying the task with the result
|
||
self.finish(self.result.wait(), None)
|
||
# deregister listener to avoid memory leaks
|
||
NotificationQueue.deregister(self._handler, 'command')
|
||
|
||
|
||
@ApiController('test')
|
||
class Test(RESTController):
|
||
|
||
def _run_task(self, osd_id):
|
||
task = TaskManager.run("test/task", {}, mgr.send_command,
|
||
[CommandResult(''), 'osd', osd_id,
|
||
json.dumps({'prefix': 'perf histogram dump'})],
|
||
executor=SendCommandExecutor())
|
||
return task.wait(1.0)
|
||
|
||
def get(self, osd_id):
|
||
status, value = self._run_task(osd_id)
|
||
return {'status': status, 'value': value}
|
||
```
|
||
|
||
**上面的 `SendCommandExecutor` executor class 可以用于任何对 `MgrModule.send_command` 的调用**——这意味着**对于我们在 controller 里用到的每一个非阻塞 API,只需要实现一个自定义 executor class**。
|
||
|
||
**默认的 executor**(当没有给 `TaskManager.run` 传 executor 对象时使用)是 **`ThreadedExecutor`**。可以在 `tools.py` 文件里查看它的实现。
|
||
|
||
## 10.36 如何更新异步任务的执行进度?
|
||
|
||
异步任务基础设施支持更新正在执行任务的执行进度。**进度可以从任务正在执行的代码内部更新**——那通常正是我们有进度信息的地方。
|
||
|
||
**要从任务代码内部更新进度,`TaskManager` class 提供了一个获取当前 task 对象的方法:**
|
||
|
||
```python
|
||
TaskManager.current_task()
|
||
```
|
||
|
||
> **上述方法仅在使用默认 executor `ThreadedExecutor` 执行任务时可用。**
|
||
|
||
**`current_task()` 方法返回当前的 `Task` 对象。`Task` 对象提供两个更新执行进度值的公开方法:**
|
||
|
||
| 方法 | 参数含义 |
|
||
|---|---|
|
||
| `set_progress(percentage)` | 一个整数,表示我们想给任务设置的**绝对百分比** |
|
||
| `inc_progress(delta)` | 一个整数,表示我们想**在当前执行进度百分比上增加的增量** |
|
||
|
||
**示例:一个触发新任务并更新其进度的 controller**
|
||
|
||
```python
|
||
import random
|
||
import time
|
||
import cherrypy
|
||
from ..tools import TaskManager, ApiController, BaseController
|
||
|
||
|
||
@ApiController('dummy_task')
|
||
class DummyTask(BaseController):
|
||
def _dummy(self):
|
||
top = random.randrange(100)
|
||
for i in range(top):
|
||
TaskManager.current_task().set_progress(i*100/top)
|
||
# or TaskManager.current_task().inc_progress(100/top)
|
||
time.sleep(1)
|
||
return "finished"
|
||
|
||
@cherrypy.expose
|
||
@cherrypy.tools.json_out()
|
||
def default(self):
|
||
task = TaskManager.run("dummy/task", {}, self._dummy)
|
||
return task.wait(5) # wait for five seconds
|
||
```
|
||
|
||
## 10.37 前端如何处理异步任务?
|
||
|
||
**所有执行中的以及最近完成的异步任务都会显示在菜单栏的 "Background-Tasks" 上,如果已完成则显示在 "Recent-Notifications" 上。**
|
||
|
||
对于**每个任务**,必须提供:
|
||
|
||
1. 三种状态(running、success、failure)各一个 **operation name**;
|
||
2. 一个**说明谁被牵涉其中**的函数;
|
||
3. 错误描述(如果有的话)。
|
||
|
||
做法:**追加到 `TaskManagerMessageService.messages`**。**必须这样做才能在所有任务和状态之间保持一致性。**
|
||
|
||
### Operation Object
|
||
|
||
保证所有任务之间的一致性。它**由三个动词组成**,分别对应不同状态,例如:
|
||
|
||
```typescript
|
||
{running: 'Creating', failure: 'create', success: 'Created'}
|
||
```
|
||
|
||
**时态规则(明确要求):**
|
||
|
||
1. **running 用现在分词**(present participle),例如 `'Updating'`;
|
||
2. **失败消息总是以 `'Failed to '` 开头**,后面接**动词原形**(present tense),例如 `'update'`;
|
||
3. **成功用过去式**(past tense),例如 `'Updated'`。
|
||
|
||
### Involves Function
|
||
|
||
保证一个任务的所有消息之间的一致性,它描述**操作牵涉到谁**。它是一个**接收 task 的 metadata 并返回字符串**的函数,例如返回 ``"RBD 'somePool/someImage'"``。
|
||
|
||
### 组合后的消息
|
||
|
||
| 状态 | 生成的消息 |
|
||
|---|---|
|
||
| Failure | `Failed to create RBD 'somePool/someImage'` |
|
||
| Running | `Creating RBD 'somePool/someImage'` |
|
||
| Success | `Created RBD 'somePool/someImage'` |
|
||
|
||
### 自动 vs 手动
|
||
|
||
- **自动处理任务用 `TaskWrapperService.wrapTaskAroundCall`。**
|
||
- 如果出于某种原因 `wrapTaskAroundCall` 对你不生效,你必须**手动通过 `TaskManagerService.subscribe` 订阅你的异步任务**,并提供一个 callback,在成功时通知用户。
|
||
- 一个通知可以用 **`NotificationService.notifyTask`** 触发。它会使用 `TaskManagerMessageService.messages` 基于任务状态显示消息。
|
||
- **API 错误的通知由 `ApiInterceptorService` 处理。**
|
||
|
||
### 用法示例
|
||
|
||
```typescript
|
||
export class TaskManagerMessageService {
|
||
// ...
|
||
messages = {
|
||
// Messages for task 'rbd/create'
|
||
'rbd/create': new TaskManagerMessage(
|
||
// Message prefixes
|
||
['create', 'Creating', 'Created'],
|
||
// Message suffix
|
||
(metadata) => `RBD '${metadata.pool_name}/${metadata.image_name}'`,
|
||
(metadata) => ({
|
||
// Error code and description
|
||
'17': `Name is already used by RBD '${metadata.pool_name}/${
|
||
metadata.image_name}'.`
|
||
})
|
||
),
|
||
// ...
|
||
};
|
||
// ...
|
||
}
|
||
|
||
export class RBDFormComponent {
|
||
// ...
|
||
createAction() {
|
||
const request = this.createRequest();
|
||
// Subscribes to 'call' with submitted 'task' and handles notifications
|
||
return this.taskWrapper.wrapTaskAroundCall({
|
||
task: new FinishedTask('rbd/create', {
|
||
pool_name: request.pool_name,
|
||
image_name: request.name
|
||
}),
|
||
call: this.rbdService.create(request)
|
||
});
|
||
}
|
||
// ...
|
||
}
|
||
```
|
||
|
||
## 10.38 REST API 文档
|
||
|
||
Ceph-Dashboard 为 **Ceph RESTful API** 提供两种文档:
|
||
|
||
| 类型 | 位置 |
|
||
|---|---|
|
||
| **Static documentation(静态文档)** | 见 `mgr ceph api`。来自一个**带版本的规范**,位于 `src/pybind/mgr/dashboard/openapi.yaml` |
|
||
| **Interactive documentation(交互式文档)** | 从一个运行中的 Ceph-Dashboard 实例访问(右上角的 **`?` 图标 > API Docs**) |
|
||
|
||
**如果对 `controllers/` 目录做了改动,很可能会导致生成的 OpenAPI 规范发生变化。** 为此官方实现了一个 checker 来阻止非预期的变更。**该检查由 Pull Request CI(`make check`)自动触发,也可以手工调用:**
|
||
|
||
```bash
|
||
tox -e openapi-check
|
||
```
|
||
|
||
**如果该 checker 失败,说明当前 PR 正在修改 Ceph API,因此:**
|
||
|
||
1. **应该显式更新带版本的 OpenAPI 规范:**
|
||
```bash
|
||
tox -e openapi-fix
|
||
```
|
||
2. **会请求 `@ceph/api` 团队进行评审**(通过 GitHub CODEOWNERS 自动完成),以评估变更影响。
|
||
|
||
**此外,可以从 OpenAPI 规范生成 Sphinx 文档:**
|
||
|
||
```bash
|
||
tox -e openapi-doc
|
||
```
|
||
|
||
### 10.38.1 两个用于补充文档的装饰器
|
||
|
||
Ceph RESTful OpenAPI 规范是从 `controllers/` 目录里的 `Controllers` **动态生成**的。但**默认情况下它不够详细**,因此有**两个应当被使用**的装饰器:
|
||
|
||
| 装饰器 | 用途 | 参数 |
|
||
|---|---|---|
|
||
| `@EndpointDoc()` | 端点的文档 | 四个可选参数:`description`、`group`、`parameters`、`responses` |
|
||
| `@ControllerDoc()` | controller 或与端点关联的 group 的文档 | **只取前两个参数**:`description` 和 `group` |
|
||
|
||
**各参数含义:**
|
||
|
||
- **`description`**:一个字符串,对象的简短(1-2 句)描述。
|
||
- **`group`**:默认情况下,一个端点与**同一 controller class 里的其他端点**归为一组。`group` 是一个字符串,可以把一个端点**或**一个 class 里的所有端点**指派给另一个 controller 或一个有意义的 group 名**。
|
||
- **`parameters`**:一个 dict,用于描述 path、query 或 request body 参数。
|
||
- 默认情况下,一个端点的**所有**参数都会列在 Swagger UI 页面上,包括该参数是 optional/required 以及默认值的信息。
|
||
- **但是**:参数**没有描述**,参数类型**只在某些情况下**显示。
|
||
- 添加信息时,每个参数应如下例那样描述。**注意参数类型应表示为 python 内置类型,而不是字符串。允许的值:`str`、`int`、`bool`、`float`。**
|
||
|
||
```python
|
||
@EndpointDoc(parameters={'my_string': (str, 'Description of my_string')})
|
||
def method(my_string): pass
|
||
```
|
||
|
||
**body 参数可以有更复杂的情况:**
|
||
|
||
- **如果参数是一个 dictionary**,类型应被一个包含其嵌套参数的 `dict` 替换。描述嵌套参数时使用与其他参数相同的格式。**但是,所有嵌套参数默认都被设为 required。** 如果某个嵌套参数是可选的,必须像下面例子里的 `item2` 那样指定。如果嵌套参数被设为可选,**也可以指定默认值**(嵌套参数的默认值**不会**自动提供)。
|
||
|
||
```python
|
||
@EndpointDoc(parameters={
|
||
'my_dictionary': ({
|
||
'item1': (str, 'Description of item1'),
|
||
'item2': (str, 'Description of item2', True), # item2 is optional
|
||
'item3': (str, 'Description of item3', True, 'foo'), # item3 is optional with 'foo' as default value
|
||
}, 'Description of my_dictionary')})
|
||
def method(my_dictionary): pass
|
||
```
|
||
|
||
- **如果参数是基本类型的 `list`**,类型应用方括号包起来。
|
||
|
||
```python
|
||
@EndpointDoc(parameters={'my_list': ([int], 'Description of my_list')})
|
||
def method(my_list): pass
|
||
```
|
||
|
||
- **如果参数是带嵌套参数的 `list`**,嵌套参数应放在一个 dictionary 里并用方括号包起来。
|
||
|
||
```python
|
||
@EndpointDoc(parameters={
|
||
'my_list': ([{
|
||
'list_item': (str, 'Description of list_item'),
|
||
'list_item2': (str, 'Description of list_item2')
|
||
}], 'Description of my_list')})
|
||
def method(my_list): pass
|
||
```
|
||
|
||
- **`responses`**:一个 dict,用于描述响应。**描述响应的规则与 request body 参数相同,只有一点不同:响应还需要被指派给相关的响应码**,如下例:
|
||
|
||
```python
|
||
@EndpointDoc(responses={
|
||
'400':{'my_response': (str, 'Description of my_response')}})
|
||
def method(): pass
|
||
```
|
||
|
||
## 10.39 Python 中的错误处理
|
||
|
||
**良好的错误处理是创造良好用户体验、提供良好 API 的关键要求。**
|
||
|
||
**Dashboard 代码不应重复 C++ 代码。** 因此,**如果 C++ 里的错误处理已足以提供良好反馈,就不必再包一层去捕获这些错误**。另一方面,**输入校验是捕获错误并生成最佳错误消息的最佳位置**。如果需要,**尽早生成错误**。
|
||
|
||
后端提供几种标准的返回错误方式。
|
||
|
||
**1)通用的 Internal Server Error:**
|
||
|
||
```text
|
||
Status Code: 500
|
||
{
|
||
"version": <cherrypy version, e.g. 13.1.0>,
|
||
"detail": "The server encountered an unexpected condition which prevented it from fulfilling the request.",
|
||
}
|
||
```
|
||
|
||
**2)后端生成的错误,标准错误格式:**
|
||
|
||
```text
|
||
Status Code: 400
|
||
{
|
||
"detail": str(e), # E.g. "[errno -42] <some error message>"
|
||
"component": "rbd", # this can be null to represent a global error code
|
||
"code": "3", # Or a error name, e.g. "code": "some_error_key"
|
||
}
|
||
```
|
||
|
||
**3)如果 API 端点使用了 `@ViewCache` 临时缓存结果,错误长这样:**
|
||
|
||
```text
|
||
Status Code 400
|
||
{
|
||
"detail": str(e), # E.g. "[errno -42] <some error message>"
|
||
"component": "rbd", # this can be null to represent a global error code
|
||
"code": "3", # Or a error name, e.g. "code": "some_error_key"
|
||
'status': 3, # Indicating the @ViewCache error status
|
||
}
|
||
```
|
||
|
||
**4)如果 API 端点使用了 task,错误长这样:**
|
||
|
||
```text
|
||
Status Code 400
|
||
{
|
||
"detail": str(e), # E.g. "[errno -42] <some error message>"
|
||
"component": "rbd", # this can be null to represent a global error code
|
||
"code": "3", # Or a error name, e.g. "code": "some_error_key"
|
||
"task": { # Information about the task itself
|
||
"name": "taskname",
|
||
"metadata": {...}
|
||
}
|
||
}
|
||
```
|
||
|
||
**原则:**
|
||
|
||
- **我们的 WebUI 应该把 API 生成的错误展示给用户**,尤其是 wizard 和 dialog 里与字段相关的错误,或者显示非侵入式通知。
|
||
- **在 Python 里处理异常应当是一种例外(exception)。** 一般而言,项目里应该**只有很少的 exception handler**。
|
||
- **默认情况下,把错误传播给 API**,因为它无论如何会处理所有异常。
|
||
- 一般而言,**在 handler 里通过加 `logger.exception()` 并附一段描述来记录异常**。
|
||
- **我们需要区分用户错误、内部错误和编程错误。使用不同的异常类型会减轻 API 层和 UI 的工作。**
|
||
- **标准的 Python 错误**(如 `SystemError`、`ValueError`、`KeyError`)**最终会变成 API 里的 internal server errors**。
|
||
- **一般而言,不要在 REST API 里 `return` 错误响应。** 它们会由 error handler 返回。**相反,应该 raise 合适的异常。**
|
||
|
||
## 10.40 插件(Plug-ins)
|
||
|
||
新功能可以通过**插件架构**提供。这种方式带来的好处中,最显著的是**松耦合开发**。随着 Ceph Dashboard 功能越来越丰富,其代码库也越来越复杂。**插件架构基于 hook 的本质允许以受控的方式扩展功能,并隔离改动范围。**
|
||
|
||
**Ceph Dashboard 依赖 `Pluggy`(https://pluggy.readthedocs.io)提供插件支持。** 在 pluggy 之上,官方实现了一个**基于接口(interface-based)的方法,并带有一些安全检查**(方法覆盖检查和抽象方法检查)。
|
||
|
||
### 10.40.1 创建新插件的步骤
|
||
|
||
1. **在 `src/pybind/mgr/dashboard/plugins` 下新增一个文件。**
|
||
2. **import `PLUGIN_MANAGER` 实例和 `Interfaces`。**
|
||
3. **创建一个继承所需 interfaces 的 class。** 插件库会检查 interfaces 的所有方法是否都被正确覆盖。
|
||
4. **在 `PLUGIN_MANAGER` 实例里注册该插件。**
|
||
5. **从 Ceph Dashboard 的 `module.py` 里 import 该插件**(**目前没有实现动态加载**)。
|
||
|
||
### 10.40.2 可用的 Mixins(helpers)
|
||
|
||
| Mixin | 说明 |
|
||
|---|---|
|
||
| `CanMgr` | 让插件可以通过 `self.mgr` 访问 `mgr` 实例 |
|
||
|
||
### 10.40.3 可用的 Interfaces
|
||
|
||
| Interface | 要求与执行时机 |
|
||
|---|---|
|
||
| `Initializable` | 覆盖 `init()` hook。该方法在 dashboard 模块的**最开始**、**所有 import 执行完之后**运行 |
|
||
| `Setupable` | 覆盖 `setup()` hook。该方法在 Ceph Dashboard 的 `serve()` 方法里运行——**在 CherryPy 配置好之后,但在它启动之前**。是插件初始化逻辑的占位点 |
|
||
| `HasOptions` | 覆盖 `get_options()` hook,**返回一个 `Options()` 列表**。这里返回的 options 会被加到 `MODULE_OPTIONS` |
|
||
| `HasCommands` | 覆盖 `register_commands()` hook,**定义插件能处理的命令并用 `@CLICommand` 装饰它们**。命令**可以选择性返回**,以便能被外部调用(这让单元测试更容易) |
|
||
| `HasControllers` | 覆盖 `get_controllers()` hook,**按常规方式定义并返回 controllers** |
|
||
| `FilterRequest.BeforeHandler` | 覆盖 `filter_request_before_handler()` hook。该方法接收一个 `cherrypy.request` 对象进行处理。**通常的实现**会根据 `request` 元数据和其他条件,让某些请求通过,或者 **raise 一个 `cherrypy.HTTPError`** |
|
||
|
||
> **新 interfaces 和 hooks 应该在需要实现新功能时随之添加。** 上面这份清单**只包含现有插件所需的 hooks**。
|
||
|
||
### 10.40.4 完整插件示例
|
||
|
||
```python
|
||
# src/pybind/mgr/dashboard/plugins/mute.py
|
||
|
||
from . import PLUGIN_MANAGER as PM
|
||
from . import interfaces as I
|
||
|
||
from mgr_module import CLICommand, Option
|
||
import cherrypy
|
||
|
||
@PM.add_plugin
|
||
class Mute(I.CanMgr, I.Setupable, I.HasOptions, I.HasCommands,
|
||
I.FilterRequest.BeforeHandler, I.HasControllers):
|
||
@PM.add_hook
|
||
def get_options(self):
|
||
return [Option('mute', default=False, type='bool')]
|
||
|
||
@PM.add_hook
|
||
def setup(self):
|
||
self.mute = self.mgr.get_module_option('mute')
|
||
|
||
@PM.add_hook
|
||
def register_commands(self):
|
||
@CLICommand("dashboard mute")
|
||
def _(mgr):
|
||
self.mute = True
|
||
self.mgr.set_module_option('mute', True)
|
||
return 0
|
||
|
||
@PM.add_hook
|
||
def filter_request_before_handler(self, request):
|
||
if self.mute:
|
||
raise cherrypy.HTTPError(500, "I'm muted :-x")
|
||
|
||
@PM.add_hook
|
||
def get_controllers(self):
|
||
from ..controllers import ApiController, RESTController
|
||
|
||
@ApiController('/mute')
|
||
class MuteController(RESTController):
|
||
def get(_):
|
||
return self.mute
|
||
|
||
return [MuteController]
|
||
```
|
||
|
||
### 10.40.5 用 `SimplePlugin` 简化
|
||
|
||
此外,官方提供了一个创建插件的 helper:**`SimplePlugin`**,它简化了基本任务(Options、Commands 和常见的 Mixins)。上面的插件可以改写成:
|
||
|
||
```python
|
||
from . import PLUGIN_MANAGER as PM
|
||
from . import interfaces as I
|
||
from .plugin import SimplePlugin as SP
|
||
|
||
import cherrypy
|
||
|
||
@PM.add_plugin
|
||
class Mute(SP, I.Setupable, I.FilterRequest.BeforeHandler, I.HasControllers):
|
||
OPTIONS = [
|
||
SP.Option('mute', default=False, type='bool')
|
||
]
|
||
|
||
def shut_up(self):
|
||
self.set_option('mute', True)
|
||
self.mute = True
|
||
return 0
|
||
|
||
COMMANDS = [
|
||
SP.Command("dashboard mute", handler=shut_up)
|
||
]
|
||
|
||
@PM.add_hook
|
||
def setup(self):
|
||
self.mute = self.get_option('mute')
|
||
|
||
@PM.add_hook
|
||
def filter_request_before_handler(self, request):
|
||
if self.mute:
|
||
raise cherrypy.HTTPError(500, "I'm muted :-x")
|
||
|
||
@PM.add_hook
|
||
def get_controllers(self):
|
||
from ..controllers import ApiController, RESTController
|
||
|
||
@ApiController('/mute')
|
||
class MuteController(RESTController):
|
||
def get(_):
|
||
return self.mute
|
||
|
||
return [MuteController]
|
||
```
|
||
|
||
## 10.41 Dashboard 章节速查表
|
||
|
||
| 想做的事 | 命令 / 位置 |
|
||
|---|---|
|
||
| 装前端依赖 | `cd src/pybind/mgr/dashboard/frontend && npm ci` |
|
||
| 起前端 dev server | 先建 `proxy.conf.json`(照抄 `proxy.conf.json.sample`),再 `npm start` → http://localhost:4200/ |
|
||
| 构建前端 | `npm run build`(`-- --prod` 生产构建 → https://localhost:8443) |
|
||
| 生成代码文档 | `npm run doc-build` → `documentation/`;`npm run doc-serve` → http://localhost:8444 |
|
||
| lint / 自动修复 | `npm run lint` / `npm run fix` |
|
||
| 前端单测 | `npm run test`(Jest) |
|
||
| 全量 E2E | `cd src/pybind/mgr/dashboard && ./run-frontend-e2e-tests.sh`(`-d` 指定设备,`-r/-u/-p` 远程) |
|
||
| cephadm E2E | `./src/pybind/mgr/dashboard/ci/cephadm/run-cephadm-e2e-tests.sh`(需 KCLI) |
|
||
| 活跃开发期 E2E | `npm run e2e` / `npm run e2e:ci` / `npx cypress open` / `npx cypress run` |
|
||
| 后端依赖 | `cd src/pybind/mgr/dashboard && pip install -r requirements.txt` |
|
||
| 后端单测 | `../../../script/run_tox.sh --tox-env py3,lint,check` 或 `tox -e py3` |
|
||
| 单个后端测试 | `tox -e py3 tests/test_rgw_client.py::RgwClientTest::test_ssl_verify` |
|
||
| PEP8 自动修复 | `run_tox.sh --tox-env fix` 或 `tox -e fix` |
|
||
| 覆盖率报告 | `coverage html` → `htmlcov/` |
|
||
| 后端 API 测试 | `./run-backend-api-tests.sh [tasks.mgr.dashboard.xxx]` |
|
||
| OpenAPI 检查/修复/文档 | `tox -e openapi-check` / `tox -e openapi-fix` / `tox -e openapi-doc` |
|
||
| i18n 提取 | `npm run i18n:extract` |
|
||
| 查 dashboard URL | `./bin/ceph mgr services` |
|
||
|
||
> 引自:[Ceph Dashboard Developer Documentation (formerly HACKING.rst)](https://docs.ceph.com/en/tentacle/dev/developer_guide/dash-devel/)
|
||
|
||
---
|
||
|
||
# 十一、Jaeger 分布式追踪开发
|
||
|
||
## 11.1 背景
|
||
|
||
**Jaeger + Opentracing** 为分布式系统提供开箱即用的追踪服务,并因其简单性和标准化而成为被广泛使用的标准。
|
||
|
||
Ceph 使用**经过修改的 `jaeger-cpp-client`**(https://github.com/ceph/jaeger-client-cpp)作为提供给 **Opentracing API** 的 backend,它负责**收集 span**。这些 span 是用**智能指针**构造的,携带时间戳、**TraceID** 以及其他元信息(如与 span 关联的特定 tag/log),以便在分布式系统中唯一标识它。
|
||
|
||
## 11.2 基础架构与术语
|
||
|
||
官方只给了一个内部链接:**参见 Ceph Tracing 文档的「BASIC ARCHITECTURE AND TERMINOLOGY」一节**(相对路径 `../../../jaegertracing/#basic-architecture-and-terminology`)。
|
||
|
||
> ⚠️ **文档未明确**:`developer_guide/jaegertracing.rst` 本身**没有展开**架构与术语,只指向 `doc/jaegertracing.rst`(不在本册源文范围内)。本册因此不在此复述架构细节。
|
||
|
||
## 11.3 如何开始使用 tracing?
|
||
|
||
**在 Ceph 里启用 jaegertracing 需要:部署 Jaeger 守护进程 + 带 Jaeger 编译 Ceph**,编排好以便**在 vstart 集群中给开发者使用**。这里用的是一个 jaeger **all-in-one docker**(https://www.jaegertracing.io/docs/1.22/getting-started/#all-in-one),**不推荐用于生产,但用于测试可以**。
|
||
|
||
**全部步骤如下:**
|
||
|
||
**1. 用 install-deps 更新系统上的 Jaeger 依赖:**
|
||
|
||
```bash
|
||
$ WITH_JAEGER=true ./install-deps.sh
|
||
```
|
||
|
||
**2. 带 Jaeger 编译 Ceph:**
|
||
|
||
- 对**已预编译的构建**:
|
||
|
||
```bash
|
||
$ cd build
|
||
$ cmake -DWITH_JAEGER=ON ..
|
||
```
|
||
|
||
- 对**用 `do_cmake.sh` 的全新编译**:
|
||
|
||
```bash
|
||
$ ./do_cmake.sh -DWITH_JAEGER=ON && ninja vstart
|
||
```
|
||
|
||
**3. 编译成功后,用 `--jaeger` 启动一个 vstart 集群**,它会**用容器部署服务(docker/podman)部署 jaeger all-in-one**:
|
||
|
||
```bash
|
||
$ MON=1 MGR=0 OSD=1 ../src/vstart.sh --with-jaeger
|
||
```
|
||
|
||
> 如果部署不成功,你可以**手工部署 all-in-one 服务**,然后**不带 jaeger** 启动 vstart 集群也可以。
|
||
|
||
> ⚠️ 注意:官方第 2 步的命令是 `--jaeger`,第 3 步的命令是 `--with-jaeger`。**这是源文里的写法,本册原样保留,不做「纠正」**。
|
||
|
||
**4. 用 rados-bench write 测试 trace:**
|
||
|
||
```bash
|
||
$ bin/rados -p test bench 5 write --no-cleanup
|
||
```
|
||
|
||
**另见**:https://medium.com/@deepikaupadhyay/using-jaeger-cpp-client-for-distributed-tracing-in-ceph-8b1f4906ca2
|
||
|
||
> 引自:[JAEGER- DISTRIBUTED TRACING](https://docs.ceph.com/en/tentacle/dev/developer_guide/jaegertracing/)
|
||
|
||
---
|
||
|
||
## 文档未明确项
|
||
|
||
以下事项在 `doc/dev/developer_guide/` 的 18 个源文里**没有说明**,本册不做猜测:
|
||
|
||
| # | 事项 | 说明 |
|
||
|---|---|---|
|
||
| 1 | **完整的 `src/` 目录布局表** | 官方 `developer_guide` 没有任何 `src/` 逐目录布局表。本册 1.4 节只列出源文里可确证的路径 |
|
||
| 2 | **`Fixes:` / `Resolves:` 等 commit trailer 的完整规范** | 本册源文只提到 `Signed-off-by:`(由 `git commit -as` 生成)与 merge commit 的 `Reviewed-by:`;`Fixes:` 未被本册源文提及,官方指向 `CONTRIBUTING.rst` / `SubmittingPatches.rst` |
|
||
| 3 | **GDB 的具体子命令清单** | `debugging-gdb.rst` 只给了 `gdb ./bin/ceph-osd -p <pid>` 和 `./ceph-debug-docker.sh <branch-name>`,没有 `bt`、`thread apply all bt` 等命令说明,仅给了一个外部链接 |
|
||
| 4 | **Sentry 的任何内容** | `tests-sentry-developers-guide.rst` 全文只有 "To be updated. Feel free to contribute." |
|
||
| 5 | **更新 git submodules 的具体步骤** | `basic-workflow.rst` 提到「如果 `git status` 出现 Untracked files,见 updating submodules 步骤」,但该步骤不在本册源文内 |
|
||
| 6 | **Jaeger 的基础架构与术语** | `jaegertracing.rst` 只链接到 `doc/jaegertracing.rst`,本册源文不含其内容 |
|
||
| 7 | **PR 里可用的完整 Jenkins 命令列表** | 官方说「这些命令列表会自动附加在每个新 PR 描述的末尾」,但本册源文只举了 `jenkins retest this please` 和 `jenkins test signed` 两个例子,未给出完整清单 |
|
||
| 8 | **`teuthology-suite` 的全部选项** | 官方多次让读者跑 `teuthology-suite --help`,本册源文只详述了 `-v`、`-m`、`-c`、`-s`、`-p`、`--filter`、`-e`、`--dry-run`、`--subset`、`--limit`、`-N`、`--rerun`/`-R`、`--suite-repo`、`--suite-branch`、`--no-nested-subset`、`--force-priority`、`--seed`、`-d`、`--timeout`、`flavor` 等 |
|
||
| 9 | **`--subset` 背后真正的筛选逻辑** | 官方明说「理解背后真正的逻辑需要读 teuthology 源码」 |
|
||
| 10 | **Dashboard 前端各功能模块(pool / RBD / RGW / CephFS 等页面)的业务实现** | `dash-devel.rst` 是开发指南,不是功能文档;具体页面功能见本手册第 13 册(监控告警与 Dashboard 篇) |
|
||
| 11 | **单元测试 `add_ceph_test` / `add_ceph_unittest` 的完整参数语义** | 官方只说明二者的用途区别,参数细节指向 `cmake/modules/AddCephTest.cmake` |
|
||
| 12 | **`documenting.rst` 的完整内容** | 该文件位于 `doc/dev/documenting.rst`,不在本册源文范围;本册第 9 章只给出与贡献流程相关的摘要,完整内容见第 18 册 |
|
||
| 13 | **Tentacle 当期的实际分支名与 CI 平台清单** | `merging.rst` 用 hammer/infernalis/jewel 等历史版本举例;`teuthology-intro.rst` 的平台清单是「as of September 2020」的 RHEL/CentOS 8 + Ubuntu 18.04。Tentacle 的当前状态文档未明确 |
|
||
| 14 | **Ceph Dashboard 前端 Angular 的具体版本号** | 官方只给了 Node(>= 20.13.1)和 NPM(>= 10.5.2)的下限;Angular 与 Bootstrap 版本让读者自己看 `package.json` |
|