nova数据库模块的开发和使用

nova数据库模块的开发和使用

在nova.db这一块E\F\G\H这几个版本差异不大,但是从G版开始加入了conductor,不允许compute直接访问数据库,所以在compute的代码里调用数据库需要通过conductor。
如果要增加一个新的功能,而且这个功能需要操作数据库,在操作数据库这个方面一般分为两个步骤:一、db模块中的内容编写,主要包括数据表的创建、功能及api的编写;二、compute模块中,对db提供的api调用方法的编写。

推荐优先学习sqlalchemy模块

openstack环境版本:H版(G版类似,F\E版的区别会有说明)

一、db模块中的内容编写

1.描述数据表
nova数据库的创建:
第一次同步nova数据库时的操作:
nova-manage db sync


这个指定的代码在nova.db.sqlalchemy.migration.py中:
def db_sync(version=None):
 if version is not None:
 try:
 version = int(version)
 except ValueError:
 raise exception.NovaException(_("version should be an integer"))
 current_version = db_version()
 repository = _find_migrate_repo()
 if version is None or version > current_version:
 return versioning_api.upgrade(get_engine(), repository, version)
 else:
 return versioning_api.downgrade(get_engine(), repository,version)



因为version=None,所以走到upgrade里,它最后会找到nova.db.sqlalchemy.migrate_repo.versions.133_folsom.py这个文件里面的upgrade方法,代码很长,这里仅截取创建数据表相关的部分:
首先,写一个数据表的结构(以下这个就是nova.instances的表结构):
    instances = Table('instances', meta,        Column('created_at', DateTime),        Column('updated_at', DateTime),        Column('deleted_at', DateTime),        Column('deleted', Boolean),        Column('id', Integer, primary_key=True, nullable=False),        Column('internal_id', Integer),        Column('user_id', String(length=255)),        Column('project_id', String(length=255)),        Column('image_ref', String(length=255)),        Column('kernel_id', String(length=255)),        Column('ramdisk_id', String(length=255)),        Column('server_name', String(length=255)),        Column('launch_index', Integer),        Column('key_name', String(length=255)),        Column('key_data', MediumText()),        Column('power_state', Integer),        Column('vm_state', String(length=255)),        Column('memory_mb', Integer),        Column('vcpus', Integer),        Column('hostname', String(length=255)),        Column('host', String(length=255)),        Column('user_data', MediumText()),        Column('reservation_id', String(length=255)),        Column('scheduled_at', DateTime),        Column('launched_at', DateTime),        Column('terminated_at', DateTime),        Column('display_name', String(length=255)),        Column('display_description', String(length=255)),        Column('availability_zone', String(length=255)),        Column('locked', Boolean),        Column('os_type', String(length=255)),        Column('launched_on', MediumText()),        Column('instance_type_id', Integer),        Column('vm_mode', String(length=255)),        Column('uuid', String(length=36)),        Column('architecture', String(length=255)),        Column('root_device_name', String(length=255)),        Column('access_ip_v4', String(length=255)),        Column('access_ip_v6', String(length=255)),        Column('config_drive', String(length=255)),        Column('task_state', String(length=255)),        Column('default_ephemeral_device', String(length=255)),        Column('default_swap_device', String(length=255)),        Column('progress', Integer),        Column('auto_disk_config', Boolean),        Column('shutdown_terminate', Boolean),        Column('disable_terminate', Boolean),        Column('root_gb', Integer),        Column('ephemeral_gb', Integer),        Column('cell_name', String(length=255)),        mysql_engine='InnoDB',        mysql_charset='utf8'    )


创建表:
    tables = [……, instances, ……]        for table in tables:        try:            table.create()        except Exception:            LOG.info(repr(table))            LOG.exception(_('Exception while creating table.'))            raise



也就是说,想在nova-manage db sync的时候创建一个新的表,就需要现在upgrade方法中加入一个表的结构描述,再把这个表的名称加入tables这个list元素中。

2.model的建立
假设已经创建了这个表,现在需要的是为这个表编写api。
打开nova.db.sqlalchemy.models.py,这个文件中是用来定义数据表模型的,把一个数据表转化成一个类。
以snapshot的model为例:
class Snapshot(BASE, NovaBase):    """Represents a block storage device that can be attached to a VM."""    __tablename__ = 'snapshots'    __table_args__ = ()    id = Column(String(36), primary_key=True, nullable=False)    deleted = Column(String(36), default="")    @property    def name(self):        return CONF.snapshot_name_template % self.id    @property    def volume_name(self):        return CONF.volume_name_template % self.volume_id    user_id = Column(String(255))    project_id = Column(String(255))    volume_id = Column(String(36), nullable=False)    status = Column(String(255))    progress = Column(String(255))    volume_size = Column(Integer)    scheduled_at = Column(DateTime)    display_name = Column(String(255))    display_description = Column(String(255))



