Openstack修改系統網絡配額

1. 前言
neutron在安裝配置完成以後,openstack爲了實現對全部tenant對網絡資源的使用,針對neutron設置有專門的配額,以防止租戶使用過多的資源,而對其餘的tenant形成影響。和nova的quota相相似,neutron也使用單獨的一個驅動來實現網絡neutron的配額控制。
2. neutron默認的配額
neutron默認的配額針對network,port,router,subnet,floatingip作了配額方面的限定,參考neutron的配置文件,獲取quota的配額內容爲:python

[root@controller ~]# vim /etc/neutron/neutron.conf 
[quotas]
quota_driver = neutron.db.quota_db.DbQuotaDriver        配額驅動
quota_items = network,subnet,port                       quota限定的範疇
default_quota = -1                                      默認的quota,-1表示沒有限制(未啓用)
quota_network = 10                                      創建的network個數
quota_subnet = 10                                       創建的subnet個數
quota_port = 50                                         容許的port個數
quota_security_group = 10                               安全組的個數
quota_security_group_rule = 100                         安全組規規則條數
quota_vip = 10                                          vip個數,如下的quota_member和quota_health_monitors 都用於LBaaS場景
quota_pool = 10                                         pool個數
quota_member = -1                                       member個數
quota_health_monitors = -1                              monitor個數
quota_router = 10                                       router的個數
quota_floatingip = 50                                   floating-ip個數

3. 修改neutron的配額
查看neutron默認的配額mysql

[root@controller ~]# keystone tenant-list
+----------------------------------+----------+---------+
|                id                |   name   | enabled |
+----------------------------------+----------+---------+
| 842ab3268a2c47e6a4b0d8774de805ae |  admin   |   True  |
| 7ff1dfb5a6f349958c3a949248e56236 | companyA |   True  |        #獲得tenant的uuid號
| 10d1465c00d049fab88dec1af0f56b1b |   demo   |   True  |
| 3b57a14f7c354a979c9f62b60f31a331 | service  |   True  |
+----------------------------------+----------+---------+
[root@controller ~]# neutron quota-show --tenant-id 7ff1dfb5a6f349958c3a949248e56236
+---------------------+-------+
| Field               | Value |
+---------------------+-------+
| floatingip          | 50    |
| health_monitor      | -1    |
| member              | -1    |
| network             | 10    |
| pool                | 10    |
| port                | 50    |            #port,每臺虛擬機都須要一個ip,即一個port,很容易就超過配額
| router              | 10    |
| security_group      | 10    |
| security_group_rule | 100   |
| subnet              | 10    |
| vip                 | 10    |
+---------------------+-------+

修改neutron配額sql

[root@controller ~]# neutron quota-update --network 20 --subnet 20 --port 100  --router 5 --floatingip 100  --security-group 10 --security-group-rule 100 --tenant-id 7ff1dfb5a6f349958c3a949248e56236 
+---------------------+-------+
| Field               | Value |
+---------------------+-------+
| floatingip          | 100   |
| health_monitor      | -1    |
| member              | -1    |
| network             | 20    |
| pool                | 10    |
| port                | 100   |
| router              | 5     |
| security_group      | 10    |
| security_group_rule | 100   |
| subnet              | 20    |
| vip                 | 10    |
+---------------------+-------+

校驗neutron的quota配置數據庫

[root@controller ~]# neutron quota-show --tenant-id 7ff1dfb5a6f349958c3a949248e56236 
+---------------------+-------+
| Field               | Value |
+---------------------+-------+
| floatingip          | 100   |
| health_monitor      | -1    |
| member              | -1    |
| network             | 20    |
| pool                | 10    |
| port                | 100   |
| router              | 5     |
| security_group      | 10    |
| security_group_rule | 100   |
| subnet              | 20    |
| vip                 | 10    |
+---------------------+-------+

4.統計port的個數vim

