Shared posts

25 Nov 07:03

支持 Markdown 的网页 slides 工具总结

by Baohua Yang
mathematrix

write ppt without ppt

支持 Markdown 的网页 slides 工具总结

在注重效率的今天,很多人都不喜欢制作 PPT,特别是技术人员。流行的 PPT 制作工具往往需要用户关注太多内容无关的细节。而像 TeX 这样强大的系统又似乎太过于重量级了。
现在越来越流行制作网页格式的 slides,并通过浏览器来播放和发布。 这样做有很多优点:包括跨平台(特别在移动端)、无需特殊软件支持、分享方便、轻量级等。
可惜并不是所有人都精通网页编程技术,即便是最常见的 html 和 css,也常常造成 slides 编写人员的困扰。
一款最理想的 slides 制作工具,应该做到让用户只需要关心内容,其它的包括布局、风格等都交由软件来完成。
随着 HTML5 的流行,现在已经出现了不少这方面的工具,允许用户用 Markdown 等文本标记语言来编写好内容。它们能自动转化为网页形式,添加合适的主题,获得不亚于 PPT 软件的播放效果,甚至更为强大。
在这里介绍几种支持 Markdown 来编写内容的网页 slides 生成工具。
对于 Markdown 不熟悉的读者,推荐先花个五分钟,简单看下 Markdown 的简单语法:http://daringfireball.net/projects/Markdown。顺便说下,本文就是使用 Markdown 编写的。

slidedown

比较早期的一个利用 Markdown 生成网页 slides 的工具。
它功能比较单一,特色是支持语法高亮。后面出现的不少工具都参考了它,功能要更加丰富。
  • 官方网站:https://github.com/nakajima/slidedown
  • 推荐指数:3.5

Remarkjs

remarkjs 是一个简单的基于 Markdown 的网页 slides 生成工具。
remarkjs 生成的网页 slides 支持语法高亮、响应式设计。

Reveal.js

Reveal.js 是基于 Node.js 实现的一个网页 slides 编写框架。
它支持自定义快捷键、嵌套 slides、在网页中直接嵌入 Markdown 语法、导出 pdf、备注和 JavaScript 等强大功能。
它需要用户对 HTML 和 CSS 相关技术比较熟悉一些。支持的很多参数可以直接在模板中进行配置,比如自动翻页、快捷键等。

slidify

slidify 支持 RMarkdown 语言,可以利用 RMarkdown 文件生成效果惊艳的 HTML5 页面,并且支持发布到 GitHub、RPub、DropBox 等,当然这就需要用户熟悉 Markdown,R、LaTex 等多种语言的语法。
R Markdown 语言是 RStudio 支持的一种专门为编写 HTML 5 的 slides 而提出的混合型语言,它可以很好的混合利用 Markdown,R、LaTex 等多种语言来生成十分复杂的页面。

KeyDown

KeyDown 与 Showoff, Slidedown, HTML5 Rocks 等类似,也是专注生成单页的网页 slides。 它基于 Ruby 语言编写,利用 deck.js 转化 Markdown 为网页 slides。支持语法高亮、背景图片、快捷键等。用户要自定义一些风格可以修改 css 文件。
其使用过程十分简单,需要 Ruby、Rubygems 环境。
$ gem install keydown
$ keydown generate my_presentation
然后编写 Markdown 文件,并可以调整 css,之后进行转换。
$ keydown slides

Showoff

Showoff 是一个成熟的网页 slides 生成工具,基于 Ruby 语言编写。它借鉴了 S5 (http://meyerweb.com/eric/tools/s5/)。设计了基于 Markdown 的 DSL,因此,用户只需要懂 Markdown 语法即可。
举一个例子。
!SLIDE

# My Presentation #!

SLIDE bullets incremental transition=fade

# Bullet Points #

* first point
* second point
* third point
所生成的网页 slides 功能十分全面,可以说,能想到的功能都实现了,包括语法高亮、菜单、JavaScript、效果、备注等等。
安装和使用。
$ gem install showoff
$ git clone (showoff-repo)
$ cd (showoff-repo)
$ showoff serve
showoff 唯一的问题可能是它已经有数年没有更新了,不然完全可以打 5 分。

cleaver

cleaver 可以将 Markdown 文档转化为网页格式,基于 Ruby 语言编写,同样支持基于 Markdown 的 DSL。它的整体设计风格也是十分简约,作者还有其它几个很 cute 的小工具。
下面给出一个 clever 支持的文件的例子。
title: Basic Example
author:
name: Jordan Scales
twitter: jdan
url: http://jordanscales.com
output: basic.html
controls: true

--

# Cleaver 101
## A first look at quick HTML presentations

--

### A textual example

Content can be written in **Markdown!** New lines no longer need two angle brackets.

This will be in a separate paragraph

--

### A list of things

* Item 1
* Item B
* Item gamma

No need for multiple templates!
安装和使用:
npm install -g cleaver
cleaver path/to/something.md
它支持多种主题和语法高亮等功能,
  • 官方网站:http://jdan.github.io/cleaver/
  • 项目维护:https://github.com/jdan/cleaver
  • 推荐指数:4.5

Landslide

Landslide 可以利用 markdown、ReST 或 textile 格式文件生成 HTML5 的网页 slides,参考了 Google 的 html5slides。
Landslide 基于 Python 开发,最大的优点就是简洁,从安装到编写,到生成的 slides 风格都十分简洁。整个过程,用户只需要懂 Markdown 语法就可以。
此外,它的配置文件(.cfg)使用了类似 ini 文件的语法,也很容易理解。
安装:
pip install landslide
运行
landslide slides.md
默认会生成 html 格式的 slides。
是不是很简单?
此外,它还支持生成 pdf、快捷键、备注、自定义主题、CSS、JavaScript、注册新的语法宏等高级功能。

其它

HTML5 Rocks 是一个基于 HTML5 的强大的网页 slides 展示系统。它生成的网页可以支持多种风格,但需要用户掌握一定的网页编程技术。
另外,可能有人会推荐 Prezi(http://prezi.com),我也为 Prezi 的动态效果所惊讶。但对于关注内容的人员(特别是工程师)来说,并不十分推荐 Prezi,Prezi 往往更适合于设计或市场营销人员。
http://yeasy.blogspot.com
16 Nov 04:15

OpenStack Community Weekly Newsletter (Post Paris)

by Stefano Maffulli
mathematrix

newsletter

How Operators Can Get Involved in Kilo #OpenStackSummit

Maish Saidel-Keesing participated in the Ops Summit: How to get involved in Kilo, and shared his notes from those sessions.

Development Reports from Summit

Relevant Conversations

Tips ‘n Tricks

Security Advisories and Notices

Upcoming Events

Other News

Got Answers?

Ask OpenStack is the go-to destination for OpenStack users. Interesting questions waiting for answers:

Welcome New Reviewers, Developers and Core Reviewers

jiangfei Cserey Szilard
ZongKai LI Ran Ziv
Swati Shukla Accela Zhao
Guillaume Giamarchi yatin
Seb Hughes François Bureau
Vadim Rutkovsky habuka036
Mark McDonagh Zengfa Gao
Craige McWhirter Jorge Munoz
juigil kishore Jobin Raju
Trung Trinh John Belamaric
Scott Lowe Seb Hughes
Roozbeh Shafiee David Caro
Mike Mason Craige McWhirter
Tan Lin Jan-Erik Mångs
David Caro Adolfo Duarte
Konstantinos Papadopoulos Tan Lin
Matteo Panella hossein zabolzadeh
Lena Vinod Pandarinathan
Michael Hagedorn Pieter
Major Hayden Lan Qi song
Magnus Lundin Vidyut
Arun S A G Inessa Vasilevskaya
Pratik Mallya Gil Meir
Brian Saville Dimitri Korsch
Chris Grivas Ian Adams
Marcin Karkocha Pratik Mallya
Yash Bathia
Wei Xiaoli
Mike Mason
Anton Arefiev
Yury Konovalov
Shang Yong

OpenStack Reactions

show-the-light

When a large company join the OpenStack foundation after ignoring it for a while

The weekly newsletter is a way for the community to learn about all the various activities occurring on a weekly basis. If you would like to add content to a weekly update or have an idea about this newsletter, please leave a comment.

 

30 Sep 01:58

How to configure and run Tempest for testing OpenStack Keystone Identity V3 API?

by Priti_Desai
mathematrix

pretty useful

Prerequisite: Please make sure you have an OpenStack IceHouse instance and configured it with Keystone V3. Also, my experimental OpenStack instance has cloud_admin set in policy.json as:"cloud_admin": "rule:admin_required and domain_id:default",Get the latest tempest resources from GitHub:Create a configuration file “tempest/etc/tempest.conf” for tempest using the sample file from “tempest/etc/tempest.conf.sample”. Sample configuration file has all the configurations commented out. Here is the list of all required configuration options for running testing identity V3 API:[idenitity] # Full URI of the OpenStack Identity API (Keystone), v2 uri=http://10.0.2.15:5000/v2.0/ # Full URI of the OpenStack Identity API (Keystone), v3 uri_v3=http://10.0.2.15:5000/v3/ # Identity API version to be used for authentication for API tests. auth_version=v3 # Admin username username=admin # API key to use when authenticating. password=openstack # Domain name for authentication (Keystone V3).The same domain # applies to user and project. domain_name=Default # Role required to administrate keystone. admin_role=admin # Administrative Username to use for Keystone API requests. admin_username=admin # API key to use when authenticating as admin. admin_password=openstack # Admin domain name for authentication (Keystone V3).The same # domain applies to user and project. admin_domain_name=Default # The endpoint type to use for the identity service. endpoint_type=publicURL # Catalog type of the Identity service. catalog_type=identity [identity-feature-enabled] # Is the v3 identity API enabled (boolean value) api_v3=trueAlways run tests in virtualenv which is created automatically with all the necessary dependencies with run_tempest.sh script.Ideally, the latest tempest test cases from github should be executed successfully. But I was running into few permissions issues. I modified few tempest source files and here is the list of changes I made:CHANGE-01: tempest/tempest/services/identity/v3/json/identity_client.pyUpdated def auth() from V3TokenClientJSON class:Original: if tenant is not None: _domain = dict(name=domain) project = dict(name=tenant, domain=_domain) scope = dict(project=project) creds['auth']['scope'] = scopeChanged to: if tenant is not None: _domain = dict(name=domain) scope = dict(domain=_domain) creds['auth']['scope'] = scopeRationale: This change is modifying scope of a token. Setting the token scope to domain level instead of project level. For admin operations like creating/updating/deleting projects, creating/updating users, authentication token should have domain level scope. This might not be the ideal fix as token scope is changed and set to domain level for all test cases. The same reason applies to the following change.Also, notice that we have not specified tenant_name and admin_tenant_name in tempest.conf which are mandatory without these changes.CHANGE-02: tempest/tempest/services/identity/v3/xml/identity_client.pyUpdated def auth() from V3TokenClientXML class:Original: if tenant is not None: project = common.Element('project', name=tenant) _domain = common.Element('domain', name=domain) project.append(_domain) scope = common.Element('scope') scope.append(project) auth.append(scope)Changed to: if tenant is not None: _domain = common.Element('domain', name=domain) scope = common.Element('scope') scope.append(_domain) auth.append(scope)CHANGE-03: tempest/tempest /auth.pyUpdated def _fill_credentials() from KeystoneV3AuthProvider class.Original:if domain is not None: if self.credentials.domain_id is None: self.credentials.domain_id = domain['id'] if self.credentials.domain_name is None: self.credentials.domain_name = domain['name']Changed to:if domain is not None: if self.credentials.user_domain_id is None: self.credentials.user_domain_id = domain['id'] if self.credentials.user_domain_name is None: self.credentials.user_domain_name = domain['name']Rationale: After changing scope of authentication tokens, credentials object is changed to use appropriate attributes, user_domain_id and user_domain_name.CHANGE-04: tempest/tempest/api/identity/base.pyUpdated def _try_wrapper() from DataGenerator class.Original: try: if kwargs: func(item['id'], kwargs)Changed to: try: if kwargs: func(item['id'], **kwargs)Rationale: This function has typo and kwargs should be passed as a packed dictionary. I have a Launchpad bug created for this change here.Now, try and execute tempest test cases with run_tempest.sh:You can also run tests in debug mode using –d option, like:./run_tempest.sh -d tempest.api.identity.admin.v3.test_domainsGood luck with Tempest and Keystone V3 !
11 Jun 04:17

Openstack Quantum L3 Agent

mathematrix

neutron

License: (CC 3.0) BY-NC-SA

quantum.agent.l3_agent

L3PluginApi(proxy.RpcProxy)

a wrapper of prc call, only support get_routers(…) and get_external_network_id(…)

RouterInfo(object)

  • store infomation of whole l3, not for router only
  • it seems just hold a single router, the router’s value is stored in self.router
  • _snat_action, according to current router info
  • iptables_manager

L3NATAgent(manager.Manager)

  • cfg: external_network_bridge, interface_driver, metadata_port, send_arp_for_ha, use_namespaces, router_id, handle_internal_only_routers, gateway_external_network_id, enable_metadata_proxy
  • l3 will destroy all namespaces when init, unless router_id is set
  • the destroy work is done by self.driver.unplug(device.name)
  • _router_added(id, router) will cache it, and _create_router_namespace(ri) if needed, then ri.ip_tables_managedr.apply(), metadata_proxy will be spawned if needed
  • process_router(ri) first scan all internal_ports of ri, append new ports, remove unactive ports, and do internal_network_{added,remove} respectively. if ex_gw_port is added, then invoke external_gateway_added(…) or if is removed, invoke external_gateway_removed(…). finally, update snat rules via ri.perform_snat_action(), update dnat via process_router_floating_ips() and update routes via routes.updated()
  • it seems ex_gw_port can have many fixed_ips and _handle_router_snat_rules(…) will just use the first one
  • process_router_floating_ips(ri, ex_gw_port) will scan all floating ips, if floating ip is new then floating_ip_added(…), if floating ip does not exist, floating_ip_removed(…), if floating ip -> fixed_ip has been changed, remapping it, floating_ip_removed() old and floating_ip_added new. ri cache is updated also.
  • external_gateway_added(…) firstly ensure device exists via self.driver.plug(…), then self.driver.init_l3(…), then _send_gratuitous_arp_packet(…), then if ex_gw_port subnet’s gateway_ip exists, set default route gw
  • external_gateway_removed(…) directly unplug the device
  • external_gateway_nat_rules(…) generate rules, including internal_network_nat_rules(ex_gw_ip, internal_cidr), and POSTROUTING ! -i $if ! -o $if -m conntrack ! --ctstate DNAT -j ACCEPT
  • internal_network_added(…) ensure device exist, then driver.init_l3(…) and _send_gratuitous_arp_packet(…)
  • internal_network_removed(…) will directly unplug device from driver
  • internal_network_nat_rules(ex_gw_ip, internal_cidr) just generate rule snat -s internal_cidr -j SNAT --to-source ex_gw_ip
  • floating_ip_added(…) get interface_name via get_external_device_name(ex_gw_port.id), create IPDevice, add floating ip to device.addr and _send_gratuitous_arp_packet, then for floating_forward_rules(floating_ip, fixed_ip) iptables_manager.apply() them
  • floating_ip_removed(…) will delete floating ip from device.addr and remove floating_forward_rules(…)
  • floating_forward_rules(…) is PREROUTING -d $float -j DNAT --to $fix, OUTPUT -d $float -j DNAT --to $fix, float-snat -s $fix -j SNAT --to $float
  • router_deleted -> _router_removed(id) with sync_sem and exception
  • routers_updated -> wrapper with sync_sem and exception
  • _process_routers(routers) remove routers not meet conditions and process others via process_router(ri)
  • routes_update(ri) will replace changed route and delete removed route

why external network cannot use as internal network?

internal_network_added() will get interface name from get_internal_device_name(port_id), however, external network has different device prefix, so it will plug a new device to driver, which can be wrong or fail? and the following driver.init_l3 and gratuitous arp packet may fail too

L3NATAgentWithStateReport(L3NATAgent)

a looping report wrapper of l3NatAgent

03 Jun 03:48

[原]在Ceph中创建虚拟机流程改进之分析

by epugv

作为个人学习笔记分享,有任何问题欢迎交流!

最近在Gerrit中看到一个change:https://review.openstack.org/#/c/94295/ , 它主要是对当前在Ceph中创建虚拟机的流程的改进。如果glance的backend是ceph, 则nova创建虚拟机到RBD的流程是这样的:

通过glance从ceph中下载image --> 本地 --> 复制image到rbd

 

这个change的目的就是:不需要下载到本地,直接在rbd中复制image,以提高虚拟机创建的速度。

 

以前只知道nova从glance下载image,作为虚拟机的base,却没有仔细的了解过这个过程,正好借这个机会,看一看nova创建虚拟机到rbd过程中关于image的部分。

1 nova创建VM时image的流程

 经过nova-scheduler选择节点后,创建VM的请求到达了nova-compute,即nova/compute/manager.py:ComputeManager._run_instance():

def _run_instance(self, context, request_spec,
                      filter_properties, requested_networks, injected_files,
                      admin_password, is_first_time, node, instance,
                      legacy_bdm_in_spec):
 

关于镜像的元数据就保存在request_spec,

image_meta = request_spec['image']

取得元数据后就开始build_instance()了,但是下面的过程与image没太大关系,所以从简带过。

def _build_instance(self, context, request_spec, filter_properties,
            requested_networks, injected_files, admin_password,   is_first_time,
node, instance, image_meta, legacy_bdm_in_spec)
---->
def _spawn(self, context, instance, image_meta, network_info,
               block_device_info, injected_files, admin_password,
               set_access_ip=False)
---->
 
self.driver.spawn(context, instance, image_meta,
                              injected_files, admin_password,
                              network_info,
                              block_device_info)

这里的driver就是你用的Hypervioser, 我用的是KVM,所以这个driver.spawn=nova/virt/libvirt/driver.py:LibvirtDriver.spawn():

def spawn(self, context, instance, image_meta, injected_files,
              admin_password,network_info=None,  block_device_info=None):
       disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,
                                            instance,
                                            block_device_info,
                                            image_meta)
        self._create_image(context, instance,
                           disk_info['mapping'],
                           network_info=network_info,
                           block_device_info=block_device_info,
                           files=injected_files,
                           admin_pass=admin_password)

那么这个disk_info['mapping']是什么呢?这里有一个方法,我们可以从test_libvirt_blockinfo.py里找到答案,所以结合测试用例来看代码真的很有用。在Nova/tests/virt/libvirt/test_libvirt_blockinfo.py:

LibvirtBlockInfoTest.test_get_disk_mapping_simple_swap()里可以看到:

      expect = {
            'disk': {'bus': 'virtio', 'dev': 'vda',
                     'type': 'disk', 'boot_index': '1'},
            'disk.local': {'bus': 'virtio', 'dev': 'vdb', 'type': 'disk'},
            'root': {'bus': 'virtio', 'dev': 'vda',
                     'type': 'disk', 'boot_index': '1'}
            }

Expect就是期望disk_info['mapping']的样子。

 

下面就开始创建VM的image了:

    def _create_image(self, context, instance,
                      disk_mapping, suffix='',
                      disk_images=None, network_info=None,
                      block_device_info=None, files=None,
                      admin_pass=None, inject_files=True):
#下面这个函数就是返回VM image格式的相关类,位于 #libvirt/imagebackend.py中,这里image是rbd, 返回的就是Rbd    #类。
def image(fname, image_type=CONF.libvirt.images_type):
            return self.image_backend.image(instance,
                                     fname + suffix, image_type)
......
 
  if not booted_from_volume:
            root_fname = imagecache.get_cache_fname(disk_images, 'image_id')#以image id作为文件名
            size = instance['root_gb'] * units.Gi
 
            if size == 0 or suffix == '.rescue':
                size = None
 
#这里有点复杂,用到了回调函数fetch_image,这里的cache是Rbd的父类#Image类的cache(),主要功能是从模板,也就是glance的image, 为VM创建一个image.
           image('disk').cache(fetch_func=libvirt_utils.fetch_image,
                                context=context,
                                filename=root_fname,
                                size=size,
                                image_id=disk_images['image_id'],
                                user_id=instance['user_id'],
                               project_id=instance['project_id'])
#可以在cache()中看到:
        if not self.check_image_exists() or not os.path.exists(base):
            self.create_image(fetch_func_sync, base, size,
                              *args, **kwargs)

那么现在到class Rbd下可以找到create_image():

 def create_image(self, prepare_template, base, size, *args, **kwargs):
        if self.rbd is None:
            raise RuntimeError(_('rbd python libraries not found'))
 
        if not os.path.exists(base):
            prepare_template(target=base, max_size=size, *args, **kwargs)##这里的prepare_temple()就是 libvirt_utils.fetch_image啦。
 

libvirt_utils.fetch_image=nova/virt/libvirt/utils.fetch_image():

def fetch_image(context, target, image_id, user_id, project_id, max_size=0):
    """Grab image."""
    images.fetch_to_raw(context, image_id, target, user_id, project_id,
                        max_size=max_size)
--->
 
def fetch_to_raw(context, image_href, path, user_id, project_id, max_size=0):
    path_tmp = "%s.part" % path
    fetch(context, image_href, path_tmp, user_id, project_id,
          max_size=max_size)
 
---->
def fetch(context, image_href, path, _user_id, _project_id, max_size=0):
(image_service, image_id) = glance.get_remote_image_service(
context, image_href)#从glance获取image
with fileutils.remove_path_on_error(path):
        #这里就是把image_id的数据download到path了。Download()位于   #nova/image/glance.py。
        image_service.download(context, image_id, dst_path=path)

回到class Rbd的create_image()中,

libvirt_utils.import_rbd_image(*args)把path的image数据写入rbd,至此,整个流程就到这里结束了。

2 Change中的改进

现在回到文章开始中提到的那个change, 看看它是怎么实现的。

首先它在fetch_to_raw中加入了一个判断。

def fetch_to_raw(context, image_href, path, user_id, project_id,  max_size=0):
#判断backend是否具有‘direct_fetch’的属性,如果有,则直接返回#direct_fetch()
    if backend and hasattr(backend, 'direct_fetch'):
        try:
            return backend.direct_fetch(context, image_href)
        except exception.ImageUnacceptable:
            LOG.debug(_('could not fetch directly, falling back to download'))

给Rbd类添加了一个属性:

    def direct_fetch(self, context, image_href):
#判断driver是否支持layering(分层
#http://ceph.com/docs/firefly/dev/rbd-layering/ ,指的是块设备
#的cow克隆,支持快速创建image)
        if not self.driver.supports_layering():
            reason = _('installed version of librbd does not support cloning')
            raise exception.ImageUnacceptable(image_id=image_href,
                                              reason=reason)
 
        image_meta, locations = images.get_meta(context, image_href)
        LOG.debug(_('Image locations are: %(locs)s') % {'locs': locations})
 
        if image_meta.get('disk_format') not in ['raw', 'iso']:
            reason = _('Image is not raw format')
            raise exception.ImageUnacceptable(image_id=image_href,
                                              reason=reason)
#克隆镜像(http://ceph.com/docs/master/rbd/librbdpy/)
        for location in locations:
            if self.driver.is_cloneable(location, image_meta):
                return self.driver.clone(location, self.rbd_name)
 
        reason = _('No image locations are accessible')
        raise exception.ImageUnacceptable(image_id=image_href, reason=reason)

这样就不需要想1中的那样先把image下载到local, 在写到rbd中,直接在rbd中克隆,从而提高了虚拟机的创建速度。

3总结

借这个机会既熟悉了创建VM时的image流程,又熟悉了ceph的用法,同时学习了高手们是怎么实现一个功能的,看来review的益处大大的呀。:)

作者:epugv 发表于2014/6/1 15:47:32 原文链接
阅读:1329 评论:4 查看评论
26 May 11:24

neutron中消息队列的应用(三)

by squarey

以dhcp agent为例,分析下rpc消息的处理流程,从producer到consumer的处理过程。

1.plugin到dhcp agent

1).plugin

plugin这边,在类DhcpAgentNotifyAPI中,来看下面cast和fanout_cast这两个函数:

a.cast方法
    def _notification_host(self, context, method, payload, host):
        """Notify the agent on host."""
        self.cast(
            context, self.make_msg(method,
                                   payload=payload),
            topic= '%s.%s' % (topics.DHCP_AGENT, host))

这个方法接着调用父类RpcProxy的cast方法,接着会根据rpc_backend,调用cast方法。

def cast(conf, context, topic, msg):
    """Sends a message on a topic without waiting for a response."""
    return rpc_amqp.cast(
        conf, context, topic, msg,
        rpc_amqp.get_connection_pool(conf, Connection))

调用ampq的cast方法:

def cast(conf, context, topic, msg, connection_pool):
    """Sends a message on a topic without waiting for a response."""
    LOG.debug(_('Making asynchronous cast on %s...'), topic)
    _add_unique_id(msg)
    pack_context(msg, context)
    with ConnectionContext(conf, connection_pool) as conn:
        conn.topic_send(topic, rpc_common.serialize_msg(msg))

调用Connection类的方法:

    def topic_send(self, topic, msg, timeout= None):
        """Send a 'topic' message."""
        self.publisher_send(TopicPublisher, topic, msg, timeout)

在这里会发送消息到topic publisher,这里初始化TopicPublisher,TopicPublisher使用父类Publisher初始化。
publisher_send方法:

    def publisher_send(self, cls, topic, msg, timeout= None, **kwargs):
        """Send to a publisher based on the publisher class."""

        def _error_callback(exc):
            log_info = { 'topic': topic, 'err_str': str(exc)}
            LOG.exception(_( "Failed to publish message to topic "
                          "'%(topic)s': %(err_str)s") % log_info)

        def _publish():
            publisher = cls( self.conf, self .channel, topic, **kwargs)
            publisher.send(msg, timeout)

        self.ensure(_error_callback, _publish)

这样就将消息发送出去了。

publiser.send方法:
将消息publish到指定的exchange。

    def send(self, msg, timeout= None):
        """Send a message."""
        if timeout:
            #
            # AMQP TTL is in milliseconds when set in the header.
            #
            self.producer.publish(msg, headers={'ttl' : (timeout * 1000)})
        else:
            self.producer.publish(msg)
b.fanout_cast方法

fanout_cast方法和上面的类似,只不过它的publisher是fanout publisher。

2).dhcp agent

dhcp agent服务启动时,指定topic为dhcp_agent。dhcp agent service使能rpc来监听消息队列。(l3 agent类似)

class Service(service.Service):
    """Service object for binaries running on hosts.

    A service takes a manager and enables rpc by listening to queues based
    on topic. It also periodically runs tasks on the manager.
    """

    def __init__(self, host, binary, topic, manager, report_interval=None,
                 periodic_interval= None, periodic_fuzzy_delay=None,
                 *args, **kwargs):

        self.binary = binary
        self.manager_class_name = manager
        manager_class = importutils.import_class(self.manager_class_name)
        self.manager = manager_class(host=host, *args, **kwargs)
        self.report_interval = report_interval
        self.periodic_interval = periodic_interval
        self.periodic_fuzzy_delay = periodic_fuzzy_delay
        self.saved_args, self.saved_kwargs = args, kwargs
        self.timers = []
        super(Service, self).__init__(host, topic, manager=self.manager)

它会去调用父类的方法建立rpc,建立rpc的代码在这里,源码目录neutron/openstack/common/rpc/service.py:

    def start(self):
        super(Service, self).start()

        self.conn = rpc.create_connection(new=True)
        LOG.debug(_( "Creating Consumer connection for Service %s") %
                  self.topic)

        dispatcher = rpc_dispatcher.RpcDispatcher([self.manager],
                                                  self.serializer)

        # Share this same connection for these Consumers
        self.conn.create_consumer(self.topic, dispatcher, fanout=False)

        node_topic = '%s.%s' % ( self.topic, self .host)
        self.conn.create_consumer(node_topic, dispatcher, fanout=False)

        self.conn.create_consumer(self.topic, dispatcher, fanout=True)

        # Hook to allow the manager to do other initializations after
        # the rpc connection is created.
        if callable(getattr(self.manager, 'initialize_service_hook', None )):
            self.manager.initialize_service_hook(self)

        # Consume from all consumers in a thread
        self.conn.consume_in_thread()

简要说明下start方法:
同之前分析的linux bridge plugin一样,它会建立rpc连接。
初始化dispatcher,负责处理消息。callback为manager,这个manager即DhcpAgentWithStateReport,DhcpAgentWithStateReport继承DhcpAgent,消息的处理会在DhcpAgent中进行。
接下来创建consumer,消费消息。

这样消息的produce,到exchange,queue的初始化,以及consumer的消费,都比较清晰了。

接下来就是消息的处理了,即callback。
rpc消息从plugin到dhcp agent,消息的处理时在类DhcpAgent中完成的。

3).rpc消息处理图

rpc消息图(plugin_to_dhcp agent)

2.dhcp agent到plugin

1).dhcp agent

在类DhcpAgent中,会使用call方法,它最终会根据rpc_backend来调用对应的call方法,这里还是kombu的:

def call(conf, context, topic, msg, timeout=None):
    """Sends a message on a topic and wait for a response."""
    return rpc_amqp.call(
        conf, context, topic, msg, timeout,
        rpc_amqp.get_connection_pool(conf, Connection))

接着会去调用amqp的rpc方法:

def call(conf, context, topic, msg, timeout, connection_pool):
    """Sends a message on a topic and wait for a response."""
    rv = multicall(conf, context, topic, msg, timeout, connection_pool)
    # NOTE(vish): return the last result from the multicall
    rv = list(rv)
    if not rv:
        return
    return rv[-1]

调用multicall方法:

def multicall(conf, context, topic, msg, timeout, connection_pool):
    """Make a call that returns multiple times."""
    LOG.debug(_('Making synchronous call on %s ...'), topic)
    msg_id = uuid.uuid4().hex
    msg.update({'_msg_id': msg_id})
    LOG.debug(_('MSG_ID is %s') % (msg_id))
    _add_unique_id(msg)
    pack_context(msg, context)

    with _reply_proxy_create_sem:
        if not connection_pool.reply_proxy:
            connection_pool.reply_proxy = ReplyProxy(conf, connection_pool)
    msg.update({'_reply_q': connection_pool.reply_proxy.get_reply_q()})
    wait_msg = MulticallProxyWaiter(conf, msg_id, timeout, connection_pool)
    with ConnectionContext(conf, connection_pool) as conn:
        conn.topic_send(topic, rpc_common.serialize_msg(msg), timeout)
    return wait_msg

在topic_send方法之前,在ReplyProxy这个类中,会创建direct consumer。

class ReplyProxy(ConnectionContext):
    """Connection class for RPC replies / callbacks ."""
    def __init__(self, conf, connection_pool):
        self._call_waiters = {}
        self._num_call_waiters = 0
        self._num_call_waiters_wrn_threshold = 10
        self._reply_q = 'reply_' + uuid.uuid4().hex
        super(ReplyProxy, self).__init__(conf, connection_pool, pooled=False)
        self.declare_direct_consumer(self._reply_q, self._process_data)
        self.consume_in_thread()

topic_send方法:
将消息发送到topic publisher。

    def topic_send(self, topic, msg, timeout= None):
        """Send a 'topic' message."""
        self.publisher_send(TopicPublisher, topic, msg, timeout)

返回wait_msg,这个值是从MulticallProxyWaiter中获取的:

class MulticallProxyWaiter(object):
    def __init__(self, conf, msg_id, timeout, connection_pool):
        self._msg_id = msg_id
        self._timeout = timeout or conf.rpc_response_timeout
        self._reply_proxy = connection_pool.reply_proxy
        self._done = False
        self._got_ending = False
        self._conf = conf
        self._dataqueue = queue.LightQueue()
        # Add this caller to the reply proxy's call_waiters
        self._reply_proxy.add_call_waiter(self , self._msg_id)
        self.msg_id_cache = _MsgIdCache()

    def put(self, data):
        self._dataqueue.put(data)

    def done(self):
        if self._done:
            return
        self._done = True
        # Remove this caller from reply proxy's call_waiters
        self._reply_proxy.del_call_waiter(self ._msg_id)

    def _process_data(self, data):
        result = None
        self.msg_id_cache.check_duplicate_message(data)
        if data['failure']:
            failure = data[ 'failure']
            result = rpc_common.deserialize_remote_exception(self._conf,
                                                             failure)
        elif data.get('ending', False):
            self._got_ending = True
        else:
            result = data[ 'result']
        return result

    def __iter__(self):
        """Return a result until we get a reply with an 'ending' flag."""
        if self._done:
            raise StopIteration
        while True:
            try:
                data = self._dataqueue.get(timeout=self._timeout)
                result = self._process_data(data)
            except queue.Empty:
                self.done()
                raise rpc_common.Timeout()
            except Exception:
                with excutils.save_and_reraise_exception():
                    self.done()
            if self._got_ending:
                self.done()
                raise StopIteration
            if isinstance(result, Exception):
                self.done()
                raise result
            yield result

在这个类中会调用_process_data方法处理消息,并返回结果。

和cast和fanout_cast不同的是,call调用需要返回结果。

那么call调用之后的返回是在哪里呢?
之前讲到,consumer在消费消息时,会使用callback(msg),这个会调用class ProxyCallback类的__call__方法,进行消息的处理。
消息处理完成后,检查result。会根据result进行判断,调用类RpcContext的reply方法:

class RpcContext(rpc_common.CommonRpcContext):
    """Context that supports replying to a rpc.call."""
    def __init__(self, **kwargs):
        self.msg_id = kwargs.pop('msg_id', None)
        self.reply_q = kwargs.pop('reply_q', None)
        self.conf = kwargs.pop('conf')
        super(RpcContext, self).__init__(**kwargs)

    def deepcopy(self):
        values = self.to_dict()
        values[ 'conf '] = self.conf
        values[ 'msg_id'] = self.msg_id
        values[ 'reply_q'] = self.reply_q
        return self.__class__(**values)

    def reply(self, reply= None, failure= None, ending= False,
              connection_pool= None, log_failure= True):
        if self.msg_id:
            msg_reply( self.conf, self .msg_id, self.reply_q, connection_pool,
                      reply, failure, ending, log_failure)
            if ending:
                self.msg_id = None

调用msg_reply方法,msg_reply方法会去调用direct_send方法,将rpc消息发送到direct publisher。
这样之前初始化的direct consumer就可以根据msg_id从队列中取消息了。

2).plugin

plugin rpc的初始化在前面已经讲到。

3).rpc消息处理图

rpc消息图(dhcp agent_to_plugin)

总结:很多细节问题需要再进一步弄清楚

More:
AMQP and Nova

25 May 12:18

Python Module: mock

mathematrix

python

License: (CC 3.0) BY-NC-SA

References

  1. http://www.voidspace.org.uk/python/mock/

mock是什麼

mock is a library for testing in Python. It allows you to replace parts of your system under test with mock objects and make assertions about how they have been used.

mock是用於測試的python庫,它允許你在測試時用mock對象替換系統的一部分,並允許斷言這些對象是如何使用的。

Mock is based on the ‘action -> assertion’ pattern instead of ‘record -> replay’ used by many mocking frameworks.

Mock是基於行爲->斷言模式的,而不是許多mocking框架(例如mox)採用的記錄->回放模式。最直接的一個好處就是快,不用跑兩遍,除此此外她還有許多優勢。

mock examples

sudo pip install mock

use start/stop:

1
2
3
4
p = mock.patch("package.module.Class")
m = p.start()
assert m is package.module.Class
p.stop()

use decorator:

1
2
3
4
@mock.patch.object(package.module.Class, 'method')
@mock.patch("package.module.Class")
def test_love(self, cls, method):
    assert cls is package.module.Class

use context:

1
2
with mock.patch("package.module.Class") as m:
    assert m is package.module.Class

something else:

>>> import mock
>>> class A(): pass
...
>>> a = A()
>>> a.method = mock.MagicMock()
>>> a.method.return_value = 3
>>> a.method()
3
>>> a.method.side_effect = Exception()
>>> a.method()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python2.7/dist-packages/mock.py", line 955, in __call__
    return _mock_self._mock_call(*args, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/mock.py", line 1010, in _mock_call
    raise effect
Exception

判断函数中某个参数以某个值被调用:

1
2
3
4
5
@mock.patch.object('path.to.class', 'method_name')
def test_something(self, mocked):
    do_some_thing()
    args, kwargs = mocked.call_args
    self.assertEqual(expected_url, kwargs.get('foo'))

我覺得mock就是無中生有和移花接木,mock的字面也有模擬的意思。mock有如下幾個常見用法

Mock

Mock is a flexible mock object intended to replace the use of stubs and test doubles throughout your code. Mocks are callable and create attributes as new mocks when you access them (there is an exception). Accessing the same attribute will always return the same mock. Mocks record how you use them, allowing you to make assertions about what your code has done to them.

Mock是一個彈性的mock對象,意圖取代打樁子再重複執行代碼的做法。Mock對象是可調用的,並且當你訪問屬性時會創建屬性作爲新的mock對象。訪問同一個屬性將會放回同一個mock對象。Mock對象記錄你是如何使用他們,允許你斷言你的代碼是如何使用她們的。

assert_called_with

assert_called_once_with

assert_any_call

assert the mock has been called with the specified arguments. The assert passes if the mock has ever been called, unlike assert_called_with() and assert_called_once_with() that only pass if the call is the most recent one.

assert_has_calls(calls, any_order=False)

reset_mock()

The reset_mock method resets all the call attributes on a mock object.

called

A boolean representing whether or not the mock object has been called

call_count

An integer telling you how many times the mock object has been called

return_value

side_effect

This can either be a function to be called when the mock is called, or an exception (class or instance) to be raised.

If you pass in a function it will be called with same arguments as the mock and unless the function returns the DEFAULT singleton the call to the mock will then return whatever the function returns. If the function returns DEFAULT then the mock will return its normal value (from the return_value.

call_args

This is either None (if the mock hasn’t been called), or the arguments that the mock was last called with. This will be in the form of a tuple: the first member is any ordered arguments the mock was called with (or an empty tuple) and the second member is any keyword arguments (or an empty dictionary).

call_args_list

This is a list of all the calls made to the mock object in sequence (so the length of the list is the number of times it has been called). Before any calls have been made it is an empty list. The call object can be used for conveniently constructing lists of calls to compare with call_args_list.

method_calls

As well as tracking calls to themselves, mocks also track calls to methods and attributes, and their methods and attributes

mock_calls

mock_calls records all calls to the mock object, its methods, magic methods and return value mocks.

MagicMock

MagicMock is a subclass of Mock with all the magic methods pre-created and ready to use.

MagicMock是Mock的子類,預創建了所有的魔法方法供你玩耍。

patch

The patch() decorators makes it easy to temporarily replace classes in a particular module with a Mock object. By default patch will create a MagicMock for you. You can specify an alternative class of Mock using the new_callable argument to patch.

patch裝飾器使得用Mock對象臨時代替特定模塊的類變成了毛毛雨。patch默認創建一個MagicMock給你。你可以指定一個可選的Mock類給參數new_callable。

patch

多用来patch类,经常使唤start/stop

patch.object

多用来patch对象,常见于上下文管理器,也适用于start/stop

patch.dict

patch.dict can be used to add members to a dictionary, or simply let a test change a dictionary, and ensure the dictionary is restored when the test ends.

patch.dict可以用来增加或修改字典对象,并确保测试结束后恢复原值。

patch.multiple

start, stop, stopall

All the patchers have start and stop methods. These make it simpler to do patching in setUp methods or where you want to do multiple patches without nesting decorators or with statements.

所有的patcher都有start和stop方法。她们简化了在setUp(参见unittest)中打patch,或者在打多个patch而不使用嵌套装饰器或者上下文管理器时的操作。

25 May 12:18

neutron中消息队列的应用(二)

by squarey
mathematrix

neutron mq

这部分来看下在agent端,消息队列的处理。包括l2 agent,l3 agent和dhcp agent。

linux bridge agent:
在linux bridge agent中,类LinuxBridgeNeutronAgentRPC实例化的对象就是l2 agent。
在这个类中,setup_rpc函数,建立rpc。

    def setup_rpc(self, physical_interfaces):
        if physical_interfaces:
            mac = utils.get_interface_mac(physical_interfaces[0 ])
        else:
            devices = ip_lib.IPWrapper(self.root_helper).get_devices( True)
            if devices:
                mac = utils.get_interface_mac(devices[ 0].name)
            else:
                LOG.error(_( "Unable to obtain MAC address for unique ID. "
                            "Agent terminated!"))
                exit( 1)
        self.agent_id = '%s%s' % ('lb', (mac.replace( ":", "")))
        LOG.info(_( "RPC agent_id: %s"), self.agent_id)

        self.topic = topics.AGENT
        self.plugin_rpc = LinuxBridgePluginApi(topics.PLUGIN)
        self.state_rpc = agent_rpc.PluginReportStateAPI(topics.PLUGIN)
        # RPC network init
        self.context = context.get_admin_context_without_session()
        # Handle updates from service
        self.callbacks = LinuxBridgeRpcCallbacks(self .context,
                                                 self)
        self.dispatcher = self.callbacks.create_rpc_dispatcher()
        # Define the listening consumers for the agent
        consumers = [[topics.PORT, topics.UPDATE],
                     [topics.NETWORK, topics.DELETE],
                     [topics.SECURITY_GROUP, topics.UPDATE]]
        if cfg.CONF.VXLAN.l2_population:
            consumers.append([topics.L2POPULATION,
                              topics.UPDATE, cfg.CONF.host])
        self.connection = agent_rpc.create_consumers(self.dispatcher,
                                                     self.topic,
                                                     consumers)
        report_interval = cfg.CONF.AGENT.report_interval
        if report_interval:
            heartbeat = loopingcall.FixedIntervalLoopingCall(
                self._report_state)
            heartbeat.start(interval=report_interval)

plugin_rpc用于l2 agent向linux bridge plugin发送rpc消息,topic为q-plugin。
state_rpc用于l2 agent上报状态,topic为q-plugin。

callbacks为LinuxBridgeRpcCallbacks对象。

定义了3个consumer,上文中已经提到,分别对应port update,network delete和security_group update。
如果支持VXLAN启用了l2 population,则添加这个consumer。

consumer的topic为q-agent-notifier。

rpc消息处理如图:
图1:
rpc消息图(plugin_to_l2agent)

图2:
rpc消息图(l2agent_to_plugin)

l3 agent
在linux bridge plugin的setup_rpc方法中,初始化l3 agent notifier,用于通知l3 agent。
从plugin到l3 agent的rpc消息有:
1.与l3 agent调度相关的,如l3 agent更新,l3 agent与network绑定/解绑定。
2.router删除,以及与router和floating ip相关的操作。

有两种消息发送的方法,cast和fanout_cast。

如图:
rpc消息图(plugin_to_l3 agent)

l3 agent服务启动时,会建立rpc连接,创建consumer,消费消息等步骤,这个和dhcp agent一样。
从l3 agent到plugin的消息,是在L3PluginApi类中实现的。
消息的处理在Linux bridge plugin的LinuxBridgeRpcCallbacks类中。

使用call方法,发送rpc消息。

如图:
rpc消息图(l3 agent_to_plugin)

dhcp agent
在linux bridge plugin的setup_rpc方法中,初始化dhcp agent notifier,用于通知dhcp agent。

从plugin到dhcp agent的rpc消息主要有两种:
1.在neutron api base函数中,关于network/subnet/port的创建、更新、删除操作会发送rpc消息到dhcp agent。
2.当dhcp agent更新或者network与dhcp agent关联/解关联时,会发送rpc消息。
有两种消息发送的方法,cast和fanout_cast。

dhcp agent服务启动时,会建立rpc连接,创建consumer,消费消息等步骤。
从dhcp agent到plugin的消息,是在DhcpPluginApi类中实现的。
消息的处理在Linux bridge plugin的LinuxBridgeRpcCallbacks类中。
使用call方法,发送rpc消息。

接下来,会以dhcp agent为例,分析rpc消息的处理流程。

25 May 02:34

neutron中消息队列的应用(一)

by squarey
mathematrix

neutron mq

neutron中使用RabbitMQ基本上都是RPC的方式,分几个部分来学习下neutron中rpc的应用。
这一部分,分析neutron server端关于rpc的处理,core plugin使用linux bridge plugin,rpc_backend为kombu。

neutron server
I版本代码,在neutron server启动时,除了启动api service外,还会启动rpc service,这个服务会去调用core plugin的start_rpc_listener函数。
默认情况下,core plugin(如linux bridge,ovs)会在初始化时建立rpc;
而在ml2的代码中,除了_setup_rpc函数外,还会有start_rpc_listener函数。

core plugin
以linux bridge plugin为例,LinuxBridgePluginV2在初始化时,_setup_rpc方法用于建立rpc。

    def _setup_rpc(self):
        # RPC support
        self.service_topics = {svc_constants.CORE: topics.PLUGIN,
                               svc_constants.L3_ROUTER_NAT: topics.L3PLUGIN}
        self.conn = rpc.create_connection(new=True)
        self.callbacks = LinuxBridgeRpcCallbacks()
        self.dispatcher = self.callbacks.create_rpc_dispatcher()
        for svc_topic in self.service_topics.values():
            self.conn.create_consumer(svc_topic, self.dispatcher, fanout=False)
        # Consume from all consumers in a thread
        self.conn.consume_in_thread()
        self.notifier = AgentNotifierApi(topics.AGENT)
        self.agent_notifiers[q_const.AGENT_TYPE_DHCP] = (
            dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
        )
        self.agent_notifiers[q_const.AGENT_TYPE_L3] = (
            l3_rpc_agent_api.L3AgentNotify
        )

处理流程:
1.初始化service_topics,q-plugin和q-l3-plugin。
q-plugin队列主要用于rpc消息从l2 agent、dhcp agent到plugin。
q-l3-plugin队列用于rpc消息从l3 agent到plugin。

2.create_connection方法建立rpc连接。使用kombu的create_connection方法:

def create_connection(conf, new=True):
    """Create a connection."""
    return rpc_amqp.create_connection(
        conf, new,
        rpc_amqp.get_connection_pool(conf, Connection))

amqp的get_connection_pool方法获取一个连接池,再去调用amqp的create_connection方法,返回一个ConnectionContext对象。

3.声明callbacks对象,即LinuxBridgeRpcCallbacks的实例,用于对消息的处理。
LinuxBridgeRpcCallbacks继承了下面3个类:

class LinuxBridgeRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin,
                              l3_rpc_base.L3RpcCallbackMixin,
                              sg_db_rpc.SecurityGroupServerRpcCallbackMixin
                              )

4.在callbacks中创建rpc dispatcher。返回一个PluginRpcDispatcher对象。
最后会返回rpc.dispatcher的dispatch方法,供后面的_process_data方法调用。

这个dispatcher可以理解为proxy object,proxy object是什么意思呢?

5.根据service_topic和dispatcher,创建2个consumer。

    def create_consumer(self, topic, proxy, fanout= False):
        self.connection.create_consumer(topic, proxy, fanout)

调用impl_kombu的create_consumer方法,创建consumer:

    def create_consumer(self, topic, proxy, fanout= False):
        """Create a consumer that calls a method in a proxy object."""
        proxy_cb = rpc_amqp.ProxyCallback(
            self.conf, proxy,
            rpc_amqp.get_connection_pool( self.conf, Connection))
        self.proxy_callbacks.append(proxy_cb)

        if fanout:
            self.declare_fanout_consumer(topic, proxy_cb)
        else:
            self.declare_topic_consumer(topic, proxy_cb)

proxy_cb是ProxyCallback类的对象,proxy_cb就是callback。

类ProxyCallback:
__call__方法读取message中的method,args等参数,然后建立一个新的绿色线程调用_process_data方法。

class ProxyCallback(_ThreadPoolWithWait):
    """Calls methods on a proxy object based on method and args."""

    def __init__(self, conf, proxy, connection_pool):
        super(ProxyCallback, self).__init__(
            conf=conf,
            connection_pool=connection_pool,
        )
        self.proxy = proxy
        self.msg_id_cache = _MsgIdCache()

    def __call__(self, message_data):
        """Consumer callback to call a method on a proxy object.

        Parses the message for validity and fires off a thread to call the
        proxy object method.

        Message data should be a dictionary with two keys:
            method: string representing the method to call
            args: dictionary of arg: value

        Example: {'method': 'echo', ' args': {'value': 42}}

        """
        # It is important to clear the context here, because at this point
        # the previous context is stored in local.store.context
        if hasattr(local.store, 'context'):
            del local.store.context
        rpc_common._safe_log(LOG.debug, _( 'received %s'), message_data)
        self.msg_id_cache.check_duplicate_message(message_data)
        ctxt = unpack_context( self.conf, message_data)
        method = message_data.get( 'method')
        args = message_data.get( 'args ', {})
        version = message_data.get( 'version')
        namespace = message_data.get( 'namespace ')
        if not method:
            LOG.warn(_( 'no method for message: %s') % message_data)
            ctxt.reply(_( 'No method for message: %s') % message_data,
                       connection_pool= self.connection_pool)
            return
        self.pool.spawn_n(self._process_data, ctxt, version, method,
                          namespace, args)

_process_data方法:
_process_data方法依据method处理message,调用之前传入的proxy(即dispatcher)的dispatch方法。

     def _process_data(self, ctxt, version, method, namespace, args):
        """Process a message in a new thread.

        If the proxy object we have has a dispatch method
        (see rpc.dispatcher.RpcDispatcher), pass it the version,
        method, and args and let it dispatch as appropriate.  If not, use
        the old behavior of magically calling the specified method on the
        proxy we have here.
        """
        ctxt.update_store()
        try:
            rval = self.proxy.dispatch(ctxt, version, method, namespace,
                                       **args)
            # Check if the result was a generator
            if inspect.isgenerator(rval):
                for x in rval:
                    ctxt.reply(x, None, connection_pool=self.connection_pool)
            else:
                ctxt.reply(rval, None, connection_pool=self.connection_pool)
            # This final None tells multicall that it is done.
            ctxt.reply(ending= True, connection_pool=self.connection_pool)
        except rpc_common.ClientException as e:
            LOG.debug(_( 'Expected exception during message handling (%s)') %
                      e._exc_info[ 1])
            ctxt.reply( None, e._exc_info,
                       connection_pool= self.connection_pool,
                       log_failure= False)
        except Exception:
            # sys.exc_info() is deleted by LOG.exception().
            exc_info = sys.exc_info()
            LOG.error(_( 'Exception during message handling' ),
                      exc_info=exc_info)
            ctxt.reply( None, exc_info, connection_pool=self.connection_pool)

定义了2个topic consumer,定义consumer的类型,队列名,exchange名,并把建立好的consumer加入到consumers列表中:

    def declare_topic_consumer(self, topic, callback= None, queue_name=None,
                               exchange_name= None, ack_on_error=True):
        """Create a 'topic' consumer."""
        self.declare_consumer(functools.partial(TopicConsumer,
                                                name=queue_name,
                                                exchange_name=exchange_name,
                                                ack_on_error=ack_on_error,
                                                ),
                              topic, callback)

declare_consumer函数:

     def declare_consumer(self, consumer_cls, topic, callback):
        """Create a Consumer using the class that was passed in and
        add it to our list of consumers
        """

        def _connect_error(exc):
            log_info = { 'topic': topic, 'err_str': str(exc)}
            LOG.error(_( "Failed to declare consumer for topic '%(topic)s': "
                      "%(err_str)s" ) % log_info)

        def _declare_consumer():
            consumer = consumer_cls( self.conf, self .channel, topic, callback,
                                    six.next( self.consumer_num))
            self.consumers.append(consumer)
            return consumer

        return self.ensure(_connect_error, _declare_consumer)

TopicConsumer类:

class TopicConsumer(ConsumerBase):
    """Consumer class for 'topic'."""

    def __init__(self, conf, channel, topic, callback, tag, name=None,
                 exchange_name= None, **kwargs):
        """Init a 'topic' queue.

        :param channel: the amqp channel to use
        :param topic: the topic to listen on
        :paramtype topic: str
        :param callback: the callback to call when messages are received
        :param tag: a unique ID for the consumer on the channel
        :param name: optional queue name, defaults to topic
        :paramtype name: str

        Other kombu options may be passed as keyword arguments
        """
        # Default options
        options = { 'durable': conf.amqp_durable_queues,
                   'queue_arguments': _get_queue_arguments(conf),
                   'auto_delete': conf.amqp_auto_delete,
                   'exclusive': False}
        options.update(kwargs)
        exchange_name = exchange_name or rpc_amqp.get_control_exchange(conf)
        exchange = kombu.entity.Exchange(name=exchange_name,
                                         type= 'topic',
                                         durable=options['durable'],
                                         auto_delete=options['auto_delete' ])
        super(TopicConsumer, self).__init__(channel,
                                            callback,
                                            tag,
                                            name=name or topic,
                                            exchange=exchange,
                                            routing_key=topic,
                                            **options)

6.consume_in_thread方法,消费消息。

    def consume_in_thread(self):
        return self.connection.consume_in_thread()

调用kombu的consume_in_thread方法:
启动一个绿色线程来运行方法_consumer_thread,进而调用consume。

    def consume_in_thread(self):
        """Consumer from all queues/consumers in a greenthread."""
        @excutils.forever_retry_uncaught_exceptions
        def _consumer_thread():
            try:
                self.consume()
            except greenlet.GreenletExit:
                return
        if self.consumer_thread is None :
            self.consumer_thread = eventlet.spawn(_consumer_thread)
        return self.consumer_thread

然后调用Connection类的consume方法。

    def consume(self, limit= None):
        """Consume from all queues/consumers."""
        it = self.iterconsume(limit=limit)
        while True:
            try:
                six.next(it)
            except StopIteration:
                return

调用Connection类的iterconsume方法:
在这个方法里,_consume方法会根据队列类型调用不同的consume方法。

    def iterconsume(self, limit= None, timeout= None):
        """Return an iterator that will consume from all queues/consumers."""

        info = { 'do_consume': True}

        def _error_callback(exc):
            if isinstance(exc, socket.timeout):
                LOG.debug(_( 'Timed out waiting for RPC response: %s') %
                          str(exc))
                raise rpc_common.Timeout()
            else:
                LOG.exception(_( 'Failed to consume message from queue: %s') %
                              str(exc))
                info[ 'do_consume'] = True

        def _consume():
            if info[ 'do_consume']:
                queues_head = self.consumers[:-1]  # not fanout.
                queues_tail = self.consumers[-1]  # fanout
                for queue in queues_head:
                    queue.consume(nowait= True)
                queues_tail.consume(nowait= False)
                info[ 'do_consume'] = False
            return self.connection.drain_events(timeout=timeout)

        for iteration in itertools.count(0):
            if limit and iteration >= limit:
                raise StopIteration
            yield self.ensure(_error_callback, _consume)

在_consume这个方法里会去调用TopicConsumer的父类ConsumerBase的consume方法,返回connection的drain_events方法,处理所有channel上的events。

ConsumerBase的consume方法:

    def consume(self, *args, **kwargs):
        """Actually declare the consumer on the amqp channel.  This will
        start the flow of messages from the queue.  Using the
        Connection.iterconsume() iterator will process the messages,
        calling the appropriate callback.

        If a callback is specified in kwargs, use that.  Otherwise,
        use the callback passed during __init__()

        If kwargs['nowait'] is True, then this call will block until
        a message is read.

        """

        options = { 'consumer_tag': self.tag}
        options[ 'nowait '] = kwargs.get( 'nowait ', False)
        callback = kwargs.get( 'callback', self.callback)
        if not callback:
            raise ValueError( "No callback defined")

        def _callback(raw_message):
            message = self.channel.message_to_python(raw_message)
            self._callback_handler(message, callback)

        self.queue.consume(*args, callback=_callback, **options)

定义了一个注册函数callback,在_callback方法中,将原始消息转换为python格式的;
在_callback_handler方法中,进行消息处理,并返回ack。

    def _callback_handler(self, message, callback):
        """Call callback with deserialized message.

        Messages that are processed without exception are ack'ed.

        If the message processing generates an exception, it will be
        ack'ed if ack_on_error=True. Otherwise it will be .requeue()'ed.
        """

        try:
            msg = rpc_common.deserialize_msg(message.payload)
            callback(msg)
        except Exception:
            if self.ack_on_error:
                LOG.exception(_("Failed to process message"
                                " ... skipping it."))
                message.ack()
            else:
                LOG.exception(_("Failed to process message"
                                " ... will requeue."))
                message.requeue()
        else:
            message.ack()

callback(msg)函数比较重要,在这里会根据参数传递过来的callback,会回到之前class ProxyCallback,调用__call__方法,进行消息的处理。

这样整个consumer的过程就建立完成了。

7.初始化notifier,类AgentNotifierApi的一个对象,用于向l2 agent发送rpc消息。topics是q-agent-notifier。
有3种消息,network-delete和port-update,还有一个是父类的,和安全组相关,security_group-update。
在这里exchanges有3种:
q-agent-notifier-network-delete_fanout,q-agent-notifier-port-update_fanout,q-agent-notifier-security_group-update_fanout。

因此对应的队列也有3种:

$ sudo rabbitmqctl list_queues name |grep q-agent-notifier
q-agent-notifier-network-delete_fanout_1a56043909134873b21a21b5bb811e7e
q-agent-notifier-port-update_fanout_b8069f4d79e949f584036fd3957f5dba
q-agent-notifier-security_group-update_fanout_c81f76cb3a664f24b2878e5b9a773500

network delete和port update时,会发送fanout消息。
当安全组的rule,member,provider更新时,都会发送fanout消息。

8.初始化dhcp agent notifier和l3 agent notifier。
用于通知dhcp agent和l3 agent,topics为dhcp_agent和l3_agent。

在DhcpAgentNotifyAPI和L3AgentNotify中,会有两种消息发送的方法,cast和fanout_cast。
cast方法将消息发送到TopicPublisher,会通知指定的host。exchange为neutron,queue为dhcp_agent.host或者l3_agent.host。

fanout_cast方法则会发送消息到FanoutPublisher,通知所有的dhcp agent或者l3 agent。exchange为dhcp_agent_fanout或l3_agent_fanout;queue则是dhcp_agent_fanout_uuid或l3_agent_fanout_uuid。

20 May 09:16

Heat中的AutoScaling(2)

声明:
本博客欢迎转发,但请保留原作者信息!
新浪微博:@孔令贤HW
博客地址:http://lingxiankong.github.io/
内容系本人学习、研究和总结,如有雷同,实属荣幸!


之前的一篇博客讲了AWS和Heat中AutoScaling的机制和大概的实现,本篇是上一篇的姊妹篇,主要讲autoscaling在heat中的简单使用。

环境准备

我们还是以一个模板为例,模板内容如下:

{
  "HeatTemplateFormatVersion": "2012-12-12",
  "Description": "Template which create a base autoscaling for launch base.",
  "Parameters": {
    "InstanceType": {
      "Type": "String"
    },
    "ImageId": {
      "Type": "String"
    },
    "VPCZoneIdentifier": {
      "Type": "CommaDelimitedList"
    },
    "MinSize": {
      "Type": "String"
    },
    "MaxSize": {
      "Type": "String"
    },
    "DesiredCapacity": {
      "Type": "Number"
    },
    "Cooldown": {
      "Type": "String",
      "Default": "0"
    }
  },
  "Resources": {
    "WebServerGroup": {
      "Type": "AWS::AutoScaling::AutoScalingGroup",
      "Properties": {
        "AvailabilityZones": {
          "Fn::GetAZs": ""
        },
        "VPCZoneIdentifier": {
          "Ref": "VPCZoneIdentifier"
        },
        "LaunchConfigurationName": {
          "Ref": "LaunchConfig"
        },
        "MinSize": {
          "Ref": "MinSize"
        },
        "MaxSize": {
          "Ref": "MaxSize"
        },
        "DesiredCapacity": {
          "Ref": "DesiredCapacity"
        },
        "Cooldown": {
          "Ref": "Cooldown"
        }
      }
    },
    "WebServerScaleUpPolicy": {
      "Type": "AWS::AutoScaling::ScalingPolicy",
      "Properties": {
        "AdjustmentType": "ChangeInCapacity",
        "AutoScalingGroupName": {
          "Ref": "WebServerGroup"
        },
        "ScalingAdjustment": "1"
      }
    },
    "CPUAlarmHigh": {
      "Type": "OS::Ceilometer::Alarm",
      "Properties": {
        "meter_name": "instance",
        "statistic": "avg",
        "period": "60",
        "evaluation_periods": "1",
        "threshold": "1",
        "repeat_actions": true,
        "matching_metadata": {
          "metadata.user_metadata.groupname": {
            "Ref": "WebServerGroup"
          }
        },
        "alarm_actions": [
          {
            "Fn::GetAtt": [
              "WebServerScaleUpPolicy",
              "AlarmUrl"
            ]
          }
        ],
        "comparison_operator": "eq"
      }
    },
    "LaunchConfig": {
      "Type": "AWS::AutoScaling::LaunchConfiguration",
      "Properties": {
        "ImageId": {
          "Ref": "ImageId"
        },
        "InstanceType": {
          "Ref": "InstanceType"
        }
      }
    }
  }
}

大致的意思我就不多说了,可以参考AWS的文档。只需要注意那个告警的定义,意思是,如果每60s检测内系统中的instance资源的统计平均值等于1,就触发告警。

这个条件总是成立,这里只是为了试验,这个告警在实际使用中没有任何意义

另外,我环境上的一些资源信息如下,创建stack时需要在参数中指定,注意环境中已有3个虚拟机:

UVP:~ # nova list
+--------------------------------------+--------------------------------------------+--------+------------+-------------+--------------------+
| ID                                   | Name                                       | Status | Task State | Power State | Networks           |
+--------------------------------------+--------------------------------------------+--------+------------+-------------+--------------------+
| 735f0e66-19f9-40b1-9c13-4588051d7270 | cirros_vm                                  | ACTIVE | -          | Running     | demo_net1=10.1.1.4 |
| 3d2a4042-7859-4d6d-8fdf-719006faffe8 | waitcondition_test-MyServer-uscavjnqqbna   | ACTIVE | -          | Running     | demo_net1=10.1.1.5 |
| 57702f03-859e-4d4d-81b5-d6d61dfd1a24 | waitcondition_test_1-MyServer-e5q5bf4g4qrz | ACTIVE | -          | Running     | demo_net1=10.1.1.6 |
+--------------------------------------+--------------------------------------------+--------+------------+-------------+--------------------+
UVP:/home/kong # nova flavor-list
+----+-----------+-----------+------+-----------+------+-------+-------------+-----------+
| ID | Name      | Memory_MB | Disk | Ephemeral | Swap | VCPUs | RXTX_Factor | Is_Public |
+----+-----------+-----------+------+-----------+------+-------+-------------+-----------+
| 1  | m1.tiny   | 512       | 1    | 0         |      | 1     | 1.0         | True      |
| 2  | m1.small  | 2048      | 20   | 0         |      | 1     | 1.0         | True      |
| 3  | m1.medium | 4096      | 40   | 0         |      | 2     | 1.0         | True      |
| 4  | m1.large  | 8192      | 80   | 0         |      | 4     | 1.0         | True      |
| 5  | m1.xlarge | 16384     | 160  | 0         |      | 8     | 1.0         | True      |
+----+-----------+-----------+------+-----------+------+-------+-------------+-----------+
nUVP:/home/kong # glance image-list
+--------------------------------------+----------------------------+-------------+------------------+-----------+--------+
| ID                                   | Name                       | Disk Format | Container Format | Size      | Status |
+--------------------------------------+----------------------------+-------------+------------------+-----------+--------+
| 414bcb4d-8e68-4dfe-8d21-31e9bb3d26fd | cirros                     | qcow2       | bare             | 9159168   | active |
| 72fb9278-0957-4f3e-bc9d-9a842c6f3788 | F17-x86_64-cfntools        | qcow2       | ovf              | 476704768 | active |
| 252a1c83-7b7d-4776-a586-667d0b7311f4 | Ubuntu 12.04 cloudimg i386 | qcow2       | ovf              | 213123072 | active |
+--------------------------------------+----------------------------+-------------+------------------+-----------+--------+
UVP:/home/kong # neutron subnet-list
+--------------------------------------+------------------+----------------+--------------------------------------------------------+
| id                                   | name             | cidr           | allocation_pools                                       |
+--------------------------------------+------------------+----------------+--------------------------------------------------------+
| 5e011976-d15b-45e3-8a63-62f98f8d4a42 | external_subnet1 | 200.200.0.0/16 | {"start": "200.200.200.100", "end": "200.200.200.120"} |
| a03be99b-b78f-46d0-9311-e45fd8bcf92d | demo_subnet1     | 10.1.1.0/24    | {"start": "10.1.1.2", "end": "10.1.1.254"}             |
+--------------------------------------+------------------+----------------+--------------------------------------------------------+

创建stack

UVP:/home/kong # heat stack-create autoscaling_test -f autoscaling.template -P "InstanceType=m1.tiny;ImageId=414bcb4d-8e68-4dfe-8d21-31e9bb3d26fd;VPCZoneIdentifier=a03be99b-b78f-46d0-9311-e45fd8bcf92d;MinSize=1;MaxSize=2;DesiredCapacity=1"
+--------------------------------------+----------------------+--------------------+----------------------+
| id                                   | stack_name           | stack_status       | creation_time        |
+--------------------------------------+----------------------+--------------------+----------------------+
| b4fcc73b-83f6-42fc-8847-75c5dd829251 | waitcondition_test   | CREATE_COMPLETE    | 2014-05-13T15:07:27Z |
| f51e784d-0fbf-40ba-a580-423a2419e819 | waitcondition_test_1 | CREATE_COMPLETE    | 2014-05-13T15:49:29Z |
| 0251d96f-af3a-4823-9aca-1c5fdea6475d | autoscaling_test     | CREATE_IN_PROGRESS | 2014-05-20T13:50:17Z |
+--------------------------------------+----------------------+--------------------+----------------------+
UVP:/home/kong # heat stack-list
+--------------------------------------+----------------------+-----------------+----------------------+
| id                                   | stack_name           | stack_status    | creation_time        |
+--------------------------------------+----------------------+-----------------+----------------------+
| b4fcc73b-83f6-42fc-8847-75c5dd829251 | waitcondition_test   | CREATE_COMPLETE | 2014-05-13T15:07:27Z |
| f51e784d-0fbf-40ba-a580-423a2419e819 | waitcondition_test_1 | CREATE_COMPLETE | 2014-05-13T15:49:29Z |
| 0251d96f-af3a-4823-9aca-1c5fdea6475d | autoscaling_test     | CREATE_COMPLETE | 2014-05-20T13:50:17Z |
+--------------------------------------+----------------------+-----------------+----------------------+

查询虚拟机,看到环境中新增了一个虚拟机,该虚拟机属于stack中的autoscaling组:

UVP:/home/kong # nova list
+--------------------------------------+-------------------------------------------------------+--------+------------+-------------+--------------------+
| ID                                   | Name                                                  | Status | Task State | Power State | Networks           |
+--------------------------------------+-------------------------------------------------------+--------+------------+-------------+--------------------+
| 37f6bcd2-6964-49f0-97f9-f5c3a7e3f979 | au-ServerGroup-f35skwv4ty7k-slap352x6bpb-fv5dnk46nqim | ACTIVE | -          | Running     | demo_net1=10.1.1.8 |
| 735f0e66-19f9-40b1-9c13-4588051d7270 | cirros_vm                                             | ACTIVE | -          | Running     | demo_net1=10.1.1.4 |
| 3d2a4042-7859-4d6d-8fdf-719006faffe8 | waitcondition_test-MyServer-uscavjnqqbna              | ACTIVE | -          | Running     | demo_net1=10.1.1.5 |
| 57702f03-859e-4d4d-81b5-d6d61dfd1a24 | waitcondition_test_1-MyServer-e5q5bf4g4qrz            | ACTIVE | -          | Running     | demo_net1=10.1.1.6 |
+--------------------------------------+-------------------------------------------------------+--------+------------+-------------+--------------------+

大概过上60秒,通过Ceilometer查询statistic,发现满足告警条件,会自动增加一个虚拟机:

UVP:/home/kong # nova list
+--------------------------------------+-------------------------------------------------------+--------+------------+-------------+--------------------+
| ID                                   | Name                                                  | Status | Task State | Power State | Networks           |
+--------------------------------------+-------------------------------------------------------+--------+------------+-------------+--------------------+
| 6245912b-1711-4efa-8baf-f0ef2adebaed | au-ServerGroup-f35skwv4ty7k-65khnyblzmr5-rtnas4aj4pah | ACTIVE | -          | Running     | demo_net1=10.1.1.9 |
| 37f6bcd2-6964-49f0-97f9-f5c3a7e3f979 | au-ServerGroup-f35skwv4ty7k-slap352x6bpb-fv5dnk46nqim | ACTIVE | -          | Running     | demo_net1=10.1.1.8 |
| 735f0e66-19f9-40b1-9c13-4588051d7270 | cirros_vm                                             | ACTIVE | -          | Running     | demo_net1=10.1.1.4 |
| 3d2a4042-7859-4d6d-8fdf-719006faffe8 | waitcondition_test-MyServer-uscavjnqqbna              | ACTIVE | -          | Running     | demo_net1=10.1.1.5 |
| 57702f03-859e-4d4d-81b5-d6d61dfd1a24 | waitcondition_test_1-MyServer-e5q5bf4g4qrz            | ACTIVE | -          | Running     | demo_net1=10.1.1.6 |
+--------------------------------------+-------------------------------------------------------+--------+------------+-------------+--------------------+

其他观察点

Ceilometer

通过Ceilometer看一下告警的信息:

UVP:/home/kong # ceilometer alarm-show -a 4a394c06-0653-43be-8be6-9ac33d2515c5
+---------------------------+--------------------------------------------------------------------------+
| Property                  | Value                                                                    |
+---------------------------+--------------------------------------------------------------------------+
| alarm_actions             | [u'http://172.25.150.8:8000/v1/signal/arn%3Aopenstack%3Aheat%3A%3A57314a |
|                           | 8980b34c6ba3ece5cd19ff6214%3Astacks%2Fautoscaling_test%2F0251d96f-       |
|                           | af3a-4823-9aca-1c5fdea6475d%2Fresources%2FWebServerScaleUpPolicy?Timesta |
|                           | mp=2014-05-20T13%3A50%3A26Z&SignatureMethod=HmacSHA256&AWSAccessKeyId=48 |
|                           | c2804055b04be988f3b5b9adeac486&SignatureVersion=2&Signature=6nWhUK8FTO63 |
|                           | qDLRp0xkWx68lQGlf%2BvZgB44Dm%2BujP8%3D']                                 |
| alarm_id                  | 4a394c06-0653-43be-8be6-9ac33d2515c5                                     |
| comparison_operator       | eq                                                                       |
| description               | Alarm when instance is eq a avg of 1.0 over 60 seconds                   |
| enabled                   | True                                                                     |
| evaluation_periods        | 1                                                                        |
| exclude_outliers          | False                                                                    |
| insufficient_data_actions | []                                                                       |
| meter_name                | instance                                                                 |
| name                      | autoscaling_test-CPUAlarmHigh-wrly4nxupktb                               |
| ok_actions                | []                                                                       |
| period                    | 60                                                                       |
| project_id                | 57314a8980b34c6ba3ece5cd19ff6214                                         |
| query                     | metadata.user_metadata.groupname == autoscaling_test-WebServerGroup-     |
|                           | f35skwv4ty7k                                                             |
| repeat_actions            | True                                                                     |
| state                     | alarm                                                                    |
| statistic                 | avg                                                                      |
| threshold                 | 1.0                                                                      |
| type                      | threshold                                                                |
| user_id                   | 6114fcc32cd342859d1462d932144faa                                         |
+---------------------------+--------------------------------------------------------------------------+

注意alarm_actions字段,其实就是一个URL,还记得Heat服务简介么?错过的童鞋请恶补一下吧。

Nova

看一下通过Heat中autoscaling创建的虚拟机有什么不同之处:

UVP:/home/kong # nova show 37f6bcd2-6964-49f0-97f9-f5c3a7e3f979
+--------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------+
| Property                             | Value                                                                                                                                          |
+--------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------+
| OS-DCF:diskConfig                    | MANUAL                                                                                                                                         |
| OS-EXT-AZ:availability_zone          | nova                                                                                                                                           |
| OS-EXT-SRV-ATTR:host                 | UVP                                                                                                                                            |
| OS-EXT-SRV-ATTR:hypervisor_hostname  | UVP                                                                                                                                            |
| OS-EXT-SRV-ATTR:instance_name        | instance-00000006                                                                                                                              |
| OS-EXT-STS:power_state               | 1                                                                                                                                              |
| OS-EXT-STS:task_state                | -                                                                                                                                              |
| OS-EXT-STS:vm_state                  | active                                                                                                                                         |
| OS-SRV-USG:launched_at               | 2014-05-20T13:50:25.000000                                                                                                                     |
| OS-SRV-USG:terminated_at             | -                                                                                                                                              |
| accessIPv4                           |                                                                                                                                                |
| accessIPv6                           |                                                                                                                                                |
| config_drive                         |                                                                                                                                                |
| created                              | 2014-05-20T13:50:19Z                                                                                                                           |
| demo_net1 network                    | 10.1.1.8                                                                                                                                       |
| flavor                               | m1.tiny (1)                                                                                                                                    |
| hostId                               | c058bf4191ba5b03d9410d850e050be5ab884df3fd0d804801d39d6f                                                                                       |
| id                                   | 37f6bcd2-6964-49f0-97f9-f5c3a7e3f979                                                                                                           |
| image                                | cirros (414bcb4d-8e68-4dfe-8d21-31e9bb3d26fd)                                                                                                  |
| key_name                             | -                                                                                                                                              |
| metadata                             | {"metering.groupname": "autoscaling_test-WebServerGroup-f35skwv4ty7k", "AutoScalingGroupName": "autoscaling_test-WebServerGroup-f35skwv4ty7k"} |
| name                                 | au-ServerGroup-f35skwv4ty7k-slap352x6bpb-fv5dnk46nqim                                                                                          |
| os-extended-volumes:volumes_attached | []                                                                                                                                             |
| progress                             | 0                                                                                                                                              |
| security_groups                      | default                                                                                                                                        |
| status                               | ACTIVE                                                                                                                                         |
| tenant_id                            | 57314a8980b34c6ba3ece5cd19ff6214                                                                                                               |
| updated                              | 2014-05-20T13:50:25Z                                                                                                                           |
| user_id                              | 6114fcc32cd342859d1462d932144faa                                                                                                               |
+--------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------+

请注意其中的metadata字段的内容。Ceilometer会根据这个字段匹配监控的虚拟机指标。

15 May 02:26

微信后台存储架构

by 童燕群
07 May 13:43

Openstack ceilometer 宿主机监控模块扩展

by duyuanyuan
mathematrix

ceilometer

《Openstack ceilometer监控项扩展》(http://eccp.csdb.cn/blog/?p=352)主要介绍了对虚拟机监控项扩展, 比较简单。如何在ceilometer框架基础上,增加对宿主机、服务等的监控?本文以宿主机监控为例,详细介绍扩展方法。

首先,总体介绍Ceilometer采集监控数据到持久化存储的流程,在compute节点上通过pollster的轮询机制获取宿主机的各监控数据,通过publisher调用rpc将监控数据发送到消息队列,collector端根据约定的topic接收相应管道发送的数据,并调用storage接口 进行持久化存储,具体流程如下图所示。

监控模块增加流程

其次,在该框架下如何扩展新的监控模块,顺其自然的想法即依次修改以上模块,其重点是配置文件的设置,需要定义发送端和接收端的topic、secrete和method才能将数据顺利发送到collector端进行存储。以下详细介绍各模块的修改方法。

一、compute数据采集pollster模块

该模块主要负责数据的采集,采集宿主机CPU、内存、网络等信息,封装成自定义数据格式后通过publisher发送到消息队列,该部分与ceilometer监控项扩展相似。
1、在ceilometer/ceilometer/compute/plugin.py中定义宿主机获取数据的基类。

class ServerPollster(plugin.PollsterBase):
    @abc.abstractmethod
    def get_samples(self, manager, cache, instances):

2、在ceilometer/ceilometer/compute下新建server_pollsters包,在该包下新建server_cpu.py、server_mem.py等。继承ServerPollster类实现get_samples方法,将获取的数据封装成定义的数据格式返回即可,可参考虚拟机采集数据架构。
3、同监控项的扩展,在ceilometer/setup.cfg中增加监控项pollster的配置,最后所有修改完成后,重新安装ceilometer。

二、发送数据publisher模块

1、修改ceilometer/pipeline.yaml配置文件,配置发送和接收数据的方式。

-
    name: server_pipeline
    interval: 60
    counters:
        - "server.cpu"
        - "server.mem"
    transformers:
    publishers:
        - rpc://?target=record_server_data&meter_type=server

其中,name为pipeline的名字;interval为轮询的时间间隔;counters为该pipeline的pollster名字;transformers若发送的数据在进行publisher之前需要进一步处理,可设置transformers进行转换;publishers指定发送数据到collector的方式,target指定collector端的接收方,根据meter_type获取rpc的topic、secret和method。
同时,需要设置ceilometer/ceilometer.conf文件,设置server的topic、secret、method信息,具体配置如下:

[publisher_rpc]
server_topic=server
server_secret=True
server_method=record_server_data

2、由于增加了新的topic发送通道,所以需要对ceilometer/ceilometer/publisher/rpc.py做相应的修改。首先,需要修改初始化函数,读取pipeline和ceilometer配置文件信息。其次,修改publish_counters函数,由于pipeline文件会对所有的counters每隔10分钟轮询一次,ceilometer默认采用topic为metering,其数据封装格式相同。但由于新增不同监控模块的数据封装格式不同,所以需要分开处理,10分钟轮询时只对counter类型为sample的数据进行publish。具体如下:

if self.meter_type == 'metering':
    meters = [
            meter_message_from_counter_beta(
                counter,
                self.topic_secret[self.meter_type],
                source)
                for counter in counters if type(counter) == sample.Sample
            ]
else:
    meters = [
            meter_message_from_counter_beta(
                counter,
                self.topic_secret[self.meter_type],
                source)
                for counter in counters
            ]
topic = self.meter_type

三、接收数据collector模块

1、ceilometer/ceilometer/collector/service.py文件initialize_service_hook中定义worker用于接收rpc发送过来的数据,具体配置如下:

for k, v in cfg.CONF.publisher_rpc.iteritems():
    if k.endswith('topic'):
        self.conn.create_worker(
            v,
            rpc_dispatcher.RpcDispatcher([self]),
            'ceilometer.collector.' + v,
        )

2、定义相应的target用于接收采集的数据,如下所示:

def record_server_data(self, context, data):
    for dispatcher in self.dispatchers:
        dispatcher.record_data(context, data, 'server')

其中,最后一个参数指定本次发送过来的topic类型,在调用dispatch.record_data方法时,用于调用相应的存储数据的方法,进而使得数据持久化到不同的数据库表中。
3、dispatch也是在原基础上做了相应的调整,该调整根据当前数据接收的topic,调用不同的方法,进行数据库的存储,具体如下:

def record_data(self, context, data, meter_type):
    if not isinstance(data, list):
        data = [data]
    for meter in data:
        if self.secret_method[meter_type] and
            publisher_rpc.verify_signature(meter,
                self.secret_method[meter_type][0]):
            try:
                if meter.get('timestamp'):
                    meter['timestamp'] =
                        self.time_to_date(meter['timestamp'])
                    method = getattr(self.storage_conn.__class__,
                        self.secret_method[meter_type][1])
                    method(self.storage_conn, meter)
            except Exception as err:
                LOG.error('Failed to record metering data: %s', err)
                LOG.exception(err)
            else:
                LOG.warning(
                    'message signature invalid, discarding message: %r',
                    meter)

四、持久化存储模块

1、由于新增宿主机数据模块的监测,需增一张数据库表,所以storage模块也需做相应的修改。首先,需要修改ceilometer/ceilometer/storage/base.py文件,增加接口:

@abc.abstractmethod
    def record_server_data(self, data):

2、在ceilometer/ceilometer/storage/impl_mongodb.py的Connection类中实现在接口。
3、设计对应的数据库表结构,在ceilometer/ceilometer/storage/sqlalchemy/models.py中设置model对应的Server类。
4、在ceilometer/ceilometer/storage/sqlalchemy/migrate_repo/versions/001_add_meter_table.py中定义对应的表。

以上配置完成后,source隔离环境重新安装ceilometer,重启服务,即可在mongodb数据库中查看到新增server表,表中存储对应counter的数据。

29 Apr 08:23

Openstack Neutron 截图

by 陈沙克
mathematrix

Neutron

Openstack的网络,估计讲课讲好几天都讲不完。新版本的Icehouse在Neutron变化也很大,需要很多时间去理解。我一般的做法是去slideshare下载几个ppt,学习一下。

在Icehouse Neutron引入了ML2插件,这个需要我们重点关注一下。

下面的截图是来自两个PPT,slideshare都可以下载,我放到我的网盘里。

http://yunpan.cn/QNzCM9PxWjVw5 访问密码 88bb

http://yunpan.cn/QNzCEnUsMDsqq 访问密码 d265

redhat出品 http://assafmuller.files.wordpress.com/2014/05/neutron.pdf

 

Snap16

Snap17

Snap18

Snap19

Snap20

Snap11

 

Snap12

 

 

Snap2

 

Snap3

Snap4

Snap5

 

Snap6

Snap7

Snap8

Snap9

Snap10

29 Apr 00:51

对象存储(Object-based Storage)概述

by Admin
mathematrix

存储

作者:@刘爱贵  博客:http://blog.csdn.net/liuaigui

什么是对象存储?多次在不同场合被问起这个问题,于是就想写篇小综述文章。网上查找资料时,找到几篇不错的资料,简单整理一下,供自己和大家参考。什么是对象存储(OSD)?
存储局域网(SAN)和网络附加存储(NAS)是目前两种主流网络存储架构,而对象存储(Object-based Storage)是一种新的网络存储架构,基于对象存储技术的设备就是对象存储设备(Object-based Storage Device)简称OSD1999年成立的全球网络存储工业协会(SNIA)的对象存储设备(Object Storage Device)工作组发布了ANSI的X3T10标准。总体上来讲,对象存储(Object-Based Storage, OBS)综合了NAS和SAN的优点,同时具有SAN的高速直接访问和NAS的分布式数据共享等优势,提供了具有高性能、高可靠性、跨平台以及安全的数据共享的存储体系结构。
SAN存储架构
采用SCSI 块I/O的命令集,通过在磁盘或FC(Fiber Channel)级的数据访问提供高性能的随机I/O和数据吞吐率,它具有高带宽、低延迟的优势,在高性能计算中占有一席之地,如SGI的CXFS文件系统就是基于SAN实现高性能文件存储的,但是由于SAN系统的价格较高,且可扩展性较差,已不能满足成千上万个CPU规模的系统。
NAS存储架构
它采用NFS或CIFS命令集访问数据,以文件为传输协议,通过TCP/IP实现网络化存储,可扩展性好、价格便宜、用户易管理,如目前在集群计算中应用较多的NFS文件系统,但由于NAS的协议开销高、带宽低、延迟大,不利于在高性能集群中应用。
对象存储架构
核心是将数据通路(数据读或写)和控制通路(元数据)分离,并且基于对象存储设备(Object-based Storage Device,OSD)构建存储系统,每个对象存储设备具有一定的智能,能够自动管理其上的数据分布。对象存储结构由对象、对象存储设备、元数据服务器、对象存储系统的客户端四部分组成。
objectstorage
对象存储架构
1、对象
对象是系统中数据存储的基本单位,每个Object是数据和数据属性集的综合体,数据属性可以根据应用的需求进行设置,包括数据分布、服务质量等。在传统的存储系统中用文件或块作为基本的存储单位,块设备要记录每个存储数据块在设备上的位置。Object维护自己的属性,从而简化了存储系统的管理任务,增加了灵活性。Object的大小可以不同,可以包含整个数据结构,如文件、数据库表项等。在存储设备中,所有对象都有一个对象标识,通过对象标识OSD命令访问该对象。通常有多种类型的对象,存储设备上的根对象标识存储设备和该设备的各种属性,组对象是存储设备上共享资源管理策略的对象集合等。
block_object-storage
传统块存储与对象存储
objectstorage3
对象的组成
object4

传统的访问层次和虚拟数据访问模型

2、对象存储设备
每个OSD都是一个智能设备,具有自己的存储介质、处理器、内存以及网络系统等,负责管理本地的Object,是对象存储系统的核心。OSD同块设备的不同不在于存储介质,而在于两者提供的访问接口。OSD的主要功能包括数据存储和安全访问。目前国际上通常采用刀片式结构实现对象存储设备。OSD提供三个主要功能:
(1) 数据存储。OSD管理对象数据,并将它们放置在标准的磁盘系统上,OSD不提供块接口访问方式,Client请求数据时用对象ID、偏移进行数据读写。
(2) 智能分布。OSD用其自身的CPU和内存优化数据分布,并支持数据的预取。由于OSD可以智能地支持对象的预取,从而可以优化磁盘的性能。
(3) 每个对象元数据的管理。OSD管理存储在其上对象的元数据,该元数据与传统的inode元数据相似,通常包括对象的数据块和对象的长度。而在传统的NAS系统中,这些元数据是由文件服务器维护的,对象存储架构将系统中主要的元数据管理工作由OSD来完成,降低了Client的开销。
object5
传统模型 VS OSD模型
3、元数据服务器(Metadata Server,MDS)
MDS控制Client与OSD对象的交互,为客户端提供元数据,主要是文件的逻辑视图,包括文件与目录的组织关系、每个文件所对应的OSD等。主要提供以下几个功能:
(1) 对象存储访问。MDS构造、管理描述每个文件分布的视图,允许Client直接访问对象。MDS为Client提供访问该文件所含对象的能力,OSD在接收到每个请求时将先验证该能力,然后才可以访问。
(2) 文件和目录访问管理。MDS在存储系统上构建一个文件结构,包括限额控制、目录和文件的创建和删除、访问控制等。
(3) Client Cache一致性。为了提高Client性能,在对象存储系统设计时通常支持Client方的Cache。由于引入Client方的Cache,带来了Cache一致性问题,MDS支持基于Client的文件Cache,当Cache的文件发生改变时,将通知Client刷新Cache,从而防止Cache不一致引发的问题。
4、对象存储系统的客户端Client
为了有效支持Client支持访问OSD上的对象,需要在计算节点实现对象存储系统的Client。现有的应用对数据的访问大部分都是通过POSIX文件方式进行的,对象存储系统提供给用户的也是标准的POSIX文件访问接口。接口具有和通用文件系统相同的访问方式,同时为了提高性能,也具有对数据的Cache功能和文件的条带功能。同时,文件系统必须维护不同客户端上Cache的一致性,保证文件系统的数据一致。文件系统读访问流程:
1)客户端应用发出读请求;
2)文件系统向元数据服务器发送请求,获取要读取的数据所在的OSD;
3)然后直接向每个OSD发送数据读取请求;
4)OSD得到请求以后,判断要读取的Object,并根据此Object要求的认证方式,对客户端进行认证,如果此客户端得到授权,则将Object的数据返回给客户端;
5)文件系统收到OSD返回的数据以后,读操作完成。
对象存储文件系统的关键技术
1、分布元数据 传统的存储结构元数据服务器通常提供两个主要功能。
(1)为计算结点提供一个存储数据的逻辑视图(Virtual File System,VFS层),文件名列表及目录结构。
(2)组织物理存储介质的数据分布(inode层)。对象存储结构将存储数据的逻辑视图与物理视图分开,并将负载分布,避免元数据服务器引起的瓶颈(如NAS系统)。元数据的VFS部分通常是元数据服务器的10%的负载,剩下的90%工作(inode部分)是在存储介质块的数据物理分布上完成的。在对象存储结构,inode工作分布到每个智能化的OSD,每个OSD负责管理数据分布和检索,这样90%的元数据管理工作分布到智能的存储设备,从而提高了系统元数据管理的性能。另外,分布的元数据管理,在增加更多的OSD到系统中时,可以同时增加元数据的性能和系统存储容量。
2、并发数据访问 对象存储体系结构定义了一个新的、更加智能化的磁盘接口OSD。OSD是与网络连接的设备,它自身包含存储介质,如磁盘或磁带,并具有足够的智能可以管理本地存储的数据。计算结点直接与OSD通信,访问它存储的数据,由于OSD具有智能,因此不需要文件服务器的介入。如果将文件系统的数据分布在多个OSD上,则聚合I/O速率和数据吞吐率将线性增长,对绝大多数Linux集群应用来说,持续的I/O聚合带宽和吞吐率对较多数目的计算结点是非常重要的。对象存储结构提供的性能是目前其它存储结构难以达到的,如ActiveScale对象存储文件系统的带宽可以达到10GB/s。
对象存储参考资料:
2、SNIA:OSD系统架构
原文链接:http://blog.csdn.net/liuaigui/article/details/17973039

对象存储(Object-based Storage)概述,首发于OpenStack中国社区

27 Apr 09:17

Heat Class Cheat Sheet

声明:
本博客欢迎转发,但请保留原作者信息!
新浪微博:@孔令贤HW
博客地址:http://lingxiankong.github.io/
内容系本人学习、研究和总结,如有雷同,实属荣幸!

从F版本就开始熟悉OpenStack的代码,到现在几大核心模块的代码基本都精读过,但不得不说,目前Heat的代码编写,应该是用到Python高级语法最多的模块,各种对象建模也比其他几个模块复杂一些,初学时比较吃力。这里记录下来,给自己和感兴趣的人参考。

版本:Icehouse

Dependencies

__iadd__:接受一个 (requirer, required) 元组,进行加法操作

required_by(self, last):返回依赖该资源的所有资源

__getitem__(self, last):返回一个Dependencies对象。以last作为叶子节点。

graph(self, reverse=False):返回原来的Graph对象,或逆序的Graph对象,即:将Graph中每个资源的依赖和被依赖关系倒置。

__iter__(self):从叶子节点开始依次返回各个资源

Resource

表示一个资源

update_template_diff(self, after, before):根据两个字典,找到增加/修改/删除的键值对(如果删除,则值是None),键必须在update_allowed_keys范围内。

update_template_diff_properties(self, after, before):根据两个字典,找到增加/修改/删除的属性(如果删除,则值是None),属性必须在update_allowed_properties范围内。

add_dependencies(self, deps):根据资源的属性依赖,构造Dependencies对象的依赖关系。

required_by(self): 返回所有依赖该资源的资源名称列表

create(self):创建资源。创建之前,先进行properties的校验,然后调用handle_create方法。

get_abandon_data(self):返回abandon时的信息

update(self, after, before=None, prev_resource=None):更新资源。如果前后的json字典相同,直接返回;如果资源正在create/update/adopt,则抛失败异常;更新资源的状态(action和status),若状态变更,则增加event记录;对更新的资源属性进行校验(properties.validate());调用handle_update

validate(self):校验资源。校验DeletionPolicy(若有),校验资源属性(调用properties.validate())

delete(self):删除资源。如果资源的action属性是init(即还没有创建),直接返回;根据DeletionPolicy(“Delete”或“Snapshot”)分别调用不同的函数;目前貌似还不支持“Retain”

destroy(self):资源的销毁。除了会调用delete,也会删除db中的资源;

signal(self, details=None):在db中增加event表记录,然后调用handle_signal函数。

resource_to_template(cls, resource_type):类方法。根据资源的属性,返回资源模板内容。

FnGetRefId(self):返回资源的resource_id或者资源名称。对于与虚拟机关联的资源,resource_id一般是指虚拟机id

Properties

表示一个资源的属性集。

schema_from_params(params_snippet):静态方法。根据参数定义字典,返回Schema对象的字典。

validate(self, with_value=True):对实际的属性值进行校验。

__getitem__(self, key):根据key值,找到schema并在实际内容中解析,用schema验证。如果内容中没有,返回default值;若没有default,且该属性必选(prop.required()),抛异常;

__len__(self):属性值的个数

__contains__(self, key):属性中是否包含key

schema_to_parameters_and_properties(cls, schema):类方法。根据入参中的schema,返回参数定义字典和属性字典。被Resource对象的resource_to_template方法调用,返回资源的模板。

ResourceInfo

表示资源名称和资源实现类的关联。有几种不同的实现:ClassResourceInfo,TemplateResourceInfo,GlobResourceInfo,MapResourceInfo,实现了__lt____gt__等方法,可以对ResourceInfo对象进行排序操作。

ClassResourceInfo:很简单,从资源名称就可找到资源类(get_class(self))
TemplateResourceInfo:资源对应的类(一个模板)是TemplateResource(没看的太深入)
GlobResourceInfo:主要用来关联批量映射的情况:OS::Networking::* -> OS::Neutron::*,该类继承自MapResourceInfo。
MapResourceInfo:关联单个映射,比如OS::Networking::FloatingIp -> OS::Neutron::FloatingIp

后面两个在查找实现类时,需要依赖ResourceRegistry对象。

ResourceRegistry

资源映射的管理类。

load(self, json_snippet):根据字典信息注册资源(ResourceInfo)

register_class(self, resource_type, resource_class):同上。

iterable_by(self, resource_type, resource_name=None):根据条件迭代返回资源ResourceInfo对象

get_resource_info(self, resource_type, resource_name=None, registry_type=None):从global注册表和自身的注册表中查找满足条件的ResourceInfo对象(会对满足条件的对象从小到大排序)。

get_class(self, resource_type, resource_name=None):上述函数返回ResourceInfo对象,而这个函数返回对应的实现类。

get_types(self, support_status):获取有效资源的名称列表

根据上述两个类的设计,在使用Heat时,可以自定义资源类型和资源实现,可以增加类型,删除系统已有类型,修改系统已有类型的实现等。感兴趣的话可以在程序中打印一下ResourceRegistry对象,看里面都保存了什么信息。

Environment

维护一些heat在处理时用到的环境变量,有global env和user env。这个类中的方法,大部分都是直接调用ResourceRegistry对象的方法

PluginManager

初始化时,从入参表示的包以及CONF.plugin_dirs下的heat.engine包中加载模块

map_to_modules(self, function):对每一个模块进行函数调用

PluginMapping

一般与PluginManager共同使用

load_from_module(self, module):调用模块中的%s_mapping方法并返回

load_all(self, plugin_manager):对plugin_manager中的模块们调用load_from_module方法

Template

类中有一个描述符_plugins,访问它时,会获取到一个PluginManager对象,初始化它的入参是Template子类所在的包。

load(cls, context, template_id):类方法,从db中获取数据,构造对象

store(self, context=None):保存信息至数据库(如果还没有id)

__iter__(self):返回所有可访问的section

__len__(self):返回上述section的长度

param_schemata(self):需要子类实现的方法。返回参数和参数模式的字典

parameters(self, stack_identifier, user_params, validate_value=True, context=None):需要子类实现的方法。返回Parameters对象

functions(self):返回与子类配套的函数映射

parse(self, stack, snippet):利用上述函数返回的映射,解析入参中的字典

validate(self):简单校验。看有无不存在的section,模板中包含最基本的resource资源,每个resource资源包含Type定义。

Heat目前实现的有HeatTemplateFormatVersion、AWSTemplateFormatVersion和heat_template_version(HOT)三种模板类型

Stack

Stack对象是Heat中最重要的对象。Heat基本就是围绕Stack在开展业务。
Stack初始化时,会确保global env的初始化。

load(cls, context, stack_id=None, stack=None, resolve_data=True, parent_resource=None, show_deleted=True):从db中获取数据,构造stack对象

store(self, backup=False):保存stack信息到db,如果是创建备份,stack名称后加星号。根据CONF.deferred_auth_method配置项,创建user_creds表,stack中的user_creds_id就表示这个信息。stack中的parameters中的AWS::StackId指的是stack的arn

__iter__(self):对stack迭代时,依次返回资源(无顺序),同理__len__返回资源总数(这个跟total_resources的不同之处在于,后者返回包含嵌套资源的总数)

resource_by_refid(self, refid):根据refid查找stack中资源。会调用各个资源的FnGetRefId方法。

register_access_allowed_handler(self, credential_id, handler):在stack中为某一个用户注册一个回调函数。

access_allowed(self, credential_id, resource_name):获取回调并调用,判断对资源是否有操作权限

validate(self):会用到模板中parameter_groups段(干啥的?)。校验模板中是否有不支持的section;校验是否有参数和资源名称冲突;校验每一个资源(validate)

requires_deferred_auth(self):stack中是否有需要延迟鉴权的资源操作

state_set(self, action, status, reason):将最新的stack状态更新db,同时,会发送通知,需要配置notification_driver

preview_resources(self):调用每个resource的preview方法,返回一个list

create(self):创建stack。从叶子节点开始(所有的叶子节点同时创建),创建成功后,再创建依赖它的资源节点,这些动作发生在DependencyTaskGroup对象中。调用每个Resource对象的create方法。

adopt(self):从已有资源创建stack,需要在创建stack时提供adopt_stack_data参数

update(self, newstack):更新stack。

  • 更新db的信息(action and status), 发送开始更新的通知, 将旧stack在db中备份(名称以星号结尾,ownerid就是stack的id),返回备份的stack对象,stack中的resource对象初始状态是(init, complete)
  • 更新stack对象的(内存)属性
  • 更新的动作发生在engine/update.py文件中。更新时,会构造一个Dependencies对象,包含新stack资源的顺序、旧stack资源的逆序(因为涉及删除),以及相同名称的旧资源对新资源的依赖(保证新资源创建后才能删除旧资源)。分情况处理:
    1、新stack中的资源。如果资源在旧stack中有,更新旧资源,调用resource.update(),若在更新过程中跑出了UpdateReplace异常,则创建新资源;否则,直接创建新资源。
    2、旧stack中的资源。如果资源在新stack中有,返回;否则,删除旧stack中的实际资源和资源对象。

delete(self, action=DELETE, backup=False):删除stack。删除backup,逆序依次删除资源,删除user_creds表记录,去keystone删除trust,删除domain_project,最后删除db记录。

suspend(self)resume(self):没啥好说的。

restart_resource(self, resource_name):先destroy该资源以及对其的所有依赖,然后创建

get_availability_zones(self):调用Nova接口获取AZ名称列表

21 Apr 11:11

Openstack Ceilometer监控项扩展

by duyuanyuan
mathematrix

ceilometer

Openstack ceilometer主要用于监控虚拟机、服务(glance、image、network等)和事件。虚拟机的监控项主要包括CPU、磁盘、网络、instance。本文在现有监控项的基础上,介绍如何增加新的监控项目。

一、Ceilometer框架结构

Ceilometer监控通过在计算节点部署Compute服务,轮询其计算节点上的instance,获取各自的CPU、网络、磁盘等监控信息,发送到RabbitMQ,Collector服务负责接收信息进行持久化存储,详细框架如下图所示(点击查看大图)。

Ceilometer架构图

本文主要介绍instance的监控,获取instance的监控数据发送到message队列。instance的监控数据的获取主要通过Compute服务以pollster方式轮询各虚拟机,Compute服务类图如下(点击查看大图)。

ceilometer

通过该类图可知,新增项目需要继承ComputePollster类,并实现get_samples方法。最后通过配置即可,获取到新的监控项数据。由于,现有的Ceilometer没有对内存的实时监控,本文以内存为例,详细介绍增加新的监控项的流程。

二、新增虚拟机监控项

现有虚拟机的监控项目,没有对内存的监控。本文以内存为例,介绍增加内存监控模块的方法,增加内存监控方法比较简单,主要在compute端增加获取数据的方式,然后修改配置文件,即可将数据持久化数据库中。详细步骤如下:

1、在 ceilometer/ceilometer/compute/pollsters目录下新建文件mem.py(名字自定义)。在该文件下定义MEMPollster类并实现get_samples方法:

class MEMPollster(plugin.ComputePollster):
    def get_samples(self, manager, cache, instance):

2、get_samples中获取内存数据的方式,可参考CPU获取数据的架构,在ceilometer/ceilometer/compute/virt/inspector.py 中定义返回数据的格式:

MEMStats = collections.namedtuple('MEMStats', ['total', 'free'])

获取内存方式多样,本文采用读取proc文件获取instance所在进程占用的内存大小,有时获取的数据比instance最大内存大,所以该方式对windows系统的虚机有一定的误差,仅作为示例参考。方法定义如下:

def inspect_mems(self, instance_name):

获取数据后,按sample数据结构返回即可。

3、设计完上述获取数据的方式后,并不能轮询到内存数据,还需要修改一些设置文件。

首先,修改ceilometer/setup.cfg文件,在该文件ceilometer.poll.compute下,增加

mem = ceilometer.compute.pollsters.mem:MEMPollster

其次,由于包冲突的问题,将ceilometer安装在隔离环境中,所以source隔离环境,重新安装ceilometer,以上配置才能生效。执行 python setup.py develop 命令即可。

4、重启ceilometer服务,进入mongodb的ceilometer数据库即可在meter表中查看mem的监控数据。

16 Apr 03:58

What’s New in Icehouse Storage

by Sean Cohen
mathematrix

storage

The latest OpenStack 2014.1 release introduces many important new features across the OpenStack Storage services that includes an advanced block storage Quality of Service, a new API to support Disaster Recovery between OpenStack deployments, a new advanced Multi-Locations strategy for OpenStack Image service & many  improvements to authentication, replication and metadata in OpenStack Object storage.

Here is a Sneak Peek of the upcoming Icehouse release:

Block Storage (Cinder)
The Icehouse release includes a lot of quality and compatibility improvements such as improved block storage load distribution in Cinder Scheduler, replacing Simple/Chance Scheduler with FilterScheduler, advancing to the latest TaskFlow support in volume create, Cinder support for Quota delete was added, as well as support for automated FC SAN zone/access control management in Cinder for Fibre Channel volumes to reduce pre-zoning complexity in cloud orchestration and prevent unrestricted fabric access.

Here is a zoom-in to some of the Key New Features in Block Storage:

Advanced Storage Quality of Service
Cinder has support for multiple back-ends. By default, volumes will be allocated between back-ends to balance allocated space.  Cinder volume types can be used to have finer-grained control over where volumes will be allocated. Each volume type contains a set of key-value pairs called extra specs. Volume types are typically set in advance by the admin and can be managed using the cinder client.  These keys are interpreted by the Cinder scheduler and used to make placement decisions according to the capabilities of the available back-ends.

Volume types can be used to provide users with different Storage Tiers, that can have different performance levels (such as HDD tier, mixed HDD-SDD tier, or SSD tier), as well as  different resiliency levels (selection of different RAID levels) and features (such as Compression).

Users can then specify a tier they want when creating a volume. The Volume Retype capability that was added in the Icehouse release, extends this functionality to allow users to change a volume’s type after its creation.  This is useful for changing quality of service settings (for example a volume that sees heavy usage over time and needs a higher service tier). It also supports changing volume type feature properties (marking as  compressed/uncompressed etc.).

The new API allows vendor-supplied(or provided) drivers to support migration when retyping,. The migration is policy-based, where the policy is passed via scheduler hints.
When retyping, the scheduler checks if the volume’s current host can accept the new type. If the current host is suitable, its manager is called which calls upon the driver to change the volume’s type.

A Step towards Disaster Recovery
An important disaster recovery building block was added to the Cinder Backup API in the Icehouse release, to allow resumption in case your OpenStack cloud deployment goes into flames / falls off a cliff or suffers from any event that ends up with a corrupted service state. Cinder Backup already supports today the ability to back up the data, however in order to support a real disaster recovery between OpenStack deployments you must be able to have a complete restoration of volumes to their original state including Cinder database metadata. Cinder Backup API was extended in Icehouse to support this new functionality with the existing backup/restore api.

The new API supports:
1. Export and import backup service metadata
2. Client support for export and import backup service metadata

This capability sets the scene for the next planned step in OpenStack disaster recovery, that will be designed to extend the Cinder API to support volume replication (that is currently in the works for Juno release).

OpenStack Image Service (Glance)

The Icehouse release has many image service improvements that include:

  • Enhanced NFS servers as backend support, to allow users to configure multiple NFS servers as a backend using filesystem store and mount disks to a single directory.
  • Improved image size attribution was introduced to solve the file size or the actual size of the uploaded file confusion, by adding  a new virtual_size attribute. Where `size` refers the file size and `virtual_size` to the image virtual size. The later is useful just for some image types like qcow2. The Improved image size attribution eases the consumption of its value from Nova, Cinder and other tools relying on it.
  • Better calculation of storage quotas

 Advanced Multi-Location Strategy
The support for Multi-locations was introduced to Glance in the Havana release, to enable image domain object fetch data from multiple locations and allow glance client to consume image from multiple backend store. Starting in the Icehouse release,  a new image location selection strategy was added to the Glance image service to support a selection strategy of the best back-end storage. Another benefit of this capability is the improved consuming performance, as the end user,  can consume images faster, both in term of  ‘download’ transport handling on the API server side and also on the Glance client  side, obtaining locations by standard ‘direct URL’ interface.  These new strategy selection functions are shared between API server side and client side.

OpenStack Object Storage (Swift)
Although the biggest Swift feature  (Storage Policies) that was set for Icehouse is planned  only tp land in Juno, there were many other improvements to authentication, replication and metadata. Here is a zoom-in to some of the key new features you can expect to see in Swift with the Icehouse release:

Account-level ACLs and ACL format v2 (TempAuth)
Accounts now have a new privileged header to represent ACLs or any other form of account-level access control. The value of the header is a JSON dictionary string to be interpreted by the auth system.

Container to Container Synchronization
A new support  for sync realms was added to allow for simpler configuration of container sync. A sync realm is a set of clusters that have agreed to allow container syncing with each other  as all the contents of a container can be mirrored to another container through background synchronization. Swift cluster operators can configure their cluster to allow/accept sync requests to/from other clusters, and the user specifies where to sync their container along with a secret synchronization key.

The key is the overall cluster-to-cluster key used in combination with the external users’ key that they set on their containers’ X-Container-Sync-Key metadata header values. These keys will be used to sign each request the container sync daemon makes and used to validate each incoming container sync request. The swift-container-sync does the job of sending updates to the remote container. This is done by scanning the local devices for container databases and checking for x-container-sync-to and x-container-sync-key metadata values. If they exist, newer rows since the last sync will trigger PUTs or DELETEs to the other container.

Object Replicator – Next Generation leveraging “SSYNC”
The Object Replicator role in Swift encapsulates most logic and data needed by the object replication process, a new replicator implementation set to replace good old RSYNC with backend PUTs and DELETEs.  The initial implementation of object replication simply performed an RSYNC to push data from a local partition to all remote servers it was expected to exist on. While this performed adequately at small scale, replication times skyrocketed once directory structures could no longer be held in RAM.

We now use a modification of this scheme in which a hash of the contents for each suffix directory is saved to a per-partition hashes file. The hash for a suffix directory is invalidated when the contents of that suffix directory are modified.

SSYNC is a thin recursive wrapper around the RSYNC. Its primary goals are reliability, correctness, and speed in syncing extremely large filesystems over fast, local network connections. Work continues with a new SSYNC method where RSYNC is not used at all and instead all-Swift code is used to transfer the objects. At first, this SSYNC will just strive to emulate the RSYNC behavior. Once deemed stable it will open the way for future improvements in replication since we’ll be able to easily add code in the replication path instead of trying to alter the RSYNC code base and distributing such modifications. FOR NOW, IT IS NOT RECOMMENDED TO USE SSYNC ON PRODUCTION CLUSTERS – It is an experimental feature. In its current implementation it is probably going to be a bit slower than RSYNC, but if all goes according to plan it will end up much faster.

Other notable Icehouse improvements that were added include:

Swift Proxy Server Discoverable Capabilities to allow clients to retrieve configuration info programmatically.  The response will include information about the cluster and can be used by clients to determine which features are supported in the cluster. Early Quorum Responses that allow the proxy to respond to many types of requests as soon as it has a quorum.  Removed python-swiftclient dependency,  added support for system-level metadata on accounts and containers, added swift-container-info and swift-account-info tools,  and various bug fixes such as fixing the ring-builder crash when a ring partition was assigned to deleted, zero-weighted and normal devices.

Other key Swift features that made good progress in Icehouse and will probably land in the Juno release include:  Erasure Coded Storage in addition to replicas that will enable a cluster to reduce the overall storage footprint while maintaining a high degree of durability, and Shard large containers – as containers grow, their performance suffers. Sharding the containers (transparently to the user) would allow the containers to grow without bound.

16 Apr 00:32

cinder云硬盘挂载到物理机

by zhuxiaojie
mathematrix

cinder

最近一直在调研如何将基于glusterfs文件系统创建的文件,利用iscsi协议挂载到局域网内的物理机上,终于有了一点收获,总结一下。首先介绍一下iscsi协议。

1 iscsi

1.1  iscsi 简介

iSCSI利用了TCP/IP的port 860 和 3260 作为沟通的渠道。透过两部计算机之间利用iSCSI的协议来交换SCSI命令,让计算机可以透过高速的局域网集线来把存储局域网(SAN)模拟成为本地的储存装置。

1.2 iscsi用途

使用iSCSI SAN 的目的通常有以下两个:

存储整合 公司希望将不同的存储资源从分散在网络上的服务器移动到统一的位置(常常是数据中心); 这可以让存储的分配变得更为有效。 SAN 环境中的服务器无需任何更改硬件或电缆连接就可以得到新分配的磁盘卷。

灾难恢复 公司希望把存储资源从一个数据中心镜像到另一个远程的数据中心上,后者在出现长时间停电的情况下可以用作热备份。 特别是,iSCSI SAN 使我们只需要用最小的配置更改就可以在 WAN 上面迁移整个磁盘阵列,实质上就是,把存储变成了“可路由的”,就像普通的网络通信一样。

1.3 iscsi协议

如图1所示,iscsi分为target和initiator两部分。它利用TCP/IP技术,将存储设备端通过iscsi target功能,做成可以提供磁盘的服务器端;通过iscsi initiator功能,做成能够挂载使用iscsi target的用户端。

iscsi协议栈

2 glusterfs+iscsi解决方案

iscsi target可以是一个文件,也可以是一块磁盘或者一个分区。如果普通文件可以作为target, gluster volume中创建的文件是不是也可以作为target来被挂载呢?带着这个疑问,我做了如下尝试,如图2所示。

首先部署gluster,创建卷cinder-volume并启动,将该卷 mount到提供target的服务器的某个目录下。利用truncate命令在该卷下创建一个文件,然后以iscsi方式暴露target。最后物理机安装iscsi initiator,登录到target。

iscsi协议挂载glusterfs文件流程图

实验结果证明是可以的,接下来我一步步详细说明。

2.1  gluster

2.1.1 部署gluster

关于gluster部署,网上有很多资料,在此不再赘述。具体可以参考如下连接:

http://blog.csdn.net/dysj4099/article/details/17099161

http://navyaijm.blog.51cto.com/4647068/1258250

2.1.2 创建volumes

假设gluster peer为192.168.64.229 和 192.168.64.239, 在任意一台机器上执行(例子为229)

root@192.168.64.229:~# gluster volume create cinder-volume replica 2 192.168.64.229:/data/gluster/ 192.168.64.239:/data/gluster/

root@192.168.64.229:~# gluster volume start cinder-volume

root@192.168.64.229:~# gluster volume info

2.1.3 mount

假设提供target的服务器ip地址为192.168.64.219,则在改机器上执行以下命令:

root@192.168.64.219:~# mkdir –p /opt/cinder-volume

root@192.168.64.219:~# mount –t glusterfs 192.168.64.229:cinder-volume /opt/cinder-volume

这时,glusterfs上的卷cinder-volume 成功mount到提供target的服务器的/opt/cinder-volume文件夹下

2.1.4 创建文件夹

在提供target的服务器上(192.168.64.219)执行以下操作:

root@192.168.64.219:~# cd /opt/cinder-volume

root@192.168.64.219:~# truncate –s 2G iscsi_target

进入该文件夹,利用truncate命令(dd也可以,但是创建大文件会非常慢)创建一个大小为2G的文件“iscsi_target”,作为iscsi的 target。

接下来,用iscsi协议将该文件“iscsi_target”暴露出来,供用户挂载使用。

2.2暴露target

管理Iscsi target的工具有两种:iettgt。两种方式会同时抢占3260端口,所以大家在使用iscsi时,一定要注意该端口是被谁占用了,因为两种方式所用的配置文件和启动脚本均不同。本文利用tgt进行详细说明。

iet:配置文件(/etc/iet/ietd.conf)启动(/etc/init.d/iscsitarget start)

tgt: 配置文件(/etc/tgt/targets.conf)启动(/etc/init.d/tgtd start)

在提供target的服务器上(192.168.64.219)执行以下操作:

root@192.168.64.219:~# apt-get install tgt

root@192.168.64.219:~# /etc/init.d/tgtd start

root@192.168.64.219:~# vi /etc/tgt/ targets.conf

<target iqn.2010-10.org.openstack:iscsi-target>

    backing-store /opt/cinder-volume/iscsi_target

    incominguser judy 123456

    write-cache off

</target>

root@192.168.64.219:~# tgt-admin –update iqn.2010-10.org.openstack:iscsi-target

新增target需要在/etc/init.d/targets.conf添加相应XML。其中target后接target name(可自己定义),backing-store 表示该target 的实际位置,incominguser 表示用户端登录该target时需要提供的验证信息,write-cache 表示再写入target时是否启用写cache。关于targets.conf文件的配置,/usr/share/doc/tgt/examples/ targets.conf.example.gz里面有详细的介绍。

添加完target后,一定要执行最后一条命令,否则用户端无法发现该target。

2.3连接target

Target暴露之后,我们要在用户端查看(假设用户端ip地址为192.168.64.209),是否能够发现该target。

在用户端(192.168.64.209)执行如下操作:

root@192.168.64.209:~# apt-get install open-iscis

root@192.168.64.209:~# iscsiadm –m discovery –t st –p 192.168.64.219

192.168.64.219:3260,1 iqn.2010-10.org.openstack:iscsi-target

root@192.168.64.209:~# iscsiadm –m node –T iqn.2010-10.org.openstack:iscsi-target –p 192.168.64.219:3260 –login

 

此时无法登陆,因为该target(iqn.2010-10.org.openstack:iscsi-target 设置了登陆权限,必须要执行如下操作,设置登录的用户名和密码才能登录。

root@192.168.64.209:~# iscsiadm –m node –T iqn.2010-10.org.openstack:iscsi-target –p 192.168.64.219:3260 –o update –name=node.session.auth.authmethod=CHAP

root@192.168.64.209:~# iscsiadm –m node –T iqn.2010-10.org.openstack:iscsi-target –p 192.168.64.219:3260 –o update –name=node.session.auth.username=judy

root@192.168.64.209:~# iscsiadm –m node –T iqn.2010-10.org.openstack:iscsi-target –p 192.168.64.219:3260 –o update –name=node.session.auth.password=123456

 

最后执行登录操作即可

root@192.168.64.209:~# iscsiadm –m node –T iqn.2010-10.org.openstack:iscsi-target –p 192.168.64.219:3260 –login

Logging in to [iface: default, target: iqn.2010-10.org.openstack:iscsi-target, portal: 192.168.99.54,3260]

Login to [iface: default, target: iqn.2010-10.org.openstack:iscsi-target, portal: 192.168.99.54,3260]: successful

登录成功后,就能查到相应磁盘,可以分区格式化,mount到本地一个目录就可以使用啦!

root@192.168.64.209:~# fdisk –l

root@192.168.64.209:~# mkfs.ext4 /dev/sdb

root@192.168.64.209:~# mkdir –p iscsi_device

root@192.168.64.209:~# mount /dev/sdb iscsi_device

2.4问题

在调研过程中遇到了一些问题,供大家参考

  1. 用户A,B同时登录到target,由于cache的缘故,A所做修改,B不能实时刷新。必须在A上执行sync,将数据写入磁盘;在B上,umount,再mount才能生效
  2. 查阅资料,如果想做成共享target,必须用分布式文件系统,如GFS
05 Apr 02:20

Autoscaling with Heat on Devstack

by OpenStackPro
mathematrix

auoscaling

Autoscaling is one of the more interesting (and outstanding) features of OpenStack Heat. In this post, you’ll build a Devstack environment in a VM with Heat, Neutron, and Ceilometer. This will enable you to run the Autoscaling examples.

 

Getting Started

To get started, copy the following into a Vagrantfile and vagrant-up. This generally takes 3-5 minutes. Now that you have the VM powered on we need to get Devstack running. To do that, run the following commands:

vagrant ssh
sudo su stack
cd ~/devstack/
./stack.sh

Note: Now is the time to top off your coffee and batch process your emails. It’s going to be a while. On my box: stack.sh completed in 2679 seconds.

Basic WordPress

Now that Devstack is up and running, let’s dive right into the examples. First we’ll build a single WordPress install just to get a feel for things. To do that, run the following commands:

source ~/devstack/openrc
cd ~/heat-templates/hot/
ssh-keygen -t rsa -N ""
nova keypair-add --pub-key ~/.ssh/id_rsa.pub testkey
heat stack-create WordPress -f F20/WordPress_Native.yaml -P key_name=testkey

What we did, was set some environment variables for our credentials (as who wants to type those out EVERY time?). Next we moved into where we cloned the heat templates, generated an SSH key, and added it to Nova as ‘testkey’. Finally we launched our heat command.

Let’s break that command down some:

  • heat – The CLI tool we’re calling stack-create – This follows the noun-verb nomenclature of OpenStack. For heat, the ‘thing’ on which you operate is a stack. In this case we’re instructing heat to create a stack.
  • WordPress – This provides the name of the stack. These should be unique within a tenant.
  • -f F20/WordPress_Native.yaml – This specifies the template file to use. In this case, the Fedora 20 WordPress install.
  • -P key_name=testkey – -P tells the heat client that we are providing parameters that are requested within the template. In this case, key_name is a required parameter. So we tell it to use the testkey we created.

Ok, it should be done building now, let’s use another heat command to check:

heat stack-list
+--------------------------------------+------------+-----------------+----------------------+
| id                                   | stack_name | stack_status    | creation_time        |
+--------------------------------------+------------+-----------------+----------------------+
| 5fad5da6-75e9-40b2-a32f-86345b0263f3 | WordPress  | CREATE_COMPLETE | 2014-04-04T17:33:08Z |
+--------------------------------------+------------+-----------------+----------------------+

There we go! Let’s find out a bit more about it, we do that with heat stack-show:

 heat stack-show WordPress
+----------------------+----------------------------------------------------------
| Property             | Value
+----------------------+----------------------------------------------------------
| capabilities         | []
| creation_time        | 2014-04-04T17:33:08Z
| description          | Heat WordPress template to support F20, using only Heat
|                      | OpenStack-native resource types, and without the
|                      | requirement for heat-cfntools in the image. WordPress

Note, this can be enough of the name to be unique, or the UUID. In most cases, you’ll want to use the UUID. Finally, let’s clean up our environment to make room for the autoscale example:

heat stack-delete 5fad5da6-75e9-40b2-a32f-86345b0263f3
+--------------------------------------+------------+--------------------+----------------------+
| id                                   | stack_name | stack_status       | creation_time        |
+--------------------------------------+------------+--------------------+----------------------+
| 5fad5da6-75e9-40b2-a32f-86345b0263f3 | WordPress  | DELETE_IN_PROGRESS | 2014-04-04T17:33:08Z |
+--------------------------------------+------------+--------------------+----------------------+
heat stack-list
+----+------------+--------------+---------------+
| id | stack_name | stack_status | creation_time |
+----+------------+--------------+---------------+
+----+------------+--------------+---------------+

Autoscaling WordPress with Heat

Sorry for the delay in getting here, however, covering the basics will let us skip a lot of that now. The Autoscale environment is a bit more complex. That is, we have the following:

Load Balancer (LBaaS) –> Web Tier (Autoscaling Group) –> Database Tier

To build the stack, we need to find out some things about our environment. Specifically a subnet ID to place the load balancer and nodes on and an Image ID for the base of our webserver & db servers. Let’s do that:

neutron subnet-list
+--------------------------------------+----------------+-------------+--------------------------------------------+
| id                                   | name           | cidr        | allocation_pools                           |
+--------------------------------------+----------------+-------------+--------------------------------------------+
| 82596713-60ff-42ee-b766-6c501991cf55 | private-subnet | 10.0.0.0/24 | {"start": "10.0.0.2", "end": "10.0.0.254"} |
+--------------------------------------+----------------+-------------+--------------------------------------------+
$ glance image-list
+--------------------------------------+---------------------------------+-------------+------------------+-----------+--------+
| ID                                   | Name                            | Disk Format | Container Format | Size      | Status |
+--------------------------------------+---------------------------------+-------------+------------------+-----------+--------+
| 7b3bf53c-3875-4f7c-bc6d-eff11290078c | F19-i386-cfntools               | qcow2       | bare             | 469383680 | active |
| 134c4d81-5c00-45d5-b089-9fbd2d5b9afc | F19-x86_64-cfntools             | qcow2       | bare             | 528297984 | active |
| 64ade0c6-9e10-4257-9884-7406d68d49de | Fedora-x86_64-20-20131211.1-sda | qcow2       | bare             | 214106112 | active |
+--------------------------------------+---------------------------------+-------------+------------------+-----------+--------+

Now that we have those, let’s fire up the build:

heat stack-create WP-Autoscale -f autoscaling.yaml -P database_flavor=m1.small -P subnet_id=82596713-60ff-42ee-b766-6c501991cf55 -P key="testkey" -P flavor=m1.small -P image=7b3bf53c-3875-4f7c-bc6d-eff11290078c
+--------------------------------------+--------------+--------------------+----------------------+
| id                                   | stack_name   | stack_status       | creation_time        |
+--------------------------------------+--------------+--------------------+----------------------+
| f736fd6f-0348-4381-b4e5-7a4da4c3282e | WP-Autoscale | CREATE_IN_PROGRESS | 2014-04-04T17:45:25Z |
+--------------------------------------+--------------+--------------------+----------------------+

On my box, this takes A WHILE to complete. Once it’s done, you can use the same commands to view the environment, or, you can log into the dashboard and check it out:

Stack Detail - OpenStack Dashboard 2014-04-04 12-52-30 2014-04-04 12-52-33

04 Apr 00:24

[原][收藏]SQL教程

by llg8212

里面区分了MySQL,Sql Server,Oracle的语法差异,作为手册挺好的。

http://www.w3school.com.cn/sql/sql_primarykey.asp

作者:llg8212 发表于2014/4/3 19:06:38 原文链接
阅读:164 评论:0 查看评论
01 Apr 14:14

使用grafana和Diamond构建Graphite监控系统

mathematrix

监控

前言

在豆瓣开源项目里面有个graph-index, 提供监控服务器的状态的目录索引,基于graph-explorer. 类似衍生物很多,就包括我要说的本文用到的项目.先看看我的测试环境的几个截图

一些关键词说明

  1. graphite-web # graphite组件之一, 提供一个django的可以高度扩展的实时画图系统
  2. Whisper # graphite组件之一, 实现数据库存储. 它比rrdtool要慢,因为whisper是使用python写的,而rrdtool是使用C写的。然而速度之间的差异很小
  3. Carbon # 数据收集的结果会传给它, 它会解析数据让它可用于实时绘图. 它默认可会提示一些类型的数据,监听2003和2004端口
  4. Diamond # 他是一个提供了大部分数据收集结果功能的结合,类似cpu, load, memory以及mongodb,rabbitmq,nginx等指标.这样就不需要我大量的写各种类型,因为它都已经提供,并且它提供了可扩展的自定义类型(最后我会展示一个我自己定义的类型)
  5. grafana # 这个面板是基于node, kibana,并且可以在线编辑. 因为是kibana,所以也用到了开元搜索框架elasticsearch

PS: 其他工具可以参考这里Tools That Work With Graphite

原理解析

我没有看实际全部代码,大概的流程是这样的:

  1. 启动Carbon-cache等待接收数据(carbon用的是twisted)
  2. 启动graphite-web给grafana提供实时绘图数据api
  3. 启动grafana,调用graphite-web接口获取数据展示出来
  4. Diamond定期获取各类要监测的类型数据发给carbon(默认是5分钟,默认一小时自动重载一次配置)

实现我这个系统需要做的事情

安装graphite相关组件(我这里用的是centos)
yum --enablerepo=epel install graphite-web python-carbon -y
安装grafana需要的组件
# 增加elasticsearch的repo:
sudo  rpm --import http://packages.elasticsearch.org/GPG-KEY-elasticsearch
$cat /etc/yum.repos.d/elasticsearch.repo
[elasticsearch-1.0]
name=Elasticsearch repository for 1.0.x packages
baseurl=http://packages.elasticsearch.org/elasticsearch/1.0/centos
gpgcheck=1
gpgkey=http://packages.elasticsearch.org/GPG-KEY-elasticsearch
enabled=1
sudo yum install nginx nodejs npm java-1.7.0-openjdk elasticsearch -y
下载Diamond和grafana
git clone https://github.com/torkelo/grafana
cd grafana
sudo npm install
sudo pip install django-cors-headers configobj # 这可能因为我环境中已经有了一些模块,看缺什么安装什么
git clone https://github.com/BrightcoveOS/Diamond
cd Diamond

##### 开始修改配置

  1. 添加cors支持

在/usr/lib/python2.6/site-packages/graphite/app_settings.py:

INSTALLED_APPS里面添加corsheaders, MIDDLEWARE_CLASSES里面添加’corsheaders.middleware.CorsMiddleware’

  1. 使用nginx使用grafana

在nginx.conf 添加类型的一段配置

server {
  listen                *:80 ;

  server_name           monitor.dongwm.com; # 我用了虚拟主机
  access_log            /var/log/nginx/kibana.myhost.org.access.log;

  location / {
    add_header 'Access-Control-Allow-Origin' "$http_origin";
    add_header 'Access-Control-Allow-Credentials' 'true';
    root  /home/operation/dongwm/grafana/src;
    index  index.html  index.htm;
  }

  location ~ ^/_aliases$ {
    proxy_pass http://127.0.0.1:9200;
    proxy_read_timeout 90;
  }
  location ~ ^/_nodes$ {
    proxy_pass http://127.0.0.1:9200;
    proxy_read_timeout 90;
  }
  location ~ ^/.*/_search$ {
    proxy_pass http://127.0.0.1:9200;
    proxy_read_timeout 90;
  }
  location ~ ^/.*/_mapping$ {
    proxy_pass http://127.0.0.1:9200;
    proxy_read_timeout 90;
  }

  # Password protected end points
  location ~ ^/kibana-int/dashboard/.*$ {
    proxy_pass http://127.0.0.1:9200;
    proxy_read_timeout 90;
    limit_except GET {
      proxy_pass http://127.0.0.1:9200;
      auth_basic "Restricted";
      auth_basic_user_file /etc/nginx/conf.d/dongwm.htpasswd;
    }
  }
  location ~ ^/kibana-int/temp.*$ {
    proxy_pass http://127.0.0.1:9200;
    proxy_read_timeout 90;
    limit_except GET {
      proxy_pass http://127.0.0.1:9200;
      auth_basic "Restricted";
      auth_basic_user_file /etc/nginx/conf.d/dongwm.htpasswd;
    }
  }
  1. 修改grafana的src/config.js:

graphiteUrl: “http://”+window.location.hostname+”:8020”, # 下面会定义graphite-web启动在8020端口

  1. 修改Diamond的配置conf/diamond.conf
cp conf/diamond.conf.example conf/diamond.conf

主要修改监听的carbon服务器和端口,以及要监控什么类型的数据,看我的一个全文配置

################################################################################
# Diamond Configuration File
################################################################################

################################################################################
### Options for the server
[server]

# Handlers for published metrics.
handlers = diamond.handler.graphite.GraphiteHandler, diamond.handler.archive.ArchiveHandler

# User diamond will run as
# Leave empty to use the current user
user =

# Group diamond will run as
# Leave empty to use the current group
group =

# Pid file
pid_file = /home/dongwm/logs/diamond.pid # 换了pid的地址,因为我的服务都不会root启动

# Directory to load collector modules from
collectors_path = /home/dongwm/Diamond/src/collectors # 收集器的目录,这个/home/dongwm/Diamond就是克隆代码的地址

# Directory to load collector configs from
collectors_config_path = /home/dongwm/Diamond/src/collectors

# Directory to load handler configs from
handlers_config_path = /home/dongwm/Diamond/src/diamond/handler

handlers_path = /home/dongwm/Diamond/src/diamond/handler

# Interval to reload collectors
collectors_reload_interval = 3600 # 收集器定期会重载看有没有配置更新

################################################################################
### Options for handlers
[handlers]

# daemon logging handler(s)
keys = rotated_file

### Defaults options for all Handlers
[[default]]

[[ArchiveHandler]]

# File to write archive log files
log_file = /home/dongwm/logs/diamond_archive.log

# Number of days to keep archive log files
days = 7

[[GraphiteHandler]]
### Options for GraphiteHandler

# Graphite server host
host = 123.126.1.11

# Port to send metrics to
port = 2003

# Socket timeout (seconds)
timeout = 15

# Batch size for metrics
batch = 1

[[GraphitePickleHandler]]
### Options for GraphitePickleHandler

# Graphite server host
host = 123.126.1.11

# Port to send metrics to
port = 2004

# Socket timeout (seconds)
timeout = 15

# Batch size for pickled metrics
batch = 256

[[MySQLHandler]]
### Options for MySQLHandler

# MySQL Connection Info 这个可以你的会不同
hostname    = 127.0.0.1
port        = 3306
username    = root
password    =
database    = diamond
table       = metrics
# INT UNSIGNED NOT NULL
col_time    = timestamp
# VARCHAR(255) NOT NULL
col_metric  = metric
# VARCHAR(255) NOT NULL
col_value   = value

[[StatsdHandler]]
host = 127.0.0.1
port = 8125

[[TSDBHandler]]
host = 127.0.0.1
port = 4242
timeout = 15

[[LibratoHandler]]
user = user@example.com
apikey = abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz01

[[HostedGraphiteHandler]]
apikey = abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz01
timeout = 15
batch = 1

# And any other config settings from GraphiteHandler are valid here

[[HttpPostHandler]]

### Urp to post the metrics
url = http://localhost:8888/
### Metrics batch size
batch = 100


################################################################################
### Options for collectors
[collectors]
[[TencentCollector]] # 本来[collectors]下试没有东西的,这个是我定制的一个类型
ttype = server
[[MongoDBCollector]] # 一般情况,有一些类型是默认enabled = True,也就是启动的,但是大部分是默认不启动《需要显示指定True
enabled = True
host = 127.0.0.1 # 每种类型的参数不同
[[TCPCollector]]
enabled = True
[[NetworkCollector]]
enabled = True
[[NginxCollector]]
enabled = False # 没开启nginx_status 开启了也没用
[[ SockstatCollector]]
enabled = True
[[default]]
### Defaults options for all Collectors

# Uncomment and set to hardcode a hostname for the collector path
# Keep in mind, periods are seperators in graphite
# hostname = my_custom_hostname

# If you prefer to just use a different way of calculating the hostname
# Uncomment and set this to one of these values:

# smart             = Default. Tries fqdn_short. If that's localhost, uses hostname_short

# fqdn_short        = Default. Similar to hostname -s
# fqdn              = hostname output
# fqdn_rev          = hostname in reverse (com.example.www)

# uname_short       = Similar to uname -n, but only the first part
# uname_rev         = uname -r in reverse (com.example.www)

# hostname_short    = `hostname -s`
# hostname          = `hostname`
# hostname_rev      = `hostname` in reverse (com.example.www)

# hostname_method = smart

# Path Prefix and Suffix
# you can use one or both to craft the path where you want to put metrics
# such as: %(path_prefix)s.$(hostname)s.$(path_suffix)s.$(metric)s
# path_prefix = servers
# path_suffix =

# Path Prefix for Virtual Machines
# If the host supports virtual machines, collectors may report per
# VM metrics. Following OpenStack nomenclature, the prefix for
# reporting per VM metrics is "instances", and metric foo for VM
# bar will be reported as: instances.bar.foo...
# instance_prefix = instances

# Default Poll Interval (seconds)
# interval = 300

################################################################################
### Options for logging
# for more information on file format syntax:
# http://docs.python.org/library/logging.config.html#configuration-file-format

[loggers]

keys = root

# handlers are higher in this config file, in:
# [handlers]
# keys = ...

[formatters]

keys = default

[logger_root]

# to increase verbosity, set DEBUG
level = INFO
handlers = rotated_file
propagate = 1

[handler_rotated_file]

class = handlers.TimedRotatingFileHandler
level = DEBUG
formatter = default
# rotate at midnight, each day and keep 7 days
args = ('/home/dongwm/logs/diamond.log', 'midnight', 1, 7)

[formatter_default]

format = [%(asctime)s] [%(threadName)s] %(message)s
datefmt =
启动相关服务
sudo /etc/init.d/nginx reload
sudo /sbin/chkconfig --add elasticsearch
sudo service elasticsearch start
sudo service carbon-cache restart
sudo python /usr/lib/python2.6/site-packages/graphite/manage.py runserver 0.0.0.0:8020 # 启动graphite-web到8020端口
在每个要搜集信息的agent上面安装Diamond,并启动:
cd /home/dongm/Diamond
python ./bin/diamond --configfile=conf/diamond.conf

# PS: 也可以添加 -l -f在前台显示
自定义数据搜集类型,也就是上面的TencentCollector
# coding=utf-8 

"""
获取腾讯微博爬虫的业务指标
"""

import diamond.collector
import pymongo
from pymongo.errors import ConnectionFailure


class TencentCollector(diamond.collector.Collector): # 需要继承至diamond.collector.Collector
    PATH = '/home/dongwm/tencent_data'
    
    def get_default_config(self):
        config = super(TencentCollector, self).get_default_config()
        config.update({
            'enabled':  'True',
            'path':     'tencent',
            'method':   'Threaded',
            'ttype':    'agent' # 服务类型 包含agent和server
        })
        return config

    def collect(self):
        ttype = self.config['ttype']
        if ttype == 'server':
            try:
                db = pymongo.MongoClient()['tmp']
            except ConnectionFailure:
                return
            now_count = db.data.count()
            try:
                last_count = db.diamond.find_and_modify(
                    {}, {'$set': {'last': now_count}}, upsert=True)['last']
            except TypeError:
                last_count = 0
            self.publish('count', now_count)
            self.publish('update', abs(last_count - now_count))
        if ttype == 'agent':
            # somethings..........
添加你要绘图的类型. 这个就是打开grafana, 添加不同的row.给每个添加panel.选择metric的类型就好了
01 Apr 14:13

[转]OpenvSwitch完全使用手册

by llg8212

OpenvSwitch完全使用手册(一)-总览Overview

本文主要参考Overview of functionality and components以及Frequently Asked Questions以及结合自己的理解。

1 什么是OpenvSwitch

OpenvSwitch,简称OVS是一个虚拟交换软件,主要用于虚拟机VM环境,作为一个虚拟交换机,支持Xen/XenServer, KVM, and VirtualBox多种虚拟化技术。

在这种某一台机器的虚拟化的环境中,一个虚拟交换机(vswitch)主要有两个作用:传递虚拟机VM之间的流量,以及实现VM和外界网络的通信。

整个OVS代码用C写的。目前有以下功能:

  • Standard 802.1Q VLAN model with trunk and access ports
  • NIC bonding with or without LACP on upstream switch
  • NetFlow, sFlow(R), and mirroring for increased visibility
  • QoS (Quality of Service) configuration, plus policing
  • GRE, GRE over IPSEC, VXLAN, and LISP tunneling
  • 802.1ag connectivity fault management
  • OpenFlow 1.0 plus numerous extensions
  • Transactional configuration database with C and Python bindings
  • High-performance forwarding using a Linux kernel module

2 OpenvSwitch的组成

  • ovs-vswitchd:守护程序,实现交换功能,和Linux内核兼容模块一起,实现基于流的交换flow-based switching。
  • ovsdb-server:轻量级的数据库服务,主要保存了整个OVS的配置信息,包括接口啊,交换内容,VLAN啊等等。ovs-vswitchd会根据数据库中的配置信息工作。
  • ovs-dpctl:一个工具,用来配置交换机内核模块,可以控制转发规则。
  • ovs-vsctl:主要是获取或者更改ovs-vswitchd的配置信息,此工具操作的时候会更新ovsdb-server中的数据库。
  • ovs-appctl:主要是向OVS守护进程发送命令的,一般用不上。
  • ovsdbmonitor:GUI工具来显示ovsdb-server中数据信息。
  • ovs-controller:一个简单的OpenFlow控制器
  • ovs-ofctl:用来控制OVS作为OpenFlow交换机工作时候的流表内容。

3 OpenvSwitch和其他vswitch

这里其他的vswitch,包括VMware vNetwork distributed switch以及思科的Cisco Nexus 1000V。

VMware vNetwork distributed switch以及思科的Cisco Nexus 1000V这种虚拟交换机提供的是一个集中式的控制方式,。而OVS则是一个独立的vswitch,他运行在每个实现虚拟化的物理机器上,并提供远程管理。OVS提供了两种在虚拟化环境中远程管理的协议:一个是OpenFlow,通过流表来管理交换机的行为,一个是OVSDB management protocol,用来暴露sietch的port状态。

(二)-概念及工作流程1

1 vswitch、Bridge、Datapath

在网络中,交换机和桥都是同一个概念,OVS实现了一个虚拟机的以太交换机,换句话说,OVS也就是实现了一个以太桥。那么,在OVS中,给一个交换机,或者说一个桥,用了一个专业的名词,叫做DataPath!

要了解,OVS如何工作,首先需要知道桥的概念。

网桥也叫做桥接器,连接两个局域网的设备,网桥工作在数据链路层,将两个LAN连接,根据MAC地址来转发帧,可以看成一个“低层的路由器”(路由器工作在网络层,根据IP地质进行转发)。

1.1 网桥的工作原理

网桥处理包遵循以下几条规则:

  • 在一个接口上接收到的包不会再往那个接口上发送此包。
  • 每个接收到的包都要学习其源MAC地址。
  • 如果数据包是多播或者广播包(通过2层MAC地址确定)则要向接收端口以外的所有端口转发,如果上层协议感兴趣,则还会递交上层处理。
  • 如果数据包的地址不能再CAM表中找到,则向接收端口以外的其他端口转发。
  • 如果CAM表中能找到,则转发给相应端口,如果发送和接收都是统一端口,则不发送。

注意,网桥是以混杂模式工作的。关于网桥更多,请查阅相关资料。

2 OVS中的bridge

上面,说到,一个桥就是一个交换机。在OVS中,

ovs-vsctl add-br brname(br-int)

root@Compute2:~# ifconfig
      br-int    Link encap:Ethernet  HWaddr 1a:09:56:ea:0b:49  
      inet6 addr: fe80::1809:56ff:feea:b49/64 Scope:Link
      UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
      RX packets:1584 errors:0 dropped:0 overruns:0 frame:0
      TX packets:6 errors:0 dropped:0 overruns:0 carrier:0
      collisions:0 txqueuelen:0 
      RX bytes:316502 (316.5 KB)  TX bytes:468 (468.0 B)

当我们创建了一个交换机(网桥)以后,此时网络功能不受影响,但是会产生一个虚拟网卡,名字就是brname,之所以会产生一个虚拟网卡,是为了实现接下来的网桥(交换机)功能。有了这个交换机以后,我还需要为这个交换机增加端口(port),一个端口,就是一个物理网卡,当网卡加入到这个交换机之后,其工作方式就和普通交换机的一个端口的工作方式类似了。

ovs-vsctl add-port brname port

这里要特别注意,网卡加入网桥以后,要按照网桥的工作标准工作,那么加入的一个端口就必须是以混杂模式工作,工作在链路层,处理2层的帧,所以这个port就不需要配置IP了。(你没见过哪个交换的端口有IP的吧)

那么接下来你可能会问,通常的交换机不都是有一个管理接口,可以telnet到交换机上进行配置吧,那么在OVS中创建的虚拟交换机有木有这种呢,有的!上面既然创建交换机brname的时候产生了一个虚拟网口brname,那么,你给这个虚拟网卡配置了IP以后,就相当于给交换机的管理接口配置了IP,此时一个正常的虚拟交换机就搞定了。

ip address add 192.168.1.1/24 dev brname

最后,我们来看看一个br的具体信息:

root@Compute2:~# ovs-vsctl show
bc12c8d2-6900-42dd-9c1c-30e8ecb99a1b
Bridge "br0"
    Port "eth0"
        Interface "eth0"
    Port "br0"
        Interface "br0"
            type: internal
ovs_version: "1.4.0+build0"

首先,这里显示了一个名为br0的桥(交换机),这个交换机有两个接口,一个是eth0,一个是br0,上面说到,创建桥的时候会创建一个和桥名字一样的接口,并自动作为该桥的一个端口,那么这个虚拟接口的作用,一方面是可以作为交换机的管理端口,另一方面也是基于这个虚拟接口,实现了桥的功能。

OpenvSwitch完全使用手册(二)-概念及工作流程2

这一部分我以一个简单的例子,说明在虚拟化环境中OpenvSwitch的典型工作流程。

前面已经说到,OVS主要是用来在虚拟化环境中。虚拟机之间一个虚拟机和外网之间的通信所用,如下是一个典型的结构图:

那么,通常情况下的工作流程如下:


  • 1 VM实例instance产生一个数据包并发送至实例内的虚拟网络接口VNIC,图中就是instance中的eth0.
  • 2 这个数据包会传送到物理节点上的VNIC接口,如图就是vnet接口。
  • 3 数据包从vnet NIC出来,到达桥(虚拟交换机)br100上.
  • 4 数据包经过交换机的处理,从物理节点上的物理接口发出,如图中物理节点上的eth0.
  • 5 数据包从eth0出去的时候,是按照物理节点上的路由以及默认网关操作的,这个时候该数据包其实已经不受你的控制了。
本文链接地址: http://blog.wachang.net/2013/03/openvswitch-fullbook-2-workflow-2/
作者:llg8212 发表于2014/3/31 11:04:4...
04 Mar 08:43

Finding an OpenStack Mentor

by annegentle
mathematrix

useful

Last week I ran an internal “So You Want to be an OpenStack Contributor?” workshop showing the different ways to work on OpenStack. Here’s the slide show so you can see the way I approached it. As the Documentation Program Technical Lead you’d think I’d steer people straight to the documentation bug backlog, but I try to find out where interests lie before going straight to doc fixes. Definitely people should read the docs as a great start.

You can work on OpenStack in non-code ways, such as bug triaging. Also the OpenStack Foundation does community marketing and staffs booths at events from the community. But a great way to understand the ins and outs of OpenStack-landia is to commit a patch.

I have to admit, I didn’t know much when I first started working on OpenStack at Rackspace. The Swift team was the group I had immediate access to in person. Wow were they patient with me while I made hilarious-in-hindsite errors. I had a patch where I changed “referer” one r to “referrer” two rs, because duh that’s how referrer is spelled. Well as it turns out that’s not the way the WC3 HTTP Protocol specifies request headers since 1992 or so, woops! Then I also managed to change the RST backticks (`) to single quotes (‘) which is absolutely not going to render correctly with Sphinx. Chuck Thier patiently explained the errors I had made and how to correct them. So do not be discouraged if it’s difficult to get the hang of your first patch or two or ten. Code reviewers are happy to help you iterate and revise. I’ve heard of good and bad patch reviewing going on in the community so I encourage you to find a real person who can help you get helpful reviews.

We also have organized OpenStack mentor programs now. We’ve been participating in the GNOME Outreach Program for Women for three rounds, and we’re a participating organization with the Google Summer of Code program for 2014. There are ideas for projects on the OpenStack wiki:

We have dedicated IRC channels for new contributors – #openstack-101 and #openstack-opw on freenode. Our OPW interns have written great blog entries about getting started with OpenStack (In a nutshell:how OpenStack works) and DevStack (Installing DevStack with Vagrant). Their fresh eyes make for great starting points. I encourage us all make this work both ways – people of OpenStack, be mentors, and newcomers, seek out the people in OpenStack who want to help you get started. Updated to add: be sure to check out opensource.com for “How to Contribute to OpenStack.”

27 Feb 01:33

paste配置文件

by peichao
mathematrix

Nice post!

openstack中使用paste模块来做wsgi的配置,它的好处是将配置和代码分离。python代码写好后如果想要修改页面到app的映射关系,只需要修改配置文件即可。

关于paste模块,建议看下官方文档,写的还比较清楚

http://pythonpaste.org/deploy/

 

OK。下面以我的neutron的api-paste.ini为例看看paste提供一些什么功能,先给出我这个文件的内容:

[composite:neutron]
use = egg:Paste#urlmap
/: neutronversions
/v2.0: neutronapi_v2_0

[composite:neutronapi_v2_0]
use = call:neutron.auth:pipeline_factory
noauth = extensions neutronapiapp_v2_0
keystone = authtoken keystonecontext extensions neutronapiapp_v2_0

[filter:keystonecontext]
paste.filter_factory = neutron.auth:NeutronKeystoneContext.factory

[filter:authtoken]
paste.filter_factory=keystoneclient.middleware.auth_token:filter_factory
auth_host=hz01-sys-ip-sysagent1.hz01.baidu.com
auth_uri=http://hz01-sys-ip-sysagent1.hz01.baidu.com:5000
admin_user=neutron
admin_tenant_name=service
admin_password=mypass

[filter:extensions]
paste.filter_factory = neutron.api.extensions:plugin_aware_extension_middleware_factory

[app:neutronversions]
paste.app_factory = neutron.api.versions:Versions.factory

[app:neutronapiapp_v2_0]
paste.app_factory = neutron.api.v2.router:APIRouter.factory

首先看最基本的app段

[app:neutronversions]
paste.app_factory = neutron.api.versions:Versions.factory

“app:”前缀表示它定义了一个wsgi的application,”neutronversions”是它的名字,可以随便起。这个名字可以用在paste的loadapp中作为appname,也可以由其他app引用,在neutron中是由”composite:neutron”这个类app引用的。下面一行定义了这个application的实际代码位置,用分号分隔。“neutron.api.versions”部分指明了该python模块的路径,”Versions.factory”指明模块中callable对象的位置,整个连起来就是neutron.api.versions模块中的Versions类的factory方法。注意这里并不是直接给出了一个函数,而是给出了一个factory,wsgi会在程序启动时调用factory生成一个application callable对象,然后在收到请求时使用这个application对象处理。

接着看一个filter段:

[filter:authtoken]
paste.filter_factory=keystoneclient.middleware.auth_token:filter_factory
auth_host=hz01-sys-ip-sysagent1.hz01.baidu.com
auth_uri=http://hz01-sys-ip-sysagent1.hz01.baidu.com:5000
admin_user=neutron
admin_tenant_name=service
admin_password=mypass

同样, “filter:”前缀表示这个段定义了一个filter,”authtoken”是名字,可以随便起。下面的paste.filter_factory定义了一个filter的工厂方法,跟app一样使用”模块路径:方法路径”的格式表示。再后面几个都是自定义的参数,在调用工厂方法时会以关键字参数的形式传给该方法。

然后看看composite。composite其实跟app很像,不过它自己不处理请求,而是根据一些规则把请求分发给其它的app或者filter或者pipeline(pipeline在openstack中貌似没用到)等。先来看看上面第一个composite:

[composite:neutron]
use = egg:Paste#urlmap
/: neutronversions
/v2.0: neutronapi_v2_0

“composite:”依然表示这是一个composite段,neutron是名字。注意neutron代码中loadapp使用的appname参数是neutron(参考neutron/common/config.py文件load_paste_app函数,该参数是从neutron/service.py文件NeutronApiService类的create方法传入的),也就是说neutron使用这个composite为入口。这个composite就定义了如何分发请求:

use = egg:Paste#urlmap

这行表示使用Paste包中的urlmap这个composite app来分发流量,这是个很常用的composite,紧接着就定义了分发规则,格式就是”url:app”。例如”/: neutronversions”就表示访问url根目录的请求全部由neutronversions这个app处理,这个app刚才描述过了。”/v2.0: neutronapi_v2_0“表示访问/v2.0目录的请求由neutronapi_v2_0这个模块处理,实际上这个也是个composite。看看这个composite是怎么定义的:

[composite:neutronapi_v2_0]
use = call:neutron.auth:pipeline_factory
noauth = extensions neutronapiapp_v2_0
keystone = authtoken keystonecontext extensions neutronapiapp_v2_0

这个没有使用urlmap,而是用use直接指定了一个app处理请求。”use”这个方法可以像上面这样直接指定一个factory,但是必须加上call:前缀,也可以用URI或者appname来指定使用的方法,甚至可以指定其他文件中的app,详情移步文档吧。

好,我们用的是pipeline_factory来处理请求,后面两个”noauth”和”keystone”又起什么作用呢?它们在neutron中定义了不同auth_strategy下执行的认证策略。我们看下pipeline_factory的代码,上面路径已经写清楚了,在neutron.auth模块:

def pipeline_factory(loader, global_conf, **local_conf):
    """Create a paste pipeline based on the 'auth_strategy' config option."""
    pipeline = local_conf[cfg.CONF.auth_strategy]
    pipeline = pipeline.split()
    filters = [loader.get_filter(n) for n in pipeline[:-1]]
    app = loader.get_app(pipeline[-1])
    filters.reverse()
    for filter in filters:
        app = filter(app)
    return app

composite的factory比app和filter的factory多一个loader对象作为参数,这个loader可以使用get_app方法通过app名称获取指定的app对象,也可以通过get_filter获取filter。这里factory函数的作用就是根据neutron.conf中配置的auth_strategy执行指定的操作。如果配置的noauth,那么执行extensions这个filter,再返回neutronapiapp_v2_0这个app,如果neutron.conf中配置的是keystone,那么执行authtoken, keystonecontext, extensions三个filter,最后再返回neutronapiapp_v2_0这个app。

写的比较乱,希望自己以后还能看懂。

26 Feb 15:32

Neutron中的数据包路径

by peichao
mathematrix

Nice post!!

最近在Neutron下配置了一下gre的网络,感觉Neutron的设计有些过于技术化了。单看结构,确实比较完美,各种namespace,iptables,路由,桥,ovs用的出神入化。但是对于高可用,性能等这些非常实际的问题却没有完美的解决。就说l3-agent,严重的单点,即使用paccemaker做高可用也只能做到Active/Passive方式。另外所有流量经过L3-Agent转发,这对于L3-Agent的性能也是严峻考验。

不管怎么说,人家这东西做出来了,至少是可以用的,以后还有机会改进嘛。作为使用者,先研究清楚才是正道。我们先来看下一个数据包如果要从外网进到虚拟机,会走一个什么样的路径

openstack_路径

本图中,11.12.19.111是客户端的IP, 12.212.175.2为虚拟机的Floating IP,12.212.175.3为Router(这个Router为Neutron中的虚拟Router)。每片黄色方框为一个物理机,其中左边的物理机为计算节点,只有一个namespace,右边的物理机为网络节点,除默认名字空间外,还有两个网络名字空间,一个用于Router(l3-agent),另外一个用于dhcp-agent。

粗算一下,外网数据包想要到达虚拟机,要经过五个桥,三次路由查找,两次gre处理,还要经过n个ovs桥接口。回去还得走这么长一段路,费劲!

相对于内网到外网的互通,两个虚拟机之间通信就简单多了,这个是不需要经过l3-agent转发的,ovs-agent会在所有需要互通的物理机之间搭上隧道,比如一个租户的虚拟机分布在n个物理机上,那么每台物理机需要起n个gre口,分别指向其他n-1台物理机外加一台network节点,每个gre口的remote_ip属性不同。当有多个租户在同样的两个物理机上建立隧道时,neutron会使用gre头部的key字段来区分不同租户的报文。在gre头部被剥去后,这个key字段就会被打到vlan tag上。

下面看下ovs是如何在vlan.tag和gre.key之间转换的。

首先需要看下各个接口的编号:

ovs-ofctl dump-ports-desc br-tun

输出如下:

OFPST_PORT_DESC reply (xid=0x2):
 1(patch-int): addr:52:1c:fe:50:8b:c8
     config:     0
     state:      0
     speed: 0 Mbps now, 0 Mbps max
 2(gre-1): addr:d2:6a:b4:9e:85:47
     config:     0
     state:      0
     speed: 0 Mbps now, 0 Mbps max
 3(gre-3): addr:9e:02:ca:f0:d0:80
     config:     0
     state:      0
     speed: 0 Mbps now, 0 Mbps max
 LOCAL(br-tun): addr:26:fb:ee:eb:92:44
     config:     0
     state:      0
     speed: 0 Mbps now, 0 Mbps max

这里我有两个gre口,其中gre-1连接到network节点,gre-3连接到另一个compute节点。这里主要看下各个接口对应的编号是多少,在flow里面会用到。

然后来看下流的内容。还在这个compute节点上运行以下命令:

ovs-ofctl dump-flows br-tun

在我的机器上,看到如下输出:

NXST_FLOW reply (xid=0x4):
 cookie=0x0, duration=34204.166s, table=0, n_packets=9, n_bytes=1090, idle_age=34083, priority=1,in_port=3 actions=resubmit(,2)
 cookie=0x0, duration=346148.862s, table=0, n_packets=13241, n_bytes=1653810, idle_age=8294, hard_age=65534, priority=1,in_port=1 actions=resubmit(,1)
 cookie=0x0, duration=345511.599s, table=0, n_packets=25075, n_bytes=29074297, idle_age=8294, hard_age=65534, priority=1,in_port=2 actions=resubmit(,2)
 cookie=0x0, duration=346148.717s, table=0, n_packets=3, n_bytes=230, idle_age=65534, hard_age=65534, priority=0 actions=drop
 cookie=0x0, duration=346148.575s, table=1, n_packets=13149, n_bytes=1647714, idle_age=8294, hard_age=65534, priority=0,dl_dst=00:00:00:00:00:00/01:00:00:00:00:00 actions=resubmit(,20)
 cookie=0x0, duration=346148.431s, table=1, n_packets=92, n_bytes=6096, idle_age=8298, hard_age=65534, priority=0,dl_dst=01:00:00:00:00:00/01:00:00:00:00:00 actions=resubmit(,21)
 cookie=0x0, duration=346145.541s, table=2, n_packets=25084, n_bytes=29075387, idle_age=8294, hard_age=65534, priority=1,tun_id=0x1 actions=mod_vlan_vid:1,resubmit(,10)
 cookie=0x0, duration=346148.287s, table=2, n_packets=0, n_bytes=0, idle_age=65534, hard_age=65534, priority=0 actions=drop
 cookie=0x0, duration=346148.143s, table=3, n_packets=0, n_bytes=0, idle_age=65534, hard_age=65534, priority=0 actions=drop
 cookie=0x0, duration=346147.999s, table=10, n_packets=25084, n_bytes=29075387, idle_age=8294, hard_age=65534, priority=1 actions=learn(table=20,hard_timeout=300,priority=1,NXM_OF_VLAN_TCI[0..11],NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],load:0->NXM_OF_VLAN_TCI[],load:NXM_NX_TUN_ID[]->NXM_NX_TUN_ID[],output:NXM_OF_IN_PORT[]),output:1
 cookie=0x0, duration=346147.857s, table=20, n_packets=6, n_bytes=482, idle_age=48035, hard_age=65534, priority=0 actions=resubmit(,21)
 cookie=0x0, duration=346145.684s, table=21, n_packets=98, n_bytes=6578, idle_age=8298, hard_age=34204, priority=1,dl_vlan=1 actions=strip_vlan,set_tunnel:0x1,output:3,output:2
 cookie=0x0, duration=346147.712s, table=21, n_packets=0, n_bytes=0, idle_age=65534, hard_age=65534, priority=0 actions=drop

这是一个比较标准的openflow定义的流表,包含cookie, 多级表,计数器,定时器,匹配规则,行为等几个关键字段。先看看这几条流:3,6,7,12,13,14,这些流定义了从patch-int桥过来的数据包所需执行的行为,也就是从虚拟机发出的包。从上往下逐条看。

3.表0:当匹配到1口进入的包时,提交给表1继续匹配
6.表1:对于mac地址为单播地址的包(mac地址最高字节的最低位为0),提交给表20继续匹配
7.表1:对于mac地址为多播或广播地址的包,提交给表21继续匹配
12.表20:将数据包提交给表21继续匹配
13.表21:对于目的vlan为1的包,将vlan去掉,并且将tunnel的key设置为1,再从3和2两个口同时发出去
14.表21:没有匹配中的,丢弃。

再看看这几条流(4,8,11),这些表项匹配的目标是2口进入的报文,也就是从network节点发过来的报文。它的匹配流程如下:

4.表0:对于所有从2口进入的报文,提交给表2继续匹配
8.表2:对于tunnel key为1的报文,将vlan tag设置为1并且提交到表10继续匹配
11.表10:首先从报文中学习mac地址等信息(learn还没详细了解,以后再看),然后从1口发出去。1口是patch-int口,这个报文就被发往虚拟机了。

不过到达br-int的时候,这个报文还是带着vlan tag的,这个tag是什么时候去掉的呢?再看下br-int的配置:

ovs-vsctl show

结果显示如下(略去了br-int之外的信息):

    Bridge br-int
        Port "tap702ff8a6-6a"
            tag: 1
            Interface "tap702ff8a6-6a"
        Port "tap159506e7-0d"
            tag: 1
            Interface "tap159506e7-0d"
        Port patch-tun
            Interface patch-tun
                type: patch
                options: {peer=patch-int}
        Port "tapb67eacca-93"
            tag: 1
            Interface "tapb67eacca-93"
        Port br-int
            Interface br-int
                type: internal
    ovs_version: "1.11.0"

这里面三个tapxxxx-yy各对应一个虚拟机,注意”tag: 1″属性,可以看到,这些接口都是配置了vlan tag的,数据包经过这些接口时,vlan tag只要为1就会被自动去掉。

基本上gre模式下数据包的流向就是这样。总体来说,Neutron的灵活度还是不够高,现在还不支持端口NAT,即一个外网ip NAT到多个内网IP上,也不支持nova-network那样的multi_host。我们现在通过手工配置SNAT实现端口映射,一个示例命令如下所示,我就不解释了,注意这个命令需要配置到l3-agent的namespace中。

iptables -t nat -A neutron-l3-agent-PREROUTING -p tcp -d 12.212.175.4 --dport 22 -j DNAT --to 172.31.0.102:22

除此之外,还需要在l3-agent的namespace的qg-xxx接口上将映射的外网ip配置上去,否则没人给响应arp。

26 Feb 13:29

[原]opentack neutron学习 -- create_network实现

by llg8212
mathematrix

Neutron 干货

声明:

本博客欢迎转发,但请保留原作者信息!

博客地址:http://blog.csdn.net/llg8212

内容系本人学习、研究和总结,如有雷同,不胜荣幸!

该接口的代码流程以openvswitch的实现为例。

1.请求响应消息

1.1创建nativenetwork

请求:

{

"network":

{

"name":"sample_network",

"admin_state_up":false

}

}

应答:

{

"network":{

"admin_state_up":true,

"id":"850d3f2c-f0a5-4f8b-b1cf-5836fc0be940",

"name":"sample_network",

"shared":false,#是否可以在多个tenant共享

"status":"ACTIVE",

"subnets":[],

"tenant_id":"f667b69e4d6749749ef3bcba7251d9ce"

}

}

1.2使用provider

请求:

{

"network":

{

"name":"net-name",

"admin_state_up":true,

"provider:network_type":"vlan",

"provider:physical_network":"physnet_1",

"provider:segmentation_id":201

}

}

应答:

{

"network":

{

"status":"ACTIVE",

"subnets":[],

"name":"network-1",

"admin_state_up":true,

"tenant_id":"c1210485b2424d48804aad5d39c61b8f",

"id":"3a06dfc7-d239-4aad-9a57-21cd171c72e5",

"shared":false,

"provider:network_type":"vlan",

"provider:physical_network":"physnet_1",

"provider:segmentation_id":101

}

}

1.3 Bulk create networks

请求:

{

"networks":[

{

"name":"sample_network_1",

"admin_state_up":false

},

{

"name":"sample_network_2",

"admin_state_up":false

}

]

}

注:批量创建网络的处理和创建单个的处理流程是完全一样的,仅在neutron-server中将多个网络信息解析并放到一个list中,然后通过循环去创建。最后返回的消息也是批量的消息。

2.代码流程

create_network的过程就是neutron-server接收到restapi请求后,调用plugin的create_network方法,根据请求的参数将相应信息写入数据库并返回创建的记录的过程。其时序图如下:


疑问:发出网络创建开始和结束以及dhcp的消息有什么作用?

如果创建网络时指定了provider,在plugin创建网络的过程中会先处理provider,其处理流程如下图所示:


从以上的流程处理获得如下一张provider中网络模式和physical_network及segmentation_id的取值对应关系表(这也就是传入provider需要遵守的约束):

provider:

network_type

provider:physical_network

provider:

segmentation_id

备注

local


flat

如果设置,需跟plugin配置中network_vlan_ranges= XXX[:start:end]中的XXX;

如果没设置并且XXX为'default',physical_network设置为'default'

☒ 代码中设置为-1


vlan

☑ 在1~4094之间

已有

bpprovider_network_partial_specs,允许不指定segmentation_id

gre和vxlan

☒ 代码中设置为None

tunneling需为true



作者:llg8212 发表于2014/2/26 11:52:42 原文链接
阅读:647 评论:2 查看评论
26 Feb 13:28

[原]openstack neutron -- 概念,表结构及代码目录结构

by llg8212
mathematrix

Neutron 干货

声明:

本博客欢迎转发,但请保留原作者信息!

博客地址:http://blog.csdn.net/llg8212

内容系本人学习、研究和总结,如有雷同,不胜荣幸!

  1. Neutron概念

network:隔离的虚拟2层域,network也可以看作是一个虚拟(或逻辑)交换机。对应物理交换机。

subnet:是IPv4或IPv6的地址段。表示在指定的network中,哪些IP地址可以被分配给虚拟机。相当于IP池。

port:就是网络上的虚拟(或逻辑)交换机的端口。对应物理交换机的端口。

2. Neutron表结构

  • C- the attribute can be used increateoperations;

  • R- the attribute is returned in the response forshow orlistoperations;

  • U- the value of attribute can be updated;

  • D- the value of the attribute can be removed;

    1. network

只能被创建的租户使用,除非被创建成shared。

Attribute

Type

Required

CRUD

DefaultValue

Validation

Constraints

描述

id

uuid-str

N/A

CR

Generated

UUID_PATTERN


name

String

No

CRU

None

N/A

网络名,可以重复

admin_state_up

Bool

No

CRU

True

{True | False }

管理状态,默认为True。如果为False,整个网络不可用

status

String

N/A

R

N/A

N/A

网络的状态。ACTIVE可用状态BUILD创建中DOWN不可用状态,该网络上所有的port也不可用ERROR错误

subnets

list(uuid-str)

No

R

EmptyList

N/A


shared

Bool

No

CRU

False

{True | False }

是否设置成共享,如果为True,其它租户也可以访问

tenant_id

uuid-str

No*

CR

N/A

N/A


    1. subnet

     subnet代表了可用于分配给虚拟机的IP地址集合。每个subnet必须有一个cidr,并且必须与network相关联。Ip地址可以从整个subnetcidr中或“allocation_pools”中任选,这可以由用户来指定。

subnet也可以有一个网关,DNS名称服务器的列表,以及主机路由。所有这些信息将被应用到和该subnet关联的虚拟机网口上。

Attribute

Type

Requ

ired

CRUD

Default

ValidationConstraints

描述

id

uuid-str

N/A

CR

Generated

UUID_PATTERN


name

String

No

CRU

None

N/A

子网名,可以重复

network_id

uuid-str

Yes

CR

Generated

Existingnetwork identifier.


ip_version

int

Yes

CR

4

{4 | 6 }


cidr

string

Yes

CR

N/A

ValidCIDR in the form <network_address>/

<prefix>

子网IP的集合。例:"192.168.199.0/24"

表示从192.168.199.2192.168.199.254

的集合(192.168.199.1默认是网关)

gateway_ip

stringor null

No

CRUD

First

address inCIDR

ValidIP address or null

网关,不指定默认为cidr中的第一个IP

dns_

nameservers

list(str)

No

CRU

None

Configurablemaximum amount of nameservers per subnet. The default is 5.


allocation_

pools

list(dict)

No

CR

Every

address

in CIDR, excluding

gateway

IP if configured

Start/endof range must be valid IP

自动根据cidr创建,cidr中的所有IP和网关IP(如果指定)。

如果指定cidr为格式为192.168.199.0/24

(不指定网关ip),其

allocation_pools的值为[{"end":

   "192.168.199.254",

"start":

   "192.168.199.2"}]

host_routes

list(dict)

No

CRU

Defaultroute to gateway_ip

{'destination':<CIDR>, "nexthop": <valid IP address>}.Configurable maximum amount of routes per subnet. The default is20.

默认路由,其格式为cidr,有效的ip。例:192.168.70.0/24,

192.168.70.1

enable_dhcp

Bool

No

CRU

True

{True | False }


tenant_id

uuid-str

No*

CR

N/A

N/A

如果不使用keystone认证,该字段是必须的

    1. port

   port表示一个逻辑网络交换机虚拟交换机端口。虚拟机将其interface关联到port。虚拟机interface被注入通过逻辑端口还定义的MAC地址和IP地址(允许多个)。当IP地址关联到一个port,这也意味着该port与subnet相关联,因为IP地址是从特定子网的allocation_pools中分配的。

Attribute

Type

Required

CRUD

Default

Validation

Constraints

描述

id

uuid-str

N/A

CR

Generated

UUID_PATTERN

子网名,可以重复

name

String

No

CRU

None

N/A


network_id

uuid-str

Yes

CR

N/A

Existing

network identifier.


admin_state_

up

bool

No

CRU

True

{True | False }

管理状态,默认为True。

如果为False

整个端口不可用

status

string

N/A

R

N/A

N/A

端口的状态,和network

的status含义一致。

ACTIVE可用状态

BUILD创建中

DOWN不可用状态,

该网络上所有的port也不可用

ERROR错误

mac_address

string

No

CR

Generated

ValidMAC in 6-octet form separated by colons.

端口的MAC地址,自动生成。

fixed_ips

list(dict)

No

CRU

Automatically

allocated from pool.

ValidIP address and existing subnet identifier.


device_id

str

No

CRUD

None

Noconstraint


device_owner

str

No

CRUD

None

Noconstraint


tenant_id

uuid-str

No*

CR

N/A

N/A

如果不使用keystone认证,

该字段是必须的(和subnet一致)

  1. neutron的代码结构

|--agent:部署在Network Node上。为整个网络提供公共服务,包括如:l3-agent(实现3层网络路由的配置),dhcp-agent(提供dhcp服务)。

|--api:对外提供RestAPI访问。在neutron-server服务中提供。

|--cmd:用于发出哪些network,subnet,port,router,floatingip已经存在

|--common:neutron模块的公共

|--db:数据库

|--debug:用于测试neutron功能

|--extensions:neutron的扩展模块,如:vpnaas,l3,lbaas等

|--locale:多语言支持

|--openstack:openstack的公共模块,来源于olso-incubator

|--plugin:实现网络功能的插件。如:linuxbridge,ml2

   |--agent:部署在Compute Node节点(真正干活的)。使vm能通过网络通信,如openvswitch中的agent是通过ovs-ofctl命令修改流规则。

|--scheduler:neutron的调度模块,负载均衡功能时使用。包含:dhcp-agent和l3-agent调度

|--server:启动NeutronApiService服务

|--service:

|--tests:


作者:llg8212 发表于2014/2/26 17:25:25 原文链接
阅读:1121 评论:2 查看评论
24 Feb 11:45

房姐成长记

by 小花
mathematrix

小花更新……

我曾经在博客里吹牛过说我家在上海市有一个好大好漂亮的房子,美中不足的是我家的大房子坐落在一个叫做大桶大足浴的隔壁,每天晚上都能听到足浴房里销魂的嬉笑声和棋牌室里自摸清一色的胡牌声,为此我甚是苦恼。

但是你们又是知道的,上海的房价高耸入云,想要换个房子几乎是不可能中的不可能。于是我几乎因此夜不能寐、食不甘味。虽然王信文同学他再三向我明示,他马上就要牛逼大发了,我马上就能做房姐了,但是梦想和现实之中总有一者很骨感。

但是你们又知道的,可怜天下父母心——自从大学三年级跟王信文认识之后,信文就供我吃供我穿、教育我长大,王信文把我当做亲生的女朋友一样,我不爽的话他其实也很苦恼;你们还是知道的,可怜天下父母心——信文要是很苦恼他的话他的爹妈又很心疼。

于是有一天上午我上班时正在写文档,突然收到一条银行发来的短信消息,信文的娘给我的卡上打了200万元人民币,叫我去买房。我当时内心久久不能平息,抄起工卡准备大喊一声“老子不干了”然后就卷铺盖携巨款潜逃美国。但是信文教育过我,如果我携款潜逃一定会吃亏的,因为什么呢?还是因为他马上就要牛逼大发了,会赚到更多的钱,要是我逃走之后就分不到他赚的大钱了!我扳指一算觉得他讲的有一点道理,于是把工卡默默的放下继续写文档。

我又仔细数了一下银行卡上的0,确认的确是200万元而不是200元之后,再次产生了不想工作的冲动。但是理性的我又陷入了深深的沉思,200万元数额巨大虽然还不够买房,但是首付肯定是绰绰有余了!但是究竟应该买神马房子呢?

后面的故事就很辛酸惨烈了,一个月的时间里我一双小脚走遍上海的大街小巷,大风大雨里穿梭于各种奇形怪状的小区里,和数不胜数的中介们称兄道弟,开会的时候悄悄出来接电话,加班的时候偷偷打开网页搜房源,看了足足三四十个房子,最后也没有中意的房子。经过这一个多月对上海民生的考察,我深深发现,大家的生活压力如此之大。上海寸土寸金,好的房子都临着马路,各种车水马龙呼啸而过;安静的房子往往深藏小区中央,陈旧不堪;又好又安静的房子坐落在上海极边缘的地方;又好又安静地段又好的房子,我绝对不可能买得起……最后的最后,满眼昏花身心俱疲的我对王总说,不如我们就要XXX的那个房子吧?王总说:你脑容量太小了,根本考虑不过来,但是买房子这件事情,应当不忘初心,你难道忘了买房子的目的是为了换一个安静的地方吗?

在王总的指导下,我们终于找到了一个十分僻静的小区!!!但是世上如何能有十全十美的事情?我们又发现在这个小区的旁边坐落着上海市著名注明的日新小学,小区临着小学的操场。蒋介石同学说过,苟日新,日日新,又日新。这个日新小学十分不错,但是神经过敏的我十分担心小朋友们一大清早做早操会十分吵闹。但是坚强不屈的我觉得,好不容易选中的房子,决不能轻易放弃,于是我择了一天准备早上请假去实地考察。信文娘担心的问我,你请假要不要紧?领导会不会批评?我说你别担心了,房价这么贵,我一年的工资连个买房契税都付不起,还上个毛线班。(还上个毛线班是我的内心独白,没有说出来)

于是这天早晨,顶着灰蒙蒙的天,我六点半就提着一片面包和一盒阿华田出门,悄悄潜伏到日新小学门口。此时小朋友们还没有来上学,我远远看到一位保安大叔,立刻掏出前一天准备好的香烟递上去,向他询问小朋友们几点做早操,他斩钉截铁的说:八点半!我听罢心凉了一大截。我公司早上10点上班,我每天早上9点50起床,9点52分出门。八点半这是要闹哪样啊!从此无法睡懒觉了!

好在我是一个坚强不屈的女子,我又转到小学后门口守株待兔。不久就来了一个带绿领巾的小朋友,我就在小路上拦下了小朋友。小朋友一脸惊恐,我威逼利诱的说:“小旁友啊,侬早上几点做广播体操啊?”小朋友很害怕,说,阿姨,我早上第二节课下课后做广播体操。我又接着问:侬说的和你们保安大叔说的不一样哦!小朋友拨了拨绿领巾,抬头望着我说,阿姨我也不知道。

我十分不甘心,又守在门口等其他小朋友。不久就来了一大群一起来上学的小朋友。这拨小朋友各个带着红领巾,看上去年纪大一些。我看到其中一个小朋友带着三道杠,十分欢欣,就过去问他:大队长!你们学校几点钟做广播操呀?大队长说:第二节下课的时候做操,大概是10点钟。我心中默默为他按赞,果然不愧是大队长,思路清晰,回答准确!我又问:那你是大队长呀?一个学校就一个大队长么?真是了不起啊!大队长说:不是的,一个学校有十三个大队长。我说,那也是非常了不起的!大队长十分开心,带着他的小弟们走进了学校。

虽然大队长看上去十分可信,但是保安叔叔的话还是让我很疑惑,于是我又穿过小道,找到另外两个保安大叔。大叔同表示该校两节课后做广播操。我尖锐的提出问题:大叔,我刚才来的路上问了人家东川小学的小朋友,说人家学校八点一刻就做操了,你们前门的保安也说八点半做操,你们口径怎么不一致?两个大叔表示,我们学校就是两节课后才做操,我们学校就是跟人家不一样哦!我还想继续走访小朋友们,但是我姣好的容貌和贼头贼脑的气质引起了保安大叔的极大重视,几乎要驱赶我,我于是忙不迭的踩着高跟鞋叮咚叮咚的逃走了。逃逸路上碰到一个上学迟到的小红领巾,她正吃韭菜包子,我跟她再次确认是两节课后做广播操后这才放心的走回去。走到一半我回过头去想帮她把门牙上的韭菜抠下来,她说了谢谢然后自己伸出小手麻利的把韭菜抠了出来。

这就是我看房的辛酸经历片段节选。上班时间偷偷写博客的我希望自己的苦心会有好的结果,希望最终能买到一个如意的房子,然后能在这个辛苦恣睢的城市里安居乐业。

24 Feb 07:24

由苹果的低级Bug想到的

by 雨田
mathematrix

nice post

2014年2月22日,在这个“这么二”的日子里,苹果公司推送了 iOS 7.0.6(版本号11B651)修复了 SSL 连接验证的一个 bug。官方网页在这里:http://support.apple.com/kb/HT6147,网页中如下描述:

Impact: An attacker with a privileged network position may capture or modify data in sessions protected by SSL/TLS

Description: Secure Transport failed to validate the authenticity of the connection. This issue was addressed by restoring missing validation steps.

也就是说,这个bug会引起中间人攻击,bug的描述中说,这个问题是因为miss了对连接认证的合法性检查的步骤。

这里多说一句,一旦网上发生任何的和SSL/TL相关的bug或安全问题,不管是做为用户,还是做为程序员的你,你一定要高度重视起来。因为这个网络通信的加密协议被广泛的应用在很多很多最最需要安全的地方,如果SSL/TLS有问题的话,意味着这个世界的计算机安全体系的崩溃。

Bug的代码原因

Adam Langley的《Apple’s SSL/TLS bug 》的博文暴出了这个bug的细节。(在苹果的开源网站上,通过查看苹果的和SSL/TLS有关的代码变更,我们可以在文件sslKeyExchange.c中找到下面的代码)

static OSStatus
SSLVerifySignedServerKeyExchange(SSLContext *ctx, bool isRsa, SSLBuffer signedParams,
                                 uint8_t *signature, UInt16 signatureLen)
{
	OSStatus        err;
	...

	if ((err = SSLHashSHA1.update(&hashCtx, &serverRandom)) != 0)
		goto fail;
	if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
		goto fail;
		goto fail;
	if ((err = SSLHashSHA1.final(&hashCtx, &hashOut)) != 0)
		goto fail;
	err = sslRawVerify(ctx,
                       ctx->peerPubKey,
                       dataToSign,				/* plaintext */
                       dataToSignLen,			/* plaintext length */
                       signature,
                       signatureLen);
	if(err) {
		sslErrorLog("SSLDecodeSignedServerKeyExchange: sslRawVerify "
                    "returned %d\n", (int)err);
		goto fail;
	}

fail:
    SSLFreeBuffer(&signedHashes);
    SSLFreeBuffer(&hashCtx);
    return err;
}

注意,我高亮的地方,也就是那里有两个goto fail; 因为if语句没有加大括号,所以,只有第一个goto是属于if的,而第二个goto则是永远都会被执行到的(注:这里不是Python是C语言,缩进不代表这个语句属于同一个语句块)。也就是说,就算是前面的if检查都失败了(err  == 0),也会goto fail。我们可以看到fail标签中释放完内存后就会return err;

你想一下,这段程序在SSLHashSHA1.update()  返回成功,也就是返回0 的时候会发生什么样的事?是的,真正干活的 sslRawVerify()被bypass了。而且这个函数SSLVerifySignedServerKeyExchange() 还返回了0,也就是成功了!尼玛!你可能想到酷壳网上之前《一个空格引发的惨剧》的文章。都是低级bug。

这个低级bug在这个周末在网上被炒翻了天,你可以上Twiter上看看#gotofail的标签的盛况Goto Fail必然会成为历史上的一个经典事件

如果你喜欢XKCD,你一定会想到这个漫画:

注意:这个bug不会影响TLS 1.2版本,因为1.2版本不会用这个函数,走的是另一套机制。但是别忘了client端是可以选择版本的。

如果你想测试一下你的浏览器是否会有问题,你可以上一下当天就上线的 https://gotofail.com 网站

一些思考

下面是我对这个问题的一些思考。

0)关于编译报警

有人在说苹果的这个代码中的goto语句会产生死代码——dead code,也就是永远都不会执行到的代码,C/C++的编程器是会报警的。但,实际上,dead code在默认上的不会报警的。即使你加上-Wall,GCC 4.8.2 或 Clang 3.3 都不会报警,包括Visual Studio 2012在默认的报警级别也不会(默认是/W3级,需要上升到/W4级以上,但是升级到/W4上,你的工程可能会有N多的Warning,你不一定能看得过来)。gcc和Clang有一个参数叫:-Wunreachable-code,是可以对这种情况报警的,但即没有被包括在-Wall里。原因是,这个参数有很多的问题,因为编译器的优化代码的行为,这个参数并不能对每种情况都准确地报告。另请注意,GCC的新版本中剔除了这个参数。当然,其它一些静态的代码检查工具也可以检查这个低级的问题。

另外,是不是用IDE的代码自动化格式工具也可以帮上一点忙呢?至少可以把那个缩进变成让人一看就觉得有问题。

1)关于Code Merge 和 Code Review

你可以通过这里的代码比较看到这个bug的diff,也可以到这里看看(631行)。

diff -urN <(curl -s http://opensource.apple.com/source/Security/Security-55179.13/libsecurity_ssl/lib/sslKeyExchange.c\?txt) \ <(curl -s http://opensource.apple.com/source/Security/Security-55471/libsecurity_ssl/lib/sslKeyExchange.c\?txt) \

通过code diff你可以看到,苹果公司是在重构代码——为很多函数去掉了ctx的参数

所以,我们可以猜测,两个goto fail语句,可能是因为对code在不同branch上做merge发生的。版本工具merge代码的时候,经常性的会出现这样的问题。如果代码的diff很多,这个问题会很容易就没有注意到。就算有code review,这个有问题的代码也很难被找出来的。如果你来review下面的diff,你会注意到这个错误吗?

也就是说,在重构分支上的代码是对的,但是在分支merge的时候,被merge工具搞乱了。所以说,我们在做code merge的时候,一定要小心小心再小心,不能完全相信merge工具

2)关于测试

很明显,这个bug很难被code review发现。对于重构代码和代码merge里众多的diff,是很难被review的。

当然,“事后诸葛亮”的人们总是很容易地说这个问题可以被测试发现,但是实际情况是这样的吗?

这个问题也很难被功能测试发现,因为这个函数在是在网络握手里很深的地方,功能 测试不一定能覆盖得那么深,你要写这样的case,必需对TLS的协议栈非常熟悉,熟悉到对他所有的参数都很熟悉,并能写出针对每一个参数以及这些参数的组合做一堆test case,这个事情也是一件很复杂的事。要写出所有的case本身就是一件很难很难的事情。关于这个叫SSLVerifySignedServerKeyExchange()函数的细节,你可以看看相关的ServerKeyExchange RFC文档。

如果只看这个问题的话,你会说对这个函数做的 Unit Test 可以发现这个问题,是的。但是,别忘了SSL/TLS这么多年了,这些基础函数都应该是很稳定的了, 在事前,我们可能不会想到要去为这些稳定了多少年的函数写几个Unit Test。

只要有足够多的时间,我们是可以对所有的功能点,所有的函数都做UT,也可以去追求做代码覆盖和分支覆盖一样。但有一点我们却永远无法做到,那就是——穷举所有的负面案例。所以,对于测试来说,我们不能走极端,需要更聪明的测试。就像我在《我们需要专职的QA》文章里的说过的——测试比coding难度大多了,测试这个工作只有高级的开发人员才做得好。我从来不相信不写代码的人能做好测试。

这里,我并不是说通过测试来发现这个问题的可能性不大,我想说的是,测试很重要,单测更重要。但是,我们无法面面俱到。在我们没有关注到的地方,总会发生愚蠢的错误。

P.S.,在各大网站对这个事的讨论中,我们可以看到OS X下的curl命令居然可以接受一个没有验证过的IP地址的https的请求,虽然现在还没有人知道这事的原因,但是,这可能是没有在测试中查到的一个原因。

3)关于编码风格

对于程序员来说,在C语言中,省掉语句大括号是一件非常不明智 的事情。如我们强制使用语句块括号,那么,这两个goto fail都会在一个if的语句块里,而且也容易维护并且易读。(另外,通过这个bug,我们可以感受到,像Python那样,用缩进来表示语句块,的确是挺好的一件事)

也有人说,如果你硬要用只有单条语句,且不用语句块括号,那么,这就是一条语句,应该放在同一行上。如下所示:

if  (check_something)   do_something(); 

但是这样一来,你在单步调试代码的时候,就有点不爽了,当你step over的时候,你完全不知道if的条件是真还是假。所以,还是分多行,加上大括号会好一些。

相似的问题,我很十多年前也犯过,而且那次我出的问题也比较大,导致了用户的数据出错。那次就是维护别人的代码,别人的代码就是没有if的语句块括号,就像苹果的代码那样。我想在return z之前调用一个函数,结果就杯具了:

if ( ...... )
    return x;
if ( ...... )
    return y;
if ( ...... )
    foo();
    return z;

这个错误一不小心就犯了,因为人的大脑会相当然地认为缩进的都是一个语句块里的。但是如果原来的代码都加上了大括号,然后把缩进做正常,那么对后面维护的人会是一个非常好的事情。就不会犯我这个低级错误了。就像下面的代码一样,虽然写起来有点罗嗦,但利人利己。

if ( ...... ){
    return x;
}
if ( ...... ){
    return y;
}
if ( ...... ){
    return z;
}

与此类似的代码风格还有如下,你觉得哪个更容易阅读呢?

  • if (!p)    和  if (p == NULL)
  • if (p)    和  if (p != NULL)
  • if (!bflag)  和 if  (bflag == false)
  • if ( CheckSomthing() )  和 if ( CheckSomething() == true )

另外还有很多人在switch 语句里用case来做if,也就是说case后面没有break。就像Duff’s Device一样,再配以goto,代码就写得相当精彩了(这里有个例子

所以说,代码不是炫酷的地方是给别人读的。

另外,我在想,为什么苹果的这段代码不写成下面这样的形式?你看,下面这种情况不也很干净吗?

if (  ((err = ReadyHash(&SSLHashSHA1, &hashCtx)) != 0 )
       || ((err = SSLHashSHA1.update(&hashCtx, &clientRandom)) != 0)
       || ((err = SSLHashSHA1.update(&hashCtx, &serverRandom) != 0)
       || ((err = SSLHashSHA1.update(&hashCtx, &signedParams) != 0)
       || ((err = SSLHashSHA1.final(&hashCtx, &hashOut)) != 0)) {

     goto fail;
}

其实,还可以做一些代码上的优化,比如,把fail标签里的那些东西写成一个宏,这样就可以去掉goto语句了。

4)关于goto语句

关于goto语句,1968年,Edsger Dijkstra 投了一篇文章到Communications of the ACM。原本的标题是《A Case Against the Goto Statement》。CACM编辑Niklaus Wirth灵感来了,把标题改为我们熟知的 《Go To Statement Considered Harmful》Dijkstra写的内容也是其一贯的犀利语气,文中说:“几年前我就观察到,一个程序员的品质是其程序中goto语句的密度成反比的”,他还说,“后来我发现了为什么goto语句的使用有这么严重的后果,并相信所有高级语言都应该把goto废除掉。”  (花絮:因为,这篇文章的出现,计算学界开始用’ X considered harmful ‘当文章标题的风潮,直到有人终于受不了为止)

为什么goto语句不好呢?Dijkstra说,一个变量代表什么意义要看其上下文。一个程序用N记录房间里的人数,在大部分时候,N代表的是“目前房间里的人”。但在观察到又有一个人进房间后、把N递增的指令前的这段程序区块中,N的值代表的是“目前房间里的人数加一”。因此,要正确诠释程序的状态,必须知道程序执行的历史,或着说,知道现在“算到哪”了。

怎么谈“算到哪了”?如果是一直线执行下来的程序,我们只要指到那条语句,说“就是这里”,就可以了。如果是有循环程序,我们可能得说:“现在在循环的这个地方,循环已经执行了第i次”。如果是在函数中,我们可能得说:“现在执行到函数p的这一点;p刚刚被q调用,调用点在一个循环中,这个循环已经执行了i次”。

如果有goto语句了呢?那就麻烦了。因为电脑在执行某个指令前,可能是从程序中许许多多goto其中之一跳过来的。要谈某变量的性质也几乎变得不可能了。这就是为什么goto语句问题。

Dijkstra的这篇文章对后面很多程序员有非常深的影响,包括我在内,都觉得Goto语句能不用就不用,虽然,我在十年前的《编程修养》(这篇文章已经严重过时,某些条目已经漏洞百出)中的第23条也说过,我只认为在goto语句只有一种情况可以使用,就是苹果这个bug里的用法。但是我也同意Dijkstra,goto语句能不用就不用了。就苹果的这个问题而言,在更为高级的C++中,使用RAII技术,这样的goto语句已经没有什么存在的意义了。

Dijkstra这篇文章后来成为结构化程式论战最有名的文章之一。长达19年之后,Frank Rubin投了一篇文章到CACM,标题为《‘ Go To Considered Harmful’ Considered Harmful 》Rubin说,「虽然Dijkstra的说法既太学术又缺乏说服力」,却似乎烙到每个程序员的心里了。这样,当有人说“用goto语句来解这题可能会比较好”会被严重鄙视。于是Rubin出了一道这样的题:令XN * N的整数阵列。如果X的第i行全都是零,请输出i。如果不只一行,输出最小的i .

Rubin找了一些惯用goto和不用goto的程序员来解题,发现用goto的程序又快又清楚。而不用goto通常花了更多的时间,写出很复杂的解答。你觉得呢? 另外,你会怎么写这题的程序呢?

花絮:以后几个月的CACM热闹死了。编辑收到许多回应,两个月后刊出了其中五篇。文章也包括了《“‘GOTO Considered Harmful’ Considered Harmful” Considered Harmful? 》)

对于我而言,goto语句的弊远远大于利,在99%的情况下,我是站在反goto这边的。Java和Python就没有提供Goto语句,原因就是因为goto语句很容易被滥用!

花絮:这段时间,我在开发Nginx的模块,因为以前没有做过,而且Nginx的开发文档也不好,所以就得读一些别人的源代码。当我看了某个nginx redis的模块里的这段代码 ngx_http_redis2_reply.c 看到里面飞沙走石的goto语句,我崩溃了,当然,这是代码自动生成工具生成出来的,只是想以这个例子说明goto也是混乱代码的一种黑魔法(另,这位同学看似很喜欢goto语句,在很多代码里都能看得见,比如:这里这里,还有这里……,虽然我觉得很多goto都没有必要)。我想说,如果人把代码写成这样,那我看到这样的代码的时候,就像我在某个餐馆看到了他那肮脏的厨房,无论做菜的技艺有多高超,做的菜做得有多好看多好吃,我都恶心得一点也不想吃了)

更新:2014年3月5日 - RedHat 近日也发现个GnuTLS安全问题,与苹果的类似:无法正确检验特定的伪造SSL证书,这个总是会将伪造证书识别为有效证书。虽然Redhat的代码为if加上了花括号,但还是因为没有控制好goto,造成了bug。所以说啊,goto语句的坑是很多。

goto语句在写代码的时候也许你会很爽,但是在维护的时候,绝对是一堆坑!redhat的这个patch为原来本来只有一个label的goto又加了另一个label,现在两个label交差goto,继续挖坑……

总结

你看,我们不能完全消灭问题,但是,我们可以用下面几个手段来减少问题:

1)尽量在编译上发生错误,而不是在运行时

2)代码是让人读的,顺便让机器运行。不要怕麻烦,好的代码风格,易读的代码会减少很多问题。

3)Code Review是一件很严肃的事情,但 Code Reivew的前提条件是代码的可读性一定要很好。

4)测试是一件很重要也是很难的事情,尤其是开发人员要非常重视

5)不要走飞线,用飞线来解决问题是可耻的!所以,用goto语句来组织代码的时代过去了,你可以有很多种方式不用goto也可以把代码组织得很好。

最后,我在淘宝过去的一年里,经历过一些P1/P2故障,尤其是去年的8-9月份故障频发的月份,我发现其中有70%的P1/P2故障,就是因为没有code review,没有做好测试,大量地用飞线来解决问题,归根结底就是只重业务结果,对技术没有应有的严谨的态度和敬畏之心。

正如苹果的这个“goto fail”事件所暗喻的,如果你对技术没有应有的严谨和敬畏之心,你一定会——

Go To Fail !!!

在这里唠叨这么多,与大家共勉!

(全文完)

(转载本站文章请注明作者和出处 酷 壳 – CoolShell.cn ,请勿用于任何商业用途)

——=== 访问 酷壳404页面 寻找遗失儿童。 ===——

相关文章

20 Feb 01:36

使用python/casperjs编写终极爬虫-客户端App的抓取

by 遇见sharon
mathematrix

Nice post

1.缘起 随着移动互联网的发展,现在写web和我三年前刚开始写爬虫的时候已经改变了太多。特别是在node以及j […]