@MatheMatrix
2015-01-14T16:35:38.000000Z
字数 14068
阅读 519
Manila Network
Manila 是目前比较完善的一个 OpenStack PaaS 服务组件,它需要借助 Neutron 来完成其管理和网络连接,其网络架构主要如下图,本文会主要介绍 Manila 的网络组件代码,而不是其网络架构。

对于一个类似的 PaaS 项目,其主要需求是在 Neutron 上启动属于自己的 Service Network 和 Service Port,项目的 Agent 可以通过 Service Port SSH 连接到处于 Service Network 的 Service VM。
先看 Manila 的启动过程。Manila share 的启动没有像 Neutron 的 agent 一样直接运行特定的main()函数,而是在manila/bin/manila-service通过service完成实例化,实例化的类并不是一个hard-code的类,而是可以通过share_manager这个配置修改的,其运行过程如下:
#bin/manila-share.pyserver = service.Service.create(binary='manila-share')#manila/service.pyclass Service(object):def create(cls, host=None, binary=None, topic=None, manager=None,report_interval=None, periodic_interval=None,periodic_fuzzy_delay=None, service_name=None):if not topic:topic = binaryif not manager:subtopic = topic.rpartition('manila-')[2]manager = CONF.get('%s_manager' % subtopic, None)#manila/common/config.pycfg.StrOpt('share_manager',default='manila.share.manager.ShareManager',help='Full class name for the share manager.'),
实例化share_manager时,首先获得配置文件对象,然后加载 driver,默认为GenericShareDriver:
#manila/share/manager.pyclass ShareManager(manager.SchedulerDependentManager):def __init__(self, share_driver=None, service_name=None, *args, **kwargs):if not share_driver:share_driver = self.configuration.share_driverself.driver = importutils.import_object(share_driver, self.db, configuration=self.configuration)#manila/service.pycfg.StrOpt('share_driver',default='manila.share.drivers.generic.GenericShareDriver',help='Driver to use for share creation.'),
加载GenericShareDriver时会维护一个ssh_connections字典和service_instance_manager,这个service_instance_manager将去调用 Nova、Neutron 等的 API,它需要至少指定一个 Service VM 的用户:
#manila/share/drivers/generic.pyclass GenericShareDriver(driver.ExecuteMixin, driver.ShareDriver):def __init__(self, db, *args, **kwargs):self.ssh_connections = {}self.service_instance_manager = (service_instance.ServiceInstanceManager(self.db, driver_config=self.configuration))#manila/share/drivers/service_instance.pyclass ServiceInstanceManager(object):def __init__(self, db, *args, **kwargs):if not self.get_config_option("service_instance_user"):raise exception.ServiceInstanceException(_('Service instance user ''is not specified'))self.compute_api = compute.API()self.neutron_api = neutron.API()
实例化 Neutron API 类时,Manila 会尝试去获取一些东西,首先尝试获得admin_tenant_id(这是neutron.API的一个用@property修饰的函数,如果当前没有一个 token 的话,它会用 neutron 的用户名和密码去 keystone 的 endpoint 获取 token,此时 neutron client 会执行_extract_service_catalog,设置auth_tenant_id,如果已经有了 token 说明 client 已经执行过这个过成立,就直接返回),以确定到 Neutron 的连通性,然后再获得 Service Network ID、加载 Interface Driver(默认为manila.network.linux.interface.OVSInterfaceDriver)、配置 Service instance 网络,如果在配置文件打开了connect_share_server_to_tenant_network,还会执行connect_share_server_to_tenant_network给 Service VM 增加一个到用户网络的虚拟网卡,下面为其关键部分代码:
#manila/share/drivers/service_instance.pyclass ServiceInstanceManager(object):def __init__(self, db, *args, **kwargs):attempts = 5while attempts:try:self.service_tenant_id = self.neutron_api.admin_tenant_idbreak...self.service_network_id = self._get_service_network()self.vif_driver = importutils.import_class(self.get_config_option("interface_driver"))()self._setup_connectivity_with_service_instances()self.connect_share_server_to_tenant_network = self.get_config_option('connect_share_server_to_tenant_network')#manila/network/neutron/api.pyclass API(base.Base):@propertydef admin_tenant_id(self):if self.client.httpclient.auth_token is None:try:self.client.httpclient.authenticate()except neutron_client_exc.NeutronClientException as e:raise exception.NetworkException(code=e.status_code,message=e.message)return self.client.httpclient.auth_tenant_id
_get_service_network()这个函数是同步化的,因为他会根据配置文件里的service_network_name来获取 Network,如果有多个就报错,如果没有就主动建立,为了避免多个实例同时进入,需要在这个函数上加锁(这里的锁只是一个本地锁,所以请尽量避免多节点同时启动 manila-share)。
#manila/share/drivers/service_instance.pyclass ServiceInstanceManager(object):@utils.synchronized("service_instance_get_service_network", external=True)def _get_service_network(self):"""Finds existing or creates new service network."""service_network_name = self.get_config_option("service_network_name")networks = [network for network in self.neutron_api.get_all_tenant_networks(self.service_tenant_id)if network['name'] == service_network_name]if len(networks) > 1:raise exception.ServiceInstanceException(_('Ambiguous service ''networks.'))elif not networks:return self.neutron_api.network_create(self.service_tenant_id,service_network_name)['id']else:return networks[0]['id']
Instance Driver 是 Manila 与 Service VM 连接用的驱动,主要实现了plug、unplug这两个方法。
_setup_connectivity_with_service_instances()是 Mainila 网络模块里一个比较重要的函数,这个函数完成了创建 Service port(查找 device id 为 manila-share 的 port,如果数量大于 1 则报错,为空则创建一个并设置好其 device id、device owner 和 host_id。这个函数也是同步化的,头部加了@utils.synchronized的装饰器)、同步 port(检查 Service Network 下有无 port 没有加入的子网,如果有则将其加进去。这个函数也是同步化的)、将 port 连入 ovs 上的 br-int(如果已存在同名的 tap 设备会报错,否则是执行
ovs-vsctl -- --may-exist add-port br-int tapXXXXXXXX-XX-- set Interface tapXXXXXXXX-XX type=internal-- set Interface tapXXXXXXXX-XX external-ids:iface-id=PORT-ID-- set Interface tapXXXXXXXX-XX external-ids:iface-status=active-- set Interface tapXXXXXXXX-XX external-ids:attached-mac=%s
这个命令,然后执行ip link set tapXXXXXXXX-XX address XX:XX:XX:XX:XX:XX来设置 MAC 地址,最后将其 up 起来,注意当设备处于 UP 状态后,内核将会根据这个设备的 CIDR 地址自动添加路由表,manila 不会去维护这个路由表)、给 port 设置 ip 地址(先检查已有的 IP 地址,如果已有的 IP 里有需要的 IP 则保留,否则删掉)、检查路由表(先执行ip route list proto kernel dev tapXXXXXXXX-XX获取 Service Port 上的路由表,再挨个获取路由项对应的子网,再用ip list proto kernel match SUBNET获取对指定网段的路由表项,检查 Service port 是不是第一个,如果不是则将处于第一个的路由表项删掉然后再通过 append 添加)和清理过期设备(先获取所有 tap 开头的设备,然后检查其 CIDR 地址是否与现在的 Service Port 重复,如果有就执行 VIF Driver 的 unplug 方法)。
#/manila/share/drivers/service_instance.pyclass ServiceInstanceManager(object):def _setup_connectivity_with_service_instances(self):port = self._get_service_port()port = self._add_fixed_ips_to_service_port(port)interface_name = self.vif_driver.get_device_name(port)self.vif_driver.plug(interface_name, port['id'], port['mac_address'])ip_cidrs = []for fixed_ip in port['fixed_ips']:subnet = self.neutron_api.get_subnet(fixed_ip['subnet_id'])net = netaddr.IPNetwork(subnet['cidr'])ip_cidr = '%s/%s' % (fixed_ip['ip_address'], net.prefixlen)ip_cidrs.append(ip_cidr)self.vif_driver.init_l3(interface_name, ip_cidrs)device = ip_lib.IPDevice(interface_name)device.route.pullup_route(interface_name)self._remove_outdated_interfaces(device)
至此 Manager 的实例化过程基本结束。之后 service 会执行 start 方法(见附录一),将启动 RPC 服务、做定时状态报告等,在这里我们只关心其调用的 init_host。
首先执行 Driver 的do_setup,前面我们知道我们的默认 Driver 是GenericShareDriver,在这里再一次执行了 Nova API 和 Neutron API 的实例化,然后获取 host 在这台机器上的所有 share,依次执行ensure_share方法,确认 Share 和规则的状态,最后向 scheduler 同步状态。
启动一个 share 将从share.ShareManager.create_share进入,我们简单分析其过程,主要还是看网络方面的操作。首先需要获得 Share Network 的 ID,然后调用_provide_share_server_for_share获取 Share Server,这个函数内部定义了一个同名的 Nested Function 并返回。这个 Nested Function 的逻辑是这样的,首先根据 Host 和 Share Network 检查是否已存在可用的 Service VM,如果没有则现在数据创建一个 Share Server 的相应数据,再调用_setup_server到驱动的_setup_server真正创建 Service Server。
#manila/share/manager.pyclass ShareManager(manager.SchedulerDependentManager):def create_share(self, context, share_id, request_spec=None,filter_properties=None, snapshot_id=None):share_network_id = share_ref.get('share_network_id', None)elif share_network_id:try:share_server, share_ref = self._provide_share_server_for_share(context, share_network_id, share_id)...def _provide_share_server_for_share(self, context, share_network_id,share_id):@utils.synchronized("share_manager_%s" % share_network_id)def _provide_share_server_for_share():try:share_server = \self.db.share_server_get_by_host_and_share_net_valid(context, self.host, share_network_id)except exception.ShareServerNotFound:share_server = self.db.share_server_create(...if not exist:# Create share server on backend with data from dbshare_server = self._setup_server(context, share_server)...return share_server, share_refreturn _provide_share_server_for_share()def _setup_server(self, context, share_server, metadata=None):network_info = self._form_server_setup_info(context, share_server,share_network)server_info = self.driver.setup_server(network_info,metadata=metadata)
Manager 的_form_server_setup_info首先要获取一次 Share Network 的 ID,然后调用 Driver 的allocate_network分配 IP,我们的 Driver 还是前面看到的GenericShareDriver,它不需要这个过程,所以直接跳过去了。然后重新获取一次 Share Network 和 Network info(包括 server_id、cidr、neutron_net_id、network_allocation等信息),再将network_info发给 Driver 的setup_server,因为GenericShareDriver的 Service VM 均由service_instance_manager管理,所以请求会再转给service_instance_manager的set_up_service_instance方法最后到_create_service_instance真正建立虚拟机。
首先要获取镜像、密码或密钥、安全组(由配置选项的service_instance_security_group决定,如果不存在则自己建立,开放 CIFS、NFS、SSH、Ping 所需要的端口/规则,这里 Manila 调用的是 Nova 的 API,而且直接获取所有再过滤,可以优化),然后获取网络信息:
device owner为“manila”的 Port,作为 Service VM 用的 Port,如果打开了connect_share_server_to_tenant_network,还会再建一个 Public Port。最后再一次执行前面在启动部分介绍过的_setup_connectivity_with_service_instances,完成 Service Port 到 Service VM 的连通。
#manila/share/drivers/generic.pyclass GenericShareDriver(driver.ExecuteMixin, driver.ShareDriver):def setup_server(self, network_info, metadata=None):server = self.service_instance_manager.set_up_service_instance(self.admin_context,network_info['server_id'],network_info['neutron_net_id'],network_info['neutron_subnet_id'],)#manila/share/drivers/service_instance.pyclass ServiceInstanceManager(object):def set_up_service_instance(self, context, instance_name, neutron_net_id,neutron_subnet_id):server = self._create_service_instance(context,instance_name,neutron_net_id,neutron_subnet_id)def _create_service_instance(self, context, instance_name, neutron_net_id,neutron_subnet_id):security_group = self._get_or_create_security_group(context)network_data = self._setup_network_for_instance(neutron_net_id,neutron_subnet_id)...self._setup_connectivity_with_service_instances()def _setup_network_for_instance(self, neutron_net_id, neutron_subnet_id):subnet_name = "routed_to_%s" % neutron_subnet_idservice_subnet = self._get_service_subnet(subnet_name)
获取网络信息后,Manila 将拿着前面获取网络信息时得到的 Port 去创建 Service VM,在设置的max_time_to_build_instance时间内,不断尝试获取 Service VM 的状态,直到其成为Active状态。之后再将安全组设置到 Service VM,再在max_time_to_build_instance时间内尝试连接 Service VM 的 22 端口。
#manila/share/drivers/service_instance.pyclass ServiceInstanceManager(object):def _create_service_instance(self, context, instance_name, neutron_net_id,neutron_subnet_id):service_instance = self.compute_api.server_create(context,name=instance_name,image=service_image_id,flavor=self.get_config_option("service_instance_flavor_id"),key_name=key_name,nics=[{'port-id': port['id']} for port in network_data['ports']])...if security_group:self.compute_api.add_security_group_to_server(context,service_instance["id"], security_group.id)...if not self._check_server_availability(service_instance):raise exception.ServiceInstanceException(def _check_server_availability(self, server):while time.time() - t < self.max_time_to_build_instance:try:socket.socket().connect((server['ip'], 22))return Trueexcept socket.error as e:time.sleep(5)return False
至此可以认为 Service VM 已经创建完成,代码将再返回到 manila.share.manager.ShareManager.create_share,继续进行下面的create_share的过程。
对于用的默认的GenericShareDriver,下面就是就是调用 Cinder API 创建 Volume、Attach Volume,SSH 到 Service VM 执行格式化,挂载等操作,与网络没有多少关系就不分析了,完成后一个 Share 就可以认为已经可以使用了。
#manila/share/manager.pyclass ShareManager(manager.SchedulerDependentManager):def create_share(self, context, share_id, request_spec=None,filter_properties=None, snapshot_id=None):...export_location = self.driver.create_share(context, share_ref, share_server=share_server)#manila/share/drivers/generic.pyclass GenericShareDriver(driver.ExecuteMixin, driver.ShareDriver):@ensure_serverdef create_share(self, context, share, share_server=None):"""Creates share."""server_details = share_server['backend_details']volume = self._allocate_container(self.admin_context, share)volume = self._attach_volume(self.admin_context,share,server_details['instance_id'],volume)self._format_device(server_details, volume)self._mount_device(share, server_details, volume)location = self._get_helper(share).create_export(server_details,share['name'])return location
删除一个 share 将从share.ShareManager.delete_share进入,过程就比较简单了,先删除所有的 ACL 规则,然后对 Volume 执行 Unmount 和 Detach,最后调用 Cinder 的 API 将之删除。如果配置文件开启了delete_share_server_with_last_share那么当 Share Server 上没有 Share 时,就会删除这个 Share Server,包括调用 Nova API 删除 Service VM、移除路由器上的端口、将 Service Subnet 的 name 改为空,最后删除掉 Service Subnet 上所有 Port。因为 Service Port 上的 IP 和路由以后还可以用到,所以 Manila 不会去删除那些 IP 和路由项。
class ShareManager(manager.SchedulerDependentManager):def delete_share(self, context, share_id):...for access_ref in rules:self._deny_access(context, access_ref, share_ref, share_server)self.driver.delete_share(context, share_ref,share_server=share_server)if CONF.delete_share_server_with_last_share:if share_server and not share_server.shares:self.delete_share_server(context, share_server)class GenericShareDriver(driver.ExecuteMixin, driver.ShareDriver):def delete_share(self, context, share, share_server=None):if self._is_share_server_active(context, share_server):self._get_helper(share).remove_export(share_server['backend_details'], share['name'])self._unmount_device(share, share_server['backend_details'])self._detach_volume(self.admin_context, share,share_server['backend_details'])self._deallocate_container(self.admin_context, share)
launcher 启动部分代码,与其他 OpenStack 服务基本相同
#manila/bin/manila-share.pylauncher.launch_server(server)#manila/service.pydef launch_server(self, server, workers=1):wrap = ServerWrapper(server, workers)self.totalwrap = self.totalwrap + 1while (self.running and len(wrap.children) < wrap.workersand not wrap.failed):self._start_child(wrap)class ServerWrapper(object):def __init__(self, server, workers):self.server = serverself.workers = workersself.children = set()self.forktimes = []self.failed = Falseclass ProcessLauncher(object):def _start_child(self, wrap):pid = os.fork()if pid == 0:try:self._child_process(wrap.server)def _child_process(self, server):...launcher = Launcher()launcher.run_server(server)class Launcher(object):@staticmethoddef run_server(server):server.start()server.wait()