Glance組件解析


1 Glance基本框架圖

組件

描述

A client

任何使用Glance服務的應用。

REST API

通過REST方式暴露Glance的使用接口。

Database Abstraction Layer (DAL)

介於Glance和數據庫之間的應用程序編程接口。

Glance Domain Controller

一個中間件,實現了Glance的主要功能,比如授權、通知、規則、數據庫連接等功能。

Glance Store

負責與各種后端存儲類型進行交互,提供了一個統一的接口來訪問后端存儲。

Registry Layer

通過使用單獨的服務用於domain和DAL之間的安全交互的一個可選層。

 

2 Glance體系結構圖

 

由上圖可以看出Glance組件主要由glance-api和glance-registry兩個服務組成,glance-api是進入Glance的入口,負責接收用戶的RESTful請求,然后通過后台的Swift、Ceph等存儲系統完成鏡像的存儲與獲取。

glance-api服務一樣,glance-registry也是一個WSGI server服務,不過不同的是glance-registry處理的是與鏡像元數據相關的RESTful請求。Glance-api接收到用戶的RESTful請求后,如果該請求與元數據相關,則將其轉發給glance-registry服務。

glance-registry會解析請求的內容,並與數據庫進行交互,存取或更新鏡像的元數據,這里的元數據是指保存在數據庫中的關於鏡像的一些信息,Glance的DB模塊存儲的僅僅是鏡像的元數據。

 

3 Glance源碼結構

OpenStack的一個組件相關的代碼結構時,一般都是先看該組件源碼中的setup.cfg文件中的內容,特別是[entry_points]里console_scripts的內容,它寫明glance的各項服務的入口點:

console_scripts = 
   glance-api = glance.cmd.api:main
   glance-cache-prefetcher = glance.cmd.cache_prefetcher:main
   glance-cache-pruner = glance.cmd.cache_pruner:main
   glance-cache-manage = glance.cmd.cache_manage:main
   glance-cache-cleaner = glance.cmd.cache_cleaner:main
   glance-control = glance.cmd.control:main
   glance-manage = glance.cmd.manage:main
   glance-registry = glance.cmd.registry:main
   glance-replicator = glance.cmd.replicator:main
   glance-scrubber = glance.cmd.scrubber:main
   glance-glare = glance.cmd.glare:main

服務

描述

glance-cache-*

4個對Image Cache進行管理的工具。

glance-manage

用於Glance數據庫的管理。

glance-replicator

用於實現鏡像的復制。

glance-scrubber

用於清理已經刪除的Image。

glance-control

用於控制glance-api、glance-registry和glance-scrubber這三個服務進程的工具。

glance-glare

Glare API服務,目前還在開發中。

 

4 Glance Domain model實現

 

以下是domain model的各個層描述表:

 層

描述

Authorization

認證層:提供了一個鏡像本身或其屬性是否可以改變的驗證

Property protection

 

屬性保護層:該層是可選的,如果你在配置文件中設置了property_protection_file參數,它就變得可用。可以通過配置文件指明訪問權限。

Notifier

消息通知層:關於鏡像變化的消息和使用鏡像時發生的錯誤和警告都會被添加到消息隊列中。

Policy

規則定義層:定義鏡像操作的訪問規則,規則在/etc/policy.json文件中定義,該層進行監視並實施。

Quota

配額限制層:如果管理者對某用戶定義了鏡像大小的鏡像上傳上限,則若該用戶上傳了超過該限額的鏡像,則會上傳失敗。

Location

鏡像位置定位層:通過glance_store與后台存儲進行交互,例如上傳、下載和管理圖像位置。

1.  添加新位置時檢查位置URI是否正確;

2.  鏡像位置改變時,刪除存儲后端保存的鏡像數據;

3.  防止鏡像位置重復;

Database

數據庫層:實現與數據庫進行交互的API。

  

5 Glance的鏡像介紹及鏡像的狀態轉換

Image是Glance所管理的主要資源。類似於VMware的VM模板,它預先安裝了OS。以下圖是鏡像的狀態演變圖。

 

鏡像狀態

描述

queued

表示Glance注冊表中已保留該圖像標識,但還沒有圖像數據上傳到Glance。

saving

表示圖像的原始數據正在上傳到Glance。

active

表示在Glance中完全可用的圖像。

deactivated

表示不允許任何非管理員用戶訪問圖像數據。

killed

表示在上載圖像數據期間發生錯誤,並且圖像不可讀。

deleted

Glance保留了關於圖像的信息,但不再可用。 此狀態下的圖像將在以后自動刪除。

pending_delete

Glance尚未刪除圖像數據。 處於此狀態的圖像無法恢復。

 

6 Glance的Task

一般來說,對Image的操作有import、export、clone等幾種。Glance把這些操作統一起來抽象出了Task的概念來方便管理。Task是針對Image的異步操作,具有的一些屬性包括id、owner、狀態等。Glance同時也實現了統一的JSON格式的API來操作這些Task,比如創建、刪除、查詢狀態等。

Glance中的任務狀態有以下幾種:

任務狀態

描述

pending

表示該任務標識符已被保留給Glance中的任務。 還沒有開始處理。

processing

表示該任務正在執行中。

success

表示該任務在Glance中已經成功運行。

failure

表示執行任務期間發生錯誤,並且無法繼續處理。

 

7 Glance組件重要流程分析

7.1 Glance服務啟動過程

Glance服務主要包括兩個服務,一個是glance-api服務,一個是glance-registry服務。

glance-api服務啟動時,其入口是/cmd/api.py里的main函數並給相對應的代碼加上了注釋:

def main():
    try:
        # 初始化glance.common.config模塊的CONF對象
        config.parse_args()
        # 更新所有默認配置值,比如更新oslo.middleware模塊的默認值
        config.set_config_defaults()
        # 設置glance.common.wsgi模塊的eventlet對象的hub的實現方式,比如poll或select
        wsgi.set_eventlet_hub()
        # 啟動該服務日志模塊
        logging.setup(CONF, 'glance')
        # 配置該服務事件通知功能的默認值
        notifier.set_defaults()

        # 創建該服務的事件通知功能
        if cfg.CONF.profiler.enabled:
            _notifier = osprofiler.notifier.create("Messaging",
                                                   oslo_messaging, {},
                                                   notifier.get_transport(),
                                                   "glance", "api",
                                                   cfg.CONF.bind_host)
            osprofiler.notifier.set(_notifier)
            osprofiler.web.enable(cfg.CONF.profiler.hmac_keys)
        else:
            osprofiler.web.disable()

        # 初始化glance.common.wsgi模塊的Server類
        server = wsgi.Server(initialize_glance_store=True)
        # 用給定的應用程序啟動wsgi服務
        server.start(config.load_paste_app('glance-api'), default_port=9292)
        # 等待所有服務運行完畢
        server.wait()
    except KNOWN_EXCEPTIONS as e:
        fail(e)

if __name__ == '__main__':
    main()

對於這行代碼:

config.load_paste_app('glance-api')

它的功能是根據paste配置文件建立並返回一個WSGI應用程序。glance-api的paste配置文件是源代碼中的/etc/glance-api-paste.ini,並根據傳入的參數glance-api來找到對應的配置:

[pipeline:glance-api]
pipeline = cors healthcheck http_proxy_to_wsgi versionnegotiation osprofiler unauthenticated-context rootapp
[composite:rootapp]
paste.composite_factory = glance.api:rootapp
/: apiversions
/v1: apiv1app
/v2: apiv2app

[app:apiversions]
paste.app_factory = glance.api.versions:create_resource

[app:apiv1app]
paste.app_factory = glance.api.v1.router:API.factory

[app:apiv2app]
paste.app_factory = glance.api.v2.router:API.factory

從該配置文件我們可以看到返回的wsgi應用程序是一個經過了多個中間件(比如cors healthcheck等等)包裝的,只有最后一個rootapp不是中間件,該rootapp也可以看到其section類型是composite的,可以看到它會調用glance.api模塊的root_app_factory函數:

