博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
OpenStack与SDN控制器的集成
阅读量:4230 次
发布时间:2019-05-26

本文共 23213 字,大约阅读时间需要 77 分钟。

OpenStack与SDN控制器的集成

1.OpenStack Neutron简介

Neutron添加了一层虚拟的网络服务让租户(用户)构建自己的虚拟网络。Neutron是对网络的虚拟化,该网络可以从一个地方移动到另一个地方,而不会影响现有的连接。它可以进一步解释为一个网络管理服务,为创建和管理虚拟网络公开了一组可扩展的API(通过创建虚拟网络为OpenStack Compute节点上的虚拟机提供网络服务)。Neutron的插件架构为开源社区或第三方服务提供API。Neutron还允许供应商研究和添加新的插件,提供先进的网络功能。

目前,Neutron的虚拟网络服务没有传统网络成熟。下图描述了与Neutron组件交互的代理。组成Neutron的元素如下:
Neutron-server是python虚拟光驱, 是OpenStack网络运行在Network节点的主过程。
Plugin agents和Neutron插件一起管理虚拟交换机,Plugin agents依赖Neutron插件。
DHCP agent是Neutron的一部分,为租户的网络提供DHCP服务。
L3 agent负责层3和NAT转发来获得租户虚拟机的外部访问。

SDN对于neutron的意义在哪里?

引入SDN主要是克服Neutron的缺陷,SDN是一种网络技术,通过集中的可编程控制平面来管理整个数据平面。这样网络运营商和供应商可以控制和管理自己的虚拟化资源和网络。SDN是一种新型的网络模式,允许硬件和操作系统之间以及物理/虚拟网元和操作系统之间通过开放API通信。

2.SDN控制器跟neutron结合原理

(1)Neutron Server的介绍
A python daemon is the main process of the OpenStack networking that typically runs on the controller node (a term used in OpenStack deployments). It exposes APIs, to enforce the network model, and passes the requests to the neutron plugin.
意思就是暴露API,使得请求能够被传到plugin中。

(2)Plugins的介绍

Plugins can be either core or service. Core plugins implement the “core” Neutron API — L2 networking and IP address management. Service plugins provide “additional” services, such as the L3 router, load balancing, VPN, firewall and metering. These network services can also be provided by the core plugins by realizing the relevant API extensions. In short, plugins run on the controller node and implement the networking APIs, which interact with the Neutron server, database and agents.
Plugin分为core和additional,用来处理neutron server传过来的请求。
下图是更为详细的neutron server如何传送给plugin消息的:
(3)Plugin Agents介绍
These agents are specific to the Neutron plugin being used. They run on compute nodes and communicate with the Neutron plugin to manage virtual switches. These agents are optional in many deployments and perform local virtual switch configurations on each hypervisor.
Agent跑在compute节点之上,与neutron的plugin进行通信

ML2 的plugin都是属于core。分为type和mechanism两种。Type drivers (如flat, VLAN, GRE 和VXLAN) 定义 L2 type。 mechanism drivers (如OVS, adrivers from ODL, Cisco, NEC, etc) 负责一系列动作(更新、创建、删除)网络、子网、端口。

整个过程可以分为下面几个步骤:
1.用户通过OpenStack的界面(horizon)输入消息给networking API,再发送给Neutron server
2.Neutron server接受信息发送给plugin
3.Neutron server/plugin 更新DB
4.Plugin通过REST API发送消息给SDN控制器
5.SDN控制器接受消息然后通过南向的plugins/protocols, 如OpenFlow, OVSDB or OF-Config.

3.ml2驱动脚本实现

下面是ml2驱动脚本“mechanism_gnflush.py”用于设置计算节点和网络节点与数据中心SDN控制器对接的配置。主要是将openstack的信息传递给SDN控制器。

Openstack计算节点和网络节点配置

实验环境为CentOS7+OpenStack Juno , SDN 控制器为DCFabric。

步骤一: 工具脚本获取
在“tools/openstack-tools”目录下有shell脚本“gnflush-controller.sh”和ml2驱动脚本“mechanism_gnflush.py”用于设置计算节点和网络节点与数据中心SDN控制器对接的配置。(mechanism_gnflush.py(根据openstack官网的ODL plugin更改),mechanism_gnflush.py会在附录提供)将“mechanism_gnflush.py”这个脚本拷贝到Openstack控制节点服务器上如下目录下:

将“gnflush-controller.sh”拷贝到服务器上任意目录后续使用。

