為讓使用兆芯平臺(tái)服務(wù)器的用戶能夠更快捷的構(gòu)建IaaS系統(tǒng),充分發(fā)揮兆芯平臺(tái)的性能,特編寫本指南,供用戶參考。本指南并不依賴特定產(chǎn)品型號(hào),重在指導(dǎo)用戶在兆芯平臺(tái)服務(wù)器上的調(diào)試方法,并且會(huì)隨著評(píng)估測(cè)試的深入和成熟度增加逐步完善。
一、系統(tǒng)介紹
1.硬件環(huán)境:
測(cè)試用IaaS系統(tǒng)規(guī)模為46個(gè)節(jié)點(diǎn),均使用兆芯平臺(tái)雙路服務(wù)器構(gòu)建。其中OpenStack節(jié)點(diǎn)40個(gè),Ceph分布式存儲(chǔ)系統(tǒng)節(jié)點(diǎn)6個(gè),詳見下表。
節(jié)點(diǎn)類型 |
節(jié)點(diǎn)配置 |
數(shù)量 |
管理節(jié)點(diǎn) |
128G DDR4內(nèi)存+120G SSD+千兆網(wǎng)絡(luò)x2 |
3 |
網(wǎng)絡(luò)節(jié)點(diǎn) |
128G DDR4內(nèi)存+120G SSD+千兆網(wǎng)絡(luò)x3 |
1 |
塊存儲(chǔ)節(jié)點(diǎn) |
128G DDR4內(nèi)存+120G SSD+千兆網(wǎng)絡(luò)x2 |
1 |
對(duì)象存儲(chǔ)節(jié)點(diǎn) |
128G DDR4內(nèi)存+120G SSD+4T HDDx3+千兆網(wǎng)絡(luò)x2 |
1 |
計(jì)算節(jié)點(diǎn) |
128G DDR4內(nèi)存+120G SSD+千兆網(wǎng)絡(luò)x2 |
33 |
監(jiān)控節(jié)點(diǎn) |
128G DDR4內(nèi)存+120G SSD+千兆網(wǎng)絡(luò)x2 |
1 |
Ceph Mon節(jié)點(diǎn) |
128G DDR4內(nèi)存+120G SSD+萬兆網(wǎng)絡(luò)x2 |
3 |
Ceph OSD節(jié)點(diǎn) |
128G DDR4內(nèi)存+250G SSD+500G SSDx3+2T HDDx12+萬兆網(wǎng)絡(luò)x4 |
3 |
Rally Server |
Dell E3-1220v3服務(wù)器 |
1 |
2.軟件環(huán)境:
軟件 |
版本 |
備注 |
Host OS |
CentOS7.6 |
安裝ZX Patch v3.0.9.4 |
Python |
v2.7.5 |
|
Docker |
v19.03.8 |
|
OpenStack |
Sterin |
基于容器部署 |
Mariadb |
v10.3.10 |
多主Golare Cluster |
SQLAlchemy |
v0.12.0 |
python module |
RabbitMQ |
v3.8.14 |
搭配Erlang v22.3.4.21 |
Haproxy |
v1.5.18 |
|
KeepAlived |
v1.3.5 |
|
Ceph |
v14.2.19 |
|
Rally |
v2.0 |
并發(fā)測(cè)試工具 |
CosBench |
v0.4.2 |
對(duì)象存儲(chǔ)性能測(cè)試工具 |
TestPerf |
v2.16.0 |
消息隊(duì)列性能測(cè)試工具 |
3.存儲(chǔ)方案
各OpenStack節(jié)點(diǎn)的本地磁盤只做系統(tǒng)使用。提供給用戶的存儲(chǔ)資源統(tǒng)一使用Ceph作為后端。即nova、glance、cinder-volume和cinder-backup以及manila全部使用Ceph做后端存儲(chǔ)?;谡仔酒脚_(tái)的Ceph集群部署和調(diào)優(yōu)方法請(qǐng)參見官網(wǎng)已發(fā)布的《基于兆芯平臺(tái)部署Ceph分布式存儲(chǔ)系統(tǒng)的最佳實(shí)踐》。
部署了Swift對(duì)象存儲(chǔ)服務(wù),使用本地磁盤,但沒有配置給其他組件使用。其性能也不包含本文討論范圍之內(nèi)。
4.組網(wǎng)方案
根據(jù)兆芯平臺(tái)服務(wù)器板載網(wǎng)卡配置情況、計(jì)劃業(yè)務(wù)規(guī)模和現(xiàn)有網(wǎng)絡(luò)環(huán)境等條件,規(guī)劃集群網(wǎng)絡(luò)如下圖所示。將邏輯上的五類網(wǎng)絡(luò)收縮成三個(gè)物理網(wǎng)絡(luò)實(shí)施部署,即管理和Tunnel網(wǎng)絡(luò)公用一個(gè)物理網(wǎng)絡(luò)、部署和存儲(chǔ)網(wǎng)絡(luò)公用一個(gè)物理網(wǎng)絡(luò)以及外網(wǎng)。
- 部署網(wǎng)絡(luò):用于PXE boot及安裝軟件時(shí)訪問本地軟件源鏡像;
- 管理網(wǎng)絡(luò):用于各節(jié)點(diǎn)之間通過API訪問以及SSH訪問;
- Tunnel網(wǎng)絡(luò):用于各計(jì)算節(jié)上虛擬機(jī)互通以及和網(wǎng)絡(luò)節(jié)點(diǎn)的連接,主要承載業(yè)務(wù)的東西流量;
- 存儲(chǔ)網(wǎng)絡(luò):用于訪問統(tǒng)一存儲(chǔ)后端;
圖1 網(wǎng)絡(luò)拓?fù)?/p>
二、調(diào)優(yōu)策略和手段
1.性能指標(biāo)
由于本文主要涉及IaaS平臺(tái)性能,并不包含虛擬主機(jī)的性能,因此測(cè)試和調(diào)優(yōu)工作主要在針對(duì)OpenStack關(guān)鍵組件的性能測(cè)試。我們關(guān)注的性能指標(biāo)有:單個(gè)請(qǐng)求的完成時(shí)間、批量請(qǐng)求的95%完成時(shí)間和批量請(qǐng)求的尾端延遲。
2.測(cè)試方法
獲得優(yōu)化參數(shù)的流程主要是測(cè)試、分析、調(diào)優(yōu)、再測(cè)試。
- 測(cè)試工具
對(duì)于OpenStack組件我們使用Rally來進(jìn)行批量測(cè)試,可以使用Rally中包含的測(cè)試用例也可以根據(jù)需要自定義測(cè)試用例。Rally的測(cè)試報(bào)告中會(huì)有詳細(xì)的耗時(shí)統(tǒng)計(jì)。本測(cè)試批量請(qǐng)求的測(cè)試規(guī)模是200并發(fā),被測(cè)計(jì)算節(jié)點(diǎn)十個(gè),使用的Guest OS是Cirros。
對(duì)于單個(gè)請(qǐng)求的測(cè)試還可以使用OSprofiler。Openstack中的請(qǐng)求多數(shù)都需要經(jīng)過多個(gè)組件處理才可以完成,該工具主要用途是協(xié)助分析請(qǐng)求的處理過程,在并發(fā)測(cè)試前找出可能的耗時(shí)點(diǎn),從而提前優(yōu)化。
對(duì)于RabbitMQ我們采用了TestPerf工具,并設(shè)計(jì)了幾種典型用例來進(jìn)行性能測(cè)試。重點(diǎn)關(guān)注消息隊(duì)列的吞吐量和消息投遞的延遲時(shí)間。
- 分析方法
主要從測(cè)試結(jié)果和日志入手,優(yōu)先解決日志中的告警和報(bào)錯(cuò)事件,例如瞬時(shí)高并發(fā)請(qǐng)求導(dǎo)致的服務(wù)或數(shù)據(jù)庫連接超時(shí)、數(shù)據(jù)庫查詢失敗、資源不足等問題。
其次是基于對(duì)源代碼的理解通過添加時(shí)間戳獲取測(cè)試中的業(yè)務(wù)流或數(shù)據(jù)流關(guān)鍵節(jié)點(diǎn)的耗時(shí)數(shù)據(jù),再逐個(gè)分析高耗時(shí)點(diǎn)的耗時(shí)原因。
還有一種常用方法是在標(biāo)準(zhǔn)平臺(tái)上做對(duì)標(biāo)測(cè)試。在問題收斂到一定程度后仍無法解釋性能問題的原因時(shí)或者性能問題的原因可能是和硬件架構(gòu)或體系結(jié)構(gòu)有關(guān)時(shí),使用該方法來驗(yàn)證。
- 優(yōu)化手段
對(duì)于OpenStack控制層面的優(yōu)化方向主要有三個(gè):提高并發(fā)成功率、縮短平均完成時(shí)間和降低尾部延遲。
使用OpenStack默認(rèn)配置做并發(fā)測(cè)試會(huì)發(fā)現(xiàn),根據(jù)具體的被測(cè)功能不同,200并發(fā)的成功率差別很大,通常被測(cè)功能涉及組件越多且并發(fā)數(shù)量越大成功率越低。提高成功率的方法主要靠提升硬件性能和調(diào)整各組件的配置參數(shù)。在兆芯平臺(tái)上,硬件性能的提升除了提高關(guān)鍵硬件(內(nèi)存和網(wǎng)卡)的性能外更重要的有兩點(diǎn):一是服務(wù)器系統(tǒng)要打上兆芯BSP Patch;二是系統(tǒng)部署方案開發(fā)階段就要根據(jù)兆芯平臺(tái)的NUMA 拓?fù)浣Y(jié)構(gòu)來考慮合理的親和設(shè)置,盡量避免產(chǎn)生跨NUMA訪問,若無法避免跨NUMA訪問,則盡量使用相鄰的node,避免跨Socket訪問。而在組件配置上則需要根據(jù)對(duì)請(qǐng)求的處理路徑的跟蹤,找到耗時(shí)較長的功能。由于隨并發(fā)量增加經(jīng)常會(huì)導(dǎo)致組件處理的Retry和Timeout,這些會(huì)進(jìn)一步導(dǎo)致產(chǎn)生請(qǐng)求失敗,在無法提升組件處理效率時(shí)應(yīng)適當(dāng)增加Retry次數(shù)或Timeout時(shí)間來避免請(qǐng)求直接失敗。
為縮短平均完成時(shí)間,除了前面已提到增加硬件平臺(tái)性能外,就要清楚的找到各種delay請(qǐng)求處理的具體階段。該情況下可使用各種trace手段來梳理控制流和數(shù)據(jù)流,通過分析日志等方法統(tǒng)計(jì)各處理階段的耗時(shí)。找到關(guān)鍵耗時(shí)功能后,可調(diào)優(yōu)的手段有:
1、修改組件配置,增加處理線程數(shù),充分利用多核性能;
2、組件性能依賴操作系統(tǒng)配置的,考慮調(diào)整系統(tǒng)配置來優(yōu)化,相關(guān)配置優(yōu)化方法請(qǐng)參考對(duì)應(yīng)操作系統(tǒng)提供的調(diào)優(yōu)文檔以及兆芯官方提供的其他服務(wù)器產(chǎn)品相關(guān)調(diào)優(yōu)文檔;
3、組件對(duì)同一功能經(jīng)??梢蕴峁┒喾N方式,修改配置,使用更高效的方式;
4、從社區(qū)查找對(duì)請(qǐng)求處理性能有提高的Patch,有些處理效率低的原因是實(shí)現(xiàn)方式本身就效率低,通過Patch可能有效提高處理能力。如果沒有可用Patch,只能自行開發(fā)。
5、根據(jù)Python語言特性進(jìn)行優(yōu)化,避免線程阻塞;
6、優(yōu)化組件部署方案,根據(jù)并發(fā)壓力適當(dāng)增加組件數(shù)量或節(jié)點(diǎn)數(shù)量,也可通過組件遷移均衡各節(jié)點(diǎn)的負(fù)載壓力;
7、更新服務(wù)器OS 內(nèi)核功能Patch,提供高內(nèi)核處理能力;
8、對(duì)于通用軟件,如消息隊(duì)列,數(shù)據(jù)庫和負(fù)載均衡等,還可以選擇升級(jí)版本或選用性能更好的同類軟件來實(shí)現(xiàn)性能的提高。
本文后續(xù)章節(jié)中介紹的推薦參數(shù)均根據(jù)測(cè)試成績得出。
請(qǐng)注意,測(cè)試用例不同對(duì)各參數(shù)的值影響很大,比如測(cè)試200并發(fā)創(chuàng)建虛擬機(jī)時(shí)使用的虛擬機(jī)鏡像從Cirros改為Ubuntu,那么很可能很多地方的超時(shí)時(shí)間和Retry次數(shù)都需要增加才能保證成功率100%。因此,本文中推薦值不適用于生產(chǎn)環(huán)境,僅用作調(diào)優(yōu)參考。
三、OpenStack關(guān)鍵組件配置
1. 數(shù)據(jù)庫
OpenStack系統(tǒng)中數(shù)據(jù)庫是十分關(guān)鍵的服務(wù)。各組件都在數(shù)據(jù)庫服務(wù)中有自己的數(shù)據(jù)庫,用于保存服務(wù)、資源和用戶等相關(guān)數(shù)據(jù)。數(shù)據(jù)庫還被用作各種組件間協(xié)同的一種機(jī)制。因此,在處理各種請(qǐng)求的過程中或多或少都會(huì)涉及到數(shù)據(jù)庫的讀寫操作。
在云平臺(tái)的性能調(diào)優(yōu)過程中需要重點(diǎn)觀測(cè)數(shù)據(jù)庫請(qǐng)求響應(yīng)時(shí)間。對(duì)于運(yùn)行時(shí)數(shù)據(jù)庫請(qǐng)求響應(yīng)時(shí)間過長問題通常是比較復(fù)雜的,涉及數(shù)據(jù)庫服務(wù)器/cluster、代理服務(wù)器和客戶端(即組件端),需要一一排查,查找問題源頭并做對(duì)應(yīng)調(diào)整。本節(jié)主要介紹服務(wù)器端和客戶端的相關(guān)配置參數(shù),代理服務(wù)器端參考后續(xù)章節(jié)。
1.1 Mariadb
測(cè)試用系統(tǒng)中以三個(gè)Mariadb節(jié)點(diǎn)構(gòu)建了一個(gè)多主模式的Golare Cluster,前端通過haproxy實(shí)現(xiàn)主備高可用。數(shù)據(jù)庫的調(diào)優(yōu)方法可以參考《MySQL優(yōu)化手冊(cè)》,補(bǔ)充一個(gè)在OpenStack云系統(tǒng)中需要特別注意的參數(shù)。
- max_allowed_packet
該參數(shù)用于設(shè)置MariaDB 服務(wù)器端允許接收的最大數(shù)據(jù)包大小。有時(shí)候大的插入和更新操作會(huì)因max_allowed_packet 參數(shù)設(shè)置過小導(dǎo)致失敗。
- 推薦配置:
默認(rèn)值1024,單位Kbyte。在Open Stack的集群中,有超出默認(rèn)值大小的包,需要適當(dāng)增加大小。
修改mariadb的配置文件galera.cnf
[mysqld] max_allowed_packet = 64M |
重啟mariadb服務(wù)生效。
1.2 oslo_db
各組件訪問數(shù)據(jù)庫是通過調(diào)用oslo_db來實(shí)現(xiàn)的,實(shí)際部署中我們配置oslo_db+SQLAlchemy作為數(shù)據(jù)庫訪問的客戶端,因此,數(shù)據(jù)庫客戶端的相關(guān)配置參數(shù)和調(diào)優(yōu)手段可以參考SQLAlchemy的官方文檔。注意,SQLAlchemy的高版本會(huì)帶來性能提升,但不可隨意升級(jí),需要考慮版本兼容。
- slave_connection
Stein版oslo_db支持配置slave_connection,并且已經(jīng)有部分組件支持按讀寫分離方式訪問數(shù)據(jù)庫,例如nova,即寫數(shù)據(jù)庫操作使用connection,讀數(shù)據(jù)庫操作使用slave_connection操作。從而改善讀操作的性能。
參考配置方法:
1、通過haproxy來為讀寫操作提供不同的轉(zhuǎn)發(fā)入口,如寫操作使用3306,轉(zhuǎn)發(fā)模式配置為一主兩備;讀操作使用3307,轉(zhuǎn)發(fā)方式配置為按最小連接數(shù)。
2、為支持讀寫分離的組件在其配置文件的【database】段中增加slave_connection配置。
注意:有些組件雖然可以配置slave_connection但其代碼事件中實(shí)際上并沒有在讀數(shù)據(jù)庫時(shí)調(diào)用slave_connection,需根據(jù)具體版本仔細(xì)確認(rèn)。
- 連接池
和數(shù)據(jù)庫建立連接是一個(gè)相對(duì)耗時(shí)的過程,因此,提供了連接池機(jī)制,連接池中的連接可以被重復(fù)使用,以提高效率。用戶在調(diào)試過程中應(yīng)關(guān)注各組件和數(shù)據(jù)庫之間的連接情況,如組件的數(shù)據(jù)庫請(qǐng)求響應(yīng)時(shí)間和組件日志中數(shù)據(jù)庫連接相關(guān)信息等。若發(fā)現(xiàn)數(shù)據(jù)庫請(qǐng)求響應(yīng)時(shí)間過長且經(jīng)排查后懷疑是組件端在連接數(shù)據(jù)庫上耗時(shí)過長時(shí),可通過以下參數(shù)嘗試優(yōu)化。在本文所述測(cè)試過程中使用了默認(rèn)參數(shù)設(shè)置,用戶需根據(jù)運(yùn)行時(shí)情況進(jìn)行調(diào)優(yōu)。連接池主要配置參數(shù)如下:
min_pool_size :連接池中已連接的SQL連接數(shù)不得小于該值,默認(rèn)值是1。
max_pool_size :連接池中已連接的最大SQL連接數(shù),默認(rèn)值是5,設(shè)置0時(shí)無限制。
max_overflow :最大允許超出最大連接數(shù)的數(shù)量,默認(rèn)值是50。
pool_timeout :從連接池里獲取連接時(shí)如果無空閑的連接,且連接數(shù)已經(jīng)到達(dá)了max_pool_size+max_overflow,那么要獲取連接的進(jìn)程會(huì)等待pool_timeout秒,默認(rèn)值是30s,如果超過這個(gè)時(shí)間還沒有得到連接將會(huì)拋出異常。如果出現(xiàn)該異常,可以考慮增加連接池的連接數(shù)。
2. 消息隊(duì)列
2.1 Rabbitmq
測(cè)試用系統(tǒng)中以三節(jié)點(diǎn)構(gòu)建了一個(gè)RabbitMQ Mirror Queue Cluster,所有節(jié)點(diǎn)均為disk節(jié)點(diǎn)。
RabbitMQ的主要性能問題是消息投遞的延遲時(shí)間,但對(duì)于cluster的組織方式,其延遲時(shí)間主要消耗在鏡像隊(duì)列之間的數(shù)據(jù)一致性保證處理上,而這種處理過程十分復(fù)雜,調(diào)優(yōu)難度很大。對(duì)于Cluster目前的調(diào)優(yōu)手段有如下幾種:
1、由于Rabbitmq基于erlang運(yùn)行,而通過對(duì)比測(cè)試,高低版本性能差異較大,建議盡量使用高版本,如Rabbitmq使用3.8以上版本,erlang版本v22.3及以上(根據(jù)Rabbitmq版本具體選擇)。
2、由于Cluster的隊(duì)列鏡像數(shù)量越多,每條消息處理時(shí)在一致性上的耗時(shí)越長,因此可以根據(jù)實(shí)際情況減少鏡像隊(duì)列數(shù)量。
3、若Rabbitmq是容器化安裝,為避免CPU資源被搶占,可配置docker參數(shù),分配給Rabbitmq更多的CPU時(shí)間。
推薦的調(diào)優(yōu)參數(shù)如下:
- collect_statistics_interval
默認(rèn)情況下,Rabbitmq Server默認(rèn)以5s的間隔統(tǒng)計(jì)系統(tǒng)信息,周期內(nèi)publish、 delivery message等速率信息會(huì)以此為周期統(tǒng)計(jì)。
- 推薦配置
增加該值能夠減少Rabbitmq Server 收集大量的狀態(tài)信息而導(dǎo)致CPU利用率增加,參數(shù)單位為ms。
編輯RabbitMQ的配置文件rabbitmq.conf
collect_statistics_interval = 30000 |
重啟RabbitMQ服務(wù)生效。
- cpu_share
Docker 允許用戶為每個(gè)容器設(shè)置一個(gè)數(shù)字,代表容器的 CPU share,默認(rèn)情況下每個(gè)容器的 share 是 1024。要注意,這個(gè) share 是相對(duì)的,本身并不能代表任何確定的意義。當(dāng)主機(jī)上有多個(gè)容器運(yùn)行時(shí),每個(gè)容器占用的 CPU 時(shí)間比例為它的 share 在總額中的比例。只有在CPU資源緊張時(shí),設(shè)定的資源比例才可以顯現(xiàn)出來,如果CPU資源空閑,cpu_share值低的docker也能獲取到比例外的CPU資源。
- 推薦配置
控制節(jié)點(diǎn)上部署openstack 各組件的api server以及rabbitmq server,當(dāng)對(duì)rabbitmq做并發(fā)測(cè)試時(shí),可以適當(dāng)提高節(jié)點(diǎn)上rabbitmq docker的CPU share讓其獲得更多的CPU資源。如果條件滿足的情況下,Rabbitmq Cluster 應(yīng)該單獨(dú)部署在服務(wù)器集群上,不和其他服務(wù)搶占CPU資源。單獨(dú)部署的Rabbitmq Cluster有更高的并發(fā)能力。
配置方法:
docker update --cpu-shares 10240 rabbitmq-server
- ha-mode
Rabbitmq Cluster鏡像隊(duì)列可以配置鏡像隊(duì)列在多節(jié)點(diǎn)備份,每個(gè)隊(duì)列包含一個(gè)master節(jié)點(diǎn)和多個(gè)slave節(jié)點(diǎn)。消費(fèi)者消費(fèi)的操作現(xiàn)在master節(jié)點(diǎn)上完成,之后再slave上進(jìn)行相同的操作。生產(chǎn)者發(fā)布的消息會(huì)同步到所有的節(jié)點(diǎn)。其他的操作通過master中轉(zhuǎn),master將操作作用于slave。鏡像隊(duì)列的配置策略:
ha-mode |
ha-params |
說明 |
all |
集群中每個(gè)節(jié)點(diǎn)都有鏡像隊(duì)列 |
|
exactly |
count |
指定集群中鏡像隊(duì)列的個(gè)數(shù) |
nodes |
node names |
在指定的節(jié)點(diǎn)列表中配置鏡像隊(duì)列 |
默認(rèn)的ha-mode是all,三節(jié)點(diǎn)的集群鏡像隊(duì)列在3節(jié)點(diǎn)備份。定義策略的指令:
rabbimqctl set_policy –p vhost
pattern:正則匹配,定義的policy會(huì)根據(jù)正則表達(dá)式應(yīng)用到相應(yīng)的交換機(jī)或者隊(duì)列上。
definition:配置策略的參數(shù)。
- 推薦配置
rabbitmqctl set_policy -p / ha-exactly '^' '{'ha-mode':'exactly', 'ha-params':2}'
集群兩隊(duì)列備份比三隊(duì)列備份應(yīng)對(duì)高并發(fā)的能力更強(qiáng),前者支持的并發(fā)數(shù)是后者的1.75倍。
- CPU綁定
RabbitMQ 運(yùn)行在erlang 虛擬機(jī)中。本文環(huán)境之中使用的erlang版本支持SMP,采用多調(diào)度器多隊(duì)列的機(jī)制,即啟動(dòng)erlang虛擬機(jī)時(shí),默認(rèn)會(huì)根據(jù)系統(tǒng)邏輯CPU核數(shù)啟動(dòng)相同數(shù)量的調(diào)度器(可通過啟動(dòng)參數(shù)+S限制),每個(gè)調(diào)度器都會(huì)從各自的運(yùn)行隊(duì)列中獲取運(yùn)行進(jìn)程。但由于OS的線程調(diào)度機(jī)制,erlang調(diào)度器線程會(huì)在各核之間遷移,這會(huì)導(dǎo)致Cache Miss增加,影響性能??梢酝ㄟ^參數(shù)+sbt 來設(shè)置調(diào)度器和邏輯核綁定。Erlang支持多種綁定策略,詳見Erlang說明文檔。
- 推薦配置
默認(rèn)配置為db,按numa node輪流綁定,盡量使用到所有node。但由于調(diào)度器多任務(wù)隊(duì)列之間存在balance機(jī)制,任務(wù)會(huì)在隊(duì)列間遷移,因此,為了更好的利用cache,在本文測(cè)試環(huán)境下上推薦將+stb 配置為nnts,即調(diào)度器線程按numa node順序進(jìn)行綁定。
編輯RabbitMQ的配置文件rabbitmq-env.conf
RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS='+sbt nnts' |
重啟RabbitMQ 服務(wù)生效。
用戶還可根據(jù)實(shí)際環(huán)境采取其他Erlang虛擬機(jī)調(diào)優(yōu)方法配合使用(如啟用Hipe等),但具體Erlang虛擬機(jī)的參數(shù)介紹和調(diào)優(yōu)手段不在本文討論范圍。
- 心跳保持
Hearbeat用來檢測(cè)通信的對(duì)端是否?;??;驹硎菣z測(cè)對(duì)應(yīng)的socket鏈接上數(shù)據(jù)的收發(fā)是否正常,如果有一段時(shí)間沒有收發(fā)數(shù)據(jù),則向?qū)Χ税l(fā)送一個(gè)心跳檢測(cè)包,如果一段時(shí)間內(nèi)沒有回應(yīng)則認(rèn)為心跳超時(shí)對(duì)端可能異常crash。在負(fù)載重時(shí)組件可能無法及時(shí)處理heartbeat消息而導(dǎo)致Rabbitmq Server沒有在超時(shí)時(shí)間內(nèi)收到心跳檢測(cè)應(yīng)答。Rabbitmq Server因組件超時(shí)未應(yīng)答而關(guān)閉連接導(dǎo)致錯(cuò)誤。適當(dāng)增加組件和Rabbitmq Server的心跳超時(shí)時(shí)間以避免該錯(cuò)誤。參考Cinder章節(jié)中相關(guān)介紹。
編輯RabbitMQ的配置文件rabbitmq.conf
heartbeat = 180 |
重啟RabbitMQ服務(wù)生效。
2.2 oslo_messaging
各功能組件和Rabbitmq的連接都是調(diào)用oslo_messaging來實(shí)現(xiàn)的,該庫中提供的如下幾個(gè)參數(shù)可用于優(yōu)化高負(fù)載時(shí)RPC處理性能。
rpc_conn_pool_size :RPC連接池的大小,默認(rèn)值是30。
executor_thread_pool_size :執(zhí)行RPC處理的線程或協(xié)程數(shù)量,默認(rèn)值是64。
rpc_response_timeout :RPC調(diào)用響應(yīng)超時(shí)時(shí)間,默認(rèn)值是60s。
我們的測(cè)試中前兩個(gè)參數(shù)使用了默認(rèn)值,rpc_response_timeout根據(jù)各組件的實(shí)際處理能力做了適當(dāng)增加。
3. Nova
3.1 nova-api
- Workers
nova-api是一個(gè)WSGI Server,亦可叫api server,其負(fù)責(zé)接收外部發(fā)來的REST請(qǐng)求。API Server啟動(dòng)時(shí)會(huì)根據(jù)配置文件建立一定數(shù)量的worker線程來接收請(qǐng)求,如果線程數(shù)量不足以滿足并發(fā)量,就會(huì)出現(xiàn)排隊(duì),導(dǎo)致請(qǐng)求處理時(shí)間增加。因此,管理員可以根據(jù)實(shí)際的業(yè)務(wù)量來設(shè)置worker數(shù)量。對(duì)于獨(dú)占一個(gè)節(jié)點(diǎn)的api server,workers通常可以設(shè)置到等同于CPU核數(shù),但在多個(gè)api server同部署于一個(gè)節(jié)點(diǎn)時(shí)(如在控制節(jié)點(diǎn)上),物理CPU資源有限,就需要根據(jù)服務(wù)器處理能力和各api server的負(fù)載情況調(diào)整各自的workers數(shù)量。
對(duì)于容器方式部署的api server,還可以考慮配置容器使用的CPU和內(nèi)存資源的numa親和,盡量做到各容器的NUMA親和分開,從而減少CPU資源搶占以及更好的利用cache資源。該方法對(duì)其他以容器方式部署的服務(wù)也同樣適用。
在nova-api的配置文件中有幾個(gè)配置項(xiàng),如:osapi_compute_workers和metadata_workers,可以根據(jù)節(jié)點(diǎn)服務(wù)器能力和負(fù)載情況進(jìn)行調(diào)整。
其他組件,如glance等都有自己的api server,與nova-api類似,可以通過增加workers數(shù)量來改善請(qǐng)求處理性能。
- Patch:熱遷移性能
Nova Compute在執(zhí)行l(wèi)ive-migrate()時(shí),子線程執(zhí)行_live_migration_operation()函數(shù)和主線程執(zhí)行_live_migration_monitor()函數(shù)都會(huì)訪問Instance Fields。如果此前未曾訪問過Instance.fields,則可能同時(shí)調(diào)用nova/objects/instance.py:class Instance. obj_load_attr()函數(shù),utils.temporary_mutation()函數(shù)的重入會(huì)導(dǎo)致執(zhí)行后Context.read_deleted賦值為“yes”。后續(xù)在_update_usage_from_instances()時(shí)會(huì)統(tǒng)計(jì)不必要統(tǒng)計(jì)的已經(jīng)刪除的instances,增加耗時(shí)。 Stein版本的OpenStack存在上述漏洞,后續(xù)的版本由于其他操作提前獲取過Intance.fields,從而nova-compute不需要在live-migrate時(shí)調(diào)用obj_load_attr()。Bug提交信息見鏈接:https://bugs.launchpad.net/nova/+bug/1941819??梢酝ㄟ^補(bǔ)丁https://github.com/openstack/nova/commit/84db8b3f3d202a234221ed265ec00a7cf32999c9 在Nova API中提前獲取Instance.fileds以避免該Bug。
補(bǔ)丁詳情參見附件A1.1
3.2 Nova-scheduler & nova-conductor
這兩個(gè)服務(wù)同樣也有workers配置參數(shù)來控制處理線程的數(shù)量,可以根據(jù)實(shí)際業(yè)務(wù)量來調(diào)整,我們的測(cè)試中設(shè)置為默認(rèn)值即可滿足。Nova-conductor的workers默認(rèn)值是CPU核數(shù)。Nova-scheduler的默認(rèn)值是當(dāng)使用filter-scheduler時(shí)是CPU核數(shù),使用其他Scheduler時(shí)默認(rèn)值是1。
3.3 nova-computer
- vcpu_pin_set
限定Guest可以使用compute node上的pCPUs的范圍,給Host適當(dāng)留下一些CPU以保證正常運(yùn)作。解決因虛擬機(jī)高負(fù)載情況下爭搶CPU資源導(dǎo)致Host性能不足的問題。
- 推薦配置
在每個(gè)numa node上都預(yù)留一個(gè)物理CPU核供Host使用。以兩numa nodes 平臺(tái)為例,可選擇預(yù)留cpu0和cpu15供Host使用,配置方法如下:
編輯nova-compute的配置文件nova.conf
vcpu_pin_set = 1-14 |
重啟nova_compute服務(wù)生效。
- reserved_host_memory_mb
該參數(shù)用于在計(jì)算節(jié)點(diǎn)上預(yù)留內(nèi)存給Host系統(tǒng)使用,避免虛擬主機(jī)占用掉過多內(nèi)存,導(dǎo)致Host系統(tǒng)上的任務(wù)無法正常運(yùn)行。
- 推薦配置
設(shè)置值須根據(jù)實(shí)際內(nèi)存總量、Host系統(tǒng)中運(yùn)行的tasks以及預(yù)期虛擬機(jī)會(huì)占用的最大內(nèi)存量來決定,建議至少預(yù)留1024MB供系統(tǒng)使用。
編輯nova-compute的配置文件nova.conf
reserved_host_memory_mb=4096 #單位MB |
重啟nova_compute服務(wù)生效。
- cpu_allocation_ratio
配置vCPU的可超配比例。
- 推薦配置
根據(jù)ZHAOXIN CPU的性能,桌面云系統(tǒng)中,單個(gè)pCPU可以虛擬2個(gè)vCPU。
編輯nova-compute的配置文件nova.conf
[DEFAULT] cpu_allocation_ratio = 2 |
重啟nova_compute服務(wù)生效。
- block_device_allocate_retries
創(chuàng)建有block device的虛擬機(jī)時(shí),需要從blank、image或者snaphot創(chuàng)建volume。在volume被attach到虛擬機(jī)之前,其狀態(tài)必須是“available”。block_device_allocate_retries指定nova檢查volume狀態(tài)是否“available”的次數(shù)。相關(guān)參數(shù)有block_device_allocate_retries_interval,指定檢查狀態(tài)的查詢間隔,默認(rèn)值3,單位s。
- 推薦配置
默認(rèn)值是60次。當(dāng)cinder負(fù)載較重時(shí),60次查詢之后可能volume的狀態(tài)不是“available”,適當(dāng)增加查詢次數(shù),避免虛擬機(jī)創(chuàng)建失敗。
編輯nova-compute的配置文件nova.conf
[DEFAULT] block_device_allocate_retries = 150 |
重啟nova_compute服務(wù)生效。
- vif_plugging_timeout
nova-compute等待Neutron VIF plugging event message arrival的超時(shí)時(shí)間。
- 推薦配置
默認(rèn)值300,單位s。當(dāng)創(chuàng)建虛擬機(jī)并發(fā)數(shù)高時(shí),可能無法在300s內(nèi)收到該event。兆芯桌面云系統(tǒng)測(cè)試200并發(fā)創(chuàng)建虛擬機(jī)時(shí),耗時(shí)約360s,該值可以根據(jù)系統(tǒng)的最大并發(fā)數(shù)適當(dāng)調(diào)整。
編輯nova-compute的配置文件nova.conf
[DEFAULT] vif_plugging_timeout = 500 |
重啟nova_compute服務(wù)生效。
- Patch:熱遷移性能
該補(bǔ)丁完成了兩個(gè)功能:去掉遷移過程中不必要的get_volume_connect() 函數(shù)調(diào)用,以及減少不要的Neutron訪問。該補(bǔ)丁能夠讓熱遷移更高效,熱遷移的無服務(wù)時(shí)間更短。補(bǔ)丁地址:
https://review.opendev.org/c/openstack/nova/+/795027
https://opendev.org/openstack/nova/commit/6488a5dfb293831a448596e2084f484dd0bfa916
補(bǔ)丁詳情參見附件A1.2
4. Cinder
4.1 cinder-api
- Workers
參見nova-api。不同的是Stein版中cinder-api采用httpd部署,因此,其除了可以調(diào)優(yōu)cinder支持的各種workers(如osapi_volume_workers),還可以調(diào)優(yōu)cinder-wsgi.conf中的processes和threads。類似的組件還有keystone和horizon等。
編輯cinder-wsgi.conf
WSGIDaemonProcess cinder-api processes=12 threads=3 user=cinder group=cinder display-name=%{GROUP} python-path=/var/lib/kolla/venv/lib/python2.7/site-packages ……
|
重啟cinder-api服務(wù)生效。
- rpc_response_timeout
Cinder-api等待RPC消息返回的超時(shí)時(shí)間
- 推薦配置
默認(rèn)值 60,單位s。在高并發(fā)的attach_volume時(shí),cinder-volume響應(yīng)cinder-api的時(shí)間較長。如果報(bào)告rpc timeout的錯(cuò)誤,可以適當(dāng)調(diào)大該值。
編輯cinder-volume的配置文件cinder.conf
[DEFAULT] rpc_response_timeout = 600 |
重啟cinder-api服務(wù)生效。
4.2 cinder-volume
- 心跳保持
和Rabbitmq之間
參考RabbitMQ章中的“心跳保持”小節(jié)。
Cinder的heartbeat_timeout_threshold用來設(shè)置心跳超時(shí)時(shí)間,會(huì)以1/2心跳超時(shí)時(shí)間為間隔發(fā)送心跳檢測(cè)信號(hào)。
- 推薦配置
cinder-volume heartbeat_timeout_threshold默認(rèn)值為60,單位為s,在負(fù)載重時(shí)可能無法在及時(shí)處理heartbeat消息而導(dǎo)致Rabbitmq Server沒有在超時(shí)時(shí)間內(nèi)收到心跳檢測(cè)應(yīng)答。Rabbitmq Server因Cinder-volume超時(shí)未應(yīng)答而關(guān)閉連接,進(jìn)而導(dǎo)致一系列錯(cuò)誤。適當(dāng)增加Cinder-volume和Rabbitmq Server的心跳超時(shí)時(shí)間以避免該錯(cuò)誤,不建議禁用心跳檢測(cè)機(jī)制(heartbeat=0)。
編輯cinder-volume的配置文件cinder.conf
[oslo_messaging_rabbit] heartbeat_timeout_threshold = 180 |
重啟cinder-volume服務(wù)生效。
服務(wù)之間
OpenStack是一個(gè)分布式系統(tǒng),由運(yùn)行在不同主機(jī)上的各個(gè)服務(wù)組成來共同完成各項(xiàng)工作。每個(gè)服務(wù)都會(huì)定時(shí)向數(shù)據(jù)庫中更新自己的update time,服務(wù)間可通過查詢對(duì)方的update time是否超過設(shè)置的service_down_time來判斷服務(wù)是否在線。這也可以看作是一種心跳機(jī)制。
在高負(fù)載時(shí),數(shù)據(jù)庫訪問可能延遲增加,同時(shí)運(yùn)行上報(bào)的周期任務(wù)會(huì)因CPU資源被占用導(dǎo)致延遲上報(bào),這些都有可能引發(fā)誤報(bào)service down。
- 推薦配置
report_interval:狀態(tài)報(bào)告間隔,即心跳間隔,默認(rèn)10,單位s。
service_down_time:距離上一次心跳的最長時(shí)間,默認(rèn)60,單位s。超過這個(gè)時(shí)間沒有心跳則認(rèn)為服務(wù)Down。
report-interval一定要小于service_down_time。適當(dāng)增加service_down_time,避免cinder-volume的周期性任務(wù)占用cpu導(dǎo)致沒有及時(shí)報(bào)告狀態(tài)而被誤認(rèn)為Down。
編輯cinder-volume的配置文件cinder.conf
service_down_time = 120 |
重啟cinder_volume服務(wù)生效。
- rbd_exclusive_cinder_pool
OpenStack Ocata引入了參數(shù)rbd_exclusive_cinder_pool,如果RBD pool是Cinder獨(dú)占,則可以設(shè)置rbd_exclusive_cinder_pool=true。Cinder用查詢數(shù)據(jù)庫的方式代替輪詢后端所有volumes的方式獲取provisioned size,這會(huì)明顯減少查詢時(shí)間,同時(shí)減輕Ceph 集群和Cinder-volume 服務(wù)的負(fù)載。
- 推薦配置
編輯cinder-volume的配置文件cinder.conf
[DEFAULT] Enable_backends =rbd-1 [rbd-1] rbd_exclusive_cinder_pool = true |
重啟cinder-volume服務(wù)生效。
- image_volume_cache_enabled
從Liberty版本開始,Cinder能夠使用image volume cahe,能夠提高從image創(chuàng)建volume的性能。從image第一次創(chuàng)建volume的同時(shí)會(huì)創(chuàng)建屬于快存儲(chǔ)Internal Tenant的cached image-volume 。后續(xù)從該image創(chuàng)建volume時(shí)從cached image-volume 克隆,不需要將image 下載到本地再傳入volume。
- 推薦配置
cinder_internal_tenant_project_id:指定OpenStack的項(xiàng)目“service”的ID
cinder_internal_tenant_user_id:指定OpenStack的用戶“cinder”的ID
image_volume_cache_max_size_gb:指定cached image-volume的最大size,設(shè)置為0,即不對(duì)其限制。
image_volume_cache_max_count:指定cached image-volume的最大數(shù)量,設(shè)置為0,即不對(duì)其限制。
編輯cinder-volume的配置文件cinder.conf
[DEFAULT] cinder_internal_tenant_project_id = c4076a45bcac411bacf20eb4fecb50e0 cinder_internal_tenant_user_id = 4fe8e33010fd4263be493c1c9681bec8 [backend_defaults] image_volume_cache_enabled=True image_volume_cache_max_size_gb = 0 image_volume_cache_max_count = 0 |
重啟cinder-volume服務(wù)生效。
5. Neutron
5.1 Neutron Service
neutron-service是neutron組件的api server,其配置優(yōu)化參考nova-api中的介紹,可調(diào)整參數(shù)有api_workers和metadata_workers。
- rpc_workers
在neutron的設(shè)計(jì)架構(gòu)上,核心服務(wù)和各plugin的處理是先主進(jìn)程fork子進(jìn)程,再在子進(jìn)程中創(chuàng)建協(xié)程來運(yùn)行處理程序,從而實(shí)現(xiàn)可利用到多核的并發(fā)處理。rpc_workers是用來控制為RPC處理創(chuàng)建的進(jìn)程數(shù)量,默認(rèn)值是api_workers的一半。由于我們的系統(tǒng)是基于容器部署,因此該值使用默認(rèn)值即可。
5.2 Neutron DHCP Agent
這里的兩個(gè)補(bǔ)丁主要影響網(wǎng)絡(luò)節(jié)點(diǎn)上的neutron服務(wù)。
- 改善Network Port管理效率
Patch1
Neutron DHCP agent中用Pyroute2 的“ip route”命令替換oslo.rootwrap庫中該linux命令。該補(bǔ)丁讓Neutron DHCP agent創(chuàng)建和刪除port時(shí)更加高效。補(bǔ)丁地址:
https://opendev.org/openstack/neutron/commit/06997136097152ea67611ec56b345e5867184df5
補(bǔ)丁詳情參見附件A1.3。
Patch2
Neutron DHCP agent中用oslo.privsep庫的“dhcp_release”命令替換oslo.rootwrap庫該linux 命令。該補(bǔ)丁讓Neutron DHCP agent創(chuàng)建和刪除port時(shí)更加高效。補(bǔ)丁地址:
https://opendev.org/openstack/neutron/commit/e332054d63cfc6a2f8f65b1b9de192ae0df9ebb3
https://opendev.org/openstack/neutron/commit/2864957ca53a346418f89cc945bba5cdcf204799
補(bǔ)丁詳情參見附件A1.4。
5.3 Neuton OpenvSwitch Agent
- 改善Network Port處理效率
polling_interval
Neutron L2 Agent如果配置的是openvswitch agent,neutron-openvswitch-agent啟動(dòng)后會(huì)運(yùn)行一個(gè)RPC循環(huán)任務(wù)來處理端口添加、刪除、修改。通過配置項(xiàng)polling_interval指定RPC循環(huán)執(zhí)行的間隔。
- 推薦配置
默認(rèn)值是2,單位s。減少該值可以使得端口狀態(tài)更新更快,特別是熱遷移過程中,減少該值可以減少遷移時(shí)間。但如果設(shè)置為0會(huì)導(dǎo)致neutron-openvswitch-agent占用過多的CPU資源。
編輯neutron-openvswitch-agent的配置文件ml2_conf.ini
[agent] polling_interval = 1 |
重啟計(jì)算節(jié)點(diǎn)的neutron-openvswitch-agent服務(wù)生效。
Patch
Neutron openvswitch agent中用oslo.privsep庫替換oslo.rootwrap庫的“iptables”和“ipset”命令。該補(bǔ)丁能讓Neutron openvswitch agent處理network port時(shí)更加高效。補(bǔ)丁地址:
https://opendev.org/openstack/neutron/commit/6c75316ca0a7ee2f6513bb6bc0797678ef419d24
https://opendev.org/openstack/neutron/commit/5a419cbc84e26b4a3b1d0dbe5166c1ab83cc825b
補(bǔ)丁詳情參見附件A1.5。
5.4 熱遷移Down Time時(shí)間優(yōu)化
openstack stein版本在熱遷移的測(cè)試中,虛機(jī)遷移到目標(biāo)主機(jī)后,網(wǎng)絡(luò)不能及時(shí)ping通,存在比較明顯的延時(shí)現(xiàn)象。原因是虛機(jī)遷移成功后會(huì)立刻發(fā)送RARP廣播,而此時(shí)虛機(jī)網(wǎng)卡對(duì)應(yīng)的port還沒真正up。Bug信息:
https://bugs.launchpad.net/neutron/+bug/1901707
https://bugs.launchpad.net/neutron/+bug/1815989
補(bǔ)丁詳情參見附錄B1.1--B1.7, 涉及neutron和nova模塊:
https://review.opendev.org/c/openstack/neutron/+/790702
https://review.opendev.org/c/openstack/nova/+/767368
5.5 網(wǎng)絡(luò)性能優(yōu)化
網(wǎng)絡(luò)為了獲得穩(wěn)定的高性能,在部署虛機(jī)時(shí),網(wǎng)卡硬中斷和對(duì)應(yīng)虛機(jī),最好限定在位于同一Cluster的CPU上,這樣可以避免不必要的cache miss,進(jìn)而提升網(wǎng)絡(luò)的穩(wěn)定性和性能。
5.6 VXLAN 性能優(yōu)化
主流隧道網(wǎng)絡(luò)普遍基于UDP協(xié)議實(shí)現(xiàn),例如VXLAN,當(dāng)UDP校驗(yàn)和字段為零時(shí),會(huì)導(dǎo)致接收端在處理VXLAN報(bào)文時(shí)不能及時(shí)進(jìn)行GRO(generic receive offload)處理,進(jìn)而嚴(yán)重影響網(wǎng)絡(luò)性能。該問題社區(qū)已經(jīng)修正,具體信息可以參見下面鏈接:
打上該補(bǔ)丁后,萬兆網(wǎng)卡情況下,同樣的VXLAN iperf3測(cè)試,成績可以提升2倍以上。
補(bǔ)丁詳情參見附錄C1.1
6. Keystone
- 并發(fā)進(jìn)程數(shù)
WSGIDaemonProcess守護(hù)進(jìn)程的配置參數(shù)。Processes定義了守護(hù)進(jìn)程啟動(dòng)的進(jìn)程數(shù)。
- 推薦配置
默認(rèn)值為1。當(dāng)keystone壓力較大時(shí),1個(gè)WSGI進(jìn)程無法處理較大的并發(fā)數(shù),適當(dāng)增加processes的值,大于CPU cores number的意義不大。
編輯keystone的配置文件wsgi-keystone.conf
WSGIDaemonProcess keystone-public processes=12 threads=1 user=keystone group=keystone display-name=%{GROUP} python-path=/var/lib/kolla/venv/lib/python2.7/site-packages ……
WSGIDaemonProcess keystone-admin processes=12 threads=1 user=keystone group=keystone display-name=%{GROUP} python-path=/var/lib/kolla/venv/lib/python2.7/site-packages ……
|
重啟keystone服務(wù)生效。
7. Haproxy
- Timeout設(shè)置
1、timeout http-request:HTTP請(qǐng)求的最大超時(shí)時(shí)間
2、timeout queue:當(dāng)server端請(qǐng)求數(shù)量達(dá)到了maxconn,新到的connections會(huì)被添加到指定的queue。當(dāng)requests在queue上等待超過timeout queue時(shí),request被認(rèn)為不被服務(wù)而丟棄,返回503 error給client端。
3、timeout connect :connection連接上后端服務(wù)器的超時(shí)時(shí)間。
4、timeout client :client端發(fā)送數(shù)據(jù)或者應(yīng)答時(shí),客戶端最大的非活躍時(shí)間
5、timeout server:server端最大的非活躍時(shí)間
- 推薦配置
編輯haproxy的配置文件haproxy.cfg,單位s表示秒,單位m表示分。
defaults timeout http-request 100s timeout queue 4m timeout connect 100s timeout client 10m timeout server 10m。 |
重啟haproxy服務(wù)生效。
- 最大連接數(shù)
Haproxy可以配置全局的maxconn定義Haproxy同時(shí)最大的連接數(shù),也可以為后端服務(wù)配置maxconn定義該服務(wù)的最大連接數(shù),可以為前端配置maxconn定義此端口的最大連接數(shù)。系統(tǒng)的ulimit -n的值一定要大于maxconn。
- 推薦配置
全局的maxconn默認(rèn)值為4000,應(yīng)用過程中在Haproxy的控制臺(tái)觀察到全局連接數(shù)不夠,將其增加到40000。
編輯Haproxy的配置文件haproxy.cfg
global maxconn 40000 |
重啟haproxy服務(wù)生效。
- 處理線程數(shù)
設(shè)置haproxy的負(fù)責(zé)均衡并發(fā)進(jìn)程數(shù),OpenStack Stein的Haproxy 版本為1.5.18。該參數(shù)在haproxy 2.5 版本中已移除,由nbthread參數(shù)指定線程數(shù)量。
- 推薦配置
由于Haproxy的負(fù)載較重,推薦適當(dāng)增大該參數(shù)。
編輯Haproxy的配置文件haproxy.cfg
global nbproc 4 |
重啟haproxy服務(wù)生效。
- 權(quán)重
Haproxy后端配置參數(shù)weight可以配置server的權(quán)重,取值范圍0-256,權(quán)重越大,分給這個(gè)server的請(qǐng)求就越多。weight為0的server將不會(huì)被分配任何新的連接。所有server的默認(rèn)值為1。
- 推薦配置
當(dāng)多個(gè)后端的主機(jī)壓力不一致時(shí),可以將壓力大的主機(jī)上的server的權(quán)重適當(dāng)減少,從而使各主機(jī)負(fù)載均衡。
以3 控制節(jié)點(diǎn)的小集群為例:測(cè)試過程中,controller03上整體cpu使用率較高達(dá)95%+,其他兩個(gè)控制節(jié)點(diǎn)cpu使用率約在70%,各個(gè)控制節(jié)點(diǎn)keystone的 cpu使用率均較高。減少controller03上keystone server的權(quán)重,從而減少controller03的cpu壓力。
編輯Haproxy的配置文件keystone.cfg
listen keystone_external mode http http-request del-header X-Forwarded-Proto option httplog option forwardfor http-request set-header X-Forwarded-Proto https if { ssl_fc } bind haproxy-ip-addr:5000 maxconn 5000 server controller01 server-ip-addr:5000 check inter 2000 rise 2 fall 5 maxconn 3000 weight 10 server controller02 server-ip-addr:5000 check inter 2000 rise 2 fall 5 maxconn 3000 weight 10 server controller03 server-ip-addr:5000 check inter 2000 rise 2 fall 5 maxconn 3000 weight 9 |
重啟haproxy服務(wù)生效。
~~~~~~~~~~~~~~~~~~
附錄
A1.1
diff -ruN nova-bak/api/openstack/compute/migrate_server.py nova/api/openstack/compute/migrate_server.py --- nova-bak/api/openstack/compute/migrate_server.py 2021-09-10 11:20:15.774990677 +0800 +++ nova/api/openstack/compute/migrate_server.py 2021-09-10 11:23:22.239098421 +0800 @@ -157,7 +157,9 @@ 'conductor during pre-live-migration checks ' ''%(ex)s'', {'ex': ex}) else: - raise exc.HTTPBadRequest(explanation=ex.format_message()) + raise exc.HTTPBadRequest(explanation=ex.format_message()) + except exception.OperationNotSupportedForSEV as e: + raise exc.HTTPConflict(explanation=e.format_message()) except exception.InstanceIsLocked as e: raise exc.HTTPConflict(explanation=e.format_message()) except exception.ComputeHostNotFound as e: diff -ruN nova-bak/api/openstack/compute/suspend_server.py nova/api/openstack/compute/suspend_server.py --- nova-bak/api/openstack/compute/suspend_server.py 2021-09-10 11:25:03.847439106 +0800 +++ nova/api/openstack/compute/suspend_server.py 2021-09-10 11:27:09.958950964 +0800 @@ -40,7 +40,8 @@ self.compute_api.suspend(context, server) except exception.InstanceUnknownCell as e: raise exc.HTTPNotFound(explanation=e.format_message()) - except exception.InstanceIsLocked as e: + except (exception.OperationNotSupportedForSEV, + exception.InstanceIsLocked) as e: raise exc.HTTPConflict(explanation=e.format_message()) except exception.InstanceInvalidState as state_error: common.raise_http_conflict_for_instance_invalid_state(state_error, diff -ruN nova-bak/compute/api.py nova/compute/api.py --- nova-bak/compute/api.py 2021-09-10 11:31:55.278077457 +0800 +++ nova/compute/api.py 2021-09-10 15:32:28.131175652 +0800 @@ -215,6 +215,23 @@ return fn(self, context, instance, *args, **kwargs) return _wrapped
+def reject_sev_instances(operation): + '''Decorator. Raise OperationNotSupportedForSEV if instance has SEV + enabled. + ''' + + def outer(f): + @six.wraps(f) + def inner(self, context, instance, *args, **kw): + if hardware.get_mem_encryption_constraint(instance.flavor, + instance.image_meta): + raise exception.OperationNotSupportedForSEV( + instance_uuid=instance.uuid, + operation=operation) + return f(self, context, instance, *args, **kw) + return inner + return outer +
def _diff_dict(orig, new): '''Return a dict describing how to change orig to new. The keys @@ -690,6 +707,9 @@ ''' image_meta = _get_image_meta_obj(image)
+ API._validate_flavor_image_mem_encryption(instance_type, image_meta) + + # Only validate values of flavor/image so the return results of # following 'get' functions are not used. hardware.get_number_of_serial_ports(instance_type, image_meta) @@ -701,6 +721,19 @@ if validate_pci: pci_request.get_pci_requests_from_flavor(instance_type)
+ @staticmethod + def _validate_flavor_image_mem_encryption(instance_type, image): + '''Validate that the flavor and image don't make contradictory + requests regarding memory encryption. + :param instance_type: Flavor object + :param image: an ImageMeta object + :raises: nova.exception.FlavorImageConflict + ''' + # This library function will raise the exception for us if + # necessary; if not, we can ignore the result returned. + hardware.get_mem_encryption_constraint(instance_type, image) + + def _get_image_defined_bdms(self, instance_type, image_meta, root_device_name): image_properties = image_meta.get('properties', {}) @@ -3915,6 +3948,7 @@ return self.compute_rpcapi.get_instance_diagnostics(context, instance=instance)
+ @reject_sev_instances(instance_actions.SUSPEND) @check_instance_lock @check_instance_cell @check_instance_state(vm_state=[vm_states.ACTIVE]) @@ -4699,6 +4733,7 @@ diff=diff) return _metadata
+ @reject_sev_instances(instance_actions.SUSPEND) @check_instance_lock @check_instance_cell @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.PAUSED]) diff -ruN nova-bak/exception.py nova/exception.py --- nova-bak/exception.py 2021-09-10 11:35:25.491284738 +0800 +++ nova/exception.py 2021-09-10 11:36:09.799787563 +0800 @@ -536,6 +536,10 @@ msg_fmt = _('Unable to migrate instance (%(instance_id)s) ' 'to current host (%(host)s).')
+class OperationNotSupportedForSEV(NovaException): + msg_fmt = _('Operation '%(operation)s' not supported for SEV-enabled ' + 'instance (%(instance_uuid)s).') + code = 409
class InvalidHypervisorType(Invalid): msg_fmt = _('The supplied hypervisor type of is invalid.') diff -ruN nova-bak/objects/image_meta.py nova/objects/image_meta.py --- nova-bak/objects/image_meta.py 2021-09-10 15:16:30.530628464 +0800 +++ nova/objects/image_meta.py 2021-09-10 15:19:26.999151245 +0800 @@ -177,6 +177,9 @@ super(ImageMetaProps, self).obj_make_compatible(primitive, target_version) target_version = versionutils.convert_version_to_tuple(target_version) + + if target_version < (1, 24): + primitive.pop('hw_mem_encryption', None) if target_version < (1, 21): primitive.pop('hw_time_hpet', None) if target_version < (1, 20): @@ -298,6 +301,11 @@ # is not practical to enumerate them all. So we use a free # form string 'hw_machine_type': fields.StringField(), + + # boolean indicating that the guest needs to be booted with + # encrypted memory + 'hw_mem_encryption': fields.FlexibleBooleanField(), +
# One of the magic strings 'small', 'any', 'large' # or an explicit page size in KB (eg 4, 2048, ...) diff -ruN nova-bak/scheduler/utils.py nova/scheduler/utils.py --- nova-bak/scheduler/utils.py 2021-09-10 15:19:58.172561042 +0800 +++ nova/scheduler/utils.py 2021-09-10 15:35:05.630393147 +0800 @@ -35,7 +35,7 @@ from nova.objects import instance as obj_instance from nova import rpc from nova.scheduler.filters import utils as filters_utils - +import nova.virt.hardware as hw
LOG = logging.getLogger(__name__)
@@ -61,6 +61,27 @@ # Default to the configured limit but _limit can be # set to None to indicate 'no limit'. self._limit = CONF.scheduler.max_placement_results + image = (request_spec.image if 'image' in request_spec + else objects.ImageMeta(properties=objects.ImageMetaProps())) + self._translate_memory_encryption(request_spec.flavor, image) + + def _translate_memory_encryption(self, flavor, image): + '''When the hw:mem_encryption extra spec or the hw_mem_encryption + image property are requested, translate into a request for + resources:MEM_ENCRYPTION_CONTEXT=1 which requires a slot on a + host which can support encryption of the guest memory. + ''' + # NOTE(aspiers): In theory this could raise FlavorImageConflict, + # but we already check it in the API layer, so that should never + # happen. + if not hw.get_mem_encryption_constraint(flavor, image): + # No memory encryption required, so no further action required. + return + + self._add_resource(None, orc.MEM_ENCRYPTION_CONTEXT, 1) + LOG.debug('Added %s=1 to requested resources', + orc.MEM_ENCRYPTION_CONTEXT) +
def __str__(self): return ', '.join(sorted( diff -ruN nova-bak/virt/hardware.py nova/virt/hardware.py --- nova-bak/virt/hardware.py 2022-02-23 10:45:42.320988102 +0800 +++ nova/virt/hardware.py 2021-09-10 14:05:25.145572630 +0800 @@ -1140,6 +1140,67 @@
return flavor_policy, image_policy
+def get_mem_encryption_constraint(flavor, image_meta, machine_type=None): + '''Return a boolean indicating whether encryption of guest memory was + requested, either via the hw:mem_encryption extra spec or the + hw_mem_encryption image property (or both). + Also watch out for contradictory requests between the flavor and + image regarding memory encryption, and raise an exception where + encountered. These conflicts can arise in two different ways: + 1) the flavor requests memory encryption but the image + explicitly requests *not* to have memory encryption, or + vice-versa + 2) the flavor and/or image request memory encryption, but the + image is missing hw_firmware_type=uefi + 3) the flavor and/or image request memory encryption, but the + machine type is set to a value which does not contain 'q35' + This can be called from the libvirt driver on the compute node, in + which case the driver should pass the result of + nova.virt.libvirt.utils.get_machine_type() as the machine_type + parameter, or from the API layer, in which case get_machine_type() + cannot be called since it relies on being run from the compute + node in order to retrieve CONF.libvirt.hw_machine_type. + :param instance_type: Flavor object + :param image: an ImageMeta object + :param machine_type: a string representing the machine type (optional) + :raises: nova.exception.FlavorImageConflict + :raises: nova.exception.InvalidMachineType + :returns: boolean indicating whether encryption of guest memory + was requested + ''' + + flavor_mem_enc_str, image_mem_enc = _get_flavor_image_meta( + 'mem_encryption', flavor, image_meta) + + flavor_mem_enc = None + if flavor_mem_enc_str is not None: + flavor_mem_enc = strutils.bool_from_string(flavor_mem_enc_str) + + # Image property is a FlexibleBooleanField, so coercion to a + # boolean is handled automatically + + if not flavor_mem_enc and not image_mem_enc: + return False + + _check_for_mem_encryption_requirement_conflicts( + flavor_mem_enc_str, flavor_mem_enc, image_mem_enc, flavor, image_meta) + + # If we get this far, either the extra spec or image property explicitly + # specified a requirement regarding memory encryption, and if both did, + # they are asking for the same thing. + requesters = [] + if flavor_mem_enc: + requesters.append('hw:mem_encryption extra spec in %s flavor' % + flavor.name) + if image_mem_enc: + requesters.append('hw_mem_encryption property of image %s' % + image_meta.name) + + _check_mem_encryption_uses_uefi_image(requesters, image_meta) + _check_mem_encryption_machine_type(image_meta, machine_type) + + LOG.debug('Memory encryption requested by %s', ' and '.join(requesters)) + return True
def _get_numa_pagesize_constraint(flavor, image_meta): '''Return the requested memory page size |
A1.2
diff -ruN nova-bak/compute/manager.py nova/compute/manager.py --- nova-bak/compute/manager.py 2021-07-07 14:40:15.570807168 +0800 +++ nova/compute/manager.py 2021-10-18 19:02:37.931655551 +0800 @@ -7013,7 +7013,8 @@ migrate_data)
# Detaching volumes. - connector = self.driver.get_volume_connector(instance) + connector = None + #connector = self.driver.get_volume_connector(instance) for bdm in source_bdms: if bdm.is_volume: # Detaching volumes is a call to an external API that can fail. @@ -7033,6 +7034,8 @@ # remove the volume connection without detaching from # hypervisor because the instance is not running # anymore on the current host + if connector is None: + connector = self.driver.get_volume_connector(instance) self.volume_api.terminate_connection(ctxt, bdm.volume_id, connector) @@ -7056,8 +7059,10 @@
# Releasing vlan. # (not necessary in current implementation?) - - network_info = self.network_api.get_instance_nw_info(ctxt, instance) + + #changed by Fiona + #network_info = self.network_api.get_instance_nw_info(ctxt, instance) + network_info = instance.get_network_info()
self._notify_about_instance_usage(ctxt, instance, 'live_migration._post.start', |
A1.3
diff -ruN neutron-bak/agent/l3/router_info.py neutron-iproute/agent/l3/router_info.py --- neutron-bak/agent/l3/router_info.py 2020-12-14 18:00:23.683687327 +0800 +++ neutron-iproute/agent/l3/router_info.py 2022-02-23 15:18:15.650669589 +0800 @@ -748,8 +748,10 @@ for ip_version in (lib_constants.IP_VERSION_4, lib_constants.IP_VERSION_6): gateway = device.route.get_gateway(ip_version=ip_version) - if gateway and gateway.get('gateway'): - current_gateways.add(gateway.get('gateway')) +# if gateway and gateway.get('gateway'): +# current_gateways.add(gateway.get('gateway')) + if gateway and gateway.get('via'): + current_gateways.add(gateway.get('via')) for ip in current_gateways - set(gateway_ips): device.route.delete_gateway(ip) for ip in gateway_ips: diff -ruN neutron-bak/agent/linux/ip_lib.py neutron-iproute/agent/linux/ip_lib.py --- neutron-bak/agent/linux/ip_lib.py 2020-12-14 18:03:47.951878754 +0800 +++ neutron-iproute/agent/linux/ip_lib.py 2022-02-23 15:19:03.981457532 +0800 @@ -48,6 +48,8 @@ 'main': 254, 'local': 255}
+IP_RULE_TABLES_NAMES = {v: k for k, v in IP_RULE_TABLES.items()} + # Rule indexes: pyroute2.netlink.rtnl # Rule names: https://www.systutorials.com/docs/linux/man/8-ip-rule/ # NOTE(ralonsoh): 'masquerade' type is printed as 'nat' in 'ip rule' command @@ -592,14 +594,18 @@ def _dev_args(self): return ['dev', self.name] if self.name else []
- def add_gateway(self, gateway, metric=None, table=None): - ip_version = common_utils.get_ip_version(gateway) - args = ['replace', 'default', 'via', gateway] - if metric: - args += ['metric', metric] - args += self._dev_args() - args += self._table_args(table) - self._as_root([ip_version], tuple(args)) +# def add_gateway(self, gateway, metric=None, table=None): +# ip_version = common_utils.get_ip_version(gateway) +# args = ['replace', 'default', 'via', gateway] +# if metric: +# args += ['metric', metric] +# args += self._dev_args() +# args += self._table_args(table) +# self._as_root([ip_version], tuple(args)) + + def add_gateway(self, gateway, metric=None, table=None, scope='global'): + self.add_route(None, via=gateway, table=table, metric=metric, + scope=scope)
def _run_as_root_detect_device_not_found(self, options, args): try: @@ -618,41 +624,16 @@ args += self._table_args(table) self._run_as_root_detect_device_not_found([ip_version], args)
- def _parse_routes(self, ip_version, output, **kwargs): - for line in output.splitlines(): - parts = line.split() -
- # Format of line is: ' - route = {k: v for k, v in zip(parts[1::2], parts[2::2])} - route['cidr'] = parts[0] - # Avoids having to explicitly pass around the IP version - if route['cidr'] == 'default': - route['cidr'] = constants.IP_ANY[ip_version] - - # ip route drops things like scope and dev from the output if it - # was specified as a filter. This allows us to add them back. - if self.name: - route['dev'] = self.name - if self._table: - route['table'] = self._table - # Callers add any filters they use as kwargs - route.update(kwargs) - - yield route - - def list_routes(self, ip_version, **kwargs): - args = ['list'] - args += self._dev_args() - args += self._table_args() - for k, v in kwargs.items(): - args += [k, v] - - output = self._run([ip_version], tuple(args)) - return [r for r in self._parse_routes(ip_version, output, **kwargs)] + def list_routes(self, ip_version, scope=None, via=None, table=None, + **kwargs): + table = table or self._table + return list_ip_routes(self._parent.namespace, ip_version, scope=scope, + via=via, table=table, device=self.name, **kwargs)
def list_onlink_routes(self, ip_version): routes = self.list_routes(ip_version, scope='link') - return [r for r in routes if 'src' not in r] +# return [r for r in routes if 'src' not in r] + return [r for r in routes if not r['source_prefix']]
def add_onlink_route(self, cidr): self.add_route(cidr, scope='link') @@ -660,34 +641,12 @@ def delete_onlink_route(self, cidr): self.delete_route(cidr, scope='link')
- def get_gateway(self, scope=None, filters=None, ip_version=None): - options = [ip_version] if ip_version else [] - - args = ['list'] - args += self._dev_args() - args += self._table_args() - if filters: - args += filters - - retval = None - - if scope: - args += ['scope', scope] - - route_list_lines = self._run(options, tuple(args)).split('\n') - default_route_line = next((x.strip() for x in - route_list_lines if - x.strip().startswith('default')), None) - if default_route_line: - retval = dict() - gateway = DEFAULT_GW_PATTERN.search(default_route_line) - if gateway: - retval.update(gateway=gateway.group(1)) - metric = METRIC_PATTERN.search(default_route_line) - if metric: - retval.update(metric=int(metric.group(1))) - - return retval + def get_gateway(self, scope=None, table=None, + ip_version=constants.IP_VERSION_4): + routes = self.list_routes(ip_version, scope=scope, table=table) + for route in routes: + if route['via'] and route['cidr'] in constants.IP_ANY.values(): + return route
def flush(self, ip_version, table=None, **kwargs): args = ['flush'] @@ -696,16 +655,11 @@ args += [k, v] self._as_root([ip_version], tuple(args))
- def add_route(self, cidr, via=None, table=None, **kwargs): - ip_version = common_utils.get_ip_version(cidr) - args = ['replace', cidr] - if via: - args += ['via', via] - args += self._dev_args() - args += self._table_args(table) - for k, v in kwargs.items(): - args += [k, v] - self._run_as_root_detect_device_not_found([ip_version], args) + def add_route(self, cidr, via=None, table=None, metric=None, scope=None, + **kwargs): + table = table or self._table + add_ip_route(self._parent.namespace, cidr, device=self.name, via=via, + table=table, metric=metric, scope=scope, **kwargs)
def delete_route(self, cidr, via=None, table=None, **kwargs): ip_version = common_utils.get_ip_version(cidr) @@ -1455,3 +1409,53 @@ retval[device['vxlan_link_index']]['name'])
return list(retval.values()) + +def add_ip_route(namespace, cidr, device=None, via=None, table=None, + metric=None, scope=None, **kwargs): + '''Add an IP route''' + if table: + table = IP_RULE_TABLES.get(table, table) + ip_version = common_utils.get_ip_version(cidr or via) + privileged.add_ip_route(namespace, cidr, ip_version, + device=device, via=via, table=table, + metric=metric, scope=scope, **kwargs) + + +def list_ip_routes(namespace, ip_version, scope=None, via=None, table=None, + device=None, **kwargs): + '''List IP routes''' + def get_device(index, devices): + for device in (d for d in devices if d['index'] == index): + return get_attr(device, 'IFLA_IFNAME') + + table = table if table else 'main' + table = IP_RULE_TABLES.get(table, table) + routes = privileged.list_ip_routes(namespace, ip_version, device=device, + table=table, **kwargs) + devices = privileged.get_link_devices(namespace) + ret = [] + for route in routes: + cidr = get_attr(route, 'RTA_DST') + if cidr: + cidr = '%s/%s' % (cidr, route['dst_len']) + else: + cidr = constants.IP_ANY[ip_version] + table = int(get_attr(route, 'RTA_TABLE')) + value = { + 'table': IP_RULE_TABLES_NAMES.get(table, table), + 'source_prefix': get_attr(route, 'RTA_PREFSRC'), + 'cidr': cidr, + 'scope': IP_ADDRESS_SCOPE[int(route['scope'])], + 'device': get_device(int(get_attr(route, 'RTA_OIF')), devices), + 'via': get_attr(route, 'RTA_GATEWAY'), + 'priority': get_attr(route, 'RTA_PRIORITY'), + } + + ret.append(value) + + if scope: + ret = [route for route in ret if route['scope'] == scope] + if via: + ret = [route for route in ret if route['via'] == via] + + return ret diff -ruN neutron-bak/cmd/sanity/checks.py neutron-iproute/cmd/sanity/checks.py --- neutron-bak/cmd/sanity/checks.py 2022-02-23 11:33:16.934132708 +0800 +++ neutron-iproute/cmd/sanity/checks.py 2022-02-23 15:20:10.562018672 +0800 @@ -36,6 +36,7 @@ from neutron.common import utils as common_utils from neutron.plugins.ml2.drivers.openvswitch.agent.common \ import constants as ovs_const +from neutron.privileged.agent.linux import dhcp as priv_dhcp
LOG = logging.getLogger(__name__)
@@ -230,8 +231,8 @@
def dhcp_release6_supported(): - return runtime_checks.dhcp_release6_supported() - +# return runtime_checks.dhcp_release6_supported() + return priv_dhcp.dhcp_release6_supported()
def bridge_firewalling_enabled(): for proto in ('arp', 'ip', 'ip6'): @@ -363,7 +364,8 @@
default_gw = gw_dev.route.get_gateway(ip_version=6) if default_gw: - default_gw = default_gw['gateway'] +# default_gw = default_gw['gateway'] + default_gw = default_gw['via']
return expected_default_gw == default_gw
diff -ruN neutron-bak/privileged/agent/linux/ip_lib.py neutron-iproute/privileged/agent/linux/ip_lib.py --- neutron-bak/privileged/agent/linux/ip_lib.py 2020-12-14 18:26:08.339307939 +0800 +++ neutron-iproute/privileged/agent/linux/ip_lib.py 2022-02-23 15:20:39.477439105 +0800 @@ -634,3 +634,50 @@ if e.errno == errno.ENOENT: raise NetworkNamespaceNotFound(netns_name=namespace) raise + +@privileged.default.entrypoint +@lockutils.synchronized('privileged-ip-lib') +def add_ip_route(namespace, cidr, ip_version, device=None, via=None, + table=None, metric=None, scope=None, **kwargs): + '''Add an IP route''' + try: + with get_iproute(namespace) as ip: + family = _IP_VERSION_FAMILY_MAP[ip_version] + if not scope: + scope = 'global' if via else 'link' + scope = _get_scope_name(scope) + if cidr: + kwargs['dst'] = cidr + if via: + kwargs['gateway'] = via + if table: + kwargs['table'] = int(table) + if device: + kwargs['oif'] = get_link_id(device, namespace) + if metric: + kwargs['priority'] = int(metric) + ip.route('replace', family=family, scope=scope, proto='static', + **kwargs) + except OSError as e: + if e.errno == errno.ENOENT: + raise NetworkNamespaceNotFound(netns_name=namespace) + raise + + +@privileged.default.entrypoint +@lockutils.synchronized('privileged-ip-lib') +def list_ip_routes(namespace, ip_version, device=None, table=None, **kwargs): + '''List IP routes''' + try: + with get_iproute(namespace) as ip: + family = _IP_VERSION_FAMILY_MAP[ip_version] + if table: + kwargs['table'] = table + if device: + kwargs['oif'] = get_link_id(device, namespace) + return make_serializable(ip.route('show', family=family, **kwargs)) + except OSError as e: + if e.errno == errno.ENOENT: + raise NetworkNamespaceNotFound(netns_name=namespace) + raise + |
A1.4
diff -ruN neutron-bak/agent/linux/dhcp.py neutron-dhcprelease/agent/linux/dhcp.py --- neutron-bak/agent/linux/dhcp.py 2020-12-15 09:59:29.966957908 +0800 +++ neutron-dhcprelease/agent/linux/dhcp.py 2022-02-23 15:10:14.169101010 +0800 @@ -25,6 +25,7 @@ from neutron_lib import constants from neutron_lib import exceptions from neutron_lib.utils import file as file_utils +from oslo_concurrency import processutils from oslo_log import log as logging from oslo_utils import excutils from oslo_utils import fileutils @@ -41,6 +42,7 @@ from neutron.common import ipv6_utils from neutron.common import utils as common_utils from neutron.ipam import utils as ipam_utils +from neutron.privileged.agent.linux import dhcp as priv_dhcp
LOG = logging.getLogger(__name__)
@@ -476,7 +478,8 @@
def _is_dhcp_release6_supported(self): if self._IS_DHCP_RELEASE6_SUPPORTED is None: - self._IS_DHCP_RELEASE6_SUPPORTED = checks.dhcp_release6_supported() + self._IS_DHCP_RELEASE6_SUPPORTED = ( + priv_dhcp.dhcp_release6_supported()) if not self._IS_DHCP_RELEASE6_SUPPORTED: LOG.warning('dhcp_release6 is not present on this system, ' 'will not call it again.') @@ -485,24 +488,28 @@ def _release_lease(self, mac_address, ip, ip_version, client_id=None, server_id=None, iaid=None): '''Release a DHCP lease.''' - if ip_version == constants.IP_VERSION_6: - if not self._is_dhcp_release6_supported(): - return - cmd = ['dhcp_release6', '--iface', self.interface_name, - '--ip', ip, '--client-id', client_id, - '--server-id', server_id, '--iaid', iaid] - else: - cmd = ['dhcp_release', self.interface_name, ip, mac_address] - if client_id: - cmd.append(client_id) - ip_wrapper = ip_lib.IPWrapper(namespace=self.network.namespace) try: - ip_wrapper.netns.execute(cmd, run_as_root=True) - except RuntimeError as e: + if ip_version == constants.IP_VERSION_6: + if not self._is_dhcp_release6_supported(): + return + + params = {'interface_name': self.interface_name, + 'ip_address': ip, 'client_id': client_id, + 'server_id': server_id, 'iaid': iaid, + 'namespace': self.network.namespace} + priv_dhcp.dhcp_release6(**params) + else: + params = {'interface_name': self.interface_name, + 'ip_address': ip, 'mac_address': mac_address, + 'client_id': client_id, + 'namespace': self.network.namespace} +# LOG.info('Rock_DEBUG: DHCP release construct params %(params)s.', {'params': params}) + priv_dhcp.dhcp_release(**params) + except (processutils.ProcessExecutionError, OSError) as e: # when failed to release single lease there's # no need to propagate error further - LOG.warning('DHCP release failed for %(cmd)s. ' - 'Reason: %(e)s', {'cmd': cmd, 'e': e}) + LOG.warning('DHCP release failed for params %(params)s. ' + 'Reason: %(e)s', {'params': params, 'e': e})
def _output_config_files(self): self._output_hosts_file() diff -ruN neutron-bak/cmd/sanity/checks.py neutron-dhcprelease/cmd/sanity/checks.py --- neutron-bak/cmd/sanity/checks.py 2022-02-23 11:33:16.934132708 +0800 +++ neutron-dhcprelease/cmd/sanity/checks.py 2022-02-23 15:11:07.536446402 +0800 @@ -36,6 +36,7 @@ from neutron.common import utils as common_utils from neutron.plugins.ml2.drivers.openvswitch.agent.common \ import constants as ovs_const +from neutron.privileged.agent.linux import dhcp as priv_dhcp
LOG = logging.getLogger(__name__)
@@ -230,8 +231,8 @@
def dhcp_release6_supported(): - return runtime_checks.dhcp_release6_supported() - +# return runtime_checks.dhcp_release6_supported() + return priv_dhcp.dhcp_release6_supported()
def bridge_firewalling_enabled(): for proto in ('arp', 'ip', 'ip6'): @@ -363,7 +364,8 @@
default_gw = gw_dev.route.get_gateway(ip_version=6) if default_gw: - default_gw = default_gw['gateway'] +# default_gw = default_gw['gateway'] + default_gw = default_gw['via']
return expected_default_gw == default_gw
diff -ruN neutron-bak/privileged/__init__.py neutron-dhcprelease/privileged/__init__.py --- neutron-bak/privileged/__init__.py 2020-04-23 14:45:14.000000000 +0800 +++ neutron-dhcprelease/privileged/__init__.py 2022-02-23 15:10:29.209584186 +0800 @@ -27,3 +27,11 @@ caps.CAP_DAC_OVERRIDE, caps.CAP_DAC_READ_SEARCH], ) + +dhcp_release_cmd = priv_context.PrivContext( + __name__, + cfg_section='privsep_dhcp_release', + pypath=__name__ + '.dhcp_release_cmd', + capabilities=[caps.CAP_SYS_ADMIN, + caps.CAP_NET_ADMIN] +) |
A1.5
diff -ruN neutron-bak/agent/linux/ipset_manager.py neutron/agent/linux/ipset_manager.py --- neutron-bak/agent/linux/ipset_manager.py 2022-02-16 15:11:40.419016919 +0800 +++ neutron/agent/linux/ipset_manager.py 2022-02-16 15:17:02.328133786 +0800 @@ -146,7 +146,7 @@ cmd_ns.extend(['ip', 'netns', 'exec', self.namespace]) cmd_ns.extend(cmd) self.execute(cmd_ns, run_as_root=True, process_input=input, - check_exit_code=fail_on_errors) + check_exit_code=fail_on_errors, privsep_exec=True)
def _get_new_set_ips(self, set_name, expected_ips): new_member_ips = (set(expected_ips) - diff -ruN neutron-bak/agent/linux/iptables_manager.py neutron/agent/linux/iptables_manager.py --- neutron-bak/agent/linux/iptables_manager.py 2022-02-16 15:05:53.853147520 +0800 +++ neutron/agent/linux/iptables_manager.py 2021-07-07 14:59:16.000000000 +0800 @@ -475,12 +475,15 @@ args = ['iptables-save', '-t', table] if self.namespace: args = ['ip', 'netns', 'exec', self.namespace] + args - return self.execute(args, run_as_root=True).split('\n') + #return self.execute(args, run_as_root=True).split('\n') + return self.execute(args, run_as_root=True, + privsep_exec=True).split('\n')
def _get_version(self): # Output example is 'iptables v1.6.2' args = ['iptables', '--version'] - version = str(self.execute(args, run_as_root=True).split()[1][1:]) + #version = str(self.execute(args, run_as_root=True).split()[1][1:]) + version = str(self.execute(args, run_as_root=True, privsep_exec=True).split()[1][1:]) LOG.debug('IPTables version installed: %s', version) return version
@@ -505,8 +508,10 @@ args += ['-w', self.xlock_wait_time, '-W', XLOCK_WAIT_INTERVAL] try: kwargs = {} if lock else {'log_fail_as_error': False} + #self.execute(args, process_input='\n'.join(commands), + # run_as_root=True, **kwargs) self.execute(args, process_input='\n'.join(commands), - run_as_root=True, **kwargs) + run_as_root=True, privsep_exec=True, **kwargs) except RuntimeError as error: return error
@@ -568,7 +573,8 @@ if self.namespace: args = ['ip', 'netns', 'exec', self.namespace] + args try: - save_output = self.execute(args, run_as_root=True) + #save_output = self.execute(args, run_as_root=True) + save_output = self.execute(args, run_as_root=True, privsep_exec=True) except RuntimeError: # We could be racing with a cron job deleting namespaces. # It is useless to try to apply iptables rules over and @@ -769,7 +775,8 @@ args.append('-Z') if self.namespace: args = ['ip', 'netns', 'exec', self.namespace] + args - current_table = self.execute(args, run_as_root=True) + #current_table = self.execute(args, run_as_root=True) + current_table = self.execute(args, run_as_root=True, privsep_exec=True) current_lines = current_table.split('\n')
for line in current_lines[2:]: diff -ruN neutron-bak/agent/linux/utils.py neutron/agent/linux/utils.py --- neutron-bak/agent/linux/utils.py 2022-02-16 15:06:03.133090388 +0800 +++ neutron/agent/linux/utils.py 2021-07-08 09:34:12.000000000 +0800 @@ -38,6 +38,7 @@ from neutron.agent.linux import xenapi_root_helper from neutron.common import utils from neutron.conf.agent import common as config +from neutron.privileged.agent.linux import utils as priv_utils from neutron import wsgi
@@ -85,13 +86,24 @@ if run_as_root: cmd = shlex.split(config.get_root_helper(cfg.CONF)) + cmd LOG.debug('Running command: %s', cmd) - obj = utils.subprocess_popen(cmd, shell=False, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + #obj = utils.subprocess_popen(cmd, shell=False, + # stdin=subprocess.PIPE, + # stdout=subprocess.PIPE, + # stderr=subprocess.PIPE) + obj = subprocess.Popen(cmd, shell=False, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return obj, cmd
+def _execute_process(cmd, _process_input, addl_env, run_as_root): + obj, cmd = create_process(cmd, run_as_root=run_as_root, addl_env=addl_env) + _stdout, _stderr = obj.communicate(_process_input) + returncode = obj.returncode + obj.stdin.close() + _stdout = helpers.safe_decode_utf8(_stdout) + _stderr = helpers.safe_decode_utf8(_stderr) + return _stdout, _stderr, returncode +
def execute_rootwrap_daemon(cmd, process_input, addl_env): cmd = list(map(str, addl_env_args(addl_env) + cmd)) @@ -103,31 +115,45 @@ LOG.debug('Running command (rootwrap daemon): %s', cmd) client = RootwrapDaemonHelper.get_client() try: - return client.execute(cmd, process_input) + #return client.execute(cmd, process_input) + returncode, __stdout, _stderr = client.execute(cmd, process_input) except Exception: with excutils.save_and_reraise_exception(): LOG.error('Rootwrap error running command: %s', cmd) + _stdout = helpers.safe_decode_utf8(_stdout) + _stderr = helpers.safe_decode_utf8(_stderr) + return _stdout, _stderr, returncode
def execute(cmd, process_input=None, addl_env=None, check_exit_code=True, return_stderr=False, log_fail_as_error=True, - extra_ok_codes=None, run_as_root=False): + extra_ok_codes=None, run_as_root=False, privsep_exec=False): try: if process_input is not None: _process_input = encodeutils.to_utf8(process_input) else: _process_input = None - if run_as_root and cfg.CONF.AGENT.root_helper_daemon: - returncode, _stdout, _stderr = ( - execute_rootwrap_daemon(cmd, process_input, addl_env)) + #if run_as_root and cfg.CONF.AGENT.root_helper_daemon: + # returncode, _stdout, _stderr = ( + # execute_rootwrap_daemon(cmd, process_input, addl_env)) + #else: + # obj, cmd = create_process(cmd, run_as_root=run_as_root, + # addl_env=addl_env) + # _stdout, _stderr = obj.communicate(_process_input) + # returncode = obj.returncode + # obj.stdin.close() + #_stdout = helpers.safe_decode_utf8(_stdout) + #_stderr = helpers.safe_decode_utf8(_stderr) + + if run_as_root and privsep_exec: + _stdout, _stderr, returncode = priv_utils.execute_process( + cmd, _process_input, addl_env) + elif run_as_root and cfg.CONF.AGENT.root_helper_daemon: + _stdout, _stderr, returncode = execute_rootwarp_daemon( + cmd, process_input, addl_env) else: - obj, cmd = create_process(cmd, run_as_root=run_as_root, - addl_env=addl_env) - _stdout, _stderr = obj.communicate(_process_input) - returncode = obj.returncode - obj.stdin.close() - _stdout = helpers.safe_decode_utf8(_stdout) - _stderr = helpers.safe_decode_utf8(_stderr) + _stdout, _stderr, returncode = _execute_process( + cmd, _process_input, addl_env, run_as_root)
extra_ok_codes = extra_ok_codes or [] if returncode and returncode not in extra_ok_codes: diff -ruN neutron-bak/cmd/ipset_cleanup.py neutron/cmd/ipset_cleanup.py --- neutron-bak/cmd/ipset_cleanup.py 2022-02-16 15:18:00.727786180 +0800 +++ neutron/cmd/ipset_cleanup.py 2021-07-07 15:00:03.000000000 +0800 @@ -38,7 +38,8 @@ def remove_iptables_reference(ipset): # Remove any iptables reference to this IPset cmd = ['iptables-save'] if 'IPv4' in ipset else ['ip6tables-save'] - iptables_save = utils.execute(cmd, run_as_root=True) + #iptables_save = utils.execute(cmd, run_as_root=True) + iptables_save = utils.execute(cmd, run_as_root=True, privsep_exec=True)
if ipset in iptables_save: cmd = ['iptables'] if 'IPv4' in ipset else ['ip6tables'] @@ -50,7 +51,8 @@ params = rule.split() params[0] = '-D' try: - utils.execute(cmd + params, run_as_root=True) + #utils.execute(cmd + params, run_as_root=True) + utils.execute(cmd + params, run_as_root=True, privsep_exec=True) except Exception: LOG.exception('Error, unable to remove iptables rule ' 'for IPset: %s', ipset) @@ -65,7 +67,8 @@ LOG.info('Destroying IPset: %s', ipset) cmd = ['ipset', 'destroy', ipset] try: - utils.execute(cmd, run_as_root=True) + #utils.execute(cmd, run_as_root=True) + utils.execute(cmd, run_as_root=True, privsep_exec=True) except Exception: LOG.exception('Error, unable to destroy IPset: %s', ipset)
@@ -75,7 +78,8 @@ LOG.info('Destroying IPsets with prefix: %s', conf.prefix)
cmd = ['ipset', '-L', '-n'] - ipsets = utils.execute(cmd, run_as_root=True) + #ipsets = utils.execute(cmd, run_as_root=True) + ipsets = utils.execute(cmd, run_as_root=True, privsep_exec=True) for ipset in ipsets.split('\n'): if conf.allsets or ipset.startswith(conf.prefix): destroy_ipset(conf, ipset) diff -ruN neutron-bak/privileged/agent/linux/utils.py neutron/privileged/agent/linux/utils.py --- neutron-bak/privileged/agent/linux/utils.py 1970-01-01 08:00:00.000000000 +0800 +++ neutron/privileged/agent/linux/utils.py 2021-07-07 14:58:21.000000000 +0800 @@ -0,0 +1,82 @@ +# Copyright 2020 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the 'License'); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os +import re + +from eventlet.green import subprocess +from neutron_lib.utils import helpers +from oslo_concurrency import processutils +from oslo_utils import fileutils + +from neutron import privileged + +
+NETSTAT_PIDS_REGEX = re.compile(r'.* (?P + + +@privileged.default.entrypoint +def find_listen_pids_namespace(namespace): + return _find_listen_pids_namespace(namespace) + + +def _find_listen_pids_namespace(namespace): + '''Retrieve a list of pids of listening processes within the given netns + This method is implemented separately to allow unit testing. + ''' + pids = set() + cmd = ['ip', 'netns', 'exec', namespace, 'netstat', '-nlp'] + output = processutils.execute(*cmd) + for line in output[0].splitlines(): + m = NETSTAT_PIDS_REGEX.match(line) + if m: + pids.add(m.group('pid')) + return list(pids) + + +@privileged.default.entrypoint +def delete_if_exists(path, remove=os.unlink): + fileutils.delete_if_exists(path, remove=remove) + + +@privileged.default.entrypoint +def execute_process(cmd, _process_input, addl_env): + obj, cmd = _create_process(cmd, addl_env=addl_env) + _stdout, _stderr = obj.communicate(_process_input) + returncode = obj.returncode + obj.stdin.close() + _stdout = helpers.safe_decode_utf8(_stdout) + _stderr = helpers.safe_decode_utf8(_stderr) + return _stdout, _stderr, returncode + + +def _addl_env_args(addl_env): + '''Build arguments for adding additional environment vars with env''' + + # NOTE (twilson) If using rootwrap, an EnvFilter should be set up for the + # command instead of a CommandFilter. + if addl_env is None: + return [] + return ['env'] + ['%s=%s' % pair for pair in addl_env.items()] + + +def _create_process(cmd, addl_env=None): + '''Create a process object for the given command. + The return value will be a tuple of the process object and the + list of command arguments used to create it. + ''' + cmd = list(map(str, _addl_env_args(addl_env) + list(cmd))) + obj = subprocess.Popen(cmd, shell=False, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + return obj, cmd |
B1.1
--- neutron-bak/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py 2022-08-02 17:02:51.213224245 +0800 +++ neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py 2022-08-02 17:02:09.181883012 +0800 @@ -161,8 +161,8 @@ self.enable_distributed_routing = agent_conf.enable_distributed_routing self.arp_responder_enabled = agent_conf.arp_responder and self.l2_pop
- host = self.conf.host - self.agent_id = 'ovs-agent-%s' % host + self.host = self.conf.host + self.agent_id = 'ovs-agent-%s' % self.host
self.enable_tunneling = bool(self.tunnel_types)
@@ -245,7 +245,7 @@ self.phys_ofports, self.patch_int_ofport, self.patch_tun_ofport, - host, + self.host, self.enable_tunneling, self.enable_distributed_routing, self.arp_responder_enabled) @@ -289,7 +289,7 @@ # or which are used by specific extensions. self.agent_state = { 'binary': 'neutron-openvswitch-agent', - 'host': host, + 'host': self.host, 'topic': n_const.L2_AGENT_TOPIC, 'configurations': {'bridge_mappings': self.bridge_mappings, c_const.RP_BANDWIDTHS: self.rp_bandwidths, @@ -1671,6 +1671,7 @@ skipped_devices = [] need_binding_devices = [] binding_no_activated_devices = set() + migrating_devices = set() agent_restarted = self.iter_num == 0 devices_details_list = ( self.plugin_rpc.get_devices_details_list_and_failed_devices( @@ -1696,6 +1697,12 @@ skipped_devices.append(device) continue
+ migrating_to = details.get('migrating_to') + if migrating_to and migrating_to != self.host: + LOG.info('Port %(device)s is being migrated to host %(host)s.', + {'device': device, 'host': migrating_to}) + migrating_devices.add(device) + if 'port_id' in details: LOG.info('Port %(device)s updated. Details: %(details)s', {'device': device, 'details': details}) @@ -1729,7 +1736,7 @@ if (port and port.ofport != -1): self.port_dead(port) return (skipped_devices, binding_no_activated_devices, - need_binding_devices, failed_devices) + need_binding_devices, failed_devices, migrating_devices)
def _update_port_network(self, port_id, network_id): self._clean_network_ports(port_id) @@ -1821,10 +1828,12 @@ need_binding_devices = [] skipped_devices = set() binding_no_activated_devices = set() + migrating_devices = set() start = time.time() if devices_added_updated: (skipped_devices, binding_no_activated_devices, - need_binding_devices, failed_devices['added']) = ( + need_binding_devices, failed_devices['added'], + migrating_devices) = ( self.treat_devices_added_or_updated( devices_added_updated, provisioning_needed)) LOG.debug('process_network_ports - iteration:%(iter_num)d - ' @@ -1847,7 +1856,7 @@ # TODO(salv-orlando): Optimize avoiding applying filters # unnecessarily, (eg: when there are no IP address changes) added_ports = (port_info.get('added', set()) - skipped_devices - - binding_no_activated_devices) + binding_no_activated_devices - migrating_devices) self._add_port_tag_info(need_binding_devices) self.sg_agent.setup_port_filters(added_ports, port_info.get('updated', set())) |
B1.2
--- neutron-bak/conf/common.py 2022-08-02 17:07:18.239265163 +0800 +++ neutron/conf/common.py 2021-09-08 17:08:59.000000000 +0800 @@ -166,6 +166,24 @@ help=_('Type of the nova endpoint to use. This endpoint will' ' be looked up in the keystone catalog and should be' ' one of public, internal or admin.')), + cfg.BoolOpt('live_migration_events', default=True, + help=_('When this option is enabled, during the live ' + 'migration, the OVS agent will only send the ' + ''vif-plugged-event' when the destination host ' + 'interface is bound. This option also disables any ' + 'other agent (like DHCP) to send to Nova this event ' + 'when the port is provisioned.' + 'This option can be enabled if Nova patch ' + 'https://review.opendev.org/c/openstack/nova/+/767368 ' + 'is in place.' + 'This option is temporary and will be removed in Y and ' + 'the behavior will be 'True'.'), + deprecated_for_removal=True, + deprecated_reason=( + 'In Y the Nova patch ' + 'https://review.opendev.org/c/openstack/nova/+/767368 ' + 'will be in the code even when running a Nova server in ' + 'X.')), ] |
B1.3
--- neutron-bak/agent/rpc.py 2021-08-25 15:29:11.000000000 +0800 +++ neutron/agent/rpc.py 2021-09-15 16:34:09.000000000 +0800 @@ -25,8 +25,10 @@ from neutron_lib import constants from neutron_lib.plugins import utils from neutron_lib import rpc as lib_rpc +from oslo_config import cfg from oslo_log import log as logging import oslo_messaging +from oslo_serialization import jsonutils from oslo_utils import uuidutils
from neutron.agent import resource_cache @@ -323,8 +325,10 @@ binding = utils.get_port_binding_by_status_and_host( port_obj.bindings, constants.ACTIVE, raise_if_not_found=True, port_id=port_obj.id) - if (port_obj.device_owner.startswith( - constants.DEVICE_OWNER_COMPUTE_PREFIX) and + migrating_to = migrating_to_host(port_obj.bindings) + if (not (migrating_to and cfg.CONF.nova.live_migration_events) and + port_obj.device_owner.startswith( + constants.DEVICE_OWNER_COMPUTE_PREFIX) and binding[pb_ext.HOST] != host): LOG.debug('Device %s has no active binding in this host', port_obj) @@ -357,7 +361,8 @@ 'qos_policy_id': port_obj.qos_policy_id, 'network_qos_policy_id': net_qos_policy_id, 'profile': binding.profile, - 'security_groups': list(port_obj.security_group_ids) + 'security_groups': list(port_obj.security_group_ids), + 'migrating_to': migrating_to, } LOG.debug('Returning: %s', entry) return entry @@ -365,3 +370,40 @@ def get_devices_details_list(self, context, devices, agent_id, host=None): return [self.get_device_details(context, device, agent_id, host) for device in devices] + +# TODO(ralonsoh): move this method to neutron_lib.plugins.utils +def migrating_to_host(bindings, host=None): + '''Return the host the port is being migrated. + + If the host is passed, the port binding profile with the 'migrating_to', + that contains the host the port is being migrated, is compared to this + value. If no value is passed, this method will return if the port is + being migrated ('migrating_to' is present in any port binding profile). + + The function returns None or the matching host. + ''' + #LOG.info('LiveDebug: enter migrating_to_host 001') + for binding in (binding for binding in bindings if + binding[pb_ext.STATUS] == constants.ACTIVE): + profile = binding.get('profile') + if not profile: + continue + ''' + profile = (jsonutils.loads(profile) if isinstance(profile, str) else + profile) + migrating_to = profile.get('migrating_to') + ''' + # add by michael + if isinstance(profile, str): + migrating_to = jsonutils.loads(profile).get('migrating_to') + #LOG.info('LiveDebug: migrating_to_host 001 migrating_to: %s', migrating_to) + else: + migrating_to = profile.get('migrating_to') + #LOG.info('LiveDebug: migrating_to_host 002 migrating_to: %s', migrating_to) + + if migrating_to: + if not host: # Just know if the port is being migrated. + return migrating_to + if migrating_to == host: + return migrating_to + return None |
B1.4
--- neutron-bak/db/provisioning_blocks.py 2021-08-25 15:43:47.000000000 +0800 +++ neutron/db/provisioning_blocks.py 2021-09-03 09:32:41.000000000 +0800 @@ -137,8 +137,7 @@ context, standard_attr_id=standard_attr_id): LOG.debug('Provisioning complete for %(otype)s %(oid)s triggered by ' 'entity %(entity)s.', log_dict) - registry.notify(object_type, PROVISIONING_COMPLETE, - 'neutron.db.provisioning_blocks', + registry.notify(object_type, PROVISIONING_COMPLETE, entity, context=context, object_id=object_id)
|
B1.5
--- neutron-bak/notifiers/nova.py 2021-08-25 16:02:33.000000000 +0800 +++ neutron/notifiers/nova.py 2021-09-03 09:32:41.000000000 +0800 @@ -13,6 +13,8 @@ # License for the specific language governing permissions and limitations # under the License.
+import contextlib + from keystoneauth1 import loading as ks_loading from neutron_lib.callbacks import events from neutron_lib.callbacks import registry @@ -66,6 +68,16 @@ if ext.name == 'server_external_events'] self.batch_notifier = batch_notifier.BatchNotifier( cfg.CONF.send_events_interval, self.send_events) + self._enabled = True + + @contextlib.contextmanager + def context_enabled(self, enabled): + stored_enabled = self._enabled + try: + self._enabled = enabled + yield + finally: + self._enabled = stored_enabled
def _get_nova_client(self): global_id = common_context.generate_request_id() @@ -163,6 +175,10 @@ return self._get_network_changed_event(port)
def _can_notify(self, port): + if not self._enabled: + LOG.debug('Nova notifier disabled') + return False + if not port.id: LOG.warning('Port ID not set! Nova will not be notified of ' 'port status change.') |
B1.6
--- nova-bak/compute/manager.py 2022-08-02 16:27:45.943428128 +0800 +++ nova/compute/manager.py 2021-09-03 09:35:24.529858458 +0800 @@ -6637,12 +6637,12 @@ LOG.error(msg, msg_args)
@staticmethod - def _get_neutron_events_for_live_migration(instance): + def _get_neutron_events_for_live_migration(instance, migration): # We don't generate events if CONF.vif_plugging_timeout=0 # meaning that the operator disabled using them. - if CONF.vif_plugging_timeout and utils.is_neutron(): - return [('network-vif-plugged', vif['id']) - for vif in instance.get_network_info()] + if CONF.vif_plugging_timeout: + return (instance.get_network_info() + .get_live_migration_plug_time_events()) else: return []
@@ -6695,7 +6695,8 @@ ''' pass
- events = self._get_neutron_events_for_live_migration(instance) + events = self._get_neutron_events_for_live_migration( + instance, migration) try: if ('block_migration' in migrate_data and migrate_data.block_migration): |
B1.7
--- nova-bak/network/model.py 2022-08-02 16:27:47.490437859 +0800 +++ nova/network/model.py 2021-09-03 09:35:24.532858440 +0800 @@ -469,6 +469,14 @@ return (self.is_hybrid_plug_enabled() and not migration.is_same_host())
+ @property + def has_live_migration_plug_time_event(self): + '''Returns whether this VIF's network-vif-plugged external event will + be sent by Neutron at 'plugtime' - in other words, as soon as neutron + completes configuring the network backend. + ''' + return self.is_hybrid_plug_enabled() + def is_hybrid_plug_enabled(self): return self['details'].get(VIF_DETAILS_OVS_HYBRID_PLUG, False)
@@ -527,20 +535,26 @@ return jsonutils.dumps(self)
def get_bind_time_events(self, migration): - '''Returns whether any of our VIFs have 'bind-time' events. See - has_bind_time_event() docstring for more details. + '''Returns a list of external events for any VIFs that have + 'bind-time' events during cold migration. ''' return [('network-vif-plugged', vif['id']) for vif in self if vif.has_bind_time_event(migration)]
+ def get_live_migration_plug_time_events(self): + '''Returns a list of external events for any VIFs that have + 'plug-time' events during live migration. + ''' + return [('network-vif-plugged', vif['id']) + for vif in self if vif.has_live_migration_plug_time_event] + def get_plug_time_events(self, migration): - '''Complementary to get_bind_time_events(), any event that does not - fall in that category is a plug-time event. + '''Returns a list of external events for any VIFs that have + 'plug-time' events during cold migration. ''' return [('network-vif-plugged', vif['id']) for vif in self if not vif.has_bind_time_event(migration)]
- class NetworkInfoAsyncWrapper(NetworkInfo): '''Wrapper around NetworkInfo that allows retrieving NetworkInfo in an async manner. |
C1.1
--- linux-3.10.0-1062.18.1.el7.orig/net/ipv4/udp_offload.c 2020-02-12 21:45:22.000000000 +0800 +++ linux-3.10.0-1062.18.1.el7/net/ipv4/udp_offload.c 2022-08-17 15:56:27.540557289 +0800 @@ -261,7 +261,7 @@ struct sk_buff **udp_gro_receive(struct struct sock *sk;
if (NAPI_GRO_CB(skb)->encap_mark || - (skb->ip_summed != CHECKSUM_PARTIAL && + (uh->check && skb->ip_summed != CHECKSUM_PARTIAL && NAPI_GRO_CB(skb)->csum_cnt == 0 && !NAPI_GRO_CB(skb)->csum_valid)) goto out; |