[root@controller ~]# neutron port-list
+--------------------------------------+------+-------------------+---------------------------------------------------------------------------------------+
| id                                   | name | mac_address       | fixed_ips                                                                             |
+--------------------------------------+------+-------------------+---------------------------------------------------------------------------------------+
| 0060ec4a-957d-4571-b730-6b4a9bb3baf8 |      | fa:16:3e:48:42:3d | {"subnet_id": "9654a807-d4fa-49f1-abb6-2e45d776c69f", "ip_address": "10.16.4.19"}     |
| 00942be0-a3a9-471d-a4ba-336db0ee1539 |      | fa:16:3e:73:75:03 | {"subnet_id": "ad4a5ffc-3ccc-42c4-89a1-61e7b18632a3", "ip_address": "10.16.6.96"}     |
| 0119045c-8219-4744-bd58-a7e77294832c |      | fa:16:3e:10:ed:7f | {"subnet_id": "9654a807-d4fa-49f1-abb6-2e45d776c69f", "ip_address": "10.16.4.71"}     |
| 04f7d8ea-1849-4938-9ef7-e8114893132f |      | fa:16:3e:50:86:1b | {"subnet_id": "ad4a5ffc-3ccc-42c4-89a1-61e7b18632a3", "ip_address": "10.16.6.27"}     |

[root@controller ~]# neutron port-list |wc -l            #超過配額時,須要修改
194

5. 總結
隨着時間的推移,當愈來愈多的instance加入到openstack中,port也會相應增長,一個ip對應一個port,因此當port達到配額時,openstack會組織用戶繼續分配虛擬機,此時,就須要修改neutron的配額了,關於neutron配額的報錯,能夠參考neutron的日誌/var/log/neutron/neutron-server.log,能夠根據日誌的信息,定位到報錯的緣由,具體不贅述。
6. 附錄
neutron實現quota的代碼解讀安全

[root@controller ~]# vim /usr/lib/python2.6/site-packages/neutron/db/quota_db.py

import sqlalchemy as sa

from neutron.common import exceptions
from neutron.db import model_base
from neutron.db import models_v2

'''
quota數據庫表的表結構,tenant默認集成的配額從這裏獲取
mysql> desc quotas;
+-----------+--------------+------+-----+---------+-------+
| Field     | Type         | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| id        | varchar(36)  | NO   | PRI | NULL    |       |
| tenant_id | varchar(255) | YES  | MUL | NULL    |       |
| resource  | varchar(255) | YES  |     | NULL    |       |
| limit     | int(11)      | YES  |     | NULL    |       |
+-----------+--------------+------+-----+---------+-------+
'''
class Quota(model_base.BASEV2, models_v2.HasId):
    """Represent a single quota override for a tenant.

    If there is no row for a given tenant id and resource, then the
    default for the quota class is used.
    """
    tenant_id = sa.Column(sa.String(255), index=True)
    resource = sa.Column(sa.String(255))
    limit = sa.Column(sa.Integer)

'''
quota配額的具體實現,根據數據庫的配置內容,實現quota的控制,即quota的增刪改查方法
'''
class DbQuotaDriver(object):
    """Driver to perform necessary checks to enforce quotas and obtain quota
    information.

    The default driver utilizes the local database.
    """

    '''
    獲得租戶tenant的quota,執行neutron quota-show --tenant-id uuid時調用的方法
    '''
    @staticmethod
    def get_tenant_quotas(context, resources, tenant_id):
        """Given a list of resources, retrieve the quotas for the given
        tenant.

        :param context: The request context, for access checks.
        :param resources: A dictionary of the registered resource keys.
        :param tenant_id: The ID of the tenant to return quotas for.
        :return dict: from resource name to dict of name and limit
        """

        # init with defaults    獲得quota默認的配額項item,即所謂的network,subnet,port和router等,以及對應的值
        tenant_quota = dict((key, resource.default)
                            for key, resource in resources.items())

        # update with tenant specific limits    從數據庫中獲取最新的quota配置信息,並更新
        q_qry = context.session.query(Quota).filter_by(tenant_id=tenant_id)
        tenant_quota.update((q['resource'], q['limit']) for q in q_qry)

        return tenant_quota

    '''
    quota的刪除,即執行neutron quota-delete 的方法,刪除以後,tenant將會集成默認的的quota配置
    '''
    @staticmethod
    def delete_tenant_quota(context, tenant_id):
        """Delete the quota entries for a given tenant_id.

        Atfer deletion, this tenant will use default quota values in conf.
        """
        #從neutron。quotas數據庫中查詢到全部的quota配置以後,過略某個具體的tenant的quota,以後執行delete()方法將其刪除
        with context.session.begin():
            tenant_quotas = context.session.query(Quota)
            tenant_quotas = tenant_quotas.filter_by(tenant_id=tenant_id)
            tenant_quotas.delete()

    '''
    獲得全部租戶tenant的配額資源,即執行neutron quota-list所查看的內容
    '''
    @staticmethod
    def get_all_quotas(context, resources):
        """Given a list of resources, retrieve the quotas for the all tenants.

        :param context: The request context, for access checks.
        :param resources: A dictionary of the registered resource keys.
        :return quotas: list of dict of tenant_id:, resourcekey1:
        resourcekey2: ...
        """
        tenant_default = dict((key, resource.default)
                              for key, resource in resources.items())

        all_tenant_quotas = {}

        for quota in context.session.query(Quota):
            tenant_id = quota['tenant_id']

            # avoid setdefault() because only want to copy when actually req'd
            #若是quotas表中,沒有找到配置選項,說明使用默認的quota配置,直接用默認的copy過來便可,有配置則繼承quotas表中的配置
            tenant_quota = all_tenant_quotas.get(tenant_id)
            if tenant_quota is None:
                tenant_quota = tenant_default.copy()
                tenant_quota['tenant_id'] = tenant_id
                all_tenant_quotas[tenant_id] = tenant_quota

            tenant_quota[quota['resource']] = quota['limit']

        return all_tenant_quotas.values()

    '''
                更新quota的配置,即執行neutron quota-update命令的具體實現
    '''
    @staticmethod
    def update_quota_limit(context, tenant_id, resource, limit):
        with context.session.begin():
            tenant_quota = context.session.query(Quota).filter_by(
                tenant_id=tenant_id, resource=resource).first()

            #有配置內容,則更新,沒有則根據資源的配置內容,在數據庫中添加對應的條目
            if tenant_quota:
                tenant_quota.update({'limit': limit})
            else:
                tenant_quota = Quota(tenant_id=tenant_id,
                                     resource=resource,
                                     limit=limit)
                context.session.add(tenant_quota)

    def _get_quotas(self, context, tenant_id, resources, keys):
        """Retrieves the quotas for specific resources.

        A helper method which retrieves the quotas for the specific
        resources identified by keys, and which apply to the current
        context.

        :param context: The request context, for access checks.
        :param tenant_id: the tenant_id to check quota.
        :param resources: A dictionary of the registered resources.
        :param keys: A list of the desired quotas to retrieve.

        """
        desired = set(keys)
        sub_resources = dict((k, v) for k, v in resources.items()
                             if k in desired)

        # Make sure we accounted for all of them...
        if len(keys) != len(sub_resources):
            unknown = desired - set(sub_resources.keys())
            raise exceptions.QuotaResourceUnknown(unknown=sorted(unknown))

        # Grab and return the quotas (without usages)
        quotas = DbQuotaDriver.get_tenant_quotas(
            context, sub_resources, tenant_id)

        return dict((k, v) for k, v in quotas.items())

    '''
    neutron quota的校驗,即在執行過程當中,調用該方法,確認tenant的quota是否在合理的範圍內
    '''
    def limit_check(self, context, tenant_id, resources, values):
        """Check simple quota limits.

        For limits--those quotas for which there is no usage
        synchronization function--this method checks that a set of
        proposed values are permitted by the limit restriction.

        This method will raise a QuotaResourceUnknown exception if a
        given resource is unknown or if it is not a simple limit
        resource.

        If any of the proposed values is over the defined quota, an
        OverQuota exception will be raised with the sorted list of the
        resources which are too high.  Otherwise, the method returns
        nothing.

        :param context: The request context, for access checks.
        :param tenant_id: The tenant_id to check the quota.
        :param resources: A dictionary of the registered resources.
        :param values: A dictionary of the values to check against the
                       quota.
        """

        # Ensure no value is less than zero    quota的配置值不能爲負數
        unders = [key for key, val in values.items() if val < 0]
        if unders:
            raise exceptions.InvalidQuotaValue(unders=sorted(unders))

        # Get the applicable quotas
        quotas = self._get_quotas(context, tenant_id, resources, values.keys())

        # Check the quotas and construct a list of the resources that
        # would be put over limit by the desired values
        overs = [key for key, val in values.items()
                 if quotas[key] >= 0 and quotas[key] < val]
        if overs:
            raise exceptions.OverQuota(overs=sorted(overs))