def root_app_factory(loader, global_conf, **local_conf):
    if not CONF.enable_v1_api and '/v1' in local_conf:
        del local_conf['/v1']
    if not CONF.enable_v2_api and '/v2' in local_conf:
        del local_conf['/v2']
    return paste.urlmap.urlmap_factory(loader, global_conf, **local_conf)

我們主要關注的是v2版本的,可以知道其對應的是glance.api.v2.router:API類:

class API(wsgi.Router):
    """WSGI router for Glance v2 API requests."""
    def __init__(self, mapper):
        custom_image_properties = images.load_custom_properties()
        reject_method_resource = wsgi.Resource(wsgi.RejectMethodController())

        schemas_resource = schemas.create_resource(custom_image_properties)
        mapper.connect('/schemas/image',
                       controller=schemas_resource,
                       action='image',
                       conditions={'method': ['GET']},
                       body_reject=True)
        mapper.connect('/schemas/image',
                       controller=reject_method_resource,
                       action='reject',
                       allowed_methods='GET')

這里只列出一部分,重點是看mapper.connect里的格式,它描述的是請求API對應的controller和action。

glance-registry服務啟動時跟glance-api類似,不過glance-api的監聽端口是9292,glance-registry的監聽端口是9191。

 

7.2  列舉鏡像過程

當我們使用命令openstack image list查看鏡像文件時,從后台日志中可以看到這個請求GET /v2/images,該請求會在route.py進行匹配,可以知道它使用的是images這個controllerindex函數

mapper.connect('/images',
               controller=images_resource,
               action='index',
               conditions={'method': ['GET']})

index函數中我們可以看到以下關鍵代碼:

image_repo = self.gateway.get_repo(req.context)
images = image_repo.list(marker=marker, limit=limit,
                         sort_key=sort_key,
                         sort_dir=sort_dir,
                         filters=filters,
                         member_status=member_status)
if len(images) != 0 and len(images) == limit:
    result['next_marker'] = images[-1].image_id
result['images'] = images
return result

我們需要再看看get_repo函數:

def get_repo(self, context):
    image_repo = glance.db.ImageRepo(context, self.db_api)
    store_image_repo = glance.location.ImageRepoProxy(
        image_repo, context, self.store_api, self.store_utils)
    quota_image_repo = glance.quota.ImageRepoProxy(
        store_image_repo, context, self.db_api, self.store_utils)
    policy_image_repo = policy.ImageRepoProxy(
        quota_image_repo, context, self.policy)
    notifier_image_repo = glance.notifier.ImageRepoProxy(
        policy_image_repo, context, self.notifier)
    if property_utils.is_property_protection_enabled():
        property_rules = property_utils.PropertyRules(self.policy)
        pir = property_protections.ProtectedImageRepoProxy(
            notifier_image_repo, context, property_rules)
        authorized_image_repo = authorization.ImageRepoProxy(
            pir, context)
    else:
        authorized_image_repo = authorization.ImageRepoProxy(
            notifier_image_repo, context)

    return authorized_image_repo

可以看到這里經過了多個類的封裝,其實就是glance domain module的實現,返回時是一個經過多個類封裝后的對象,這里封裝的類都是繼承自domain_proxy.Repo基類的:

class Repo(object):
    def __init__(self, base, item_proxy_class=None, item_proxy_kwargs=None):
        self.base = base
        self.helper = Helper(item_proxy_class, item_proxy_kwargs)

    def get(self, item_id):
        return self.helper.proxy(self.base.get(item_id))

    def list(self, *args, **kwargs):
        items = self.base.list(*args, **kwargs)
        return [self.helper.proxy(item) for item in items]

所以它能夠做到把方法一層一層往上傳遞。

glance domain model的各層的實現功能上面我們已經講過了,這里主要針對該list來講,從源代碼來看,該list方法繼承類有重寫list方法的層有auth層、policy層和db層,首先在auth層,該層調用到policy層,該層進行權限檢查,查看此行為是否有權限,然后傳遞到db層,我們主要看下db層的list方法實現:

