(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
- 使用主机的nameserver
总结来说,neutron内部创建子网的代码主要是将对应的数据存储在数据库内部。如果在创建的时候没有分配
子网网段,则为这个子网创建一个子网的网段。