“Cinder show 不存在的id,查詢時(shí)間很長(zhǎng)”分析

問題: 使用“cinder show”查詢一個(gè)不存在的volumeid吸祟,使用時(shí)間很長(zhǎng)瑟慈。

[root@node1 ~]# time cinder show aksjdfkajsdkfjsakdf
ERROR: No volume with a name or ID of 'aksjdfkajsdkfjsakdf' exists.
real    0m35.374s
user    0m0.855s
sys     0m0.107s

分析:

1. cinderclient.v2.shell.do_show說(shuō)明

指令“cinder show”的入口函數(shù)是cinderclient.v2.shell.do_show

@utils.arg('volume',
           metavar='<volume>',
           help='Name or ID of volume.')
def do_show(cs, args):
    """Shows volume details."""
    info = dict()
    volume = utils.find_volume(cs, args.volume)
    info.update(volume._info)

    if 'readonly' in info['metadata']:
        info['readonly'] = info['metadata']['readonly']

info.pop('links', None)
# 打印字典
    utils.print_dict(info,
                     formatters=['metadata', 'volume_image_metadata',
                                 'attachments'])

@utils.arg 是用于定義參數(shù)的標(biāo)簽桃移。

指令參數(shù)根據(jù)必填、選填分為兩種:

  • 1)@utils.arg('volume', metavar='<volume>', help='Name or ID of volume.')
    必填參數(shù)葛碧,使用時(shí)不用帶參數(shù)名借杰。例如”cinder show 123123”,這里的123123就是<volume>进泼。
  • 2)@utils.arg('--tenant', type=str, dest='tenant', nargs='?', metavar='<tenant>', help='Display information from single tenant (Admin only).')
    選填參數(shù)蔗衡,名字多了個(gè)破折號(hào) --,指令使用時(shí)必須帶參數(shù)名乳绕。例如”cinder list --tenant 123123”绞惦。

根據(jù)參數(shù)值分類:

  • 1)@utils.arg('volume', metavar='<volume>', nargs='+', help='Name or ID of volume or volumes to delete.')
    如果設(shè)置nargs='+',代表值為數(shù)組型洋措,以空格分隔济蝉,輸入1個(gè)以上。如”cinder delete 111 2222 3333”呻纹,刪除volume_id=111堆生、volume_id=222、volume_id=333 的volume雷酪。
    此外還有nargs='*' 表示參數(shù)可設(shè)置零個(gè)或多個(gè)淑仆;nargs=' '+' 表示參數(shù)可設(shè)置一個(gè)或多個(gè);nargs='?' 表示參數(shù)可設(shè)置零個(gè)或一個(gè)哥力。
  • 2)@utils.arg('--metadata', type=str, nargs='*', metavar='<key=value>', default=None, help='Snapshot metadata key and value pairs. Default=None.')
    設(shè)置metavar='<key=value>'蔗怠,值為鍵值對(duì)形式。比如”cinder storage-create --metadata storage_protocol='fc' storage_ip='172.24.1.21' ”

@utils.arg定義的參數(shù)會(huì)傳入def do_show(cs, args)的args吩跋,通過(guò)args.xxxx來(lái)調(diào)用寞射。

2.utils.find_volume(cs, args.volume) 說(shuō)明

cinderclient.utils.find_volume:
def find_volume(cs, volume):
    """Get a volume by name or ID."""
return find_resource(cs.volumes, volume)
def find_resource(manager, name_or_id):
    """Helper for the _find_* methods."""
    # first try to get entity as integer id
    try:
        if isinstance(name_or_id, int) or name_or_id.isdigit():
            return manager.get(int(name_or_id))
    except exceptions.NotFound:
        pass
    else:
        # now try to get entity as uuid
        try:
            uuid.UUID(name_or_id)
            return manager.get(name_or_id)
        except (ValueError, exceptions.NotFound):
            pass
    if sys.version_info <= (3, 0):
        name_or_id = encodeutils.safe_decode(name_or_id)
    try:
        try:
            resource = getattr(manager, 'resource_class', None)
            name_attr = resource.NAME_ATTR if resource else 'name'
            return manager.find(**{name_attr: name_or_id})
        except exceptions.NotFound:
            pass
        # finally try to find entity by human_id
        try:
            return manager.find(human_id=name_or_id)
        except exceptions.NotFound:
            msg = "No %s with a name or ID of '%s' exists." % \
                (manager.resource_class.__name__.lower(), name_or_id)
            raise exceptions.CommandError(msg)
    except exceptions.NoUniqueMatch:
        msg = ("Multiple %s matches found for '%s', use an ID to be more"
               " specific." % (manager.resource_class.__name__.lower(),
                               name_or_id))
        raise exceptions.CommandError(msg)

