分享

OpenStack新手开发指南

 
pig2 发表于 2013-11-11 12:47:34 [显示全部楼层] 只看大图 回帖奖励 阅读模式 关闭右栏 150 192509
本帖最后由 nettman 于 2014-1-20 22:07 编辑

OpenStack开发指南

目录
1、nova源码架构介绍2
1.1、源码的获取2
1.2、开发环境的搭建2
1.3、nova模块调用介绍6
1.4、nova源码模块功能介绍7
2、数据库表结构的扩展8
2.1、nova表结构的扩展8
2.2、keystone表结构的扩展10
3、resetful接口服务的扩展12
3.1、nova数据库调用接口服务的扩展12
3.2、compute接口的扩展17
3.3、keystone接口服务的扩展20
3.4、基于openstack服务、配置架构自定义服务模块21
4、dashboard源码介绍25
4.1 horizon代码模块介绍26
4.2 中文化的功能实现26
4.3 页面按钮的添加27
4.4 列表中下拉菜单的添加29
4.5 列表中文字链接的添加29




1、nova源码架构介绍
1.1、源码的获取
openstack的源码可以从安装好的openstack环境上直接copy下来使用,也可以从官网下载(红帽地址:http://repos.fedorapeople.org/repos/openstack/),ubuntu环境下openstack的源码目录为:/usr/share/pyshared/,这里面的源码安装好后会被外链到/usr/lib/python2.7/dist-packages/,这是python2.7以后的版本的做法,所有的python项目都会采取这种存储方式;centos环境下openstack一般会安装python2.6的环境,所以openstack的源码存储在/usr/lib/python2.6/site-packages/。

1.2、开发环境的搭建
第一步、从官网下载python安装包(http://www.python.org/getit/),如果是openstack的环境下开发,可以忽略这一步,因为安装openstack的时候已经自动安装了python环境。
第二步、然后安装开发python程序的IDE,python的程序一般使用PyDev作为开发工具,它可以被集成到eclipse和aptana等IDE中。PyDev的下载地址:http://pydev.org/download.html,aptana的下载地址:http://aptana.org/,当前发布的是aptana3.0,默认已经集成了PyDev。当前我使用的是aptana,由于3.0使用的面板背景色是黑色的,我又习惯使用2.0的风格怎么办那,3.0有添加了一个颜色模板的菜单,选择一下“Aptana Studio 2.x”就可以了,如下图。
1.png
第三步、导入openstack的项目,打开IED,在左边的面板空白处右键,左键点击“import”, 如下图:

2.png
从弹出的窗口从选择“Existing Folder As New Project”,导入一个文件夹作为一个工程,如下图所示:
3.png
然后选择一个openstack项目,比如我当前选择了nova文件夹,起一个工程名,只要自己好记就行,下边的单选框可以不用选,点击完成就行了,如下图:
4.png
导入进来的nova目录结构如下图:
5.png
1.3、nova模块调用介绍
6.png
nova-api :起到Cloud Controller的作用,主要为所有的API查询提供了一个接口(比如Openstack API ,EC2 API),引发多数业务流程的活动(如运行一个实例),并实施一些政策(主要是配额检查)。
nova-schedule :接受一个消息队列的虚拟实例请求,通过算法决定该请求应该在那台主机上运行,这个算法可以由我们指定。即起到调度器(Scheduler)的作用.
nova-compute:是一个非常重要的守护进程,负责创建和终止虚拟机实例,即管理着虚拟机实例的生命周期。该模块内部非常复杂,基本原理是简单的,就是接受来自队列的动作然后执行一些列的系统操作(如启动一个KVM实例),并且更新数据库的状态。
nova-network :该守护进程跟nova-compute and nova-volume 2个模块的功能是相似的。接受来自队列的任务,然后执行相应的任务对网络进行操作(比如:安装网桥接口和改变iptable规则)
Queue:为各个模块之间的通信提供起到一个集线器的作用,即数据交换中心。目前是采用RabbitMQ ,理论上是可以采用任何的基于python ampqlib的AMPQ message queue。
SQL database: 存储云基础设施构建时和运行时状态。包括可用的实例类型,正在使用的实例类型,可用的网络和项目。理论上,OpenStack Compute是支持所有基于 SQL-Alchemy的数据库,但目前广泛使用的数据库主要是Sqlite3,Mysql,PostgreSQL。
Glance:该项目独立于Openstack Compute,起到镜像的作用。在该项目中,主要包括三个部分: glance-api, glance-registry and 镜像存储。Glance-api接受API调用,glance-registry存储和检索镜像的元数据。镜像存储Image blobs。存储可以选择不同的存储方案,比如用Swift实现存储。
Dashboard:该项目是一个可选的项目,主要是为开发者等提供API。

1.4、nova源码模块功能介绍
7.jpg
2、数据库表结构的扩展
2.1、nova表结构的扩展
第一步、创建表的数据库结构,创建表结构的文件存在于目录nova.db.sqlalchemy.migrate_repo.versions,当执行nova-manage db sync命令时就会按照这个目录下文件开头的序列号顺序执行。添加表时只需要实现一个类似的文件,把名字的开头命名为最大序列值加1。比如我要创建一张表,表名为:domains,在这个versions下创建一个084_add_domains.py的文件,文件内容如下
#-*- coding: utf-8 -*-
from sqlalchemy import Boolean, Column, DateTime, Integer
from sqlalchemy import MetaData, String, Table
from nova import log as logging
LOG = logging.getLogger(__name__)

def upgrade(migrate_engine):
    ''' 创建表 '''
    meta = MetaData()
    meta.bind = migrate_engine
    #生成创建表sql语句
    domains = Table('domains', meta,
            Column('id', Integer(), primary_key=True, nullable=False),
            Column('name',
                   String(length=255, convert_unicode=False,
                          assert_unicode=None,
                          unicode_error=None, _warn_on_bytestring=False)),
            Column('ttl', Integer()),
            Column('rdtype',
                   String(length=255, convert_unicode=False,
                          assert_unicode=None,
                          unicode_error=None, _warn_on_bytestring=False)),
            Column('rdata',
                   String(length=255, convert_unicode=False,
                          assert_unicode=None,
                          unicode_error=None, _warn_on_bytestring=False)),
            Column('project_id',
                   String(length=255, convert_unicode=False,
                          assert_unicode=None,
                          unicode_error=None, _warn_on_bytestring=False)),
            Column('created_at', DateTime(timezone=False)),
            Column('updated_at', DateTime(timezone=False)),
            mysql_engine='InnoDB'
            )
    try:
        domains.create()
    except Exception, e:
        LOG.exception('Exception while creating domains table, error' % str(e))
        raise

    #构造初始化信息
    DOMAINDS = {
    'NS': dict(name='test.com', rdata='ns0.test.com.', rdtype='NS', ttl=86400),
    'SOA': dict(name='test.com', rdata='test.com. www.test.com. 200309181 28800 7200 86400 28800', rdtype='SOA', ttl=86400)}
    try:
        #逐行插入初始化信息
        domain_insert = domains.insert()
        for name, values in DOMAINDS.iteritems():
            domain_insert.execute({'name': values["name"],
                       'rdata': values["rdata"],
                       'rdtype': values["rdtype"],
                       'ttl': values["ttl"]})
    except Exception, e:
        LOG.exception('Exception while seeding domains table, error: %s' % str(e))
        raise


def downgrade(migrate_engine):
    ''' 删除表 '''
    meta = MetaData()
    meta.bind = migrate_engine
    domains = Table('domains', meta, autoload=True)
domains.drop()
第二步、生成表结构的模型,nova的表对象模型存在于nova.db.sqlalchemy.migrate_repo.models.py文件中,需要在这里面添加一个实现类,位置无所谓,但一般新添加的都放到文件的最末尾,这里我添加一个domains的模型类,代码如下:
class Domain(BASE, PhysicalBase):
    """ 虚拟机主机名 """
    __tablename__ = "domains"
    id = Column(Integer, primary_key=True)
    name = Column(String(255))
    ttl = Column(Integer)
    rdtype = Column(String(255))
    rdata = Column(String(255))
project_id = Column(String(255))
第三步、实现表查询,因为nova使用的事sqlalchemy,所以查询只需要按照它的规则构造就行,数据库查询的方法都在nova.db.sqlalchemy.api.py文件中,可以实现增删改查功能,下边我实现一个创建功能的语句,代码如下:
@require_admin_context
def domain_create(context, values):
    if model_query(context, models.Domain, read_deleted="yes").\
                filter(and_(models.Domain.name == values['name'], models.Domain.rdata == values['rdata'])).\
                count() == 0:
        domain = models.Domain()
        domain.update(values)
        domain.project_id = context.project_id
        domain.save()
        return domain
    else:
        return None
第四步、添加一个表查询语句的封装结构,为了安全起见,nova的其它模块不直接调用执行数据库查询的方法,而是调用一个上层封装的方法。在nova.db.api.py文件中添加一行调用就行,nova的其它模块调用数据库接口都是引用的这个文件,代码如下:
def domain_create(context, values):
return IMPL.domain_create(context, values)

2.2、keystone表结构的扩展
Keystone的表结构扩展相对容易,虽然数据库中间件使用的也是sqlalchemy,但是在keystone中只需要在keystone.identity.backends.sql.py文件中实现一个抽象类就可以,使用命令执行keystone-manage db_sync后表结构就可以正常创建。
第一步,构建表结构,现在我添加一个表tenant_key,实现的代码如下:
class TenantKey(sql.ModelBase, sql.DictBase):
    __tablename__ = 'tenant_key'
    id = sql.Column(sql.String(64), primary_key=True)
    tenant_id = sql.Column(sql.String(64), unique=True)
    extra = sql.Column(sql.JsonBlob())

    @classmethod
    def from_dict(cls, tenant_dict):
        # shove any non-indexed properties into extra
        extra = {}
        for k, v in tenant_dict.copy().iteritems():
            # TODO(termie): infer this somehow
            if k not in ['id', 'tenant_id', 'extra']:
                extra[k] = tenant_dict.pop(k)

        tenant_dict['extra'] = extra
        return cls(**tenant_dict)

    def to_dict(self):
        extra_copy = copy.deepcopy(self.extra)
        extra_copy['id'] = self.id
        extra_copy['tenant_id'] = self.tenant_id
        return extra_copy
keystone中每个表都需要有extra这个字段,内容最多两个元素的字典数据,在接口调用时会把字典中的键值构造成表的字段,对应值的数据类型也会原封不变的恢复。比如一条记录extra的值为:{“addresses”: [“192.168.71.101”, “192.168.71.102”]},值是一个列表,存到数据库是原样存储的,接口返回时会还会被构造为一个列表。
每个实体类都有from_dict、to_dict两个方法,这个是用来接口和数据库进行数据转换时用的函数。

第二步,添加查询语句,查询语句需要在下边的Identity类中添加,下边是实现的创建tenant_key记录的代码:
def create_tenant_key(self, tenant_id):
session = self.get_session()
with session.begin():
tenant_key_ref = TenantKey()
tenant_key_ref.id = uuid.uuid4().hex
tenant_key_ref.tenant_id = tenant_id
tenant_key_ref.extra = {'public_key': public_key}
session.add(tenant_key_ref)
session.flush()
return tenant_key_ref.to_dict()

第三步,添加调用封装,和nova的类似,需要加一层封装,这里的封装更像是一个java中的接口方法的添加,在keystone.identity.core.py的Driver添加一个方法,以供上层模块调用数据库接口,如果只是在backends.sql.py中内部调用的话,此步可以省略。

3、resetful接口服务的扩展
3.1、nova数据库调用接口服务的扩展
Openstack的所有模块之间的交互都是采用reset接口的方式实现的,所以掌握模块reset接口的扩展至关重要,而接口的扩展中数据库调用的接口相对容易实现。
Nova的接口服务都是在nova.api.openstack下边实现,而对于扩展或者发行版来说,openstack的版本升级是很大的一个隐患,所以接口的扩展一般都独立写成一个文件,或者一个单独的类、方法。
nova.api.openstack下边有两个模块,一个是compute这个主要是虚拟机和宿主机相关的接口实现,volume是快存储相关接口的实现。
第一步,实现接口的数据库调用模块,现在实现一个宿主机相关的接口文件,在nova.api.openstack.compute下边添加一个文件:machines.py,实现一个查询所有machine实例列表接口,代码如下:
#-*- coding:utf-8 -*-

import webob.exc

from nova.api.openstack import common
from nova.api.openstack.compute.views import machines as views_machines
from nova.api.openstack import wsgi
from nova.api.openstack import xmlutil
from nova import exception
from nova import flags
from nova import db
from nova import log as logging


LOG = logging.getLogger(__name__)
FLAGS = flags.FLAGS


def make_machine(elem, detailed=False):
    elem.set('floating_ip')
    elem.set('name')
    elem.set('id')

    if detailed:
        elem.set('updated')
        elem.set('created')

class MinimalMachinesTemplate(xmlutil.TemplateBuilder):
    def construct(self):
        root = xmlutil.TemplateElement('machines')
        elem = xmlutil.SubTemplateElement(root, 'machine', selector='machines')
        make_machine(elem)
        xmlutil.make_links(root, 'machines_links')
        return xmlutil.MasterTemplate(root, 1, nsmap=machine_nsmap)

class MachinesTemplate(xmlutil.TemplateBuilder):
    def construct(self):
        root = xmlutil.TemplateElement('machines')
        elem = xmlutil.SubTemplateElement(root, 'machine', selector='machines')
        make_machine(elem, detailed=True)
        return xmlutil.MasterTemplate(root, 1, nsmap=machine_nsmap)

class Controller(wsgi.Controller):
    _view_builder_class = views_machines.ViewBuilder

    def __init__(self, **kwargs):
        super(Controller, self).__init__(**kwargs)

    @wsgi.serializers(xml=MinimalMachinesTemplate)
    def index(self, req):
        """ 获取machines所有实例名字信息 """
        context = req.environ['nova.context']

        try:
            all_machines = db.machine_get_all(context)
        except exception.Invalid as e:
            raise webob.exc.HTTPBadRequest(explanation=str(e))
        return self._view_builder.index(req, all_machines)

    @wsgi.serializers(xml=MachinesTemplate)
    def detail(self, req):
        """ 获取machine所有列表的详细字段信息 """
        context = req.environ['nova.context']
        try:
            all_machines = db.machine_get_all(context)
        except exception.Invalid as e:
            raise webob.exc.HTTPBadRequest(explanation=str(e))

        return self._view_builder.detail(req, all_machines)

def create_resource():
return wsgi.Resource(Controller())

MinimalMachinesTemplate、MachinesTemplate两个类用来进行接口数据输出时json格式的转化,先使用xml定义json的模板。
Index方法将在默认调用时显示,detail显示详细的数据参数,调用时在url的最后添加/detail就行。
req.environ['nova.context']用来获取请求头中的用户信息,只有符合权限的用户才能调用相应接口,返回对应用户的数据信息。
machine_get_all是nova.db.api.py中的方法,从文件最上边的模块导入中可以看到。
views_machines.ViewBuilder用来在接口返回时向模板填充数据
第二步,实现接口调用后的展示部分, 在nova.api.openstack.compute.views下添加machines.py文件,代码如下:
#-*- coding:utf-8 -*-

import os.path

from nova.api.openstack import common
from nova import flags
from nova import utils


FLAGS = flags.FLAGS


class ViewBuilder(common.ViewBuilder):

    _collection_name = "machine"

    def basic(self, request, machine):
        return {
            "machine": {
                "id": machine.get("uuid"),
                "name": machine.get("domain_name"),
                "floating_ip": machine.get("floating_ip"),
                "links": self._get_links(request, machine["uuid"]),
            },
        }

    def show(self, request, machine):
        machine2dict = {
            "id": machine.get("uuid"),
            'name': machine.get("domain_name"),
            'floating_ip': machine.get("floating_ip"),
            "created": self._format_time(machine.get("created_at")),
            "updated": self._format_time(machine.get("updated_at")),
            "links": self._get_links(request, machine["uuid"]),
        }
        return dict(machine=machine2dict)

    def detail(self, request, machines):
        list_func = self.show
        return self._list_view(list_func, request, machines)

    def index(self, request, machines):
        list_func = self.basic
        return self._list_view(list_func, request, machines)

    def _list_view(self, list_func, request, machines):
        machine_list = [list_func(request, machine)["machine"] for machine in machines]
        machines_links = self._get_collection_links(request, machines)
        machines_dict = dict(machines=machine_list)

        if machines_links:
            machines_dict["machines_links"] = machines_links

        return machines_dict

    def _get_links(self, request, identifier):
        """Return a list of links for this machine."""
        return [{
            "rel": "self",
            "href": self._get_href_link(request, identifier),
        },
        {
            "rel": "bookmark",
            "href": self._get_bookmark_link(request, identifier),

    @staticmethod
    def _format_time(date_string):
        """Return standard format for given date."""
        if date_string is not None:
            return date_string.strftime('%Y-%m-%d %H:%M:%S')

这个文件就属于web框架中MVC中的页面展示部分

本帖被以下淘专辑推荐:

已有(140)人评论

跳转到指定楼层
故意 发表于 2013-11-11 13:41:05
第一个支持下
回复

使用道具 举报

rsgg03 发表于 2013-11-11 13:46:28

我也顶顶顶
回复

使用道具 举报

网名还没想好 发表于 2013-11-11 14:30:11
赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞
回复

使用道具 举报

微仔 发表于 2013-11-12 09:47:44
感谢分享!学习中。。
回复

使用道具 举报

lixinzhangok 发表于 2013-11-12 10:46:48
提示: 作者被禁止或删除 内容自动屏蔽
回复

使用道具 举报

pig2 发表于 2013-11-13 12:18:01
回复

使用道具 举报

匿名  发表于 2013-11-27 01:01:41
不错,学习中
回复

使用道具

匿名  发表于 2013-11-27 01:03:45
为什么见不到下载地址
回复

使用道具

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

推荐上一条 /2 下一条