@MatheMatrix
2015-01-15T00:35:38.000000Z
字数 14068
阅读 388
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.py
server = service.Service.create(binary='manila-share')
#manila/service.py
class 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 = binary
if not manager:
subtopic = topic.rpartition('manila-')[2]
manager = CONF.get('%s_manager' % subtopic, None)
#manila/common/config.py
cfg.StrOpt('share_manager',
default='manila.share.manager.ShareManager',
help='Full class name for the share manager.'),
实例化share_manager
时,首先获得配置文件对象,然后加载 driver,默认为GenericShareDriver
:
#manila/share/manager.py
class ShareManager(manager.SchedulerDependentManager):
def __init__(self, share_driver=None, service_name=None, *args, **kwargs):
if not share_driver:
share_driver = self.configuration.share_driver
self.driver = importutils.import_object(
share_driver, self.db, configuration=self.configuration)
#manila/service.py
cfg.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.py
class 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.py
class 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.py
class ServiceInstanceManager(object):
def __init__(self, db, *args, **kwargs):
attempts = 5
while attempts:
try:
self.service_tenant_id = self.neutron_api.admin_tenant_id
break
...
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.py
class API(base.Base):
@property
def 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.py
class 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.py
class 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.py
class 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 db
share_server = self._setup_server(context, share_server)
...
return share_server, share_ref
return _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.py
class 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.py
class 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_id
service_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.py
class 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 True
except 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.py
class 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.py
class GenericShareDriver(driver.ExecuteMixin, driver.ShareDriver):
@ensure_server
def 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.py
launcher.launch_server(server)
#manila/service.py
def launch_server(self, server, workers=1):
wrap = ServerWrapper(server, workers)
self.totalwrap = self.totalwrap + 1
while (self.running and len(wrap.children) < wrap.workers
and not wrap.failed):
self._start_child(wrap)
class ServerWrapper(object):
def __init__(self, server, workers):
self.server = server
self.workers = workers
self.children = set()
self.forktimes = []
self.failed = False
class 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):
@staticmethod
def run_server(server):
server.start()
server.wait()