pig2 发表于 2013-11-11 12:47:34

OpenStack新手开发指南

本帖最后由 nettman 于 2014-1-20 22:07 编辑

OpenStack开发指南
目录1、nova源码架构介绍21.1、源码的获取21.2、开发环境的搭建21.3、nova模块调用介绍61.4、nova源码模块功能介绍72、数据库表结构的扩展82.1、nova表结构的扩展82.2、keystone表结构的扩展103、resetful接口服务的扩展123.1、nova数据库调用接口服务的扩展123.2、compute接口的扩展173.3、keystone接口服务的扩展203.4、基于openstack服务、配置架构自定义服务模块214、dashboard源码介绍254.1 horizon代码模块介绍264.2 中文化的功能实现264.3 页面按钮的添加274.4 列表中下拉菜单的添加294.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”就可以了,如下图。第三步、导入openstack的项目,打开IED,在左边的面板空白处右键,左键点击“import”, 如下图:
从弹出的窗口从选择“Existing Folder As New Project”,导入一个文件夹作为一个工程,如下图所示: 然后选择一个openstack项目,比如我当前选择了nova文件夹,起一个工程名,只要自己好记就行,下边的单选框可以不用选,点击完成就行了,如下图: 导入进来的nova目录结构如下图: 1.3、nova模块调用介绍 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源码模块功能介绍 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, Integerfrom sqlalchemy import MetaData, String, Tablefrom nova import log as loggingLOG = 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_contextdef 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 = 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_copykeystone中每个表都需要有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().hextenant_key_ref.tenant_id = tenant_idtenant_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 commonfrom nova.api.openstack.compute.views import machines as views_machinesfrom nova.api.openstack import wsgifrom nova.api.openstack import xmlutilfrom nova import exceptionfrom nova import flagsfrom nova import dbfrom 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 commonfrom nova import flagsfrom 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 = 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中的页面展示部分
第三步、实现访问地址和控制器的路由配置,在nova.api.openstack.compute.__init__.py文件中,添加如下代码:from nova.api.openstack.compute import machinesself.resources['machines'] = machines.create_resource()mapper.resource("machine", "machines",controller=self.resources['machines'],collection={'detail': 'GET'})第四步、重启nova-api,执行/etc/init.d/nova-api restart第五步、调试接口是否可以正常使用,执行命令:curl -X GET -H "X-Auth-Token:091bdfe658d7408bb25886a9a1772f90" http://192.168.70.201:8774/v1.1/ ... 178cd1079d/machines,将会调用nova.api.openstack.compute.machines.py的index方法curl -X GET -H "X-Auth-Token:091bdfe658d7408bb25886a9a1772f90" http://192.168.70.201:8774/v1.1/ ... 79d/machines/detail 将会调用nova.api.openstack.compute.machines.py的detail方法,正常情况下将会返回数据库中的信息第六步、编写python-novaclient客户端程序,写好的api接口怎么让horizon使用哪,这就需要python的客户端帮忙了,这部分的代码相对比较简单。找到novaclient所在的目录,python2.6可以在/usr/lib/python2.6/site-packages/novaclient中找到源码,在novaclient.v1.1目录下添加machines.py文件,代码如下:#-*- coding:utf-8 -*-
from novaclient import base

class Machine(base.Resource):    HUMAN_ID = True
    def __repr__(self):      return "<Machine: %s>" % self.name
class MachineManager(base.ManagerWithFind):    """    Manage :class:`machine` resources.    """    resource_class = Machine
    def list(self, detailed=True):      if detailed is True:            return self._list("/machines/detail", "machines")      else:            return self._list("/machines", "machines")然后在novaclient.v1.1.client.py中导入machines文件,from novaclient.v1_1 import machines的__init__构造函数中添加:self.machines = machines.MachineManager(self)第七步,horizon的接口调用都会在进行一次封装,在horizon.api.nova.py中添加代码如下:def machine_list(request):return novaclient(request).machines.list()现在为止一个完整的nova接口就算完成了,剩下的就是horizon代码里面的调用,这个下边会将。
3.2、compute接口的扩展Compute的接口实现是openstack当中层级最多的,因为他是属于多节点的调用,用到了rabbitMQ任务调度模块,底层又调用了libvirt接口,所以实现起来可能有点麻烦,但是理清了各个文件的职能,那就只剩下往里面填充代码了,下边是一个虚拟机操作的一个接口调用流程图:
现在我按照这个流程实现一个虚拟机快照功能,接口一般从底层往上写,这样可以一级级调试,代码写完就可以运行,避免最后代码都写完了,一旦出错就不知道到底是哪一层出的问题。第一步,编写快照功能的底层实现,在nova.virt.libvirt.connection.py的LibvirtConnection类中实现一个快照的方法,代码如下:@exception.wrap_exception()def create_vm_snapshot(self, context, instance, snapshot_name, snapshot_description):    try:      #获取虚拟机实例      virt_dom = self._lookup_by_name(instance['name'])    except exception.InstanceNotFound:      raise exception.InstanceNotRunning()
    try:      xmldesc = '<?xml version="1.0" ?>\      <domainsnapshot>\      <name>%s</name>\      <description>%s</description>\      <domain>\            <uuid>%s</uuid>\      </domain>\      </domainsnapshot>' % (snapshot_name, snapshot_description, instance['uuid'])      LOG.info('snapshot xml: %s' % xmldesc)
      #创建虚拟机快照      snapshot_dom = virt_dom.snapshotCreateXML(xmldesc.encode('utf-8', 'ignore'), 0)
      if snapshot_dom is None:            LOG.error('create instance %s snapshot %s is error' % (instance['name'], snapshot_name))      else:            LOG.info('create snapshot %s is successful' % snapshot_name)    except Exception, e:      LOG.error('create snapshot is error: %s' % str(e))      raise exception.CreateSnapshotFailed(snapshot_name=snapshot_name)
第二步,在nova.compute.manager.py中构造调用底层实现的参数,manager.py这个文件在每个compute节点都会运行,所以如果多节点的环境,进行代码更新时要全部更新,实现代码如下:@exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())@checks_instance_lock@wrap_instance_faultdef create_vm_snapshot(self, context, instance_uuid, snapshot_name, description):    """Create an snapshot on this instance."""    context = context.elevated()    instance_ref = self.db.instance_get_by_uuid(context, instance_uuid)
    current_vm_state = instance_ref['vm_state']    self._instance_update(context,                        instance_ref['id'],                        task_state=task_states.SNAPSHOTTING,                        vm_state=vm_states.SNAPSHOTTING)    LOG.audit(_("snapshotting instance %s"), instance_uuid, context=context)    self._notify_about_instance_usage(instance_ref, "create_snapshot.start")
    try:      self.driver.create_vm_snapshot(context, instance_ref, snapshot_name, description)    finally:      self._instance_update(context, instance_ref['id'], task_state=None, vm_state=current_vm_state)self._notify_about_instance_usage(instance_ref, "create_snapshot.end")
第三步、完成快照功能的接口封装,在nova.compute.api.py的API类中实现方法的封装,代码如下:def create_vm_snapshot(self, context, instance, snapshot_name, description):    params = {'snapshot_name': snapshot_name, 'description': description}self._cast_compute_message('create_vm_snapshot', context, instance, params=params)以下的工作就是nova.api.openstack.compute层以上的工作了,3.1章节已经讲过,这里就不在描述。nova其它模块的接口实现流程类似,实现方式也是类似的,可以参考compute接口去实现。
3.3、keystone接口服务的扩展Keystone接口扩展相对容易,一是因为底层主要是调用数据库,二是它不涉及多节点架构。在2.2章节keystone表结构扩展时,创建了一个tenant_key的表,下面就实现一个获取表数据实例的接口。第一步、实现一个表查询接口,在keystone.identity.core.py的Driver类中添加一个抽象接口,代码如下:def get_tenant_key(self, tenant_id):raise exception.NotImplemented()传递一个tenant_id参数用来获取一个对象实例第二步、在keystone.identity.backends.sql.py的Identity类中添加sql对象查询实现,代码如下:def get_tenant_key(self, tenant_id):session = self.get_session()tenant_key_ref = session.query(TenantKey).filter_by(tenant_id=tenant_id).first()if not tenant_key_ref:returnreturn tenant_key_ref.to_dict()第三步、实现服务接口的调用封装,在keystone.identity.core.py的TenantController类中添加一个方法,代码如下:def get_tenant_key(self, context, tenant_id):    self.assert_admin(context)    tenant_key = self.identity_api.get_tenant_key(context, tenant_id)return {'tenant_key': tenant_key}第四步、添加接口路由匹配规则,在keystone.identity.core.py的AdminRouter,add_routes方法中,添加如下代码:mapper.connect('/tenant_keys/{tenant_id}',               controller=tenant_controller,               action='get_tenant_key',               conditions=dict(method=['GET']))第五步、验证接口的可用性,执行命令curl -X GET -H "X-Auth-Token:12630d16e09147d0b27da73ca2b43601" -H "Content-type:application/json" http://192.168.70.201:35357/v2.0/tenant_keys/ 8a9b447e1f4e466ab3cc5557110e0fed,如果可以正常返回数据库中对应的数据,服务端的接口就算可以用了,剩下的工作和novaclient的做法类似,可以参考novaclient的实现来扩展keystoneclient的接口。
3.4、基于openstack服务、配置架构自定义服务模块使用openstack中已有的代码,稍加改动就可以实现一个服务架构模块。这部分改动比较凌乱,下面讲的不能完全涵盖要做的工作,剩下的都是些python相关的代码调整,需要有一定的python基础的人自己解决。做这个工作之前,我研究了nova、keystone、swift、glance的服务代码,发现nova部分要整合多个模块(nova-compute、nova-network、nova-volume等),架构比较复杂,keystone、swift和本身的业务集成太紧密,耦合度太强,不好分离。Glance的服务模块相对容易分离,本身glance提供的接口也不太多,代码的研究也很容易。这几个模块的服务提供机制,最底层都是一样的基于一个wsgi.py模块,里面的内容稍微有点差异,多的是在上层的封装上有些不同,下面我们讲怎么基于glance的服务提供代码,构造自己的服务模块架构。第一步、拷贝glance相关代码到自己的模块根目录中,创建一个根目录,我这里叫“monitor”,自己的监控模块,拷贝glance. __init__.py、glance. vcsversion.py、glance. version.py、glance.api、glance.common到monitor文件夹下。第二步、删除不相关的文件,上一步拷贝的monitor.api、monitor.common中有些文件是不需要的,也有些是glance用到的,都要清除掉。删除monitor.api.middleware、monitor.api. cached_images.py、monitor.api.policy.py、monitor.api.v1.filters.py、monitor.api.v1.images.py、monitor.api.v1.members.py、monitor.api.common.animation.py、monitor.api.common. crypt.py、monitor.api.common. policy.py。第三步、修改源码中的包引用代码,把from glance 改为 from monitor,删除不存在的包引用,删除相关的代码调用。第四步、拷贝相关配置、启动脚本,拷贝/etc/glance/glance-api.conf内容到monitor.etc.monitor. monitor-api.conf,删除不相关的配置,修改bind_port的端口为9009(根据自己机器上的端口划分分配),下面是必须的配置项:# Show more verbose log output (sets INFO log level output)verbose = True
# Show debugging output in logs (sets DEBUG log level output)debug = True
# Address to bind the API serverbind_host = 0.0.0.0
# Port the bind the API server tobind_port = 9009
# Log to this file. Make sure you do not set the same log# file for both the API and registry servers!log_file = /var/log/monitor/api.log
# Send logs to syslog (/dev/log) instead of to file specified by `log_file`use_syslog = False拷贝/etc/glance/ glance-api-paste.ini内容到monitor.etc.monitor. monitor-api-paste.ini,删除不相关的配置,在keystone中创建monitor账户,修改admin_user的值为monitor,正确的配置项如下:pipeline = versionnegotiation authtoken context apiv1app
paste.app_factory = monitor.common.wsgi:app_factorymonitor.app_factory = monitor.api.v1.router:API
paste.filter_factory = monitor.common.wsgi:filter_factorymonitor.filter_factory = monitor.api.middleware.version_negotiation:VersionNegotiationFilter
paste.filter_factory = monitor.common.wsgi:filter_factorymonitor.filter_factory = monitor.common.context:ContextMiddleware
paste.filter_factory = keystone.middleware.auth_token:filter_factoryservice_host = control.n.glodon.orgservice_port = 5000service_protocol = httpauth_host = control.n.glodon.orgauth_port = 35357auth_protocol = httpauth_uri = http://control.n.glodon.org:5000/admin_tenant_name = tenantadmin_user = monitoradmin_password = service拷贝/usr/bin/glance-api内容到monitor.usr.bin.monitor-api文件,修改后的内容如下:#!/usr/bin/python
"""Monitor API Server"""
import gettextimport osimport sys
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv),                                 os.pardir,                                 os.pardir))if os.path.exists(os.path.join(possible_topdir, 'monitor', '__init__.py')):    sys.path.insert(0, possible_topdir)
gettext.install('monitor', unicode=1)
from monitor.common import configfrom monitor.common import wsgi

if __name__ == '__main__':    try:      conf = config.MonitorConfigOpts()      conf()
      app = config.load_paste_app(conf)
      server = wsgi.Server()      server.start(app, conf, default_port=9009)      server.wait()    except RuntimeError, e:      sys.exit("ERROR: %s" % str(e))第五步、部署服务模块,测试服务的可用性,把monitor代码拷贝到/usr/lib/python2.7/dist-packages/目录下,拷贝monitor-api、monitor-api.conf、monitor-api-paste.ini到对应的目录,也可以在对应的目录创建软链接,执行python /usr/bin/monitor-api& 如果运行没有出错,运行netstat –npl | grep 9009能看到monitor-api进程,说明这个服务模块就算完成了。第六步、编写api接口验证接口的可用性,完成一个测试接口需要完成两个部分的编程:monitor.api.v1.router.py,monitor.api.v1.controller.py,这部分和openstack接口的扩展时类似的,下边是monitor.api.v1.router.py的代码:#-*- coding: utf-8 -*-
import loggingimport routes
from monitor.api.v1 import controllerfrom monitor.common import wsgi
logger = logging.getLogger(__name__)

class API(wsgi.Router):    def __init__(self, conf, **local_conf):      self.conf = conf      mapper = routes.Mapper()
      monitor_resource = controller.create_resource(conf)      mapper.resource("monitor", "monitors", controller=monitor_resource,                        collection={'detail': 'GET'})      mapper.connect("/", controller=monitor_resource, action="index")      super(API, self).__init__(mapper)
然后是monitor.api.v1.controller.py的代码:#-*- coding: utf-8 -*-
import loggingimport monitor.api.v1from monitor.common import wsgi
logger = logging.getLogger(__name__)

class Controller(object):    def __init__(self, conf):      self.conf = conf
def index(self, req):      logging.info('******** index **********')      return {'monitors': [{'id': '123', 'name': 'test'}]}
    def detail(self, req):      logging.info('********** detail *******')      return {'monitors': [{'id': '123', 'name': 'test', 'created_at': '2012-12-12 14:36'}]}
最后验证接口的可用性,执行curl -X GET -H "X-Auth-Token:12630d16e09147d0b27da73ca2b43601" -H "Content-type:application/json" http://localhost:9009/v1/monitors,如果返回{'id': '123', 'name': 'test'}恭喜你大功告成了。
4、dashboard源码介绍这部分是页面实现代码,在我看来这部分是要进行大量改动的,如果要进行扩展这部分肯定是要修改的,而且openstack自带的页面布局不太合理,存在重复页面,交互性不太好,没有分页。Dashboard代码分两部分:openstack-dashboard、horizon,openstack-dashboard中存放了系统用到的所有资源文件、apache也是映射这部分代码,horizon是系统具体显示的代码实现,具体实现基于django框架,但是又和django标准的project+app代码架构不太一样。
4.1 horizon代码模块介绍
4.2 中文化的功能实现Openstack的E版并没有实现horizon部分的中文化工作,中文简体的需要自己实现。编辑horizon.locale.zh_CN.LC_MESSAGES.django.po文件,然后在/usr/lib/python2.7/dist-packages/horizon目录执行django-admin compilemessages --locale=zh_CN,django自带的编译工具会将django.po编译成django.mo,django.mo是django页面中进行中文化会加载的文件,然后需要重启apache才能重新加载django.mo文件。django.po文件中有两个关键字:msgid、msgstr,msgid后边指定英文单词或句子,使用双引号包裹,中间使用空格分隔;msgstr后边指定翻译的文字,格式和msgid一样,格式如下所示:msgid "Other"msgstr "其它"
msgid "Please log in to continue."msgstr "请重新登录"
4.3 页面按钮的添加Horizon部分主要使用了三种模板table、form、tab,table用来进行数据库记录的列表展示,可以执行单独、批量操作;form是点击按钮、链接后弹出页面上的表单,会在页面自动生成一个上下排列的提交表单;tab是一种列表项显示模式,只用到了虚拟机相关信息的展示。horizon的按钮分两种,一种是直接执行的按钮,默认只支持批量删除的按钮;另一种是会弹出一个页面的按钮。直接执行的按钮一般要进行批量操作,也能进行单独执行,需要先选择操作对象,然后点击按钮执行相关操作。下边将分别讲一下这两种按钮怎么扩展,这需要知道一些django的开发经验,因为urls.py、views.py、forms.py文件的代码编码和所有的django编码风格都是一样的。首先讲怎么扩展一个链接按钮,由于horizon没有实现在页面上传镜像,下边实现一个上传镜像的按钮:第一步、编写一个upload的链接映射,编辑horizon.dashboards.nova.images_and_snapshots.images.urls.py,在patternsz中添加url(r'^upload/$', UploadView.as_view(), name='upload')第二步、实现控制层的页面跳转、页面数据、样式渲染代码,编辑horizon.dashboards.nova.images_and_snapshots.images.views.py,添加UploadView类,form_class表示使用哪个表单模板,template_name表示使用哪个页面模板,代码如下:class UploadView(forms.ModalFormView):    form_class = UploadImageFormtemplate_name = 'nova/images_and_snapshots/images/upload.html'第三步、编辑页面模板,按照既有的格式,需要完成两个文件:upload.html和_upload.html,文件添加到horizon.dashboards.templates.nova.images_and_snapshots.images,可以参考该文件夹下的update.html和_update.html进行改造,由于比较简单,代码也比较多就不贴出来了。第四步、生成form模板,编辑horizon.dashboards.nova.images_and_snapshots.images.forms.py,completion_view后边的地址表示表单取消时的跳转地址,当表单提交时会进入handle方法,api.image_create是glance已经提供的方法,代码如下:class UploadImageForm(forms.SelfHandlingForm):    completion_view = 'horizon:nova:images_and_snapshots:index'    CONTAINER_CHOICES = (("ovf", "ovf"),                        ("ami", "ami"))    DISK_CHOICES = (("qcow2", "qcow2"),                   ("raw", "raw"))
    name = forms.CharField(max_length="255", label=_("Name"))    kernel = forms.CharField(max_length="36", label=_("Kernel ID"), required=False)    ramdisk = forms.CharField(max_length="36", label=_("Ramdisk ID"), required=False)    architecture = forms.CharField(label=_("Architecture"), required=False)    container_format = forms.ChoiceField(label=_("Container Format"), choices=CONTAINER_CHOICES)    disk_format = forms.ChoiceField(label=_("Disk Format"), choices=DISK_CHOICES)    image_file = forms.FileField(label=_("File"))
    def handle(self, request, data):      # TODO add public flag to image meta properties      error_uploading = _('Unable to upload image "%s".')      image_file = self.files['image_file']
      meta = {'is_public': True,                'disk_format': data['disk_format'],                'container_format': data['container_format'],                'name': data['name'],                'properties': {}}      if data['kernel']:            meta['properties']['kernel_id'] = data['kernel']      if data['ramdisk']:            meta['properties']['ramdisk_id'] = data['ramdisk']      if data['architecture']:            meta['properties']['architecture'] = data['architecture']
      try:            api.image_create(request, meta, image_file)            messages.success(request, _('Image was successfully upload.'))      except:            exceptions.handle(request, error_uploading % data['name'])      return shortcuts.redirect(self.get_success_url())第五步、生成一个按钮模板,编辑horizon.dashboards.nova.images_and_snapshots.images.tables.py,name是按钮的唯一标识,最好不要和别的按钮重复,verbose_name是国际化的英文显示,url是点击按钮后的链接地址,classes是样式单的类名,代码如下:class UploadImage(tables.LinkAction):    name = "upload"    verbose_name = _("Upload Image")    url = "horizon:nova:images_and_snapshots:images:upload"classes = ("ajax-modal", "btn-edit")第六步、在列表中添加按钮,编辑tables.py文件的ImagesTable添加按钮,table_actions = (UploadImage, DeleteImage),然后重启apache在页面上进行调试就行了。
下面讲怎么构造一个批量操作按钮,相对要比弹出页面的按钮要好实现,因为一个直接执行的按钮相当于一个ajax的调用,并不需要构造页面和form等。第一步、生成按钮模板,下边是虚拟机重启的按钮代码:class RebootInstance(tables.BatchAction):    name = "reboot"    action_present = _("Reboot")    action_past = _("Rebooted")    data_type_singular = _("Instance")    data_type_plural = _("Instances")    classes = ('btn-danger', 'btn-reboot')
    def allowed(self, request, instance=None):      return instance.status in ACTIVE_STATES or instance.status == 'SHUTOFF'
    def action(self, request, obj_id):      api.server_reboot(request, obj_id)第二步、在列表中添加按钮,编辑tables.py文件的ImagesTable内部类的table_actions属性。
4.4 列表中下拉菜单的添加首先要实现一个有效的链接地址,然后添加一个按钮模板,这部分操作和上边讲的链接按钮一样的,最后在tables.py的row_actions中将这个按钮名添加进去就行了。
4.5 列表中文字链接的添加首先实现一个有效的链接地址,然后在tables.py对应实例的属性中添加这个链接地址即可,下边是虚拟机的一个链接地址代码:class InstancesTable(tables.DataTable):    name = tables.Column("name", link="horizon:nova:instances_and_volumes:" \                                    "instances:detail",                         verbose_name=_("Instance Name"))文档下载地址:**** Hidden Message *****










故意 发表于 2013-11-11 13:41:05

第一个支持下

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

故意 发表于 2013-11-11 13:41
第一个支持下

我也顶顶顶

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

赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞

微仔 发表于 2013-11-12 09:47:44

感谢分享!学习中。。

lixinzhangok 发表于 2013-11-12 10:46:48

commsealin 发表于 2013-11-13 12:14:16

不错,好文

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

commsealin 发表于 2013-11-13 12:14
不错,好文

谢谢

夷陵落魄生 发表于 2013-11-27 01:01:41

不错,学习中

夷陵落魄生 发表于 2013-11-27 01:03:45

为什么见不到下载地址
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: OpenStack新手开发指南