[关闭]
@MatheMatrix 2015-01-15T00:35:38.000000Z 字数 14068 阅读 388

Manila 网络模块

Manila Network


前言

Manila 是目前比较完善的一个 OpenStack PaaS 服务组件,它需要借助 Neutron 来完成其管理和网络连接,其网络架构主要如下图,本文会主要介绍 Manila 的网络组件代码,而不是其网络架构。

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这个配置修改的,其运行过程如下:

  1. #bin/manila-share.py
  2. server = service.Service.create(binary='manila-share')
  3. #manila/service.py
  4. class Service(object):
  5. def create(cls, host=None, binary=None, topic=None, manager=None,
  6. report_interval=None, periodic_interval=None,
  7. periodic_fuzzy_delay=None, service_name=None):
  8. if not topic:
  9. topic = binary
  10. if not manager:
  11. subtopic = topic.rpartition('manila-')[2]
  12. manager = CONF.get('%s_manager' % subtopic, None)
  13. #manila/common/config.py
  14. cfg.StrOpt('share_manager',
  15. default='manila.share.manager.ShareManager',
  16. help='Full class name for the share manager.'),

Driver 的加载

实例化share_manager时,首先获得配置文件对象,然后加载 driver,默认为GenericShareDriver

  1. #manila/share/manager.py
  2. class ShareManager(manager.SchedulerDependentManager):
  3. def __init__(self, share_driver=None, service_name=None, *args, **kwargs):
  4. if not share_driver:
  5. share_driver = self.configuration.share_driver
  6. self.driver = importutils.import_object(
  7. share_driver, self.db, configuration=self.configuration)
  8. #manila/service.py
  9. cfg.StrOpt('share_driver',
  10. default='manila.share.drivers.generic.GenericShareDriver',
  11. help='Driver to use for share creation.'),

GenericShareDriver 中的 service_instance_manager

加载 Nova、Neutron API

加载GenericShareDriver时会维护一个ssh_connections字典和service_instance_manager,这个service_instance_manager将去调用 Nova、Neutron 等的 API,它需要至少指定一个 Service VM 的用户:

  1. #manila/share/drivers/generic.py
  2. class GenericShareDriver(driver.ExecuteMixin, driver.ShareDriver):
  3. def __init__(self, db, *args, **kwargs):
  4. self.ssh_connections = {}
  5. self.service_instance_manager = (
  6. service_instance.ServiceInstanceManager(
  7. self.db, driver_config=self.configuration))
  8. #manila/share/drivers/service_instance.py
  9. class ServiceInstanceManager(object):
  10. def __init__(self, db, *args, **kwargs):
  11. if not self.get_config_option("service_instance_user"):
  12. raise exception.ServiceInstanceException(_('Service instance user '
  13. 'is not specified'))
  14. self.compute_api = compute.API()
  15. 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 增加一个到用户网络的虚拟网卡,下面为其关键部分代码:

  1. #manila/share/drivers/service_instance.py
  2. class ServiceInstanceManager(object):
  3. def __init__(self, db, *args, **kwargs):
  4. attempts = 5
  5. while attempts:
  6. try:
  7. self.service_tenant_id = self.neutron_api.admin_tenant_id
  8. break
  9. ...
  10. self.service_network_id = self._get_service_network()
  11. self.vif_driver = importutils.import_class(
  12. self.get_config_option("interface_driver"))()
  13. self._setup_connectivity_with_service_instances()
  14. self.connect_share_server_to_tenant_network = self.get_config_option(
  15. 'connect_share_server_to_tenant_network')
  16. #manila/network/neutron/api.py
  17. class API(base.Base):
  18. @property
  19. def admin_tenant_id(self):
  20. if self.client.httpclient.auth_token is None:
  21. try:
  22. self.client.httpclient.authenticate()
  23. except neutron_client_exc.NeutronClientException as e:
  24. raise exception.NetworkException(code=e.status_code,
  25. message=e.message)
  26. return self.client.httpclient.auth_tenant_id

获取 Service Network