实际上就是把数据库里的字段一个个定义出来,主要有两个部分:__tablename__和字段的定义。这个类是继承了BASE和NovaBase这两个类,所以编写起来会很方便,因为NovaBase类中已经提供了一些基本的方法和实用的字段(可以在nova.openstack.common.db.sqlalchemy.models.py文件中查看NovaBase所继承的三个类),比如:
class TimestampMixin(object):    created_at = Column(DateTime, default=timeutils.utcnow)    updated_at = Column(DateTime, onupdate=timeutils.utcnow)


这个类提供了创建时间和更新时间(注意,NovaBase所提供的字段也需要写入133_folsom.py中表结构的定义里)。

3.api的编写
拥有model之后,就需要提供操作这个model的api,进入nova.db.sqlalchemy.api.py。
api一般分为创建、查询、更新、删除,以flavor的一些操作为例:
创建:
@require_admin_contextdef flavor_create(context, values):    """Create a new instance type. In order to pass in extra specs,    the values dict should contain a 'extra_specs' key/value pair:    {'extra_specs' : {'k1': 'v1', 'k2': 'v2', ...}}    """    specs = values.get('extra_specs')    specs_refs = []    if specs:        for k, v in specs.iteritems():            specs_ref = models.InstanceTypeExtraSpecs()            specs_ref['key'] = k            specs_ref['value'] = v            specs_refs.append(specs_ref)    values['extra_specs'] = specs_refs    instance_type_ref = models.InstanceTypes()    instance_type_ref.update(values)    try:        instance_type_ref.save()    except db_exc.DBDuplicateEntry as e:        if 'flavorid' in e.columns:            raise exception.InstanceTypeIdExists(flavor_id=values['flavorid'])        raise exception.InstanceTypeExists(name=values['name'])    except Exception as e:        raise db_exc.DBError(e)    return _dict_with_extra_specs(instance_type_ref)


@require_admin_context

这是一个修饰函数,相当于一个函数加工,这里的这个修饰作用是检查context,看是否为admin权限。这个可以自定义,一般使用
@require_context
意味着需要传入context才可以执行。
instance_type_ref = models.InstanceTypes()
这里创建了一个models中InstanceTypes类的实例,通过instance_type_ref.update(values)为实例添加一个新的记录(update这个方法就是由NovaBase这个类提供的,非常方便)。然后通过instance_type_ref.save()把这条记录存入数据表当中(save也是由NovaBase这个类提供的,可以参考nova.openstack.common.db.sqlalchemy.models.py中ModelBase这个类)。

创建方法的总结:
1.需要传入context和values(这个是一个包含字段与字段值的字典元素)
2.创建一个数据表model的实例(somemodel_ref = models.SomeModel())
3.将values更新到实例中(somemodel_ref.update(values))
4.存入数据表中(somemodel_ref.save())

查询:
@require_contextdef flavor_get(context, id):    """Returns a dict describing specific instance_type."""    result = _instance_type_get_query(context).\                        filter_by(id=id).\                        first()    if not result:        raise exception.InstanceTypeNotFound(instance_type_id=id)    return _dict_with_extra_specs(result)def _instance_type_get_query(context, session=None, read_deleted=None):    query = model_query(context, models.InstanceTypes, session=session,                       read_deleted=read_deleted).\                       options(joinedload('extra_specs'))    if not context.is_admin:        the_filter = [models.InstanceTypes.is_public == True]        the_filter.extend([            models.InstanceTypes.projects.any(project_id=context.project_id)        ])        query = query.filter(or_(*the_filter))    return query    def model_query(context, model, *args, **kwargs):    """Query helper that accounts for context's `read_deleted` field.    :param context: context to query under    :param session: if present, the session to use    :param read_deleted: if present, overrides context's read_deleted field.    :param project_only: if present and context is user-type, then restrict            query to match the context's project_id. If set to 'allow_none',            restriction includes project_id = None.    :param base_model: Where model_query is passed a "model" parameter which is            not a subclass of NovaBase, we should pass an extra base_model            parameter that is a subclass of NovaBase and corresponds to the            model parameter.    """    session = kwargs.get('session') or get_session()    read_deleted = kwargs.get('read_deleted') or context.read_deleted    project_only = kwargs.get('project_only', False)    def issubclassof_nova_base(obj):        return isinstance(obj, type) and issubclass(obj, models.NovaBase)    base_model = model    if not issubclassof_nova_base(base_model):        base_model = kwargs.get('base_model', None)        if not issubclassof_nova_base(base_model):            raise Exception(_("model or base_model parameter should be "                              "subclass of NovaBase"))    query = session.query(model, *args)    default_deleted_value = base_model.__mapper__.c.deleted.default.arg    if read_deleted == 'no':        query = query.filter(base_model.deleted == default_deleted_value)    elif read_deleted == 'yes':        pass  # omit the filter to include deleted and active    elif read_deleted == 'only':        query = query.filter(base_model.deleted != default_deleted_value)    else:        raise Exception(_("Unrecognized read_deleted value '%s'")                            % read_deleted)    if nova.context.is_user_context(context) and project_only:        if project_only == 'allow_none':            query = query.\                filter(or_(base_model.project_id == context.project_id,                           base_model.project_id == None))        else:            query = query.filter_by(project_id=context.project_id)    return query    


model_query是实际执行查询的函数,它的作用是先获取一个数据库的session(这是sqlalchemy定义的一个实例,可以在nova.openstack.common.db.sqlalchemy.session.py中查看get_session这个函数),返回一个query(这是通过session实例的query方法,根据传入的参数查询数据表后获取的返回数据)。

在编写查询api的时候可以不用关心这个。主要的是这个函数的编写:
def _instance_type_get_query(context, session=None, read_deleted=None):    query = model_query(context, models.InstanceTypes, session=session,                       read_deleted=read_deleted).\                       options(joinedload('extra_specs'))    if not context.is_admin:        the_filter = [models.InstanceTypes.is_public == True]        the_filter.extend([            models.InstanceTypes.projects.any(project_id=context.project_id)        ])        query = query.filter(or_(*the_filter))    return query    


model_query需要传入context、model(模型的名字)、session,还有一些参数如read_deleted(read_deleted值为"yes"或者"no",用于是选择否获取deleted为true的记录,因为openstack几乎不删除记录,只是把记录的deleted值从0改成1)
一些完整的实例:
获取SomeModel中id字段值为some_id的第一个没有被删除的数据:
result = model_query(context, models.SomeModel, session=session,                     read_deleted="no").\            filter_by(id=some_id).\            first()


            
获取SomeModel中size字段值为some_size的第一个没有被删除的数据:
result = model_query(context, models.SomeModel, session=session,                     read_deleted="no").\            filter_by(size=some_size).\            first()       

    
            
获取SomeModel中id字段值为some_id的所有没有被删除的数据:        
result = model_query(context, models.SomeModel, session=session,                     read_deleted="no").\            filter_by(id=some_id).\            all()


大概就是这个样子。
查询方法的总结:
1.根据model_query编写一个正确的查询语句
2.创建filter或者不创建
3.选择all或者first(其实也是列表和字符串的区别)

更新:
@require_contextdef some_update(context, some_id, values):    session = get_session()    with session.begin():        some_ref = some_get(context, some_id, session=session)        some_ref.update(values)        some_ref.save(session=session)


更新这个比较简单,和创建很像,区别是创建方法中的some_ref是models.py中数据模型的实例,而更新方法中的some_ref是通过查询得到的一个model_query实例。

这个方法就不总结了。

删除:
@require_admin_contextdef some_destroy(context, some_id):    session = get_session()    with session.begin():        session.query(models.SomeModel).\                filter_by(id=some_id).\                update({'deleted': True,                        'deleted_at': timeutils.utcnow(),                        'updated_at': literal_column('updated_at')})


删除这个也比较容易理解,查询到需要删除的那个记录,把deleted更新为True(或者1等等布尔值为true的值)。

这个也不总结了。。

4.把api封装一下
进入nova.db.api.py
将在nova.db.sqlalchemy.api.py中编写的api加入这个文件,示例:
def some_create(context, values):    return IMPL.some_create(context, values)def some_destroy(context, some_id):    return IMPL.some_destroy(context, some_id)def some_get(context, some_id):    return IMPL.some_get(context, some_id)def some_get_by_size(context, some_size):    return IMPL.some_get_by_size(context, some_size)def some_get_all(context):    return IMPL.some_get_all(context)def some_update(context, some_id, values):    return IMPL.some_update(context, some_id, values)


到这里,在nova.db中的编程就基本结束了


二、compute模块中的调用方法编写
先说E\F版的nova.compute.manager.py中调用数据库的方法:
通过Manager类就可以导入db,只用使用self.db.some_api就能直接掉nova.db.api里的方法,非常方便T_T

再说G\H版中通过conductor来调用nova.db:
当你要调用nova.db.api中的方法时,需要在以下几个文件中添加相应的方法。
示例,现在nova.db.api中有这么一个方法:some_create(context, values)用于创建某条记录,在nova.compute.manager.py中一个功能会使用到它,这个功能里就必须有这么一条语句:
self.conductor_api.some_create(ontext, values)

(当然在这里无论是函数名还是参数都可以随意,除了context)
它会调入nova.conductor.api.py中LocalAPI类的some_create这个方法(LocalAPI代表这个功能的操作在本计算节点执行)LocalAPI类中some_create方法可以这样编写:
def some_create(self, context, values):    return self._manager.some_create(context, values)


接着调入nova.conductor.rpcapi.py中ConductorAPI类的some_create方法,可以这么编写:
def some_create(self, context, values):    cctxt = self.client.prepare(version='1.50')    return cctxt.call(context, 'some_create',                        values=values)


这里conductor还调用了rpc,也就是说这个创建数据表中记录的动作通过消息发回了控制节点,被控制节点指配给nova.conductor.manager.py中ConductorManager类的some_create方法来执行。
在这个方法里,就可以做和E\F版nova.compute.manager.py中一样的操作了,就是直接调用nova.db.api.py中的方法。
def some_create(self, context, values):    self.db.some_create(context, values)


这样基本就完成了一个功能在操作nova数据库方面的编程。

你可能感兴趣的:(云计算)