IaaS软件用户面临的共同挑战是如何快速、准确地找到一个想要的资源;例如,从10,000台虚拟机中发现有EIP(16.16.16.16)的虚拟机。大多数IaaS软件通过API中的特定查询逻辑解决这个问题。ZStack不用特定查询,而是配备了一个框架,这个框架可以自动为每个资源的每个字段生成查询,并联合跨越了多个资源的查询,帮助用户管理云端数量庞大的资源。

动机

一个中型的云可以管理几百台物理主机和成千上万台虚拟机,因为IaaS软件很少有全部的查询API,导致寻找想要的资源成为挑战。大多数IaaS软件只允许用户使用少量条件(如name,UUID)查询资源,这些条件硬编码在查询API中。如果用户想要使用硬编码之外的条件做一个查询,例如,通过创建日期查询虚拟机,他们可能不得不最终列出所有虚拟机,然后用for..loop来过滤结果。使用任意字段查询资源至今在大多数IaaS软件都不被完全支持,更不用说联合查询;例如,如果用户想要找到一个虚拟机,这个虚拟机的网卡应用了特定的安全组规则,他们可能不得不列出所有资源(虚拟机,安全组),然后做两次for..loop。
另一方面,相比类似JIRA的软件,大多数IaaS软件的UI是最粗糙简陋的。许多开发人员可能并没有意识到糟糕UI的根源不是因为UI开发人员缺乏CSS/HTML/JavaScript的技能,而是软件本身并不能提供强大的API来支持复杂的UI;例如,为了实现一个类似JIRA过滤器的功能,即只显示满足指定条件的资源,UI可能需要做很多的需要listing all then filtering by for..loop的后置处理工作,这些后置处理工作将把大量的资源拧在一起。

IaaS软件因此饱受折磨了一段时间;对此的解药就是要提供一种机制,这种机制可以自动为每个资源的每个字段都生成查询,而且可以处理join查询。

问题

大多数IaaS软件使用关系型数据库(如MySQL)作为后台数据库,在这种数据库中资源通常被安排在单独的表中,比如虚拟机表,主机表,云盘表。对于每一个资源,都有一个API用来获取该资源的单独的一条,它可能被命名为describe API、list API或query API;这些API通常有硬编码的参数,用来暴露一部分数据库表的列,允许用户通过少量的查询条件查询资源;这些参数是精心选择的,通常是对API设计者自身非常重要的列,例如,name、UUID。然而,由于并不是所有的列都被暴露,用户经常遇到他们想查询的列不存在的情况,这样他们就必须检索所有的资源,然后使用一个或多个for..loop进行后置处理。
一个复杂的查询场景,可能需要使用联合查询,这种查询通常跨越多个数据库表;例如,找到一个EIP为16.16.16.16的虚拟机,它可能涉及虚拟表、网卡表和EIP表。一些IaaS软件使用数据库视图解决这个问题,这是另一种硬编码方式,只能以固定的格式join选中的表,而在现实中表是能以非常复杂的方式被join的。在软件升级过程中,如果一个视图指向的任一表已经改变了的话,视图也需要进行数据库迁移操作。

查询API

为了避免在API中手动编码查询逻辑,并给用户提供能在任何地方查询任何东西的灵活的查询,ZStack创建了一个框架,这个框架可以自动为所有资源生成查询,并且不需要开发者写代码去实现查询逻辑;更进一步,该框架还可以生成各种join查询,只要所需的表已经通过外键连接。
在以下篇幅中,我们将用zone作为一个例子来阐述这个令人惊叹的框架。一个zone在数据库中有下面的这些列:

FIELD DESCRIPTION
uuid zone UUID
name zone name
description zone description
state zone state
type zone type
createDate the time the zone was created
lastOpDate the last time the zone was operated

用户可以通过任何一个字段或字段组合来查询zone,并采用常规的SQL比较运算符如’=’, ‘!=’, ‘>’, ‘>=’, ‘<’, ‘<=’, ‘in’, ‘not in’, ‘is null’, ‘is not null’, ‘like’, ‘not like’。

注意:在命令行工具中,一些运算符有不同的格式:’in’(?=), ‘not in’(!?=), ‘is null’(=null), ‘is not null’(!=null), ‘like’(~=), ‘not like’(!~=).

  1. QueryZone name=west-coast-zone
  2. QueryZone name=west-coast-zone state=Enabled

因为zone是ZStack中主要资源的祖先,很多资源都或多或少和它有关系;例如,一个运行中的虚拟机总是在一个zone内。像这种关系可以生成联合查询,如:

  1. QueryZone vmInstance.name=web-vm1

如上表格所示,一个zone不会暴露任何叫vmInstance的字段,但在上述查询中有一个条件是由’vmInstance’开始的。这种查询在ZStack中称为扩展查询。这里vmInstance代表VM表,VM表有一个字段为zoneUuid(外键)指向zone表,因此查询框架可以理解它们的关系并生成联合查询。上面的例子可以被解释为“寻找运行着名字为web-vm1的虚拟机的zone”。进一步扩展这个例子,因为虚拟机网卡表有外键指向VM表,并且EIP表有外键指向虚拟机网卡表,查询zone也可以使用EIP作为条件:

  1. QueryZone vmInstance.vmNics.eip.vipIp=16.16.16.16

查询被解释为“查找一个区域,它上面的虚拟机的网卡的EIP为 16.16.16.16”。现在您知道了查询接口的强大之处了!我们甚至可以创建一些非常复杂的查询:

  1. QueryVolumeSnapshot volume.vmInstance.vmNics.l3Network.l2Network.attachedClusterUuids=13238c8e0591444e9160df4d3636be82

这个复杂的查询目的是找到磁盘快照,目标磁盘快照是由虚拟机磁盘创建的,而该虚拟机有网卡在L3网络上,这个L3网络的父L2网络则是附加在一个集群上的,这个集群的uuid是13238c8e0591444e9160df4d3636be82。不要惊慌,你很少需要这么复杂的查询,但它确实证明了框架的能力。此外,SQL的一些特性例如选择字段、排序、计数和分页也是支持的:

  1. QueryL3Network name=L3-SYSTEM-PUBLIC count=true
  2. QueryL3Network l2NetworkUuid=33107835aee84c449ac04c9622892dec limit=10
  3. QueryL3Network l2NetworkUuid=33107835aee84c449ac04c9622892dec start=10 limit=100
  4. QueryL3Network fields=name,uuid l2NetworkUuid=33107835aee84c449ac04c9622892dec
  5. QueryL3Network l2NetworkUuid=33107835aee84c449ac04c9622892dec sortBy=createDate sortDirection=desc

实现

尽管查询API功能是如此强大,实现却是非常简洁的。当添加一个新的资源时,开发人员不需要写任何关于查询逻辑的代码,除了定义查询API和资源本身。要实现zone的查询API,开发人员需要:

1.使用查询元数据注解zone的inventory

  1. @Inventory(mappingVOClass = ZoneVO.class)
  2. @PythonClassInventory
  3. @ExpandedQueries({
  4. @ExpandedQuery(expandedField = "vmInstance", inventoryClass = VmInstanceInventory.class,
  5. foreignKey = "uuid", expandedInventoryKey = "zoneUuid"),
  6. @ExpandedQuery(expandedField = "cluster", inventoryClass = ClusterInventory.class,
  7. foreignKey = "uuid", expandedInventoryKey = "zoneUuid"),
  8. @ExpandedQuery(expandedField = "host", inventoryClass = HostInventory.class,
  9. foreignKey = "uuid", expandedInventoryKey = "zoneUuid"),
  10. @ExpandedQuery(expandedField = "primaryStorage", inventoryClass = PrimaryStorageInventory.class,
  11. foreignKey = "uuid", expandedInventoryKey = "zoneUuid"),
  12. @ExpandedQuery(expandedField = "l2Network", inventoryClass = L2NetworkInventory.class,
  13. foreignKey = "uuid", expandedInventoryKey = "zoneUuid"),
  14. @ExpandedQuery(expandedField = "l3Network", inventoryClass = L3NetworkInventory.class,
  15. foreignKey = "uuid", expandedInventoryKey = "zoneUuid"),
  16. @ExpandedQuery(expandedField = "backupStorageRef", inventoryClass = BackupStorageZoneRefInventory.class,
  17. foreignKey = "uuid", expandedInventoryKey = "zoneUuid", hidden = true),
  18. })
  19. @ExpandedQueryAliases({
  20. @ExpandedQueryAlias(alias = "backupStorage", expandedField = "backupStorageRef.backupStorage")
  21. })
  22. public class ZoneInventory implements Serializable{
  23. private String uuid;
  24. private String name;
  25. private String description;
  26. private String state;
  27. private String type;
  28. private Timestamp createDate;
  29. private Timestamp lastOpDate;
  30. }

上面的注解声明了zone和其他资源之间的关系,这是zone扩展查询的基础。

2.定义一个查询API

  1. @AutoQuery(replyClass = APIQueryZoneReply.class, inventoryClass = ZoneInventory.class)
  2. public class APIQueryZoneMsg extends APIQueryMessage {
  3. }

3.在区域的API配置文件中声明查询API

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <service xmlns="http://zstack.org/schema/zstack">
  3. <id>zone</id>
  4. <interceptor>ZoneApiInterceptor</interceptor>
  5. <message>
  6. <name>org.zstack.header.zone.APIQueryZoneMsg</name>
  7. <serviceId>query</serviceId>
  8. </message>
  9. </service>

API APIQueryZoneMsg通过指定服务ID query被路由到查询服务。就是这样了,查询逻辑不需要一行代码;查询服务会把其余部分自动完成。所有的ZStack查询API都像这样定义,添加新资源的查询API是非常容易的。

当前限制

主要的限制是在查询条件中,只有逻辑AND是被支持的,OR是不被支持的。例如:

  1. QueryZone name=west-coast-zone state=Enabled

上述查询语句可以被解释为“寻找区域名字为west-coast且state是Enabled的区域”我们这么做的原因是我们由ZStack源代码中SQL的使用分析得出99%的组合的查询条件都是基于AND逻辑的。另一方面,如果逻辑OR在没有创建DSL的情况下就被引入,要保持代码简洁是非常困难的。然而,在很多情况下,OR可以使用比较操作in(?=)实现:

  1. QueryZone name=west-coast-zone state?=Enabled,Disabled

上述例子表述的是“寻找名字为west-coast的区域,并且它的状态是Enabled或Disabled”,将来,我们将引入DSL风格的查询语言,例如:

  1. QueryZone name=west-coast-zone AND (state=Enabled OR state=Disabled)

总结

这篇文章中,我们演示了ZStack的查询API。通过使用这个强大的工具,用户能以类似关系型数据库的方式查询任何资源。将来,ZStack将建立一套高级的UI,它可以使用查询API创建各种各样的视图(过滤器),例如,展示所有运行在同一L3网络的虚拟机,为IaaS UI的用户体验带来革命性的改变。