_get_service_network()这个函数是同步化的,因为他会根据配置文件里的service_network_name来获取 Network,如果有多个就报错,如果没有就主动建立,为了避免多个实例同时进入,需要在这个函数上加锁(这里的锁只是一个本地锁,所以请尽量避免多节点同时启动 manila-share)。

  1. #manila/share/drivers/service_instance.py
  2. class ServiceInstanceManager(object):
  3. @utils.synchronized("service_instance_get_service_network", external=True)
  4. def _get_service_network(self):
  5. """Finds existing or creates new service network."""
  6. service_network_name = self.get_config_option("service_network_name")
  7. networks = [network for network in self.neutron_api.
  8. get_all_tenant_networks(self.service_tenant_id)
  9. if network['name'] == service_network_name]
  10. if len(networks) > 1:
  11. raise exception.ServiceInstanceException(_('Ambiguous service '
  12. 'networks.'))
  13. elif not networks:
  14. return self.neutron_api.network_create(self.service_tenant_id,
  15. service_network_name)['id']
  16. else:
  17. return networks[0]['id']

重要的 _setup_connectivity_with_service_instances

Instance Driver 是 Manila 与 Service VM 连接用的驱动,主要实现了plugunplug这两个方法。
_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 设备会报错,否则是执行

  1. ovs-vsctl -- --may-exist add-port br-int tapXXXXXXXX-XX
  2. -- set Interface tapXXXXXXXX-XX type=internal
  3. -- set Interface tapXXXXXXXX-XX external-ids:iface-id=PORT-ID
  4. -- set Interface tapXXXXXXXX-XX external-ids:iface-status=active
  5. -- 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 方法)。

  1. #/manila/share/drivers/service_instance.py
  2. class ServiceInstanceManager(object):
  3. def _setup_connectivity_with_service_instances(self):
  4. port = self._get_service_port()
  5. port = self._add_fixed_ips_to_service_port(port)
  6. interface_name = self.vif_driver.get_device_name(port)
  7. self.vif_driver.plug(interface_name, port['id'], port['mac_address'])
  8. ip_cidrs = []
  9. for fixed_ip in port['fixed_ips']:
  10. subnet = self.neutron_api.get_subnet(fixed_ip['subnet_id'])
  11. net = netaddr.IPNetwork(subnet['cidr'])
  12. ip_cidr = '%s/%s' % (fixed_ip['ip_address'], net.prefixlen)
  13. ip_cidrs.append(ip_cidr)
  14. self.vif_driver.init_l3(interface_name, ip_cidrs)
  15. device = ip_lib.IPDevice(interface_name)
  16. device.route.pullup_route(interface_name)
  17. 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 将从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。

  1. #manila/share/manager.py
  2. class ShareManager(manager.SchedulerDependentManager):
  3. def create_share(self, context, share_id, request_spec=None,
  4. filter_properties=None, snapshot_id=None):
  5. share_network_id = share_ref.get('share_network_id', None)
  6. elif share_network_id:
  7. try:
  8. share_server, share_ref = self._provide_share_server_for_share(
  9. context, share_network_id, share_id)
  10. ...
  11. def _provide_share_server_for_share(self, context, share_network_id,
  12. share_id):
  13. @utils.synchronized("share_manager_%s" % share_network_id)
  14. def _provide_share_server_for_share():
  15. try:
  16. share_server = \
  17. self.db.share_server_get_by_host_and_share_net_valid(
  18. context, self.host, share_network_id)
  19. except exception.ShareServerNotFound:
  20. share_server = self.db.share_server_create(
  21. ...
  22. if not exist:
  23. # Create share server on backend with data from db
  24. share_server = self._setup_server(context, share_server)
  25. ...
  26. return share_server, share_ref
  27. return _provide_share_server_for_share()
  28. def _setup_server(self, context, share_server, metadata=None):
  29. network_info = self._form_server_setup_info(context, share_server,
  30. share_network)
  31. server_info = self.driver.setup_server(network_info,
  32. metadata=metadata)

创建 Share Server

Manager 的_form_server_setup_info首先要获取一次 Share Network 的 ID,然后调用 Driver 的allocate_network分配 IP,我们的 Driver 还是前面看到的GenericShareDriver,它不需要这个过程,所以直接跳过去了。然后重新获取一次 Share Network 和 Network info(包括 server_idcidrneutron_net_idnetwork_allocation等信息),再将network_info发给 Driver 的setup_server,因为GenericShareDriver的 Service VM 均由service_instance_manager管理,所以请求会再转给service_instance_managerset_up_service_instance方法最后到_create_service_instance真正建立虚拟机。

获取网络信息

首先要获取镜像、密码或密钥、安全组(由配置选项的service_instance_security_group决定,如果不存在则自己建立,开放 CIFS、NFS、SSH、Ping 所需要的端口/规则,这里 Manila 调用的是 Nova 的 API,而且直接获取所有再过滤,可以优化),然后获取网络信息:

  1. 先获取 Service Subnet,这个 Service Subnet 是 Manila 的 Service Network 下的子网,其名称均为"routed_to_xxx"这样,在获取时如果没有则尝试找一个没有用的(name 为空)子网,将其重命名返回,如果再没有就建立一个;
  2. 然后把用户的子网(Share Network)相连的路由器连接到获得的 Service Subnet;
  3. 在 Service Subnet 里建一个device owner为“manila”的 Port,作为 Service VM 用的 Port,如果打开了connect_share_server_to_tenant_network,还会再建一个 Public Port。

最后再一次执行前面在启动部分介绍过的_setup_connectivity_with_service_instances,完成 Service Port 到 Service VM 的连通。

  1. #manila/share/drivers/generic.py
  2. class GenericShareDriver(driver.ExecuteMixin, driver.ShareDriver):
  3. def setup_server(self, network_info, metadata=None):
  4. server = self.service_instance_manager.set_up_service_instance(
  5. self.admin_context,
  6. network_info['server_id'],
  7. network_info['neutron_net_id'],
  8. network_info['neutron_subnet_id'],
  9. )
  10. #manila/share/drivers/service_instance.py
  11. class ServiceInstanceManager(object):
  12. def set_up_service_instance(self, context, instance_name, neutron_net_id,
  13. neutron_subnet_id):
  14. server = self._create_service_instance(context,
  15. instance_name,
  16. neutron_net_id,
  17. neutron_subnet_id)
  18. def _create_service_instance(self, context, instance_name, neutron_net_id,
  19. neutron_subnet_id):
  20. security_group = self._get_or_create_security_group(context)
  21. network_data = self._setup_network_for_instance(neutron_net_id,
  22. neutron_subnet_id)
  23. ...
  24. self._setup_connectivity_with_service_instances()
  25. def _setup_network_for_instance(self, neutron_net_id, neutron_subnet_id):
  26. subnet_name = "routed_to_%s" % neutron_subnet_id
  27. service_subnet = self._get_service_subnet(subnet_name)

创建 Service VM 虚拟机

获取网络信息后,Manila 将拿着前面获取网络信息时得到的 Port 去创建 Service VM,在设置的max_time_to_build_instance时间内,不断尝试获取 Service VM 的状态,直到其成为Active状态。之后再将安全组设置到 Service VM,再在max_time_to_build_instance时间内尝试连接 Service VM 的 22 端口。

  1. #manila/share/drivers/service_instance.py
  2. class ServiceInstanceManager(object):
  3. def _create_service_instance(self, context, instance_name, neutron_net_id,
  4. neutron_subnet_id):
  5. service_instance = self.compute_api.server_create(
  6. context,
  7. name=instance_name,
  8. image=service_image_id,
  9. flavor=self.get_config_option("service_instance_flavor_id"),
  10. key_name=key_name,
  11. nics=[{'port-id': port['id']} for port in network_data['ports']])
  12. ...
  13. if security_group:
  14. self.compute_api.add_security_group_to_server(
  15. context,
  16. service_instance["id"], security_group.id)
  17. ...
  18. if not self._check_server_availability(service_instance):
  19. raise exception.ServiceInstanceException(
  20. def _check_server_availability(self, server):
  21. while time.time() - t < self.max_time_to_build_instance:
  22. try:
  23. socket.socket().connect((server['ip'], 22))
  24. return True
  25. except socket.error as e:
  26. time.sleep(5)
  27. return False

Service VM 创建完成

至此可以认为 Service VM 已经创建完成,代码将再返回到 manila.share.manager.ShareManager.create_share,继续进行下面的create_share的过程。

完成 Share 的创建

对于用的默认的GenericShareDriver,下面就是就是调用 Cinder API 创建 Volume、Attach Volume,SSH 到 Service VM 执行格式化,挂载等操作,与网络没有多少关系就不分析了,完成后一个 Share 就可以认为已经可以使用了。

  1. #manila/share/manager.py
  2. class ShareManager(manager.SchedulerDependentManager):
  3. def create_share(self, context, share_id, request_spec=None,
  4. filter_properties=None, snapshot_id=None):
  5. ...
  6. export_location = self.driver.create_share(
  7. context, share_ref, share_server=share_server)
  8. #manila/share/drivers/generic.py
  9. class GenericShareDriver(driver.ExecuteMixin, driver.ShareDriver):
  10. @ensure_server
  11. def create_share(self, context, share, share_server=None):
  12. """Creates share."""
  13. server_details = share_server['backend_details']
  14. volume = self._allocate_container(self.admin_context, share)
  15. volume = self._attach_volume(
  16. self.admin_context,
  17. share,
  18. server_details['instance_id'],
  19. volume)
  20. self._format_device(server_details, volume)
  21. self._mount_device(share, server_details, volume)
  22. location = self._get_helper(share).create_export(
  23. server_details,
  24. share['name'])
  25. return location

删除一个 Share

删除一个 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 和路由项。

  1. class ShareManager(manager.SchedulerDependentManager):
  2. def delete_share(self, context, share_id):
  3. ...
  4. for access_ref in rules:
  5. self._deny_access(context, access_ref, share_ref, share_server)
  6. self.driver.delete_share(context, share_ref,
  7. share_server=share_server)
  8. if CONF.delete_share_server_with_last_share:
  9. if share_server and not share_server.shares:
  10. self.delete_share_server(context, share_server)
  11. class GenericShareDriver(driver.ExecuteMixin, driver.ShareDriver):
  12. def delete_share(self, context, share, share_server=None):
  13. if self._is_share_server_active(context, share_server):
  14. self._get_helper(share).remove_export(
  15. share_server['backend_details'], share['name'])
  16. self._unmount_device(share, share_server['backend_details'])
  17. self._detach_volume(self.admin_context, share,
  18. share_server['backend_details'])
  19. self._deallocate_container(self.admin_context, share)

附录一:launcher 启动代码

launcher 启动部分代码,与其他 OpenStack 服务基本相同

  1. #manila/bin/manila-share.py
  2. launcher.launch_server(server)
  3. #manila/service.py
  4. def launch_server(self, server, workers=1):
  5. wrap = ServerWrapper(server, workers)
  6. self.totalwrap = self.totalwrap + 1
  7. while (self.running and len(wrap.children) < wrap.workers
  8. and not wrap.failed):
  9. self._start_child(wrap)
  10. class ServerWrapper(object):
  11. def __init__(self, server, workers):
  12. self.server = server
  13. self.workers = workers
  14. self.children = set()
  15. self.forktimes = []
  16. self.failed = False
  17. class ProcessLauncher(object):
  18. def _start_child(self, wrap):
  19. pid = os.fork()
  20. if pid == 0:
  21. try:
  22. self._child_process(wrap.server)
  23. def _child_process(self, server):
  24. ...
  25. launcher = Launcher()
  26. launcher.run_server(server)
  27. class Launcher(object):
  28. @staticmethod
  29. def run_server(server):
  30. server.start()
  31. server.wait()
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注