調(diào)用了cinderclient.utils.find_resource(manager, name_or_id)方法,find_resource的邏輯是:
① 如果id是int型锌钮,直接調(diào)用VolumeManager.get桥温。如果沒查到,走下一步梁丘。
② id轉(zhuǎn)換成uuid侵浸,直接調(diào)用VolumeManager.get。如果沒查到氛谜,走下一步掏觉。
③ 如果volumes類定義了NAME_ATTR屬性,用{name_attr: name_or_id}作為條件調(diào)用VolumeManager.find查詢值漫。如果沒查到澳腹,走下一步。
④ 用{human_id:name_or_id}作為條件調(diào)用VolumeManager.find查詢。

3.VolumeManager.get說(shuō)明

cinderclient.v2.volumes.VolumeManager#get
    def get(self, volume_id):
        """Get a volume.
        :param volume_id: The ID of the volume to get.
        :rtype: :class:`Volume`
        """
        return self._get("/volumes/%s" % volume_id, "volume")

self._get調(diào)用cinderclient.base.Manager#_get
    def _get(self, url, response_key=None):
        resp, body = self.api.client.get(url)
        if response_key:
            return self.resource_class(self, body[response_key], loaded=True,
                                       resp=resp)
        else:
            return self.resource_class(self, body, loaded=True, resp=resp)

self.resource_class在cinderclient.v2.volumes.VolumeManager定義:resource_class = Volume酱塔。所以返回值是body[response_key]轉(zhuǎn)換出的cinderclient.v2.volumes.Volume(base.Resource)對(duì)象沥邻。

4.VolumeManager.find說(shuō)明

VolumeManager.find調(diào)用cinderclient.base.ManagerWithFind#findall(self, **kwargs):

    def find(self, **kwargs):
        """
        Find a single item with attributes matching ``**kwargs``.
        This isn't very efficient for search options which require the
        Python side filtering(e.g. 'human_id')
        """
        matches = self.findall(**kwargs)
        num_matches = len(matches)
        if num_matches == 0:
            msg = "No %s matching %s." % (self.resource_class.__name__, kwargs)
            raise exceptions.NotFound(404, msg)
        elif num_matches > 1:
            raise exceptions.NoUniqueMatch
        else:
            matches[0].append_request_ids(matches.request_ids)
            return matches[0]
    def findall(self, **kwargs):
        """
        Find all items with attributes matching ``**kwargs``.
        This isn't very efficient for search options which require the
        Python side filtering(e.g. 'human_id')
        """
        # Want to search for all tenants here so that when attempting to delete
        # that a user like admin doesn't get a failure when trying to delete
        # another tenant's volume by name.
        search_opts = {'all_tenants': 1}
        # Pass 'name' or 'display_name' search_opts to server filtering to
        # increase search performance.
        if 'name' in kwargs:
            search_opts['name'] = kwargs['name']
        elif 'display_name' in kwargs:
            search_opts['display_name'] = kwargs['display_name']
        found = common_base.ListWithMeta([], None)
        searches = kwargs.items()
        listing = self.list(search_opts=search_opts)
        found.append_request_ids(listing.request_ids)
        # Not all resources attributes support filters on server side
        # (e.g. 'human_id' doesn't), so when doing findall some client
        # side filtering is still needed.
        for obj in listing:
            try:
                if all(getattr(obj, attr) == value
                       for (attr, value) in searches):
                    found.append(obj)
            except AttributeError:
                continue
        return found