def list(self, marker=None, limit=None, sort_key=None,
         sort_dir=None, filters=None, member_status='accepted'):
    sort_key = ['created_at'] if not sort_key else sort_key
    sort_dir = ['desc'] if not sort_dir else sort_dir
    db_api_images = self.db_api.image_get_all(
        self.context, filters=filters, marker=marker, limit=limit,
        sort_key=sort_key, sort_dir=sort_dir,
        member_status=member_status, return_tag=True)
    images = []
    for db_api_image in db_api_images:
        db_image = dict(db_api_image)
        image = self._format_image_from_db(db_image, db_image['tags'])
        images.append(image)
    return images

這些代碼中最重要的代碼是image_get_all方法的調用,返回的是從數據庫中查詢到的image的信息。

image_get_all方法實現是在\glance\db\sqlalchemy\api.py文件中,sqlalchemy模塊是對數據庫的操作進行了封裝的,這里不詳細描述它里面的實現細節。

 

7.3  上傳鏡像過程

就通過命令行開啟debug模式上傳鏡像,比如:

openstack image create "oop" --file test_image.img --disk-format raw --container-format bare --public --debug

從輸出的內容可以看到該命令執行一共發出了三個請求:

(1) 第一個請求:GET call to glance-api for http://controller:9292/v2/schemas/image used request id req-507983f9-643a-4330-a658-5b9f6803e91d

通過glance/api/v2/routesr.py.API定義的路由映射可知,該請求會對應到/v2/schemas.py.Controller類的image函數:

def image(self, req):
    return self.image_schema.raw()

image_schema的raw方法實現:

def raw(self):
    raw = super(PermissiveSchema, self).raw()
    raw['additionalProperties'] = {'type': 'string'}
    return raw

Schema的raw方法實現:

def raw(self):
    raw = {
        'name': self.name,
        'properties': self.properties,
        'additionalProperties': False,
    }
    if self.definitions:
        raw['definitions'] = self.definitions
    if self.required:
        raw['required'] = self.required
    if self.links:
        raw['links'] = self.links
    return raw

從上述代碼可以知道獲得的是鏡像所支持的屬性字典定義,調用者就可以根據這些信息來驗證用戶輸入的參數是否有效。

(2) 第二個請求:POST call to glance-api for http://controller:9292/v2/images used request id req-e4cdac4f-8294-4a14-8308-1fbd46f3ce9c   HTTP/1.1 201 Created

通過glance/api/v2/routesr.py.API定義的路由映射可知它這里是調用了/v2/images.py.ImageController類的create函數:

def create(self, req, image, extra_properties, tags):
    image_factory = self.gateway.get_image_factory(req.context)
    image_repo = self.gateway.get_repo(req.context)
    try:
        image = image_factory.new_image(extra_properties=extra_properties,
                                        tags=tags, **image)
        image_repo.add(image)

    return image

這里返回了兩個對象image_factory和image_repo,也是經過了glance domain module封裝的對象。

image_factory對象所對應的責任鏈上,我們這里只看db層new_image方法實現即可,其它層都是一些校驗檢查之類的:

def new_image(self, image_id=None, name=None, visibility='shared',
              min_disk=0, min_ram=0, protected=False, owner=None,
              disk_format=None, container_format=None,
              extra_properties=None, tags=None, **other_args):
    extra_properties = extra_properties or {}
    self._check_readonly(other_args)
    self._check_unexpected(other_args)
    self._check_reserved(extra_properties)

    if image_id is None:
        image_id = str(uuid.uuid4())
    created_at = timeutils.utcnow()
    updated_at = created_at
    status = 'queued'

    return Image(image_id=image_id, name=name, status=status,
                 created_at=created_at, updated_at=updated_at,
                 visibility=visibility, min_disk=min_disk,
                 min_ram=min_ram, protected=protected,
                 owner=owner, disk_format=disk_format,
                 container_format=container_format,
                 extra_properties=extra_properties, tags=tags or [])

可以看到new_image函數最終會返回一個image對象,對象里面包含了該鏡像的屬性值,比如鏡像id,鏡像創建時間等等,並且將該對象的狀態置為queued。

image_repo對象對應的責任鏈上,我們主要看location和db層。

location 層的add方法的實現:

def add(self, image):
    result = super(ImageRepoProxy, self).add(image)
    self._set_acls(image)
    return result

可以看到先調用了db 層的add方法,查看db層的add方法實現:

def add(self, image):
    # 獲取到對應數據庫相關字段的屬性組合成字典返回
    image_values = self._format_image_to_db(image)
    if (image_values['size'] is not None
       and image_values['size'] > CONF.image_size_cap):
        raise exception.ImageSizeLimitExceeded
    # the updated_at value is not set in the _format_image_to_db
    # function since it is specific to image create
    image_values['updated_at'] = image.updated_at
    # 根據image_values字典中的值創建一個image對象
    new_values = self.db_api.image_create(self.context, image_values)
    self.db_api.image_tag_set_all(self.context,
                                  image.image_id, image.tags)
    image.created_at = new_values['created_at']
    image.updated_at = new_values['updated_at']

主要看self.db_api.image_create函數的實現:

def image_create(context, values, v1_mode=False):
    """Create an image from the values dictionary."""
    image = _image_update(context, values, None, purge_props=False)
    if v1_mode:
        image = db_utils.mutate_image_dict_to_v1(image)
    return image

這個函數的主要實現在於_image_update的實現,這個函數的實現比較長,我只寫出比較重要的代碼:

def _image_update(context, values, image_id, purge_props=False,
                  from_state=None):
    # 獲取一個跟數據庫表有關系對象映射的對象
    image_ref = models.Image()
    # 將values對象里的鍵值對更新到image_ref對象中
    image_ref.update(values)
    values = _validate_image(image_ref.to_dict())
    _update_values(image_ref, values)
    # 將image_ref中的值插入到數據庫表中,save里的實現是數據庫表和python對象的        關系對象映射里的實現
    image_ref.save(session=session)
    return image_get(context, image_ref.id)

這里看下image_get函數的實現:
def image_get(context, image_id, session=None, force_show_deleted=False,
              v1_mode=False):
    # 根據image_id從數據庫中查找出對應的鏡像信息並以對象形式返回
    image = _image_get(context, image_id, session=session,
                       force_show_deleted=force_show_deleted)
    # 為圖像的位置字段生成合適的字典列表,比如鏡像的url,其實就是將上面的image對象里包含的值全部展開,變成字典形式的字符串返回
    image = _normalize_locations(context, image.to_dict(),
                                 force_show_deleted=force_show_deleted)
    if v1_mode:
        image = db_utils.mutate_image_dict_to_v1(image)
    return image

現在回到location層的add函數中,接着是執行self._set_acls(image)代碼,下面是_set_acls函數的實現:

def _set_acls(self, image):
    public = image.visibility == 'public'
    member_ids = []
    if image.locations and not public:
        member_repo = _get_member_repo_for_store(image,
                                                 self.context,
                                                 self.db_api,
                                                 self.store_api)
        member_ids = [m.member_id for m in member_repo.list()]
    # 調用store.set_acls,設置image的讀寫權限(這里的set_als的實現是在glance_store項目代碼里的)
    for location in image.locations:
        self.store_api.set_acls(location['url'], public=public,
                                read_tenants=member_ids,
                                context=self.context)

可以說這第二個請求基本上都是跟創建鏡像的元數據相關,並將其保存到數據庫中,但后面也看到會需要跟glance-store項目進行交互進行鏡像的讀寫權限的設置。

glance-store 向 glance-api 提供文件 backend.py 作為 store 操作的統一入口

(2) 第三個請求:PUT call to glance-api for

http://controller:9292/v2/images/251c497f-2482-426f-9403-f026529c9e3b/file used request id req-0cc373cf-5917-41c4-8349-29ad8d14c757

通過glance/api/v2/routesr.py.API定義的路由映射可知它這里是調用了/v2/image_data.py.ImageDataController類的upload函數,這里只列出一些重要代碼:

def upload(self, req, image_id, data, size):
    # 跟之前一樣,獲取一個經過多層封裝的責任鏈對象
    image_repo = self.gateway.get_repo(req.context)
    # 根據image_id從數據庫中取出image的信息並以對象形式返回,且該對象也經過了多層的封裝
    image = image_repo.get(image_id)
    image.status = 'saving'
    # 更新數據庫條目
    image_repo.save(image, from_state='queued')
    # set_data的調用也會在domain model各層中傳遞,該實現是使用了Helper代理類實現的
    image.set_data(data, size)
    # 更新數據庫條目,比如狀態此時會更新為saving狀態
    image_repo.save(image, from_state='saving')

這里我們主要關注location層的set_data的實現,因為是在該層將鏡像數據通過glance-store的相關接口存儲到后端存儲的,只列出關鍵性代碼:

def set_data(self, data, size=None):
    # 調用glance-store項目的接口來存儲鏡像並接收返回值
    location, size, checksum, loc_meta = self.store_api.add_to_backend(
        CONF,
        self.image.image_id,
        # 這里使用了eventle庫來讀取要上傳的鏡像數據
        utils.LimitingReader(utils.CooperativeReader(data),
                         CONF.image_size_cap),
        size,
        context=self.context,
        verifier=verifier)
    # 將存儲后返回的數據進行更新
    self.image.locations = [{'url': location, 'metadata': loc_meta,
                             'status': 'active'}]
    self.image.size = size
    self.image.checksum = checksum
    # 鏡像status的狀態更改為active
    self.image.status = 'active'

上面最主要的代碼是store_api.add_to_backend的調用,該項目代碼實現是在glance-store項目里,以下列出該函數的實現:

def add_to_backend(conf, image_id, data, size, scheme=None, context=None,
                   verifier=None):
    if scheme is None:
        # 從配置文件中獲取默認的存儲方式是什么,比如rbd
        scheme = conf['glance_store']['default_store']
    # 根據schema的類型獲取對應的后端存儲對象
    store = get_store_from_scheme(scheme)
    # 每個存儲后端對象的add()方法的調用的封裝
    return store_add_to_backend(image_id, data, size, store, context,
                                verifier)
查看store_add_to_backend函數的實現,這里只列出關鍵代碼:
def store_add_to_backend(image_id, data, size, store, context=None,
                         verifier=None):
    # 調用后端存儲對象的add方法存儲鏡像數據
    (location, size, checksum, metadata) = store.add(image_id,
                                                     data,
                                                     size,
                                                     context=context,
                                                     verifier=verifier)
    return (location, size, checksum, metadata)

因為我們后端存儲使用的是Cephrbd存儲,所以我們直接看塊存儲的add方法實現,只列出關鍵代碼:

def _create_image(self, fsid, conn, ioctx, image_name,
                  size, order, context=None):
    # 創建一個rbd image對象
    loc = self._create_image(fsid, conn, ioctx, image_name,
                             image_size, order)
    # 往rbd image對象中寫數據
    with rbd.Image(ioctx, image_name) as image:
        bytes_written = 0
        offset = 0
        chunks = utils.chunkreadable(image_file,
                                     self.WRITE_CHUNKSIZE)
        for chunk in chunks:
            if image_size == 0:
                chunk_length = len(chunk)
                length = offset + chunk_length
                bytes_written += chunk_length
                LOG.debug(_("resizing image to %s KiB") %
                          (length / units.Ki))
                image.resize(length)
            LOG.debug(_("writing chunk at offset %s") %
                      (offset))
            offset += image.write(chunk, offset)
            checksum.update(chunk)
            if verifier:
                verifier.update(chunk)
        if loc.snapshot:
            image.create_snap(loc.snapshot)
            image.protect_snap(loc.snapshot)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM