溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

glance delayed delete image代碼調(diào)用

發(fā)布時(shí)間:2020-07-14 17:37:29 來源:網(wǎng)絡(luò) 閱讀:7587 作者:沈豬豬 欄目:開發(fā)技術(shù)

glance支持延遲刪除鏡像的功能,個(gè)人覺得挺實(shí)用的,特別是在誤刪除的情況下。從某種程度來說,也算是對(duì)數(shù)據(jù)一種另類保護(hù)吧。


大致實(shí)現(xiàn)原理是:有個(gè)delayed_delete設(shè)置是否開啟延遲刪除的開關(guān),如果為True的話,每次刪除鏡像的時(shí)候都會(huì)把鏡像的狀態(tài)置為pending_delete,記錄此刻的delete_time,有個(gè)scrubber的后臺(tái)進(jìn)程會(huì)每隔一段時(shí)間(wakeup_time)去check是否有pending_delete的鏡像要?jiǎng)h除,刪除的判斷標(biāo)準(zhǔn)是:該鏡像被刪除的那個(gè)時(shí)刻的delete_time + scrub_time <= time.time(),scrub_time是鏡像要隔多少秒才真正被擦除掉。


開啟delayed_delete

[root@controller2 ~(keystone_admin)]# vim /etc/glance/glance-api.conf
delayed_delete = True


 來看glance api端刪除鏡像的時(shí)候判斷是否開啟了delayed_delete的代碼

# v1的api
glance/api/v1/p_w_picpath.py
@utils.mutating
    def delete(self, req, id):
        """
        Deletes the p_w_picpath and all its chunks from the Glance
        :param req: The WSGI/Webob Request object
        :param id: The opaque p_w_picpath identifier
        :raises: HttpBadRequest if p_w_picpath registry is invalid
        :raises: HttpNotFound if p_w_picpath or any chunk is not available
        :raises: HttpUnauthorized if p_w_picpath or any chunk is not
                deleteable by the requesting user
        """
        self._enforce(req, 'delete_p_w_picpath')
        p_w_picpath = self.get_p_w_picpath_meta_or_404(req, id)
        if p_w_picpath['protected']:
            msg = _("Image is protected")
            LOG.warn(msg)
            raise HTTPForbidden(explanation=msg,
                                request=req,
                                content_type="text/plain")
        if p_w_picpath['status'] == 'pending_delete':
            msg = (_("Forbidden to delete a %s p_w_picpath.") %
                   p_w_picpath['status'])
            LOG.warn(msg)
            raise HTTPForbidden(explanation=msg,
                                request=req,
                                content_type="text/plain")
        elif p_w_picpath['status'] == 'deleted':
            msg = _("Image %s not found.") % id
            LOG.warn(msg)
            raise HTTPNotFound(explanation=msg, request=req,
                               content_type="text/plain")
        if p_w_picpath['location'] and CONF.delayed_delete:      # 這里做了判斷
            status = 'pending_delete'
        else:
            status = 'deleted'
。。。。。。            
            

# v2的api         
glance/api/v2/p_w_picpath.py
@utils.mutating
    def delete(self, req, p_w_picpath_id):
        p_w_picpath_repo = self.gateway.get_repo(req.context)
        try:
            p_w_picpath = p_w_picpath_repo.get(p_w_picpath_id)
            p_w_picpath.delete()    # 跟進(jìn)去看
            p_w_picpath_repo.remove(p_w_picpath)
        except (glance_store.Forbidden, exception.Forbidden) as e:
            LOG.debug("User not permitted to delete p_w_picpath '%s'", p_w_picpath_id)
            raise webob.exc.HTTPForbidden(explanation=e.msg)
        except (glance_store.NotFound, exception.NotFound) as e:
            msg = (_("Failed to find p_w_picpath %(p_w_picpath_id)s to delete") %
                   {'p_w_picpath_id': p_w_picpath_id})
            LOG.warn(msg)
            raise webob.exc.HTTPNotFound(explanation=msg)
        except glance_store.exceptions.InUseByStore as e:
            msg = (_("Image %(id)s could not be deleted "
                     "because it is in use: %(exc)s") %
                   {"id": p_w_picpath_id,
                    "exc": e.msg})
            LOG.warn(msg)
            raise webob.exc.HTTPConflict(explanation=msg)
        except glance_store.exceptions.HasSnapshot as e:
            raise webob.exc.HTTPConflict(explanation=e.msg)
        except exception.InvalidImageStatusTransition as e:
            raise webob.exc.HTTPBadRequest(explanation=e.msg)
        except exception.NotAuthenticated as e:
            raise webob.exc.HTTPUnauthorized(explanation=e.msg)

            
glance/domain/proxy.py
class Image(object):
    def __init__(self, base, member_repo_proxy_class=None,
                 member_repo_proxy_kwargs=None):
        self.base = base
        self.helper = Helper(member_repo_proxy_class,
                             member_repo_proxy_kwargs)

    name = _proxy('base', 'name')
    p_w_picpath_id = _proxy('base', 'p_w_picpath_id')
    status = _proxy('base', 'status')
    created_at = _proxy('base', 'created_at')
    updated_at = _proxy('base', 'updated_at')
    visibility = _proxy('base', 'visibility')
    min_disk = _proxy('base', 'min_disk')
    min_ram = _proxy('base', 'min_ram')
    protected = _proxy('base', 'protected')
    locations = _proxy('base', 'locations')
    checksum = _proxy('base', 'checksum')
    owner = _proxy('base', 'owner')
    disk_format = _proxy('base', 'disk_format')
    container_format = _proxy('base', 'container_format')
    size = _proxy('base', 'size')
    virtual_size = _proxy('base', 'virtual_size')
    extra_properties = _proxy('base', 'extra_properties')
    tags = _proxy('base', 'tags')

    def delete(self):
        self.base.delete()   # 這里的base來自glance/domain/__init__.py

glance/domain/__init__.py
class Image(object):
    valid_state_targets = {
        # Each key denotes a "current" state for the p_w_picpath. Corresponding
        # values list the valid states to which we can jump from that "current"
        # state.
        # NOTE(flwang): In v2, we are deprecating the 'killed' status, so it's
        # allowed to restore p_w_picpath from 'saving' to 'queued' so that upload
        # can be retried.
        'queued': ('saving', 'active', 'deleted'),
        'saving': ('active', 'killed', 'deleted', 'queued'),
        'active': ('pending_delete', 'deleted', 'deactivated'),
        'killed': ('deleted',),
        'pending_delete': ('deleted',),
        'deleted': (),
        'deactivated': ('active', 'deleted'),
    }

    def __init__(self, p_w_picpath_id, status, created_at, updated_at, **kwargs):
        self.p_w_picpath_id = p_w_picpath_id
        self.status = status
        self.created_at = created_at
        self.updated_at = updated_at
        self.name = kwargs.pop('name', None)
        self.visibility = kwargs.pop('visibility', 'private')
        self.min_disk = kwargs.pop('min_disk', 0)
        self.min_ram = kwargs.pop('min_ram', 0)
        self.protected = kwargs.pop('protected', False)
        self.locations = kwargs.pop('locations', [])
        self.checksum = kwargs.pop('checksum', None)
        self.owner = kwargs.pop('owner', None)
        self._disk_format = kwargs.pop('disk_format', None)
        self._container_format = kwargs.pop('container_format', None)
        self.size = kwargs.pop('size', None)
        self.virtual_size = kwargs.pop('virtual_size', None)
        extra_properties = kwargs.pop('extra_properties', {})
        self.extra_properties = ExtraProperties(extra_properties)
        self.tags = kwargs.pop('tags', [])
        if kwargs:
            message = _("__init__() got unexpected keyword argument '%s'")
            raise TypeError(message % list(kwargs.keys())[0])
            
    def delete(self):            # base調(diào)用的是這個(gè)delete方法
        if self.protected:
            raise exception.ProtectedImageDelete(p_w_picpath_id=self.p_w_picpath_id)
        if CONF.delayed_delete and self.locations:    # 跟v1 api同樣的判斷
            self.status = 'pending_delete'
        else:
            self.status = 'deleted'
            
# v2 api有g(shù)ateway、proxy、domain這些概念,留個(gè)懸念,下次弄清楚。

這里是官方對(duì)gateway、domain、proxy的介紹:http://docs.openstack.org/developer/glance/domain_model.html


修改glance-scrubber.conf文件

[root@controller2 ~(keystone_admin)]# egrep -v "^$|^#" /etc/glance/glance-scrubber.conf
[DEFAULT]
scrub_time=300
delayed_delete=true
send_identity_headers=true
wakeup_time=60
daemon=True
admin_user=glance
admin_password=glance
admin_tenant_name=service
auth_url=http://controller2:35357/v2.0
auth_region=RegionOne
registry_host=controller2
registry_port=9191
[database]
connection = mysql+pymysql://glance:glance@controller2/glance?charset=utf8
[oslo_concurrency]
[oslo_policy]
[glance_store]
default_store = rbd
stores = rbd,http,cinder
rbd_store_pool = p_w_picpaths
rbd_store_user = glance
rbd_store_ceph_conf = /etc/ceph/ceph.conf
rbd_store_chunk_size = 8
filesystem_store_datadirs = /var/lib/glance/p_w_picpaths


啟動(dòng)glance-srubber服務(wù)

[root@controller2 ~(keystone_admin)]# service openstack-glance-scrubber start


接下來看glance scrubber的啟動(dòng)過程

glance/cmd/scrubber.py
def main():
    CONF.register_cli_opts(scrubber.scrubber_cmd_cli_opts)
    CONF.register_opts(scrubber.scrubber_cmd_opts)
    try:
        config.parse_args()
        logging.setup(CONF, 'glance')
        glance_store.register_opts(config.CONF)
        glance_store.create_stores(config.CONF)  # 會(huì)調(diào)用glance_store/backend.py的create_stores函數(shù),初始化SCHEME_TO_CLS_MAP
        glance_store.verify_default_store()
        app = scrubber.Scrubber(glance_store) # 會(huì)作為下面的store_api
        if CONF.daemon:     # 讓glance-scrubber以daemon方式存在
            server = scrubber.Daemon(CONF.wakeup_time)
            server.start(app)
            server.wait()
        else:
            app.run()
    except RuntimeError as e:
        sys.exit("ERROR: %s" % e)
        
if __name__ == '__main__':
    main()


Daemon類

glance/scrubber.py
class Daemon(object):
    def __init__(self, wakeup_time=300, threads=100):
        LOG.info(_LI("Starting Daemon: wakeup_time=%(wakeup_time)s "
                     "threads=%(threads)s"),
                 {'wakeup_time': wakeup_time, 'threads': threads})
        self.wakeup_time = wakeup_time
        self.event = eventlet.event.Event()
        # This pool is used for periodic instantiation of scrubber
        self.daemon_pool = eventlet.greenpool.GreenPool(threads)
    def start(self, application):
        self._run(application)
    def wait(self):
        try:
            self.event.wait()
        except KeyboardInterrupt:
            msg = _LI("Daemon Shutdown on KeyboardInterrupt")
            LOG.info(msg)
    def _run(self, application):
        LOG.debug("Running application")
        self.daemon_pool.spawn_n(application.run, self.event)   # 這里也用eventlet
        eventlet.spawn_after(self.wakeup_time, self._run, application)  # application就是下面Scrubber的instance
        LOG.debug("Next run scheduled in %s seconds", self.wakeup_time)


Scrubber類

class Scrubber(object):
    def __init__(self, store_api):
        LOG.info(_LI("Initializing scrubber with configuration: %s"),
                 six.text_type({'registry_host': CONF.registry_host,
                                'registry_port': CONF.registry_port}))
        self.store_api = store_api
        registry.configure_registry_client()
        registry.configure_registry_admin_creds()  # glance/registry/client/v2或v1/api.py,初始好_CLIENT_CREDS,獲得registry client需要
        # Here we create a request context with credentials to support
        # delayed delete when using multi-tenant backend storage
        admin_user = CONF.admin_user
        admin_tenant = CONF.admin_tenant_name  # 需要配置,授權(quán)用的,要獲得registry的client instance
        if CONF.send_identity_headers:      # 之前沒enable send_identity_headers,一直授權(quán)失敗,難道有坑?
            # When registry is operating in trusted-auth mode
            roles = [CONF.admin_role]
            self.admin_context = context.RequestContext(user=admin_user,
                                                        tenant=admin_tenant,
                                                        auth_token=None,
                                                        roles=roles)
            self.registry = registry.get_registry_client(self.admin_context)
        else:
            ctxt = context.RequestContext()
            self.registry = registry.get_registry_client(ctxt)
            auth_token = self.registry.auth_token
            self.admin_context = context.RequestContext(user=admin_user,
                                                        tenant=admin_tenant,
                                                        auth_token=auth_token)
        self.db_queue = get_scrub_queue()
        self.pool = eventlet.greenpool.GreenPool(CONF.scrub_pool_size)
        
# 每隔wakeup_time秒就會(huì)執(zhí)行這個(gè)run函數(shù)        
    def run(self, event=None):
        delete_jobs = self._get_delete_jobs()

        if delete_jobs:
            list(self.pool.starmap(self._scrub_p_w_picpath, delete_jobs.items()))  # 對(duì)后面可迭代對(duì)象迭代執(zhí)行_scrub_p_w_picpath函數(shù)
         
# _scrub_p_w_picpath函數(shù)          
    def _scrub_p_w_picpath(self, p_w_picpath_id, delete_jobs):
        if len(delete_jobs) == 0:
            return

        LOG.info(_LI("Scrubbing p_w_picpath %(id)s from %(count)d locations."),
                 {'id': p_w_picpath_id, 'count': len(delete_jobs)})

        success = True
        for img_id, loc_id, uri in delete_jobs:
            try:
                self._delete_p_w_picpath_location_from_backend(img_id, loc_id, uri)
            except Exception:
                success = False

        if success:
            p_w_picpath = self.registry.get_p_w_picpath(p_w_picpath_id)
            if p_w_picpath['status'] == 'pending_delete':
                self.registry.update_p_w_picpath(p_w_picpath_id, {'status': 'deleted'}) # 利用上面獲得的registry client更新p_w_picpath的狀態(tài),registry是跟數(shù)據(jù)庫打交道的
            LOG.info(_LI("Image %s has been scrubbed successfully"), p_w_picpath_id)
        else:
            LOG.warn(_LW("One or more p_w_picpath locations couldn't be scrubbed "
                         "from backend. Leaving p_w_picpath '%s' in 'pending_delete'"
                         " status") % p_w_picpath_id)

# _delete_p_w_picpath_location_from_backend函數(shù)                                                 
    def _delete_p_w_picpath_location_from_backend(self, p_w_picpath_id, loc_id, uri):
        if CONF.metadata_encryption_key:
            uri = crypt.urlsafe_decrypt(CONF.metadata_encryption_key, uri) # uri有加密,就先解密
        try:
            LOG.debug("Scrubbing p_w_picpath %s from a location.", p_w_picpath_id)
            try:
                self.store_api.delete_from_backend(uri, self.admin_context)  # store_api是glance_store/__init__.py
            except store_exceptions.NotFound:
                LOG.info(_LI("Image location for p_w_picpath '%s' not found in "
                             "backend; Marking p_w_picpath location deleted in "
                             "db."), p_w_picpath_id)

            if loc_id != '-':
                db_api.get_api().p_w_picpath_location_delete(self.admin_context,
                                                       p_w_picpath_id,
                                                       int(loc_id),
                                                       'deleted')
            LOG.info(_LI("Image %s is scrubbed from a location."), p_w_picpath_id)
        except Exception as e:
            LOG.error(_LE("Unable to scrub p_w_picpath %(id)s from a location. "
                          "Reason: %(exc)s ") %
                      {'id': p_w_picpath_id,
                       'exc': encodeutils.exception_to_unicode(e)})
            raise


# _get_delete_jobs函數(shù),獲取要?jiǎng)h除的鏡像的dict           
    def _get_delete_jobs(self):
        try:
            records = self.db_queue.get_all_locations()  # ScrubDBQueue類的get_all_locations函數(shù)
        except Exception as err:
            LOG.error(_LE("Can not get scrub jobs from queue: %s") %
                      encodeutils.exception_to_unicode(err))
            return {}

        delete_jobs = {}
        for p_w_picpath_id, loc_id, loc_uri in records:
            if p_w_picpath_id not in delete_jobs:
                delete_jobs[p_w_picpath_id] = []
            delete_jobs[p_w_picpath_id].append((p_w_picpath_id, loc_id, loc_uri))
        return delete_jobs
        

# ScrubDBQueue類的get_all_locations函數(shù) 
    def get_all_locations(self):
        """Returns a list of p_w_picpath id and location tuple from scrub queue.

        :returns: a list of p_w_picpath id, location id and uri tuple from
            scrub queue

        """
        ret = []

        for p_w_picpath in self._get_all_p_w_picpaths():
            deleted_at = p_w_picpath.get('deleted_at')
            if not deleted_at:
                continue

            # NOTE: Strip off microseconds which may occur after the last '.,'
            # Example: 2012-07-07T19:14:34.974216
            date_str = deleted_at.rsplit('.', 1)[0].rsplit(',', 1)[0]
            delete_time = calendar.timegm(time.strptime(date_str,
                                                        "%Y-%m-%dT%H:%M:%S"))

            if delete_time + self.scrub_time > time.time():  # 判斷是否到了清除的時(shí)間
                continue

            for loc in p_w_picpath['location_data']:
                if loc['status'] != 'pending_delete':   # 判斷是否是pending_delete狀態(tài)
                    continue

                if self.metadata_encryption_key:        # 判斷鏡像uri是否加密
                    uri = crypt.urlsafe_encrypt(self.metadata_encryption_key,
                                                loc['url'], 64)
                else:
                    uri = loc['url']

                ret.append((p_w_picpath['id'], loc['id'], uri))
        return ret


下面都是關(guān)于glance_store,算是glance的子項(xiàng)目了,專門和后端真正存儲(chǔ)打交道的。

glance_store/__init__.py
from .backend import *  # noqa
from .driver import *  # noqa
from .exceptions import *  # noqa

# 來看store_api.delete_from_backend函數(shù)
glance_store/backend.py
def delete_from_backend(uri, context=None):
    """Removes chunks of data from backend specified by uri."""

    loc = location.get_location_from_uri(uri, conf=CONF)
    store = get_store_from_uri(uri)
    return store.delete(loc, context=context)

# get_store_from_uri函數(shù)
def get_store_from_uri(uri):
    """
    Given a URI, return the store object that would handle
    operations on the URI.

    :param uri: URI to analyze
    """
    scheme = uri[0:uri.find('/') - 1]  # 形如 得到的會(huì)是這樣的file、rbd 
    return get_store_from_scheme(scheme)
    
# get_store_from_scheme函數(shù),從SCHEME_TO_CLS_MAP中獲取對(duì)應(yīng)的schema mapping
def get_store_from_scheme(scheme):
    """
    Given a scheme, return the appropriate store object
    for handling that scheme.
    """
    if scheme not in location.SCHEME_TO_CLS_MAP:
        raise exceptions.UnknownScheme(scheme=scheme)
    scheme_info = location.SCHEME_TO_CLS_MAP[scheme]
    store = scheme_info['store']
    if not store.is_capable(capabilities.BitMasks.DRIVER_REUSABLE):
        # Driver instance isn't stateless so it can't
        # be reused safely and need recreation.
        store_entry = scheme_info['store_entry']
        store = _load_store(store.conf, store_entry, invoke_load=True)
        store.configure()
        try:
            scheme_map = {}
            loc_cls = store.get_store_location_class()
            for scheme in store.get_schemes():
                scheme_map[scheme] = {
                    'store': store,
                    'location_class': loc_cls,
                    'store_entry': store_entry
                }
                location.register_scheme_map(scheme_map)
        except NotImplementedError:
            scheme_info['store'] = store
    return store
    
# 上面配置的stores是rbd,獲得的就是glance_store/_drivers/rbd.py
@capabilities.check
    def delete(self, location, context=None):
        """
        Takes a `glance_store.location.Location` object that indicates
        where to find the p_w_picpath file to delete.

        :param location: `glance_store.location.Location` object, supplied
                  from glance_store.location.get_location_from_uri()

        :raises: NotFound if p_w_picpath does not exist;
                InUseByStore if p_w_picpath is in use or snapshot unprotect failed
        """
        loc = location.store_location
        target_pool = loc.pool or self.pool
        self._delete_p_w_picpath(target_pool, loc.p_w_picpath, loc.snapshot)

# _delete_p_w_picpath函數(shù)        
    def _delete_p_w_picpath(self, target_pool, p_w_picpath_name,
                      snapshot_name=None, context=None):
        """
        Delete RBD p_w_picpath and snapshot.

        :param p_w_picpath_name: Image's name
        :param snapshot_name: Image snapshot's name

        :raises: NotFound if p_w_picpath does not exist;
                InUseByStore if p_w_picpath is in use or snapshot unprotect failed
        """
        with self.get_connection(conffile=self.conf_file,
                                 rados_id=self.user) as conn:
            with conn.open_ioctx(target_pool) as ioctx:
                try:
                    # First remove snapshot.
                    if snapshot_name is not None:
                        with rbd.Image(ioctx, p_w_picpath_name) as p_w_picpath:
                            try:
                                p_w_picpath.unprotect_snap(snapshot_name)
                                p_w_picpath.remove_snap(snapshot_name)
                            except rbd.ImageNotFound as exc:
                                msg = (_("Snap Operating Exception "
                                         "%(snap_exc)s "
                                         "Snapshot does not exist.") %
                                       {'snap_exc': exc})
                                LOG.debug(msg)
                            except rbd.ImageBusy as exc:
                                log_msg = (_LE("Snap Operating Exception "
                                               "%(snap_exc)s "
                                               "Snapshot is in use.") %
                                           {'snap_exc': exc})
                                LOG.error(log_msg)
                                raise exceptions.InUseByStore()

                    # Then delete p_w_picpath.
                    rbd.RBD().remove(ioctx, p_w_picpath_name)
                except rbd.ImageHasSnapshots:
                    log_msg = (_LE("Remove p_w_picpath %(img_name)s failed. "
                                   "It has snapshot(s) left.") %
                               {'img_name': p_w_picpath_name})
                    LOG.error(log_msg)
                    raise exceptions.HasSnapshot()
                except rbd.ImageBusy:
                    log_msg = (_LE("Remove p_w_picpath %(img_name)s failed. "
                                   "It is in use.") %
                               {'img_name': p_w_picpath_name})
                    LOG.error(log_msg)
                    raise exceptions.InUseByStore()
                except rbd.ImageNotFound:
                    msg = _("RBD p_w_picpath %s does not exist") % p_w_picpath_name
                    raise exceptions.NotFound(message=msg)


參考鏈接

eventlet常用函數(shù)介紹 http://www.cnblogs.com/Security-Darren/p/4168233.html


以上過程,理解不對(duì)的地方,還請(qǐng)指正,見諒!



向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI