存档

作者存档

Cinder Private Volume Type(租户卷类型) 在大规模部署中带来的好处

2015年7月9日 没有评论

问题的提出

在 OpenStack 部署中,一般将不同存储类型,不同性能规格的存储资源形成一个大的集群,对外提供云硬盘的服务,这个大的集群统一由 cinder 来管理。但是在实践中,往往会遇到一些问题。

我们知道所有的计算节点可以按照 Zone 来划分,把计算机点按机房(机柜)的位置纳入到一个 Zone 中,如果其中一个机房(机柜)因为某种原因出问题了,不会影响其它机房/柜的虚拟机和网络。 而且, 同属于一个 Zone 的云主机在发生租户内部的流量的时候,就不会超出这个 Zone 的网络,租户网络的性能得到最大的利用和提升。

不过对于存储的访问,如果没有类似于这种隔离的技术,就会造成存储网络不可控,存储流量在各个存储交换机之间乱跑。

另一方面,根据不同的要求往往划分为多种不同的类型的云硬盘:例如:

  • 根据不同的后端存储
  • 创建不同性能的云硬盘
  • 为了开发新技术,创建一些测试类型的存储卷

但是所有的存储类型对租户都是可见的,用户创建磁盘的时候可以任意选择云硬盘类型,哪怕这些类型并不希望被某些用户使用。更糟糕的是,用户创建基于新的云硬盘启动的云主机的时候,这个新的云硬盘的类型是随机的!

情况在 Kilo 版本带来的名为 Private Type(私有卷) 的功能得到改善。该功能允许创建租户私有的云硬盘类型,这些类型的云硬盘指定为只能被某些租户访问。

解决方式

综上,对之前提到的两个问题,就有了比较好的解决方案。下面以使用一个 Ceph 作为存储后端为例说明:

  • 首先,从物理层面上,将一个 Zone 内的计算节点的存储网络和适当的 Ceph 节点的存储网络放在同一个网络设备下(比如同一个交换机)。
  • 然后,创建一个 Ceph 集群的资源池,指定资源池包含上面的 Ceph 节点。
  • 最后,根据上面的资源池创建对应的的云硬盘类型,并设置改类型为私有的,将它的访问权限赋予适当的租户。

至此,没有被赋予访问权限的租户将看不到这些存储。完成了实际上的存储隔离。

需要注意的地方

注意:即使是 Kilo 版本的 OpenStack,也没有完善该功能, 需要对创建云硬盘部分进行改动,确保在 nova 创建基于云硬盘启动的云主机的时候,只能创建具有权限的云主机。

参考

具体改功能的用法请参考 这里 的干货

分类: OpenStack 标签:

QT 中控件关闭的几个 issue

2015年5月25日 没有评论

最近在写一个小应用,需要一个持久化的窗口,即用户在关闭窗口的时候需要捕获这个事件,让窗口暂时隐藏. 总结了 QT 中的控件关闭时的一些小 issue:

lastWindowClosed() and setQuitOnLastWindowClosed()

如果一个控件是一个可见的窗口类型,并且没有父容器,那么关闭该窗口导致的 结果是整个应用程序会退出. 连控件的析构函数都来不及调用。不过,QT 会在这 种情况发生的时候发送一个 QGuiApplication::lastWindowClosed() 的信号, 连接这个信号就可以获取这个事件,用来做一些清理的操作,例如:

connect(QApplication::instance(), SIGNAL(lastWindowClosed()), this, SLOT((cleanup())));

如果想忽略这种行为,在 QGuiApplication 这个类里,提供了一个静态方法, setQuitOnLastWindowClosed(bool) 来控制这种行为, 比如:

QApplication::setQuitOnLastWindowClosed(false);

这样, 当最后一个可视窗口关闭,整个程序就不会退出。

closeEvent()

QWidget 基类提供了一个 protect closeEvent() 方法,在用户点击关闭窗口 的时候,会调用改方法,使得 QWidget 子类的实现可以控制改关闭事件,比如下面的代码, 调用 QCloseEvent::ignore() 来忽略该事件,只是隐藏窗口显示.

void Window::closeEvent(QCloseEvent *event)
{
    event->ignore();
    this->hide();
}

分类: QT 标签:

比 grep 强悍太多的文本检索工具 ACK

2015年4月24日 1 条评论

对程序员来说, grep, cat, 真心是伟大的前辈们赐予我们这些晚辈最好的礼物, 不会用 grep, cat 之类程序员真心没有出息.

但是不知是否有人经常使用以下的语法, 而且感到要是 grep 要是在智能一点就好了

$ grep terminate_instance $(find . -name '*.py' | grep -v tests)

不错, awk 就是为了满足我们希望 grep 更好用这样的前提下被开发出来的, 首先从他的 官网 beyondgrep.com 就可以看出来 🙂 官网给出比 grep 好的理由是:

  • 速度非常快 因为它只搜索有意义的东西.
  • 更好的搜索方案 默认忽略那些不是你想搜索的源码的东西, .e.g. git, svn 目录
  • 为源代码搜索而设计 用更少的击键完成任务
  • 移植性非常好 该程序用 Perl 完成的, 可以在几乎所有主流平台上使用, windows, mac, linux 等
  • 免费开源

Table of Contents

安装

不求那些炫酷的安装方式, 在 一般的 linux 平台, 用包管理器, 比如在 centos/fedora:

# yum install -y ack

在 OSX, 使用 brew

$ brew install ack

使用

回到文首的那个用法, 现在可以很简单的用 ack 来完成, 而且速度暴快:

$ ack --python --ignore-dir=tests terminate_instance

该程序的一般使用很简单:

$ ack --type PATTERN

其中 type 指定需要搜索的源代码类型, 比如 –python, 其它大部分选项和 grep 一样, 文档是说 99%.

高级使用

请参考 官网

分类: Tools 标签:

Build Emacs As A Python IDE

2014年12月30日 没有评论

众所周知在很久很久以前, 初学者要把 Emacs 搭建成比较容易上手的环境, 需要 一点精力和耐心, 但这恰恰是 Emacs 的魅力所在(超高度的可定制化), 但对初学者来说, 由于 el-get包管理器 的出现, 情况变得越来越好了. 比如要马上开始用 Emacs 来作为开发 Python 的工具, 现在已经很简单了.

安装 el-get

将下列配置写入到 .emacs 中

(add-to-list 'load-path "~/.emacs.d/el-get/el-get")
(unless (require 'el-get nil 'noerror)
  (with-current-buffer
      (url-retrieve-synchronously
       "https://raw.github.com/dimitri/el-get/master/el-get-install.el")
    (let (el-get-master-branch)
      (goto-char (point-max))
      (eval-print-last-sexp))))
 
(el-get 'sync)

just it! 启动 emacs 的时候会自动初始化 el-get 需要的配置信息

jedi

jedi 主要是一个自动补全的插件, Emacs 已经有一个名为 auto-complete 的强大的 补全插件了, 通吃所有语言. 没错, jedi 的自动补全就是利用该插件作为后端了, 不仅如此, 他还可以在编写代码的时候实时查看函数的成员信息, 函数的参数信息和文档信息等, 总之很强大.

使用 el-get-install 回车, 输出 jedi 安装, 借助 el-get 的强大, 所有的依赖都会 自动安装(.e.g. auto-complete, epc)

M-x el-get-install

在 .emacs 里加入 jedi 的配置

add-hook 'python-mode-hook 'jedi:setup)
(setq jedi:complete-on-dot t)

ropemacs

ropemacs 是借助 rope, pymacs 来做 python 的工程管理的东西, 在它面前, 神马 代码重构, 代码跳转, 自动模块导入, 类成员补全神马都是浮云. (注意, 所有代码 补全我都使用 jedi 来做, 不用到这里的功能)

安装也很方便, 直接 el-get-install 然后回车输入 ropemacs 就可以了

安装完成在 .emacs 里加入

(pymacs-load "ropemacs" "rope-")
(setq ropemacs-enable-autoimport t)
 
(autoload 'pymacs-apply "pymacs")
(autoload 'pymacs-call "pymacs")
(autoload 'pymacs-eval "pymacs" nil t)
(autoload 'pymacs-exec "pymacs" nil t)
(autoload 'pymacs-load "pymacs" nil t)

flycheck

该插件是一个实时代码检查的工具, 也就是在编代码的过程中, 会根据改语言的编码 规范, 实时检查和提示源代码的错误, 然后给出警告, 比如语法错误, 编码规范 等. 该插件也是通吃所有语言, 这里我们只关注 python 相关的

直接 el-get-install 然后回车输入 flycheck 安装.

在 .emacs 里面加入

(add-hook 'after-init-hook #'global-flycheck-mode)

注意, flycheck 需要一个检查语法的后端程序, 如果是 python 的话, 推荐 pylink,

# pip install pylink

后记

不考虑其他的配置, 什么强大的 ido, ibuffer, 按键绑定配置, 窗口配置等等, 一个 标准的 python IDE 就配置好了, 就这么简单.

附上所有的 .emacs 配置

(add-to-list 'load-path "~/.emacs.d/el-get/el-get")
(unless (require 'el-get nil 'noerror)
  (with-current-buffer
      (url-retrieve-synchronously
       "https://raw.github.com/dimitri/el-get/master/el-get-install.el")
    (let (el-get-master-branch)
      (goto-char (point-max))
      (eval-print-last-sexp))))
 
(el-get 'sync)
(add-hook 'python-mode-hook 'jedi:setup)
(setq jedi:complete-on-dot t)
 
(add-hook 'after-init-hook #'global-flycheck-mode)
 
(pymacs-load "ropemacs" "rope-")
(setq ropemacs-enable-autoimport t)
 
(autoload 'pymacs-apply "pymacs")
(autoload 'pymacs-call "pymacs")
(autoload 'pymacs-eval "pymacs" nil t)
(autoload 'pymacs-exec "pymacs" nil t)
(autoload 'pymacs-load "pymacs" nil t)

分类: Emacs, python 标签: ,

给 Mac 一个 ssh-copy-id

2014年12月25日 没有评论

Mac 下默认是没有这个非常好用的工具的, 最开始我每次手动把 ~/.ssh/id_rsa.pub 的 内容追加到服务器的 ~/.ssh/authorized_keys 文件中.

后来我自己写了一个脚本, 放到可执行目录中, 爽了很多:

#!/bin/bash
 
# file: ssh-copy-id
 
if [ $# -lt 1 ]; then
    echo 'Usage: ssh-copy-id [user1@]hostname1 [user2@]hostname2]'
fi
 
KEY_FIEL="$HOME/.ssh/id_rsa.pub"
if [ -f $KEY_FIEL ]; then
    key=`cat $KEY_FIEL`
    for server in $@; do
        ssh $server "echo $key >> ~/.ssh/authorized_keys"
    done
else
    echo 'ssh key(id_rsa.pub) does not exist, run ssh-keygen to generate it'
    exit 1
fi

在后来, 我偶然发现 brew 中直接有一个包就叫 ssh-copy-id, 我靠, 一直被 linux 系统给误导了, 在 linux 系统中, ssh-copy-id 是包含在名为 openssh-clients 的包中的. 废话不说, 用这个吧, 毕竟人家写的 300 多行, 考虑了各种兼容性等等

$ brew install ssh-copy-id

分类: linux 标签:

CentOS 7 修改时区

2014年12月25日 没有评论

Linux 系统(我特指发行版, 没说内核) 下大部分软件的风格就是不会仔细去考虑向后 的兼容性, 比如你上个版本能用这种程序配置, 没准到了下一个版本, 该程序已经不见了. 比如 sysvinit 这种东西.

设置时区同样, 在 CentOS 7 中, 引入了一个叫 timedatectl 的设置设置程序.

用法很简单:

# timedatectl # 查看系统时间方面的各种状态
      Local time: 四 2014-12-25 10:52:10 CST
  Universal time: 四 2014-12-25 02:52:10 UTC
        RTC time: 四 2014-12-25 02:52:10
        Timezone: Asia/Shanghai (CST, +0800)
     NTP enabled: yes
NTP synchronized: yes
 RTC in local TZ: no
      DST active: n/a
# timedatectl list-timezones # 列出所有时区
# timedatectl set-local-rtc 1 # 将硬件时钟调整为与本地时钟一致, 0 为设置为 UTC 时间
# timedatectl set-timezone Asia/Shanghai # 设置系统时区为上海

其实不考虑各个发行版的差异化, 从更底层出发的话, 修改时间时区比想象中要简单:

# cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

分类: linux 标签:

Glance 源码分析(3) – WSGI 框架

2014年10月27日 没有评论

Table of Contents

Server

OpenStack 大多数模块封装了 eventlet 以及 eventlet.wsgi.server 组成一个 class Server 来作为一个 HTTP RestFul API Server. 咋一看他的封装好像很复杂, 但是如果了解 eventlet 的基本概念, 这部分代码其实非常简单.

eventlet 在它内部定义的 greenthread(绿色线程) 里, 它利用协程来实现并发. 协程的介绍可以看我以前写的文章: Coroutine(协程) 介绍

为了彻底实现高性能的 I/O 并发, eventlet 甚至把底层的一些 API 都做了修改, .e.g socket, listen. 更多的 eventlet 的内容不在这讨论, 可以去 官网 查看相关的文档

下面是一个简单的小例子利用 eventlet 构建一个 HTTP Server, 基本上就是 glance 的 class Server 的核心骨架:

class APP(object):
    @webob.dec.wsgify
    def __call__(self, req):
        return 'hello world'
 
# 创建一个绑定在本机 8000 端口 的 socket.
sock = eventlet.listen(('0.0.0.0', 8000))
# 启动 wsgi server
eventlet.wsgi.server(sock, APP())

that’s it, so easy. 把他保存到文件并运行, 就是一个简单的 Http server, 并且 我可以付责任的告诉你, openstack 中的 WSGI 服务就是这么运行的.

所以我们回过头看 glance.common.wsgi.py:Server 这个类, 它只不过包装了一下 eventlet 的参数, 并且 WSGI 实例是在子进程里面运行的, 支持多进程运行该服务(多核考虑), 对外提供了 Server:start()Server:wait() 两个 API, 其他的, 就真没什么了.

paste 的工厂函数

请先复习一下 paste 的相关内容. 然后看下面的图:

API 框架

图中每一个 filter 都对应着一个工厂函数(可调用的实例), 比如:

[filter:versionnegotiation]
paste.filter_factory = glance.api.middleware.version_negotiation:VersionNegotiationFilter.factory

对应的实例都是 Middleware 其实是的子类, 前面我们讲过, paste配置里面的工厂函数 所创造的是一个能调用的实例(包含__call__方法), 当收到用户请求的时候就会自动调用该 实例, 也就是调用 __call__ 方法. 这里 openstack 所有模块抽象除了一个类 Middleware, 封装了 process_request 方法. 每个 filter 子类只需要继承 该类, 如果需要做处理, 就覆盖 process_request 方法, 然后 Middleware 里面 的 __call__ 方法会根据 process_request 的返回值来判断是否交给下一个 filter 处理.

# file: glance/api/middleware/version_negotiation.py
class VersionNegotiationFilter(wsgi.Middleware):
class Middleware(object):
    def __init__(self, application):
        self.application = application
 
    @classmethod
    def factory(cls, global_conf, **local_conf):
        def filter(app):
            return cls(app)
        return filter
 
    def process_request(self, req):
        return None
 
    def process_response(self, response):
        return response
 
    @webob.dec.wsgify
    def __call__(self, req):
        # 首先调用子类的 process_request 方法, 如果子类没有实现这个方法或者
        # 返回值不为空, 那么直接将子类的返回回复给用户, 否则进行下一个 filter
        # 的处理. 这其实是一个递归的过程, 最后返回从下游(filter或app)的到的返回
        # 给用户
        response = self.process_request(req)
        if response:
            return response
        response = req.get_response(self.application)
        response.request = req
        try:
            return self.process_response(response)
        except webob.exc.HTTPException as e:
            return e

分类: OpenStack 标签:

Glance 源码分析(2) – 配置文件

2014年10月25日 没有评论

这里我们会分析 glance-api 读取以下两个配置文件

  • glance-api.conf: glance-api 的用户配置文件
  • glance-api-paste.ini: glance-api 的 WSGI 配置文件

glance-api.conf

该配置文件的读取是利用 oslo 模块来实现的, oslo 提供了 .ini 格式的配置 文件的解析, 被所有 OpenStack 模块用来解析配置文件.

oslo 的用法很简单, 下面举个简单的例子:

from oslo.config import cfg
 
default_opts = [
    cfg.StrOpt('bind_host',
               default='0.0.0.0',
               help='IP address to listen on'),
    cfg.IntOpt('bind_port',
               default=9292,
               help='Port number to listen on')
]
 
app_opt = cfg.StrOpt('name',
                     default='blog',
                     help='name of this app')
 
# cfg.CONF 是在 oslo.cfg 模块中的一个全局变量, 首先我们需要得到一个它的引用
# 然后调用 register_opt() 注册我们需要解析的配置项, 或者使用 register_opts()
# 同时注册多个配置项
# 如果配置文件中可以找到配置项, 那么使用配置项中的值, 不然使用注册该配置项时指定
# 的默认值
CONF = cfg.CONF
CONF.register_opt(app_opt, group='app')
CONF.register_opts(default_opts)
CONF(default_config_files=['app.conf'])
 
# 使用的时候可以用 CONF.cfgname 来使用, 如果该 cfgname 不在 [DEFAULT] 段下,
# 那么使用 CONF.section.cfgname 来引用
print CONF.items()
print CONF.app.name
print CONF.bind_host
print CONF.bind_port
# file: app.conf
[DEFAULT]
bind_port = 8080
[app]
name = test
# python test.py 
[('bind_port', 8080), ('config_dir', None), ('config_file', ['app.conf']), ('bind_host', '0.0.0.0'), ('app', <oslo.config.cfg.GroupAttr object at 0x7fa4a3d75c50>)]
0.0.0.0
8080
test

那么问题来了… 前面讲过, glance 中的配置文件通过 config.parse_args() 来调用的, 也就是所有的配置都是在 glance.common.config:parse_args() 中完成的, 通过上面的分析, 这个函数其实很简单, 这里就不深入探讨了. 总之, 以后, glance 的各个模块在想访问用户提供的配置的时候, 只需要使用类似以下的代码就可以了:

from oslo.config import cfg
CONF = cfg.CONF
print CONF.enable_v2_api

glance-api-paste.ini

该内容和 glance 无关, 只要搞懂了 python paste 模块的用法, 就很简单了. 关于 paste 模块的使用说明, 请参考 python paste.deploy 探索

下面把之前在框架那里的内容再贴一份:

API 框架

[pipeline:glance-api-keystone]
pipeline = versionnegotiation authtoken context rootapp
[filter:versionnegotiation]
paste.filter_factory = glance.api.middleware.version_negotiation:VersionNegotiationFilter.factory
[filter:authtoken]
paste.filter_factory = keystoneclient.middleware.auth_token:filter_factory
delay_auth_decision = true
[filter:context]
paste.filter_factory = glance.api.middleware.context:ContextMiddleware.factory
[composite:rootapp]
paste.composite_factory = glance.api:root_app_factory
/: apiversions
/v1: apiv1app
/v2: apiv2app

分类: OpenStack 标签:

Glance 源码分析(1) – 框架

2014年10月24日 没有评论

以下主要分析 V2 版本的代码, V1 和 V2的最大区别就是:

  • V1: 包含两个服务 glance-api 和 glance-registry. glance-api 接受客户端的 所有命令, 分发并响应, 涉及到数据库的操作由内部转发到 glance-registry 完成
  • V2 简化了流程, 所有的处理都在内部实现, 不需要额外的服务. 因此只需要 glance-api 一个服务

启动流程

启动流程

# glance/cmd/api.py
def main():
    # 载入配置, 下面的函数在 glance/common/config.py 中定义, 调用此函数会初始化
    # oslo 模块中的模块变量 CONF, 把 glance-api.conf 中的值设置在 CONF 的属性中,
    # 使用的时候按照 "CONF.enable_v1_api" 此方式调用, 具体的细节不多讲
    config.parse_args()
 
    # 初始化后端存储,
    # 1. 将所有后端存储的类名注册到 glance/store/__init__.py:REGISTERED_STORES
    # 2. 将所有后端存储的名字和实例注册到 glance/store/location.py:SCHEME_TO_CLS_MAP
    #    以后 glance-api 可以根据用户的请求和配置文件找到具体的后端存储的实例, 调用相应的
    #    实例的函数(add/delete)来操作
    glance.store.create_stores()
 
    # 启动 WSGI 程序
    # load_paste_app 会在默认位置(/etc/glance/)找到 glance-api-paste.ini,
    # 然后根据用户的配置(是否启用 keystone等)调用 paste.loadapp 载入相应的 app,
    # 最后传递给 server.start 启动 WSGI 应用, 具体的细节之后的系列会讲到
    server = wsgi.Server()
    server.start(config.load_paste_app('glance-api'), default_port=9292)
    server.wait()

API 框架

API 框架

# file: /etc/glance/glance-api-paste.ini
[pipeline:glance-api-keystone]
pipeline = versionnegotiation authtoken context rootapp
[filter:versionnegotiation]
paste.filter_factory = glance.api.middleware.version_negotiation:VersionNegotiationFilter.factory
[filter:authtoken]
paste.filter_factory = keystoneclient.middleware.auth_token:filter_factory
delay_auth_decision = true
[filter:context]
paste.filter_factory = glance.api.middleware.context:ContextMiddleware.factory
[composite:rootapp]
paste.composite_factory = glance.api:root_app_factory
/: apiversions
/v1: apiv1app
/v2: apiv2app

关于 paste 模块的使用说明, 请参考 python paste.deploy 探索

流程如下:

  • 首先 WSGI 接收到用户请求, 将用户的请求信息交给 paste 模块处理
  • paste 模块根据配置文件的规则依次经过 versionnegotiation authtoken context 这几个过滤器
  • 最后交由 rootapp, 这是个类型为 app 的处理, rootapp 内部再根据用户提供的版本 信息(v1/v2) 交由 apiv1app 或者 apiv2app 处理, 最后把返回 HTTP Response
  • 真正的业务逻辑在 apiv1app/apiv2app 内部实现, 见下面的处理流程

处理流程

apiv2app 定义的工厂方法在 glance/api/v2/router:API.factory 中, API 类继承自 wsgi.Router, Router 类利用了 python-route 模块做 url 的选择处理, 具体的流程 见下(详细的分析请参考后面的系列): 处理流程 关于 route 模块的使用说明, 请参考 python Route 简单使用笔记

分类: OpenStack 标签:

Glance 源码分析(0) – 介绍

2014年10月23日 没有评论

这周我把 glance 的源代码通读了一遍(业务流程主要是 V2 版本), 感觉这个模块虽然是 OpenStack 里面相对较小的模块(vs Nova), 但是他的设计模式, 架构一点都不含糊, 我看完 glance 之后再去看其它项目, 发现 API 的等框架大部分地方都是一模一样. 也就是 说看完这个模块, 再去分析其它模块就省事很多了, 以下将我的分析拆分做一个记录:

分类: OpenStack 标签: