(3)理解neutron ml2---subnet创建流程源码解析

subnet是neutron的核心资源之一,subnet是在network的基础之上构建起来。
有了子网和路由器之后,不同的虚拟机之间才有了三层通信的功能。属于不同子网
的虚拟机通过路由器才能实现相互之间的访问
下面看一下子网创建的主要流程:
/neutron/plugins/ml2/plugin.py
@utils.transaction_guard
@db_api.retry_if_session_inactive()
def create_subnet(self, context, subnet):
    result, mech_context = self._create_subnet_db(context, subnet)
    kwargs = {'context': context, 'subnet': result}
    registry.notify(resources.SUBNET, events.AFTER_CREATE, self, **kwargs)
    try:
        self.mechanism_manager.create_subnet_postcommit(mech_context)
    except ml2_exc.MechanismDriverError:
        with excutils.save_and_reraise_exception():
            LOG.error(_LE("mechanism_manager.create_subnet_postcommit "
                          "failed, deleting subnet '%s'"), result['id'])
            self.delete_subnet(context, result['id'])
    return result
self._create_subnet_db(context, subnet)
这句话从名称来看是用来将对应的数据存储到数据库中,但是实际上这个函数还做了很多
与将subnet存储到数据库无关的事情。
/neutron/plugins/ml2/plugin.py
def _create_subnet_db(self, context, subnet):
    session = context.session
    # FIXME(kevinbenton): this is a mess because create_subnet ends up
    # calling _update_router_gw_ports which ends up calling update_port
    # on a router port inside this transaction. Need to find a way to
    # separate router updates from the subnet update operation.
    setattr(context, 'GUARD_TRANSACTION', False)
    with session.begin(subtransactions=True):
        result = super(Ml2Plugin, self).create_subnet(context, subnet)
        self.extension_manager.process_create_subnet(
            context, subnet[attributes.SUBNET], result)
        network = self.get_network(context, result['network_id'])
        mech_context = driver_context.SubnetContext(self, context,
                                                    result, network)
        self.mechanism_manager.create_subnet_precommit(mech_context)
上面这段代码最主要的是:
result = super(Ml2Plugin, self).create_subnet(context, subnet)这句话调用了父类的create_subnet方法,之所以不直接使用
Self.create_subnet方法,估计是因为ML2Plugin本身实现了一个create_subnet()因此这里想调用父类的方法需要使用这种方式。
这里的父级类指的是
db_base_plugin_v2.NeutronDbPluginV2
class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
                dvr_mac_db.DVRDbMixin,
                external_net_db.External_net_db_mixin,
                sg_db_rpc.SecurityGroupServerRpcMixin,
                agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
                addr_pair_db.AllowedAddressPairsMixin,
                vlantransparent_db.Vlantransparent_db_mixin,
                extradhcpopt_db.ExtraDhcpOptMixin,
                address_scope_db.AddressScopeDbMixin,
                service_type_db.SubnetServiceTypeMixin)
我们来看一下这个db_base_plugin_v2.NeutronDbPluginV2, 这个函数封装了很多ml2对应的方法,这些方法都是
与数据库的交互, 比如在创建network的时候,使用到了如下的函数。
/neutron/db/db_base_plugin_v2.py
@db_api.retry_if_session_inactive()
def create_network(self, context, network):
    """Handle creation of a single network."""
    net_db = self.create_network_db(context, network)
    return self._make_network_dict(net_db, process_extensions=False,
                                   context=context)

def create_network_db(self, context, network):
    # single request processing
    n = network['network']
    # NOTE(jkoelker) Get the tenant_id outside of the session to avoid
    #                unneeded db action if the operation raises
    tenant_id = n['tenant_id']
    with context.session.begin(subtransactions=True):
        args = {'tenant_id': tenant_id,
                'id': n.get('id') or uuidutils.generate_uuid(),
                'name': n['name'],
                'admin_state_up': n['admin_state_up'],
                'status': n.get('status', constants.NET_STATUS_ACTIVE),
                'description': n.get('description')}
        network = models_v2.Network(**args)
        if n['shared']:
            entry = rbac_db.NetworkRBAC(
                network=network, action='access_as_shared',
                target_tenant='*', tenant_id=network['tenant_id'])
            context.session.add(entry)
        context.session.add(network)
    return network

但是在create_subnet函数里还有很多其他的操作。我们来详细看一下。

/neutron/db/db_base_plugin_v2.py

def create_subnet(self, context, subnet):

    s = subnet['subnet']
    cidr = s.get('cidr', constants.ATTR_NOT_SPECIFIED)
    prefixlen = s.get('prefixlen', constants.ATTR_NOT_SPECIFIED)
    has_cidr = validators.is_attr_set(cidr)
    has_prefixlen = validators.is_attr_set(prefixlen)
    
    # 这里判断subnet中是否设置了has_cidr以及has_prefixlen等参数,如果
    # 没有设置,这里就会报错。可以学习这里对于参数是否设置的判断。需要有一个validators这种函数
    # 错误的返回信息一般是bacRequest,说明参数设置的有问题。badRequest,这个参数是在异常里定义的。
    if has_cidr and has_prefixlen:
        msg = _('cidr and prefixlen must not be supplied together')
        raise exc.BadRequest(resource='subnets', msg=msg)

    if has_cidr:
        # turn the CIDR into a proper subnet
        net = netaddr.IPNetwork(s['cidr'])
        subnet['subnet']['cidr'] = '%s/%s' % (net.network, net.prefixlen)

    subnetpool_id = self._get_subnetpool_id(context, s)
    if not subnetpool_id and not has_cidr:
        msg = _('a subnetpool must be specified in the absence of a cidr')
        raise exc.BadRequest(resource='subnets', msg=msg)

    if subnetpool_id:
        self.ipam.validate_pools_with_subnetpool(s)
        if subnetpool_id == constants.IPV6_PD_POOL_ID:
            if has_cidr:
                # We do not currently support requesting a specific
                # cidr with IPv6 prefix delegation. Set the subnetpool_id
                # to None and allow the request to continue as normal.
                subnetpool_id = None
                self._validate_subnet(context, s)
            else:
                prefix = n_const.PROVISIONAL_IPV6_PD_PREFIX
                subnet['subnet']['cidr'] = prefix
                self._validate_subnet_for_pd(s)
    else:
        if not has_cidr:
            msg = _('A cidr must be specified in the absence of a '
                    'subnet pool')
            raise exc.BadRequest(resource='subnets', msg=msg)
        self._validate_subnet(context, s)
    # 这一步是最重要的,主要目的是创建子网,所需的参数是子网以及subnetpool_id,这里
    # 有一个context模型,主要是创建子网的上下文。
    return self._create_subnet(context, subnet, subnetpool_id)
上面的这些操作其实主要是关联子网的资源池,也就是创建子网网段。subnetpool是在K版本添加进来,用于子网资源池管理
的数据模型,这个数据模型需要和子网绑定在一起。但也可以不使用这个参数,直接指定cidr。
 
上面的函数最重要的是最后一步:
/neutron/db/db_base_plugin_v2.py
self._create_subnet(context, subnet, subnetpool_id)
def _create_subnet(self, context, subnet, subnetpool_id):
    s = subnet['subnet']

    with context.session.begin(subtransactions=True):
        network = self._get_network(context, s["network_id"])
        subnet, ipam_subnet = self.ipam.allocate_subnet(context,
                                                        network,
                                                        s,
                                                        subnetpool_id)
    if hasattr(network, 'external') and network.external:
        self._update_router_gw_ports(context,
                                     network,
                                     subnet)
    # If this subnet supports auto-addressing, then update any
    # internal ports on the network with addresses for this subnet.
    if ipv6_utils.is_auto_address_subnet(subnet):
        updated_ports = self.ipam.add_auto_addrs_on_network_ports(context,
                            subnet, ipam_subnet)
        for port_id in updated_ports:
            port_info = {'port': {'id': port_id}}
            self.update_port(context, port_id, port_info)

    return self._make_subnet_dict(subnet, context=context)

最后来看一下这个ipam函数的allocate_subnet函数的具体实现:

/neutron/db/ipam_pluggable_backend.py

def allocate_subnet(self, context, network, subnet, subnetpool_id):
    subnetpool = None

    if subnetpool_id and not subnetpool_id == constants.IPV6_PD_POOL_ID:
        subnetpool = self._get_subnetpool(context, id=subnetpool_id)
        self._validate_ip_version_with_subnetpool(subnet, subnetpool)

    # gateway_ip and allocation pools should be validated or generated
    # only for specific request
    if subnet['cidr'] is not constants.ATTR_NOT_SPECIFIED:
        subnet['gateway_ip'] = self._gateway_ip_str(subnet,
                                                    subnet['cidr'])
        subnet['allocation_pools'] = self._prepare_allocation_pools(
            subnet['allocation_pools'],
            subnet['cidr'],
            subnet['gateway_ip'])

    ipam_driver = driver.Pool.get_instance(subnetpool, context)
    subnet_factory = ipam_driver.get_subnet_request_factory()
    subnet_request = subnet_factory.get_request(context, subnet,
                                                subnetpool)
    ipam_subnet = ipam_driver.allocate_subnet(subnet_request)
    # get updated details with actually allocated subnet
    subnet_request = ipam_subnet.get_details()

    try:
        subnet = self._save_subnet(context,
                                   network,
                                   self._make_subnet_args(
                                       subnet_request,
                                       subnet,
                                       subnetpool_id),
                                   subnet['dns_nameservers'],
                                   subnet['host_routes'],
                                   subnet_request)
    except Exception:
        # Note(pbondar): Third-party ipam servers can't rely
        # on transaction rollback, so explicit rollback call needed.
        # IPAM part rolled back in exception handling
        # and subnet part is rolled back by transaction rollback.
        with excutils.save_and_reraise_exception():
            if not ipam_driver.needs_rollback():
                return

            LOG.debug("An exception occurred during subnet creation. "
                      "Reverting subnet allocation.")
            self._safe_rollback(self.delete_subnet,
                                context,
                                subnet_request.subnet_id)
    return subnet, ipam_subnet

最后的最后我们看一下最后这个保存子网的函数

/neutron/db/ipam_pluggable_backend.py

def _save_subnet(self, context,
                 network,
                 subnet_args,
                 dns_nameservers,
                 host_routes,
                 subnet_request):
    self._validate_subnet_cidr(context, network, subnet_args['cidr'])
    self._validate_network_subnetpools(network,
                                       subnet_args['subnetpool_id'],
                                       subnet_args['ip_version'])

    service_types = subnet_args.pop('service_types', [])

    subnet = models_v2.Subnet(**subnet_args)
    segment_id = subnet_args.get('segment_id')
    try:
        # 这里将subnet存在数据库里,session这个东西其实是
        #  db_api.get_session(), 所以这个东西是db的session
       # 可以直接调用并存储相应的数据。
        context.session.add(subnet)
        context.session.flush()
    except db_exc.DBReferenceError:
        raise segment_exc.SegmentNotFound(segment_id=segment_id)
    self._validate_segment(context, network['id'], segment_id)

    # NOTE(changzhi) Store DNS nameservers with order into DB one
    # by one when create subnet with DNS nameservers
    # 如果这里指定了dnsname_server,则表明该subnet内部的所有dns解析都是通过
    # 这个dns_nameserver
    if validators.is_attr_set(dns_nameservers):
        for order, server in enumerate(dns_nameservers):
            dns = subnet_obj.DNSNameServer(context,
                                           address=server,
                                           order=order,
                                           subnet_id=subnet.id)
            dns.create()
    
    # 判断是不是设置了host_routes,如果设置了就存储在相应的表里
    # 这里需要注意,如果子网设置了下一跳,则会自动为该子网创建一个路由器。
    # 通过这个路由器进行转发。
    if validators.is_attr_set(host_routes):
        for rt in host_routes:
            route = models_v2.SubnetRoute(
                subnet_id=subnet.id,
                destination=rt['destination'],
                nexthop=rt['nexthop'])
            context.session.add(route)
    
    # 判断是不是设置了servicetype,如果设置了就存在表里
    if validators.is_attr_set(service_types):
        for service_type in service_types:
            service_type_entry = sst_model.SubnetServiceType(
                subnet_id=subnet.id,
                service_type=service_type)
            context.session.add(service_type_entry)
    # 最终保存在个allocation_pools
    self.save_allocation_pools(context, subnet,
                               subnet_request.allocation_pools)

    return subnet
上面就是最终保存subnet的地方,子网设计到的表相对来说比较多,有subnetroute, subnetpool, route等。
因此在保存的时候也要将对应的数据保存在相应的表内部。这里采取的机制是使用validators来判断对应的属性是不是
设置了,如果设置则作对应的操作。
 
还有一点值得注意的是namserver, nameserver的配置方法有很多种
     1)同一个子网内使用同一个nameserver
     2) 所有的虚拟机使用同一个nameserver
  1. 使用主机的nameserver
 
总结来说,neutron内部创建子网的代码主要是将对应的数据存储在数据库内部。如果在创建的时候没有分配
子网网段,则为这个子网创建一个子网的网段。
 
 
 

转载于:https://www.cnblogs.com/zhouqi0505/p/10531464.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值