findall的邏輯是:

① 檢查kwargs字典key有無(wú)'name',有則加入查詢條件search_opts羊娃。
② 檢查kwargs字典key有無(wú)'display_name'谋国,有則加入查詢條件search_opts。
③ self.list(search_opts=search_opts) 用查詢條件查詢VolumeManager.list迁沫。如果前兩步都為否芦瘾,則查詢條件為空,那么list方法將查詢所有卷信息返回集畅。cinder的db api有配置單次查詢最大條數(shù)是1000(配置項(xiàng)osapi_max_limit近弟,默認(rèn)1000),而我們的卷目前有8k多條挺智,其中2528條可用的祷愉,所以得查三次才能查出所有卷記錄。這占用時(shí)間很長(zhǎng)赦颇。
④ 對(duì)第三步查出的卷列表做遍歷二鳄,用kwargs字典的key和value檢查,符合getattr(obj, attr) == value的記錄添加進(jìn)found數(shù)組返回媒怯。

5.human_id說(shuō)明

上文介紹cinderclient.utils.find_volume方法里用{human_id:name_or_id}作為條件調(diào)用VolumeManager.find查詢订讼。human_id是什么?我們可以看到cinderclient.v3.volumes.Volume繼承自cinderclient.apiclient.base.Resource扇苞,而Resource類里定義了HUMAN_ID = FalseNAME_ATTR = 'name'欺殿,Resource類里還有一個(gè)human_id(self)屬性方法。

cinderclient.apiclient.base.Resource

class Resource(RequestIdMixin):
    """Base class for OpenStack resources (tenant, user, etc.).
    This is pretty much just a bag for attributes.
    """
    HUMAN_ID = False
    NAME_ATTR = 'name'
    
    @property
    def human_id(self):
        """Human-readable ID which can be used for bash completion.
        """
        if self.NAME_ATTR in self.__dict__ and self.HUMAN_ID:
            return strutils.to_slug(getattr(self, self.NAME_ATTR))
        return None

根據(jù)human_id(self)屬性方法的邏輯鳖敷,我們可以這么理解:如果在cinderclient.v3.volumes.Volume類里有定義HUMAN_ID = True脖苏,且定義了類的名字屬性NAME_ATTR ,那么volume.human_id=strutils.to_slug(getattr(self, self.NAME_ATTR))定踱。strutils.to_slug這個(gè)方法的作用是把參數(shù)轉(zhuǎn)換成小寫棍潘、移除non-word的字符(比如中文)、空格轉(zhuǎn)連字符(-)崖媚、刪除前后端空格亦歉。

那么現(xiàn)在我們基本明白human_id的作用了:

human_id是用于查詢字符格式不標(biāo)準(zhǔn)的NAME_ATTR 的記錄。db里可能存的NAME_ATTR包含了非不正常的字符至扰,比如中文啊鳍徽、多出的空格啊资锰,cinderclient指令無(wú)法傳遞正確編碼的參數(shù)到服務(wù)端查詢敢课。所以通過(guò)volume.human_id=strutils.to_slug(getattr(self, self.NAME_ATTR))方法設(shè)置human_id的值為NAME_ATTR(即name)轉(zhuǎn)換格式后的值,這樣對(duì)human_id來(lái)查詢,就能查到了直秆。比如下面的例子:
數(shù)據(jù)庫(kù)里有個(gè)volume的display_name= miaomiao喵喵貓濒募,


image.png

但是在linux顯示亂碼:


image.png

如果直接使用” cinder show miaomiao喵喵貓 ” 來(lái)查詢,會(huì)提示錯(cuò)誤 “ERROR: 'utf8' codec can't decode byte 0xdf in position 8: invalid continuation byte”圾结,無(wú)法使用name來(lái)查詢:


image.png

但只要我們?cè)赾inderclient.v3.volumes.Volume類里設(shè)置HUMAN_ID=True瑰剃,strutils.to_slug(u'miaomiao喵喵貓') == 'miaomiao',即可使用`”cinder show miaomiao”查到結(jié)果。

image.png

但是我們看cinderclient.v3.volumes.Volume沒有復(fù)寫HUMAN_ID,根據(jù)屬性方法 human_id(self)的邏輯署拟,如果self.HUMAN_ID==False丧枪,返回None作為volume的human_id屬性值。所以够掠,如果用{human_id:name_or_id}作為條件調(diào)用VolumeManager.find查詢,getattr(obj, attr) == value是一定為False,find結(jié)果一定為空宝磨。

既然cinderclient.v3.volumes.Volume的human_id=None,為什么utils.find_resource(manager, name_or_id, **kwargs) 最后一步還要用human_id查詢呢盅安?用human_id查詢是不是有什么特別的用途唤锉?
我用HUMAN_ID搜索cinderclient整個(gè)項(xiàng)目,發(fā)現(xiàn)只有cinderclient.apiclient.base.Resource里有用到别瞭,其它Resource的子類都沒有復(fù)寫窿祥。

image.png

用human_id搜索cinderclient整個(gè)項(xiàng)目,也沒用對(duì)human_id特別賦值的地方蝙寨。


image.png

這樣看來(lái)壁肋,HUMAN_ID在cinderclient整個(gè)項(xiàng)目里的調(diào)用值都為False,human_id將始終未None籽慢,用{human_id:name_or_id}作為條件調(diào)用VolumeManager.find查詢?cè)诋?dāng)前的cinderclient里實(shí)在毫無(wú)意義浸遗。

解決方案:

可以考慮把{human_id:name_or_id}查詢屏蔽。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末箱亿,一起剝皮案震驚了整個(gè)濱河市跛锌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌届惋,老刑警劉巖髓帽,帶你破解...
    沈念sama閱讀 212,816評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異脑豹,居然都是意外死亡郑藏,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門瘩欺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)必盖,“玉大人拌牲,你說(shuō)我怎么就攤上這事「柚啵” “怎么了塌忽?”我有些...
    開封第一講書人閱讀 158,300評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)失驶。 經(jīng)常有香客問我土居,道長(zhǎng),這世上最難降的妖魔是什么嬉探? 我笑而不...
    開封第一講書人閱讀 56,780評(píng)論 1 285
  • 正文 為了忘掉前任擦耀,我火速辦了婚禮,結(jié)果婚禮上涩堤,老公的妹妹穿的比我還像新娘埂奈。我一直安慰自己,他們只是感情好定躏,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評(píng)論 6 385
  • 文/花漫 我一把揭開白布账磺。 她就那樣靜靜地躺著,像睡著了一般痊远。 火紅的嫁衣襯著肌膚如雪垮抗。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,084評(píng)論 1 291
  • 那天碧聪,我揣著相機(jī)與錄音冒版,去河邊找鬼。 笑死逞姿,一個(gè)胖子當(dāng)著我的面吹牛辞嗡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播滞造,決...
    沈念sama閱讀 39,151評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼续室,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了谒养?” 一聲冷哼從身側(cè)響起挺狰,我...
    開封第一講書人閱讀 37,912評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎买窟,沒想到半個(gè)月后丰泊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,355評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡始绍,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評(píng)論 2 327
  • 正文 我和宋清朗相戀三年瞳购,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片亏推。...
    茶點(diǎn)故事閱讀 38,809評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡学赛,死狀恐怖年堆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情罢屈,我是刑警寧澤,帶...
    沈念sama閱讀 34,504評(píng)論 4 334
  • 正文 年R本政府宣布篇亭,位于F島的核電站缠捌,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏译蒂。R本人自食惡果不足惜曼月,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望柔昼。 院中可真熱鬧哑芹,春花似錦、人聲如沸捕透。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)乙嘀。三九已至末购,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間虎谢,已是汗流浹背盟榴。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留婴噩,地道東北人擎场。 一個(gè)月前我還...
    沈念sama閱讀 46,628評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像几莽,于是被迫代替她去往敵國(guó)和親迅办。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評(píng)論 2 351