OpenStack代碼貢獻初體驗

 OpenStack現在已成爲開源雲平臺中的明星項目,獲得普遍關注。OpenStack的優秀出衆依賴於衆多開發者的努力,在享受其帶來的便利與快捷的同時,爲其作一份貢獻也是一個開發者的義務。  在前段時間的OpenStack的測試過程當中,我發現Nova項目中的一個Bug,因而向社區提交了Bug報告,並提交代碼修復了該Bug,從提交報告到代碼入庫經歷近一月,下面重現整個過程。python

一.發現Bug:

 Nova中的虛擬機軟刪除(soft-delete)功能,是指在一段時間內,僅將數據庫中的某虛擬機記錄作一個標記(status='SOFT-DELETE'),而後將虛擬化平臺(kvm等)中對應的虛擬機實例置爲關機狀態,當超過某一時間段後纔將虛擬機實例真正刪除;該功能爲雲平臺用戶提供了「後悔時間」,能夠在必定程度上挽回誤操做。默認狀況下,軟刪除功能是關閉的,其開啓方式是在nova配置文件中添加"reclaim_instance_interval"選項,並將其值設置爲"後悔時間"的毫秒數。git

 在描述具體Bug前,須要對openstack中的用戶管理方面的基本概念簡單介紹一下。github

openstack 用戶模型簡化版

 上圖是openstack用戶模型的簡化版本,爲了便於理解將不屬於keystone管理的quota也拿了過來。數據庫

 Bug就與軟刪除相關。具體場景是這樣的:假設OpenStack中有兩個項目和兩個用戶:普通項目A其用戶a,管理員項目Admin其用戶爲admin(用戶管理相關概念能夠查閱keystone文檔),用戶a不慎將本身的一臺虛擬機刪除了,這時求助系統管理員看看有沒有辦法恢復,好在系統開啓的軟刪除功能,並且被刪除的虛擬機還在可回收的時間範圍內,這時管理員便以admin的身份登陸系統,爲用戶a恢復了虛擬機,可是細心的管理員卻發現了一些不對:其Admin項目下並無任何虛擬機,可是其配額卻被使用了,難道這和剛纔的操做有關?再來重試一下:普通用戶刪除虛擬機,admin用戶來爲其恢復,這時配額又發生了變化,果真如此:被恢復的虛擬機的配額錯誤的添加到了Admin項目下。該Bug在最新的kilo版本中仍然存在,感興趣的同窗能夠實驗一下。api

二.定位Bug:

 發現了Bug的存在,那就更進一步,到代碼中找一下緣由吧。網絡

 如何肯定問題代碼的位置呢?這須要對Nova的項目結構有大致的瞭解,咱們來簡單瞭解一下:架構

nova 結構簡化版

 上圖是nova架構的極簡版本,與本問題無關的組件都沒有畫上去,恢復虛擬機的操做過程大體是這樣:單元測試

  1. nova api接收到用戶請求,到數據庫中查詢虛擬機詳情,將該虛擬機所在的主機、名稱等數據發送到消息隊列中;
  2. nova compute服務在監聽到相關消息後,開始執行具體操做,將虛擬機在數據庫中的記錄作些調整,調用底層驅動恢復虛擬機。

 既然軟刪除的功能層面沒有任何問題,虛擬機的刪除和恢復過程都很順利,可見不會是驅動的問題,順着API層的代碼調用往下找,很快就能夠定位了。直接看出問題的代碼片斷:測試

def restore(self, context, instance):
    # 該代碼作了刪減
    flavor = instance.get_flavor()
    # 獲取quotas對象
    num_instances, quotas = self._check_num_instances_quota(
            context, flavor, 1, 1)
    self._record_action_start(context, instance, instance_actions.RESTORE)
    try:
        if instance.host:
            instance.task_state = task_states.RESTORING
            instance.deleted_at = None
            instance.save(expected_task_state=[None])
            self.compute_rpcapi.restore_instance(context, instance)
        else:
            instance.vm_state = vm_states.ACTIVE
            instance.task_state = None
            instance.deleted_at = None
            instance.save(expected_task_state=[None])
        # 更新quotas
        quotas.commit()

 上面的這段代碼就是API層面上進行虛擬機回收的主要方法,能夠看到其中有明顯的配額操做(quotas),在解讀這段代碼前有必要先對nova中"context"的概念作個簡介。不只是nova,在openstack其餘項目中都隨處可見這個"context",它是一個包裝了用戶請求信息的對象,包含用戶的項目和認證信息等,經過它能夠簡便的進行各項目之間的API調用和用戶信息的查詢,API服務接收到用戶的每一次HTTP請求,都會建立一個新的context。.net

 回到這段代碼,咱們重點關注對quotas所做的操做:在方法的第二行,經過了一個方法獲取了quotas,有在方法的結尾執行了quotas.commit(),可以獲取到的信息很少,咱們再看一下獲取quotas的方法:_check_num_instances_quota

# 這裏只截取一部分
    def _check_num_instances_quota(self, context, instance_type, min_count,
                                   max_count):
        req_cores = max_count * instance_type['vcpus']
        vram_mb = int(instance_type.get('extra_specs', {}).get(VIDEO_RAM, 0))
        req_ram = max_count * (instance_type['memory_mb'] + vram_mb)

        try:
            quotas = objects.Quotas(context)
            quotas.reserve(context, instances=max_count,
                           cores=req_cores, ram=req_ram)
        ...
        return max_count, quotas

 這裏能夠看到獲取quotas的過程:經過當前的context建立quotas對象,而且執行了reserve操做; 咱們知道context是由HTTP請求而來,裏面保存的是發請求的用戶的信息,因此這裏的quotas對象的「全部者」也就是context中的用戶。

 結合Bug發生的場景來看:管理員還原用戶a的虛擬機,發請求的是管理員,當前context中記錄的是管理員的信息,這裏的quotas理所固然的就是管理員的,而後操做了用戶a的虛擬機,更新的倒是管理員的quotas。嗯,真相大白!

三.修復Bug:

 Bug的緣由是獲取的quotas並不屬於指望的用戶,可是直接修改context顯然不合適(會影響後續的操做),先了解一下quotas對象自身吧:

class Quotas(base.NovaObject):
    # 部分代碼
    def __init__(self, *args, **kwargs):
        super(Quotas, self).__init__(*args, **kwargs)
        # Set up defaults.
        self.reservations = []
        self.project_id = None
        self.user_id = None
        self.obj_reset_changes()
    ...
    def reserve(self, context, expire=None, project_id=None, user_id=None,
                **deltas):
        reservations = quota.QUOTAS.reserve(context, expire=expire,
                                            project_id=project_id,
                                            user_id=user_id,
                                            **deltas)
        self.reservations = reservations
        self.project_id = project_id
        self.user_id = user_id
        self.obj_reset_changes()

    def commit(self, context=None):
        if not self.reservations:
            return
        if context is None:
            context = self._context
        quota.QUOTAS.commit(context, self.reservations,
                            project_id=self.project_id,
                            user_id=self.user_id)
        self.reservations = None
        self.obj_reset_changes()

 注意看reserve方法的參數,默認爲None的project_id和user_id,這正是改變quotas屬主的方便入口!

 修改後的代碼這裏就不貼了,感興趣的同窗能夠到此次提交中看:Code Review

四.代碼提交和Review:

 openstack社區有着整套項目管理流程,這裏有一張圖可以較詳細的描述工做流程:Nova_spec_process

 由圖可見bugfix是其中最簡單的流程。

 關於如何提交代碼,這篇文章有詳細的介紹: 向 OpenStack 貢獻您的代碼。另外須要注意一點,在國內向社區提交代碼,常常會由於網絡問題致使沒法提交,幸虧找到了大牛的博客介紹了該類問題的解決辦法。  修改完代碼的單元測試和pep8本地測試固然不能少,早就知道社區對單元測試要求很嚴格,此次才真正領教了,三行代碼的修改,單元測試卻寫了30行,review期間屢次由於單元測試的問題重提代碼(哭)。社區裏面的開發者,尤爲是項目的core,對待項目有着像對本身孩子般的認真與細緻:他們會在一個本身根本不會在乎的地方提醒你、面對當前的問題他們會延伸的考慮相似的問題。他們的態度讓我首先感覺到的吃驚,而後是敬佩!

 經歷八次review、歷時近一個月,個人代碼總算是入庫了!但願個人這篇記錄能對你有幫助。

 感謝休倫公司技術總監 孫琦 提供的英文支持,社區大牛Alex Xu給出的修改建議。

 Launchpad上面的bug提交: Abnormal changes of quota usage after instance restored by admin

 代碼審查過程: Fix abnormal quota usage after restore by admin

 Git@OSC中的代碼: Fix abnormal quota usage after restore by admin

相關文章
相關標籤/搜索