步骤二:关闭相关服务
在所有Openstack计算节点和网络节点上服务器上执行如下命令(CentOS):

步骤三:配置ml2 plugin

在数据中心文件“/usr/lib/python2.6/site-packages/neutron-2014.1.3-py2.6.egg-info/entry_points.txt”中“[neutron.ml2.mechanism_drivers]”配置节点下增加GNFlush控制器ML2 plugin的entry point:

步骤四:配置ml2
配置ml2前,需要准备如下信息:

  • 租户网络类型:管理员可以根据需要选择“gre”或者“vlan”。
  • 控制器IP地址:数据中心SDN控制器所在服务器的IP地址。
  • 控制器Rest服务端口:Rest服务监听端口需要查看SDN控制器配置文件中“[rest_port]”配置,默认为“8081”

在Openstack控制中心服务器上执行如下命令修改ml2配置,以租户网络类型为“gre”为例:

在“/etc/neutron/plugins/ml2/ml2_conf.ini”文件最后面添加“ml2_gnflush”的选项:

步骤五:配置Openvswitch

配置前,需要准备如下信息:

  • 本地IP:当前服务器的IP地址。
  • 网卡名称:用于建立隧道的物理网卡。
  • 控制器IP地址:数据中心SDN控制器所在服务器的IP地址。

在所有Openstack计算节点和网络节点上服务器上执行如下命令,可以通过“--help”选项查看帮助信息:

步骤六:启动相关服务

执行如下命令启动服务:

这个是自主开发的DCFabric的拓扑图。下图是已经连接OpenStack的Open vSwitch。

