如何書寫Openstack命令列

bule_sky_fuxing發表於2017-07-10
接著之前一篇文章,再來談談 Extension 的具體實現問題。我使用的是本地資料庫加遠端API呼叫的方法,所以先要定義一下資料庫中 myextension 如何儲存。首先,我們可以在自己的 plugin 根目錄下新建一個 db 資料夾,以及三個檔案:
- neutron/
  - plugins/
    - myplugin/
      - __init__.py
      - plugin.py
      - extensions/
      - db/
        - __init__.py
        - db.py
        - models.py
db.py 用來存放資料庫的 wrapper,提供一些讀寫資料庫的方法;而 models.py 直接定義 myextension 的那些屬性如何在資料庫中儲存。下面是 models.py 的一個例子:

  1. import sqlalchemy as sa

  2. from neutron.db import model_base
  3. from neutron.db import models_v2


  4. class MyExtension(model_base.BASEV2, models_v2.HasId,
  5.                   model_v2.HasTenant):
  6.     ''' Defines the data model for my extension '''
  7.     name = sa.Column(sa.String(255), nullable=False)
複製程式碼


db.py 的實現可以參照 db_base_plugin_v2.py 中的實現方式。然後在自己的 plugin.py 中實現 myextension 的五個操作: CREATE, UPDATE, GET, SHOW,  和 DELETE。到這裡,myextension 的後臺操作就全了。

但是這樣還不夠,因為我們還需要一些方式來從前臺釋出關於 myextension 的命令,使後臺的 plugin controller 可以接收到,並進行相應的操作。這就需要修改 neutronclient 的相應程式碼,實現通過 CLI 的互動。

通過上面的介紹,我們現在的 myplugin 資料夾看上去應該是這樣的:
- neutron/
  - plugins/
    - myplugin/
      - __init__.py
      - plugin.py
      - extensions/
        - __init__.py
        - myextension.py
      - db/
        - __init__.py
        - db.py
        - models.py

我們的plugin.py看上去應該是類似這樣的:

  1. from neutron import neutron_plugin_base_v2


  2. class MyPlugin(neutron_plugin_base_v2.NeutronPluginBaseV2):
  3.     def __init__(self):
  4.         pass

  5.    [...] 
  6.   
  7.   def create_myextension(self, context, myextension):
  8.         return myextension

  9.     def update_myextension(self, context, id, myextension):
  10.         return myextension

  11.     def get_myextension(self, context, id, fields=None):
  12.         myextension = {}
  13.         return myextension

  14.     def get_myextensions(self, context, filters=None, fields=None):
  15.         myextensions = {}
  16.         return myextensions

  17.     def delete_myextension(self, context, id):
  18.         return id
  19.  
  20.   [...]
複製程式碼

這些方法需要進一步的定義來做一些有實際意義的事情,這裡我只是做一個最簡單的舉例說明。在有了上述這些檔案之後,其實整個 plugin 以及 extension 的後臺就已經搞定了,只是這個時候還沒有任何途徑來使用這些定義好的方法。所以接下來我們需要在 neutronclient 中增加一些對應的方法。

neutronclient 是一個 CLI 客戶端,可以用來與 neutron 互動。每一個 OpenStack 的 project 如 nova,neutron 等都有一個類似的自己的 CLI client。首先,我們需要在 CLI 中顯示關於 myextension 的命令,這個可以在 neutronclient/shell.py 中設定。這個檔案有一個屬性叫做 COMMAND_V2,所有可以使用的命令都以 key-value pair 的形式儲存在這個變數中:

  1. from neutronclient.neutron.v2_0.myextension import extension as my_ext
  2. COMMAND_V2 = {
  3.     'net-list': network.ListNetwork,
  4.     'net-external-list': network.ListExternalNetwork,
  5.     'net-show': network.ShowNetwork,
  6.     'net-create': network.CreateNetwork,
  7.     'net-delete': network.DeleteNetwork,
  8.     'net-update': network.UpdateNetwork,
  9.     ...
  10.   'myextension-list': my_ext.ListExtension,
  11.   'myextension-show': my_ext.ShowExtension,
  12.   'myextension-create': my_ext.CreateExtension,
  13.   'myextension-delete': my_ext.DeleteExtension,
  14.   'myextension-update': my_ext.UpdateExtension,
  15.   ...
  16.   }
複製程式碼


下一步就需要去 neutronclient/neutron/v2_0 中新建一個資料夾 myextension,並在其中新建兩個檔案: __init__.py 和 extension.py,其結構如下:
- neutronclient/
  - neutron/
    - v2_0/
      - myextension/
        - __init__.py
        - extension.py

然後再 extension.py 中分別定義五個class:List/Show/Create/Delete/UpdateExtension。

上面說到需要在 /neutronclient/v2_0/myextension/extension.py 中分別定義五個 class:List/Show/Create/Delete/UpdateExtension。具體形式如下:

  1. import argparse
  2. import logging

  3. from neutronclient.neutron import v2_0 as neutronV20
  4. from neutronclient.openstack.common.gettextutils import _

  5. RESOURCE = 'myextension'

  6. class ListExtension(neutronV20.ListCommand):
  7.     """List extensions"""

  8.     resource = RESOURCE
  9.     log = logging.getLogger(__name__ + '.ListExtension')
  10.     list_columns = ['id', 'name']
  11.   
  12.   
  13. class ShowExtension(neutronV20.ShowCommand):
  14.     """Show information of a given extension."""

  15.     resource = RESOURCE
  16.     log = logging.getLogger(__name__ + '.ShowExtension')
  17.     

  18. class CreatePhysicalGateway(neutronV20.CreateCommand):
  19.     """Create an extension."""

  20.     resource = RESOURCE
  21.     log = logging.getLogger(__name__ + '.CreateExtension')

  22.     def add_known_arguments(self, parser):
  23.         parser.add_argument(
  24.             'name', metavar='NAME',
  25.             help=_('Name of extension to create'))
  26.        

  27.     def args2body(self, parsed_args):
  28.         body = {self.resource: {
  29.             'name': parsed_args.name}}
  30.         return body



  31. class UpdateExtension(neutronV20.UpdateCommand):
  32.     """update a given extension."""

  33.     resource = RESOURCE
  34.     log = logging.getLogger(__name__ + '.UpdateExtension')      
  35. class DeleteExtension(neutronV20.DeleteCommand):
  36.     """Delete a given extension."""

  37.     resource = RESOURCE
  38.     log = logging.getLogger(__name__ + '.DeleteExtension')
複製程式碼


這些 class 處在接受 CLI 命令的第一線,負責將命令轉化成 API call。需要特別注意的是 CreateExtension 這個類,它有兩個方法 add_known_arguments 和 args2body。前者定義了 CLI 命令接受哪些引數,後者規定如何將收到的引數打包起來。

這些引數打包之後就會發給 neutron 後臺中我們自己定義的 plugin controller,但是如何傳送這些引數還需要我們去 /neutronclient/v2_0/client.py 的 Client 類中設定:
首先是 uri 路徑:

  1. myextensions_path = "/myextensions"
  2.     myextension_path = "/myextensions/%s"
複製程式碼
然後是每個操作所對應的傳遞方法:
  1. @APIParamsCall
  2.     def list_myextensions(self, retrieve_all=True, **_params):
  3.         """Fetches a list of all myextensions for a tenant."""
  4.         return self.list('myextensions', self.myextensions_path, retrieve_all,
  5.                          **_params)
  6.                          
  7.     @APIParamsCall
  8.     def show_myextension(self, myextension, **_params):
  9.         """Fetches information of a certain entry in myextension."""
  10.         return self.get(self.myextension_path % (myextension), params=_params)
  11.         
  12.     @APIParamsCall
  13.     def create_myextension(self, body=None):
  14.         """Creates a new myextension entry."""
  15.         return self.post(self.myextensions_path, body=body)
  16.         
  17.     @APIParamsCall
  18.     def delete_myextension(self, myextension):
  19.         """Deletes the specified myextension."""
  20.         return self.delete(self.myextension_path % (myextension))

  21.    @APIParamsCall
  22.     def update_myextension(self, myextension, body=None):
  23.         """Updates a myextension."""
  24.         return self.put(self.myextension_path % (myextension), body=body)
複製程式碼
如此一來,我們自己實現的 neutron plugin 就能夠收到 CLI 傳送過來的命令啦。

相關文章