附录:gnflush-controller.sh#!/bin/bashsudo ovs-vsctl del-managersudo ovs-vsctl del-br br-intsudo ovs-vsctl del-br br-tunsudo ovs-vsctl del-br br-ex local_ip=""provider_mappings=""gnflush_ip=""external_provider=""function usage {    local rc=$1    local outstr=$2    if [ "$outstr" != "" ]; then        echo "$outstr"        echo    fi    echo "Usage: `basename $0` [OPTION...]"    echo    echo "Script options:"    echo "  --local_ip IP                 IP address of the node, will be used as tunnel endpoint"    echo "  --provider_mappings MAPPINGS  physical provider mappings, i.e physnet1:eth1,physnet2:eth2"    echo "  --gnflush_ip IP               IP address of GNFlush controller"    echo "  --external_provider           physical provider, only for network node"    echo    echo "Help options:"    echo "  -?, -h, --h, --help  Display this help and exit"    echo    exit $rc}function parse_options {    while true ; do        case "$1" in        --local_ip)            shift; local_ip="$1"; shift            ;;         --provider_mappings)            shift; provider_mappings="$1"; shift            ;;         --gnflush_ip)            shift; gnflush_ip="$1"; shift            ;;         --external_provider)            shift; external_provider="$1"; shift            ;;      -? | -h | --h | --help)            usage 0            ;;        "")            break            ;;        *)            echo "Ignoring unknown option: $1"; shift;        esac    done}parse_options "$@"if [ `whoami` != "root" ]; then    usage 1 "Please execute this script as superuser or with sudo previleges."firead ovstbl <<< $(ovs-vsctl get Open_vSwitch . _uuid)if [ -n "$provider_mappings" ]; then    sudo ovs-vsctl set Open_vSwitch $ovstbl other_config:provider_mappings=$provider_mappingsfiif [ -n "$local_ip" ]; then    sudo ovs-vsctl set Open_vSwitch $ovstbl other_config:local_ip=$local_ipfiif [ -n "$gnflush_ip" ]; then    echo "setting gnflush_ip=$gnflush_ip"    sudo ovs-vsctl set-manager tcp:$gnflush_ip:6640fiif [ -n "$external_provider" ]; then    echo "create external network through $external_provider"    sudo ifconfig $external_provider promisc    sudo ovs-vsctl add-br br-ex    sudo ovs-vsctl add-port br-ex $external_provider    sudo ovs-vsctl set-controller br-ex tcp:$gnflush_ip:6633fisleep 1sudo ovs-vsctl showexit 0mechanism_gnflush.py# Copyright (c) 2013-2014 OpenStack Foundation# All Rights Reserved.##    Licensed under the Apache License, Version 2.0 (the "License"); you may#    not use this file except in compliance with the License. You may obtain#    a copy of the License at##         http://www.apache.org/licenses/LICENSE-2.0##    Unless required by applicable law or agreed to in writing, software#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the#    License for the specific language governing permissions and limitations#    under the License.# @author: Kyle Mestery, Cisco Systems, Inc.# @author: Dave Tucker, Hewlett-Packard Development Company L.P.import timefrom oslo.config import cfgimport requestsfrom neutron.common import constants as n_constfrom neutron.common import exceptions as n_excfrom neutron.common import utilsfrom neutron.extensions import portbindingsfrom neutron.openstack.common import excutilsfrom neutron.openstack.common import jsonutilsfrom neutron.openstack.common import logfrom neutron.plugins.common import constantsfrom neutron.plugins.ml2 import driver_api as apiLOG = log.getLogger(__name__)ODL_NETWORK = 'network'ODL_NETWORKS = 'networks'ODL_SUBNET = 'subnet'ODL_SUBNETS = 'subnets'ODL_PORT = 'port'ODL_PORTS = 'ports'not_found_exception_map = {ODL_NETWORKS: n_exc.NetworkNotFound,                           ODL_SUBNETS: n_exc.SubnetNotFound,                           ODL_PORTS: n_exc.PortNotFound}odl_opts = [    cfg.StrOpt('url',               help=_("HTTP URL of OpenDaylight REST interface.")),    cfg.StrOpt('username',               help=_("HTTP username for authentication")),    cfg.StrOpt('password', secret=True,               help=_("HTTP password for authentication")),    cfg.IntOpt('timeout', default=10,               help=_("HTTP timeout in seconds.")),    cfg.IntOpt('session_timeout', default=30,               help=_("Tomcat session timeout in minutes.")),]cfg.CONF.register_opts(odl_opts, "ml2_gnflush")def try_del(d, keys):    """Ignore key errors when deleting from a dictionary."""    for key in keys:        try:            del d[key]        except KeyError:            passclass JsessionId(requests.auth.AuthBase):    """Attaches the JSESSIONID and JSESSIONIDSSO cookies to an HTTP Request.    If the cookies are not available or when the session expires, a new    set of cookies are obtained.    """    def __init__(self, url, username, password):        """Initialization function for JsessionId."""        # NOTE(kmestery) The 'limit' paramater is intended to limit how much        # data is returned from ODL. This is not implemented in the Hydrogen        # release of OpenDaylight, but will be implemented in the Helium        # timeframe. Hydrogen will silently ignore this value.        self.url = str(url) + '/' + ODL_NETWORKS + '?limit=1'        self.username = username        self.password = password        self.auth_cookies = None        self.last_request = None        self.expired = None        self.session_timeout = cfg.CONF.ml2_gnflush.session_timeout * 60        self.session_deadline = 0    def obtain_auth_cookies(self):        """Make a REST call to obtain cookies for ODL authenticiation."""        r = requests.get(self.url, auth=(self.username, self.password))        r.raise_for_status()        jsessionid = r.cookies.get('JSESSIONID')        jsessionidsso = r.cookies.get('JSESSIONIDSSO')        if jsessionid and jsessionidsso:            self.auth_cookies = dict(JSESSIONID=jsessionid,                                     JSESSIONIDSSO=jsessionidsso)    def __call__(self, r):        """Verify timestamp for Tomcat session timeout."""        if time.time() > self.session_deadline:            self.obtain_auth_cookies()        self.session_deadline = time.time() + self.session_timeout        r.prepare_cookies(self.auth_cookies)        return rclass GNFlushMechanismDriver(api.MechanismDriver):    """Mechanism Driver for OpenDaylight.    This driver was a port from the Tail-F NCS MechanismDriver.  The API    exposed by GNFlush is slightly different from the API exposed by NCS,    but the general concepts are the same.    """    auth = None    out_of_sync = True    def initialize(self):        self.url = cfg.CONF.ml2_gnflush.url        self.timeout = cfg.CONF.ml2_gnflush.timeout        self.username = cfg.CONF.ml2_gnflush.username        self.password = cfg.CONF.ml2_gnflush.password        self.auth = JsessionId(self.url, self.username, self.password)        self.vif_type = portbindings.VIF_TYPE_OVS        self.vif_details = {portbindings.CAP_PORT_FILTER: True}    # Postcommit hooks are used to trigger synchronization.    def create_network_postcommit(self, context):        self.synchronize('create', ODL_NETWORKS, context)    def update_network_postcommit(self, context):        self.synchronize('update', ODL_NETWORKS, context)    def delete_network_postcommit(self, context):        self.synchronize('delete', ODL_NETWORKS, context)    def create_subnet_postcommit(self, context):        self.synchronize('create', ODL_SUBNETS, context)    def update_subnet_postcommit(self, context):        self.synchronize('update', ODL_SUBNETS, context)    def delete_subnet_postcommit(self, context):        self.synchronize('delete', ODL_SUBNETS, context)    def create_port_postcommit(self, context):        self.synchronize('create', ODL_PORTS, context)    def update_port_postcommit(self, context):        self.synchronize('update', ODL_PORTS, context)    def delete_port_postcommit(self, context):        self.synchronize('delete', ODL_PORTS, context)    def synchronize(self, operation, object_type, context):        """Synchronize ODL with Neutron following a configuration change."""        if self.out_of_sync:            self.sync_full(context)        else:            self.sync_object(operation, object_type, context)    def filter_create_network_attributes(self, network, context, dbcontext):        """Filter out network attributes not required for a create."""        try_del(network, ['status', 'subnets'])    def filter_create_subnet_attributes(self, subnet, context, dbcontext):        """Filter out subnet attributes not required for a create."""        pass    def filter_create_port_attributes(self, port, context, dbcontext):        """Filter out port attributes not required for a create."""        self.add_security_groups(context, dbcontext, port)        # TODO(kmestery): Converting to uppercase due to ODL bug        # https://bugs.opendaylight.org/show_bug.cgi?id=477        port['mac_address'] = port['mac_address'].upper()        try_del(port, ['status'])    def sync_resources(self, resource_name, collection_name, resources,                       context, dbcontext, attr_filter):        """Sync objects from Neutron over to OpenDaylight.        This will handle syncing networks, subnets, and ports from Neutron to        OpenDaylight. It also filters out the requisite items which are not        valid for create API operations.        """        to_be_synced = []        for resource in resources:            try:                urlpath = collection_name + '/' + resource['id']                self.sendjson('get', urlpath, None)            except requests.exceptions.HTTPError as e:                if e.response.status_code == 404:                    attr_filter(resource, context, dbcontext)                    to_be_synced.append(resource)        key = resource_name if len(to_be_synced) == 1 else collection_name        # 400 errors are returned if an object exists, which we ignore.        self.sendjson('post', collection_name, {key: to_be_synced}, [400])    @utils.synchronized('odl-sync-full')    def sync_full(self, context):        """Resync the entire database to ODL.        Transition to the in-sync state on success.        Note: we only allow a single thead in here at a time.        """        if not self.out_of_sync:            return        dbcontext = context._plugin_context        networks = context._plugin.get_networks(dbcontext)        subnets = context._plugin.get_subnets(dbcontext)        ports = context._plugin.get_ports(dbcontext)        self.sync_resources(ODL_NETWORK, ODL_NETWORKS, networks,                            context, dbcontext,                            self.filter_create_network_attributes)        self.sync_resources(ODL_SUBNET, ODL_SUBNETS, subnets,                            context, dbcontext,                            self.filter_create_subnet_attributes)        self.sync_resources(ODL_PORT, ODL_PORTS, ports,                            context, dbcontext,                            self.filter_create_port_attributes)        self.out_of_sync = False    def filter_update_network_attributes(self, network, context, dbcontext):        """Filter out network attributes for an update operation."""        try_del(network, ['id', 'status', 'subnets', 'tenant_id'])    def filter_update_subnet_attributes(self, subnet, context, dbcontext):        """Filter out subnet attributes for an update operation."""        try_del(subnet, ['id', 'network_id', 'ip_version', 'cidr',                         'allocation_pools', 'tenant_id'])    def filter_update_port_attributes(self, port, context, dbcontext):        """Filter out port attributes for an update operation."""        self.add_security_groups(context, dbcontext, port)        try_del(port, ['network_id', 'id', 'status', 'mac_address',                       'tenant_id', 'fixed_ips'])    create_object_map = {ODL_NETWORKS: filter_create_network_attributes,                         ODL_SUBNETS: filter_create_subnet_attributes,                         ODL_PORTS: filter_create_port_attributes}    update_object_map = {ODL_NETWORKS: filter_update_network_attributes,                         ODL_SUBNETS: filter_update_subnet_attributes,                         ODL_PORTS: filter_update_port_attributes}    def sync_single_resource(self, operation, object_type, obj_id,                             context, attr_filter_create, attr_filter_update):        """Sync over a single resource from Neutron to OpenDaylight.        Handle syncing a single operation over to OpenDaylight, and correctly        filter attributes out which are not required for the requisite        operation (create or update) being handled.        """        dbcontext = context._plugin_context        if operation == 'create':            urlpath = object_type            method = 'post'        else:            urlpath = object_type + '/' + obj_id            method = 'put'        try:            obj_getter = getattr(context._plugin, 'get_%s' % object_type[:-1])            resource = obj_getter(dbcontext, obj_id)        except not_found_exception_map[object_type]:            LOG.debug(_('%(object_type)s not found (%(obj_id)s)'),                      {'object_type': object_type.capitalize(),                      'obj_id': obj_id})        else:            if operation == 'create':                attr_filter_create(self, resource, context, dbcontext)            elif operation == 'update':                attr_filter_update(self, resource, context, dbcontext)            try:                # 400 errors are returned if an object exists, which we ignore.                self.sendjson(method, urlpath, {object_type[:-1]: resource},                              [400])            except Exception:                with excutils.save_and_reraise_exception():                    self.out_of_sync = True    def sync_object(self, operation, object_type, context):        """Synchronize the single modified record to ODL."""        obj_id = context.current['id']        self.sync_single_resource(operation, object_type, obj_id, context,                                  self.create_object_map[object_type],                                  self.update_object_map[object_type])    def add_security_groups(self, context, dbcontext, port):        """Populate the 'security_groups' field with entire records."""        groups = [context._plugin.get_security_group(dbcontext, sg)                  for sg in port['security_groups']]        port['security_groups'] = groups    def sendjson(self, method, urlpath, obj, ignorecodes=[]):        """Send json to the OpenDaylight controller."""        headers = {'Content-Type': 'application/json'}        data = jsonutils.dumps(obj, indent=2) if obj else None        if self.url:            url = '/'.join([self.url, urlpath])            LOG.debug(_('ODL-----> sending URL (%s) <-----ODL') % url)            LOG.debug(_('ODL-----> sending JSON (%s) <-----ODL') % obj)            r = requests.request(method, url=url,                                 headers=headers, data=data,                                 auth=self.auth, timeout=self.timeout)            # ignorecodes contains a list of HTTP error codes to ignore.            if r.status_code in ignorecodes:                return            r.raise_for_status()    def bind_port(self, context):        LOG.debug(_("Attempting to bind port %(port)s on "                    "network %(network)s"),                  {'port': context.current['id'],                   'network': context.network.current['id']})        for segment in context.network.network_segments:            if self.check_segment(segment):                context.set_binding(segment[api.ID],                                    self.vif_type,                                    self.vif_details,                                    status=n_const.PORT_STATUS_ACTIVE)                LOG.debug(_("Bound using segment: %s"), segment)                return            else:                LOG.debug(_("Refusing to bind port for segment ID %(id)s, "                            "segment %(seg)s, phys net %(physnet)s, and "                            "network type %(nettype)s"),                          {'id': segment[api.ID],                           'seg': segment[api.SEGMENTATION_ID],                           'physnet': segment[api.PHYSICAL_NETWORK],                           'nettype': segment[api.NETWORK_TYPE]})    def check_segment(self, segment):        """Verify a segment is valid for the OpenDaylight MechanismDriver.        Verify the requested segment is supported by ODL and return True or        False to indicate this to callers.        """        network_type = segment[api.NETWORK_TYPE]        return network_type in [constants.TYPE_LOCAL, constants.TYPE_GRE,                                constants.TYPE_VXLAN]

转载地址:http://eznqi.baihongyu.com/

你可能感兴趣的文章
js字符串常用属性与方法
查看>>
C++递归算法案例
查看>>
C++算法——异或运算解决出现次数问题
查看>>
C++数据结构——顺序栈(基本代码实现与案例)
查看>>
C++数据结构——链队列(基本代码实现与案例)
查看>>
C++数据结构——顺序表的查找(简单顺序查找、有序表的二分查找、索引顺序的查找)
查看>>
Hive 常用统计查询语句
查看>>
对象存储产生背景、发展历史、原理架构、优缺点、应用场景及开源项目对比
查看>>
Apache Ozone 分布式对象存储系统相关文档汇总
查看>>
Ozone 与 HDDS 的区别与联系
查看>>
maven失败测试用例rerun插件使用方法
查看>>
Python基础(三)
查看>>
Python入门NLP(二)
查看>>
四行Python代码,你也能从图片上识别文字!
查看>>
内网映射外网工具-ngrok
查看>>
Python带你朗读网页
查看>>
关于python,这些知识点你学会了吗?
查看>>
利用selenium爬取《西虹市首富影评》
查看>>
Python验证码识别
查看>>
机器学习、NLP和Python教程分享
查看>>