Mistral : 1 Mistral基础
1 Mistral背景
Mistral是一个OpenStack生态圈中比较新的项目,该项目的目标是:
The project is to provide capability to define, execute and manage tasks and workflows without writing code.
截至到目前开发还不到2年,最初是由Mirantis公司贡献给Openstack社区的工作流组件,提供Workflow As a Service服务,类似AWS的SWS(Simple Workflow Serivce),Hadoop生态圈中的oozie服务。它虽然没有Nova、Cinder等核心组件那么流行,部署率也不是很高,社区Pike版本的统计还没有出来,Ocata统计中Mistral的成熟度为1/7,部署率为5%,参考OpenStack Mistral,但还是得到很多开发者和用户关注,项目活跃度还是比较高的。
注意它和OpenStack资源编排服务Heat不同,二者功能可能会有重叠,但Heat注重基础资源的编排,而Mistral则主要是用于任务编排。Heat的主要应用场景是创建租户基础资源模板,管理员可以创建一个资源模板,基于这个模板用户一次请求就可以完成虚拟机创建及配置、挂载数据卷、创建网络和路由、设置安全组等。而Mistral的典型应用场景包括执行计划任务Cloud Cron,调度任务Task Scheduling,执行复杂的运行时间长的业务流程等。我们目前使用的场景是基于Cloud Cron创建定时任务,比如定时创建虚拟机快照、定时创建数据库备份等。
2 Mistral的几个概念
要研究Mistral,首先需要了解Mistral包含哪些实体,了解这些实体的关系以及转化过程。其中我总结了几个核心实体关系图如下:
action:action是最小执行单元,可以理解为一条命令或者一个OpenSack API请求。
workflow:Mistral的核心,Mistral主要围绕着workflow工作的,其由DSL语言定义,由各种action以及执行逻辑组成。
cron-trigger: 定时任务,通过crontab设定workflow执行周期。
execution:workflow进入运行状态即为execution,它是runtime态的,因此有执行状态,如running、error、success等。
task:一个execution由一个或者多个task构成,task也有运行时状态,如running、error、sucess等。
action-execution:task由多个action-execution构成,action进入运行时状态即为action-execution。
如果说workflow等同于程序,则execution相当于一个进程,task则类似于线程,action为一个函数或者一个计算机指令。
另外一个比较特别的实体member,这个主要用于分享资源给其它租户,和Glance的member功能是一样的。
接下来我们对以上涉及的几个实体概念进行详细介绍。
2.1 action
action是Mistral中最小执行单元(执行指令),对应一个命令或者一次API请求。内置OpenStack相关的actions实际上封装了所有OpenStack组件的pythonclient接口,比如nova.servers_start对应python-novaclient项目的novaclient/v2/servers.py模块的start()方法。目前nova包含227个action,cinder包含128个action,glance包含20个action,几乎涵盖了所有虚拟机管理、volume管理等。以cinder backup为例,包含的actions列表如下:
<span style="color:#00000a"><code><span style="color:#1a1a1a">int32bit $ mistral action-list | awk </span></code><code><span style="color:#f1403c">'/\scinder.backup/{print $4,$8}'</span></code><code><span style="color:#1a1a1a"> | tr -d </span></code><code><span style="color:#f1403c">','</span></code><code><span style="color:#1a1a1a"> | sed </span></code><code><span style="color:#f1403c">'s/ / -> /'</span></code>
<code><span style="color:#1a1a1a">cinder.backups_create -> volume_id</span></code>
<code><span style="color:#1a1a1a">cinder.backups_delete -> backup</span></code>
<code><span style="color:#1a1a1a">cinder.backups_export_record -> backup_id</span></code>
<code><span style="color:#1a1a1a">cinder.backups_find -> </span></code><code><span style="color:#0084ff">action_region</span></code><code><span style="color:#1a1a1a"><strong>=</strong></span></code><code><span style="color:#f1403c">""</span></code>
<code><span style="color:#1a1a1a">cinder.backups_findall -> </span></code><code><span style="color:#0084ff">action_region</span></code><code><span style="color:#1a1a1a"><strong>=</strong></span></code><code><span style="color:#f1403c">""</span></code>
<code><span style="color:#1a1a1a">cinder.backups_get -> backup_id</span></code>
<code><span style="color:#1a1a1a">cinder.backups_import_record -> backup_service</span></code>
<code><span style="color:#1a1a1a">cinder.backups_list -> </span></code><code><span style="color:#0084ff">detailed</span></code><code><span style="color:#1a1a1a"><strong>=</strong></span></code><code><span style="color:#0084ff">true</span></code>
<code><span style="color:#1a1a1a">cinder.backups_reset_state -> backup</span></code></span>
其中前面的字段是action名字,后面的是参数。用户可以通过action-get子命令查看action更详细的信息:
<span style="color:#00000a"><code><span style="color:#1a1a1a">mistral action-get cinder.backups_create</span></code>
<code><span style="color:#1a1a1a">+-------------+--------------------------------------------------------------------------------------------------------------------------+</span></code>
<code><span style="color:#1a1a1a">| Field | Value |</span></code>
<code><span style="color:#1a1a1a">+-------------+--------------------------------------------------------------------------------------------------------------------------+</span></code>
<code><span style="color:#1a1a1a">| Name | cinder.backups_create |</span></code>
<code><span style="color:#1a1a1a">| Is system | True |</span></code>
<code><span style="color:#1a1a1a">| Input | volume_id, container=null, name=null, description=null, incremental=false, force=false, snapshot_id=null, backup_id=null |</span></code>
<code><span style="color:#1a1a1a">| Description | Creates a volume backup. |</span></code>
<code><span style="color:#1a1a1a">| | |</span></code>
<code><span style="color:#1a1a1a">| | :param volume_id: The ID of the volume to backup. |</span></code>
<code><span style="color:#1a1a1a">| | :param container: The name of the backup service container. |</span></code>
<code><span style="color:#1a1a1a">| | :param name: The name of the backup. |</span></code>
<code><span style="color:#1a1a1a">| | :param description: The description of the backup. |</span></code>
<code><span style="color:#1a1a1a">| | :param incremental: Incremental backup. |</span></code>
<code><span style="color:#1a1a1a">| | :param force: If True, allows an in-use volume to be backed up. |</span></code>
<code><span style="color:#1a1a1a">| | :rtype: :class:`VolumeBackup` |</span></code>
<code><span style="color:#1a1a1a">| Tags | <none> |</span></code>
<code><span style="color:#1a1a1a">| Created at | 2017-04-11 08:19:52 |</span></code>
<code><span style="color:#1a1a1a">| Updated at | None |</span></code>
<code><span style="color:#1a1a1a">+-------------+--------------------------------------------------------------------------------------------------------------------------+</span></code></span>
除了OpenStack相关的action以外,Mistral还包含如下内置actions:
<span style="color:#00000a"><code><span style="color:#1a1a1a">std.async_noop</span></code>
<code><span style="color:#1a1a1a">std.echo</span></code>
<code><span style="color:#1a1a1a">std.email</span></code>
<code><span style="color:#1a1a1a">std.fail</span></code>
<code><span style="color:#1a1a1a">std.http</span></code>
<code><span style="color:#1a1a1a">std.javascript</span></code>
<code><span style="color:#1a1a1a">std.mistral_http</span></code>
<code><span style="color:#1a1a1a">std.noop</span></code>
<code><span style="color:#1a1a1a">std.ssh</span></code>
<code><span style="color:#1a1a1a">std.ssh_proxied</span></code>
<code><span style="color:#1a1a1a">std.wait_ssh</span></code></span>
需要注意的是,Mistral目前尚不支持动态增删action,如果需要添加自定义action必须手写代码,修改setup.cfg配置文件并重新安装部署Mistral服务,参考官方文档Creating custom action,本人写了一个脚本实现了自动发现和注册自定义action的功能,参考mistral-actions。不过Mistral支持创建Ad-hoc actions,即封装已有的action为新的action,类似于编程语言的继承关系或者模板。比如std.email需要传递很多参数,如果某些参数固定并且可以重复使用的话,我们可以创建一个action继承自std.email,创建一个新文件error_email.yaml内容如下:
<span style="color:#00000a"><code><span style="color:#1a1a1a">---</span></code>
<code><span style="color:#1a1a1a">version: '2.0'</span></code>
<code><span style="color:#1a1a1a">error_email:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">input:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">- execution_id</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">base: std.email</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">base-input:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">to_addrs: ['admin@mywebsite.org']</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">subject: 'Something went wrong with your Mistral workflow :('</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">body: |</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">Please take a look at Mistral Dashboard to find out what's wrong</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">with your workflow execution <% $.execution_id %>.</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">Everything's going to be alright!</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">-- Sincerely, Mistral Team.</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">from_addr: 'mistral@openstack.org'</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">smtp_server: 'smtp.google.com'</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">smtp_password: 'SECRET'</span></code></span>
注意:本文中action以及workflow定义均使用yaml格式,Mistral同样支持json格式,二者可以相互转化。
以上为to_addrs、subject、body等设置了默认参数值,我们基于该yaml文件创建新的action:
<span style="color:#00000a"><code><span style="color:#1a1a1a">mistral action-create error_email.yaml</span></code></span>
以后就可以复用这个action,只需要传递execution_id,而不需要重复to_addrs、subject等参数了:
<span style="color:#00000a"><code><span style="color:#1a1a1a">my_workflow:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">tasks:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">...</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">send_error_email:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">action: error_email execution_id=<% execution().id %></span></code></span>
2.2 task
task用来描述Workflow中包含的工作步骤,用来定义执行一个action之后,执行成功做什么,执行失败做什么等等,整个workflow就是由task构成的DAG图,具体使用方法查看workflow小节。
2.3 workflow
我们知道Mistral的目标就是提供workflow as service服务,因此workflow是Mistral的主体部分,一个workflow由至少一个task组成,task描述了具体的执行步骤和行为,workflow则描述了task之间的执行顺序、依赖关系、方式以及输入、输出等。
概念不多说,先上个官方例子(这个例子有问题,不能直接运行,仅作为demo使用):
<span style="color:#00000a"><code><span style="color:#1a1a1a">---</span></code>
<code><span style="color:#1a1a1a">version: '2.0'</span></code>
<code><span style="color:#1a1a1a"> </span></code>
<code><span style="color:#1a1a1a">create_vm:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">description: Simple workflow example</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">type: direct</span></code>
<code><span style="color:#1a1a1a"> </span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">input:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">- vm_name</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">- image_ref</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">- flavor_ref</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">output:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">vm_id: <% $.vm_id %></span></code>
<code><span style="color:#1a1a1a"> </span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">tasks:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">create_server:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">action: nova.servers_create name=<% $.vm_name %> image=<% $.image_ref %> flavor=<% $.flavor_ref %></span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">publish:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">vm_id: <% task(create_server).result.id %></span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">on-success:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">- wait_for_instance</span></code>
<code><span style="color:#1a1a1a"> </span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">wait_for_instance:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">action: nova.servers_find id=<% $.vm_id %> status='ACTIVE'</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">retry:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">delay: 5</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">count: 15</span></code></span>
具体的含义先不用过多纠结。只需要知道上面的例子定义了一个名称为create_vm的workflow,输入包含了三个必要参数,分别为vm_name、image_ref、flavor_ref,输出虚拟机的idvm_id。整个workflow包含了两个task,第一个task是create_server,它调用OpenStack的nova.servers_create创建虚机,on-success指定task执行成功的操作,这里是执行wait_for_instance,如果第一个task执行失败则第二个task不会执行。通过retry设定轮询间隔和轮询次数,只有当新创建的虚拟机状态为ACTIVE才算整个workflow执行成功。
workflow包含以下两种类型:
Direct Workflow
Reverse Workflow
2.3.1 direct workflow
我们前面的例子就属于Direct Workflow,这种workflow可以简单理解为正向流程,即后一个task执行需要依赖前一个task执行结果,如图:
该类型的task主要通过以下三个指令控制下一个task的执行:
on-success:此任务执行成功后需要执行的任务列表。
on-error:此任务执行出错后需要执行的任务列表。
on-complete:此任务执行结束后(不管成功还是失败)需要执行的任务列表
注意理解以上三个指令的语义,尤其是on-error指令,它类似于编程语言的异常,默认情况下如果没有on-error指令,则不会执行后面的task,并把当前workfow执行结果execution标识为ERROR。我们看一个例子:
<span style="color:#00000a"><code><span style="color:#1a1a1a">---</span></code>
<code><span style="color:#1a1a1a">version: "2.0"</span></code>
<code><span style="color:#1a1a1a">start_server:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">type: direct</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">input:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">- server_id</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">description: Start the specified server.</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">tasks:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">start_server:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">description: Start the specified server.</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">action: nova.servers_start server=<% $.server_id %></span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">on-error:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">- noop</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">on-complete:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">- wait_for_server_to_active</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">wait_for_server_to_active:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">action: int32bit.nova.servers.assert_power_status server_id=<% $.server_id %> status='running'</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">retry:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">delay: 5</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">count: 5</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">on-complete:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">- wait_for_all_tasks</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">wait_for_all_tasks:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">join: all</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">action: std.noop</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">publish:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">error_task: <% tasks(execution().id, false, 'ERROR') %></span></code></span>
以上是一个很简单workfow用于实现虚拟机的关机,我们期望的结果是只需要保证虚拟机最终状态是running即可。我们看start_server这个task的on-error为noop,即什么都不要做,但这不是多余的,如果没有该指令,start_server执行失败(比如虚拟机本来就是running状态),则会立即退出整个execution执行,并且execution状态为ERROR,这并不是我们期望的结果。
三个指令的关系可以用编程语言理解:
<span style="color:#00000a"><code><span style="color:#1a1a1a"><strong>try</strong></span></code><code><span style="color:#1a1a1a">:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">do_action</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">do_on_success</span></code>
<code><span style="color:#1a1a1a"><strong>except</strong></span></code><code><span style="color:#1a1a1a">:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">do_on_error</span></code>
<code><span style="color:#1a1a1a"><strong>finally</strong></span></code><code><span style="color:#1a1a1a">:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">do_complete</span></code></span>
workflow也支持并行,即同时执行多个task,类似于一个进程同时跑多个线程,这种行为称为fork,使用join指令等待所有的task执行结束,比如:
<span style="color:#00000a"><code><span style="color:#1a1a1a">tasks:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">A:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">action: action.x</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">on-success:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">C</span></code>
<code><span style="color:#1a1a1a"> </span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">B:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">action: action.y</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">on-success:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">C</span></code>
<code><span style="color:#1a1a1a"> </span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">C:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">join: all</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">action: action.z</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">publish:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">ret</span></code></span>
join后面的all表示等待所有task完成,你也可以设置为1或者one,这样只要其中任意一个task执行结束就好了,类型于Java并发编程的invokeAll和invokeAny的关系。
2.3.2 Reverse Workflow
在这个类型的Wrokflow中,任务的关系是反向依赖的,即执行A,如果A中声明了依赖的任务B,则需要先执行B,如图:
其中一个task的依赖使用requires指令定义。比如:
<span style="color:#00000a"><code><span style="color:#1a1a1a">tasks:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">A:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">action: action.x</span></code>
<code><span style="color:#1a1a1a"> </span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">B:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">action: action.y</span></code>
<code><span style="color:#1a1a1a"> </span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">C:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">action: action.z</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">requires: [A</span></code><code><span style="color:#1a1a1a">,</span></code><code><span style="color:#1a1a1a">B]</span></code></span>
需要注意的是,reverse workflow不能使用on-success、on-error以及on-complete指令。
2.4 DSL语言简介
我们前面定义ad-hoc actions以及workflow都使用的是yaml或者json,我们称为schema(模式),schema不仅可以使用yaml、json定义,也可以使用xml等其它任何表示语言,它和数据库的schema是类似的,它包括两个方面约束:
包括哪些字段。
字段的值类型是什么。
对schema进行定义的一套规则语法,我们称为DSL(Domain Specific Language),Mistral的DSL参考:Mistral DSL v2。
Mistral的DSL schema语法校验是通过JSON Schema完成,下面是一个非常简单的例子:
<span style="color:#00000a"><code><span style="color:#1a1a1a">{</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#175199">"title"</span></code><code><span style="color:#1a1a1a">: </span></code><code><span style="color:#f1403c">"Person"</span></code><code><span style="color:#1a1a1a">,</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#175199">"type"</span></code><code><span style="color:#1a1a1a">: </span></code><code><span style="color:#f1403c">"object"</span></code><code><span style="color:#1a1a1a">,</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#175199">"properties"</span></code><code><span style="color:#1a1a1a">: {</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#175199">"firstName"</span></code><code><span style="color:#1a1a1a">: {</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#175199">"type"</span></code><code><span style="color:#1a1a1a">: </span></code><code><span style="color:#f1403c">"string"</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">},</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#175199">"lastName"</span></code><code><span style="color:#1a1a1a">: {</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#175199">"type"</span></code><code><span style="color:#1a1a1a">: </span></code><code><span style="color:#f1403c">"string"</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">},</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#175199">"age"</span></code><code><span style="color:#1a1a1a">: {</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#175199">"description"</span></code><code><span style="color:#1a1a1a">: </span></code><code><span style="color:#f1403c">"Age in years"</span></code><code><span style="color:#1a1a1a">,</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#175199">"type"</span></code><code><span style="color:#1a1a1a">: </span></code><code><span style="color:#f1403c">"integer"</span></code><code><span style="color:#1a1a1a">,</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#175199">"minimum"</span></code><code><span style="color:#1a1a1a">: </span></code><code><span style="color:#0084ff">0</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">}</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">},</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#175199">"required"</span></code><code><span style="color:#1a1a1a">: [</span></code><code><span style="color:#f1403c">"firstName"</span></code><code><span style="color:#1a1a1a">, </span></code><code><span style="color:#f1403c">"lastName"</span></code><code><span style="color:#1a1a1a">]</span></code>
<code><span style="color:#1a1a1a">}</span></code></span>
以上定义了一个Person schema,其中包括两个必需参数firstName和lastName以及一个可选参数age,前二者的类型为string,age的值类型为integer,并且最小值限制为0。更多关于json schema可参考json schema官方文档,作者还写了本非常不错的电子书《Understanding JSON Schema》。
Mistral解析json schema使用的python库是jsonschema,其使用方法也非常简单:
<span style="color:#00000a"><code><span style="color:#1a1a1a"><strong>>>></strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a"><strong>from</strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#646464">jsonschema</span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a"><strong>import</strong></span></code><code><span style="color:#1a1a1a"> validate</span></code>
<code><span style="color:#1a1a1a"> </span></code>
<code><span style="color:#1a1a1a"><strong>>>></strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#999999"><em># A sample schema, like what we'd get from json.load()</em></span></code>
<code><span style="color:#1a1a1a"><strong>>>></strong></span></code><code><span style="color:#1a1a1a"> schema </span></code><code><span style="color:#1a1a1a"><strong>=</strong></span></code><code><span style="color:#1a1a1a"> {</span></code>
<code><span style="color:#1a1a1a"><strong>...</strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#f1403c">"type"</span></code><code><span style="color:#1a1a1a"> : </span></code><code><span style="color:#f1403c">"object"</span></code><code><span style="color:#1a1a1a">,</span></code>
<code><span style="color:#1a1a1a"><strong>...</strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#f1403c">"properties"</span></code><code><span style="color:#1a1a1a"> : {</span></code>
<code><span style="color:#1a1a1a"><strong>...</strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#f1403c">"price"</span></code><code><span style="color:#1a1a1a"> : {</span></code><code><span style="color:#f1403c">"type"</span></code><code><span style="color:#1a1a1a"> : </span></code><code><span style="color:#f1403c">"number"</span></code><code><span style="color:#1a1a1a">},</span></code>
<code><span style="color:#1a1a1a"><strong>...</strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#f1403c">"name"</span></code><code><span style="color:#1a1a1a"> : {</span></code><code><span style="color:#f1403c">"type"</span></code><code><span style="color:#1a1a1a"> : </span></code><code><span style="color:#f1403c">"string"</span></code><code><span style="color:#1a1a1a">},</span></code>
<code><span style="color:#1a1a1a"><strong>...</strong></span></code><code><span style="color:#1a1a1a"> },</span></code>
<code><span style="color:#1a1a1a"><strong>...</strong></span></code><code><span style="color:#1a1a1a"> }</span></code>
<code><span style="color:#1a1a1a"> </span></code>
<code><span style="color:#1a1a1a"><strong>>>></strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#999999"><em># If no exception is raised by validate(), the instance is valid.</em></span></code>
<code><span style="color:#1a1a1a"><strong>>>></strong></span></code><code><span style="color:#1a1a1a"> validate({</span></code><code><span style="color:#f1403c">"name"</span></code><code><span style="color:#1a1a1a"> : </span></code><code><span style="color:#f1403c">"Eggs"</span></code><code><span style="color:#1a1a1a">, </span></code><code><span style="color:#f1403c">"price"</span></code><code><span style="color:#1a1a1a"> : </span></code><code><span style="color:#0084ff">34.99</span></code><code><span style="color:#1a1a1a">}, schema)</span></code>
<code><span style="color:#1a1a1a"> </span></code>
<code><span style="color:#1a1a1a"><strong>>>></strong></span></code><code><span style="color:#1a1a1a"> validate(</span></code>
<code><span style="color:#1a1a1a"><strong>...</strong></span></code><code><span style="color:#1a1a1a"> {</span></code><code><span style="color:#f1403c">"name"</span></code><code><span style="color:#1a1a1a"> : </span></code><code><span style="color:#f1403c">"Eggs"</span></code><code><span style="color:#1a1a1a">, </span></code><code><span style="color:#f1403c">"price"</span></code><code><span style="color:#1a1a1a"> : </span></code><code><span style="color:#f1403c">"Invalid"</span></code><code><span style="color:#1a1a1a">}, schema</span></code>
<code><span style="color:#1a1a1a"><strong>...</strong></span></code><code><span style="color:#1a1a1a"> ) </span></code><code><span style="color:#999999"><em># doctest: +IGNORE_EXCEPTION_DETAIL</em></span></code>
<code><span style="color:#1a1a1a">Traceback (most recent call last):</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a"><strong>...</strong></span></code>
<code><span style="color:#1a1a1a">ValidationError: </span></code><code><span style="color:#f1403c">'Invalid'</span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a"><strong>is</strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a"><strong>not</strong></span></code><code><span style="color:#1a1a1a"> of </span></code><code><span style="color:#0084ff">type</span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#f1403c">'number'</span></code></span>
你也可以直接通过jsonschema CLI进行校验:
<span style="color:#00000a"><code><span style="color:#1a1a1a">$ jsonschema -i sample.json sample.schema</span></code></span>
3 Mistral实践
3.1 Mistral部署
Mistral相对其它OpenStack服务比较简单,也不需要像Trove那样调整网络。
Mistral主要包含以下三个服务:
mistral-api
mistral-engine
mistral-executor
以上三个服务的功能不详细介绍,配置可参考官方文档Mistral Configuration Guide。这里需要指出的是,Mistral的所有服务都是支持水平扩展的,即可以同时运行多个服务实例。
另外,Mistral服务的心跳和状态监控和Nova、Cinder等不一样,Mistral不是通过不断刷数据库实现心跳的,而是通过tooz coordinator的member管理实现的,当进程启动时,会自动注册member,进程挂了或者退出时,会从member中移除,由此判断该服务是否运行。因此,如果需要使用服务状态功能,需要配置coordinator,coordinator的backend可以是zookeeper、redis、memcached等,这里以memcached为例,配置如下:
<span style="color:#00000a"><code><span style="color:#0084ff">[coordination] # From mistral.config</span></code>
<code><span style="color:#0084ff">backend_url</span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a"><strong>=</strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#f1403c">memcached://localhost:11211 </span></code>
<code><span style="color:#0084ff">heartbeat_interval</span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a"><strong>=</strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#f1403c">5.0 </span></code></span>
配置了coordinator后,就可以使用mistral service-list查看服务列表了:
<span style="color:#00000a"><code><span style="color:#1a1a1a">$ mistral service-list</span></code>
<code><span style="color:#1a1a1a">+----------------+----------------+</span></code>
<code><span style="color:#1a1a1a">| Name | Type |</span></code>
<code><span style="color:#1a1a1a">+----------------+----------------+</span></code>
<code><span style="color:#1a1a1a">| controller_77391 | engine_group |</span></code>
<code><span style="color:#1a1a1a">| controller_80355 | api_group |</span></code>
<code><span style="color:#1a1a1a">| controller_77494 | executor_group |</span></code>
<code><span style="color:#1a1a1a">+----------------+----------------+</span></code></span>
以上controller是hostname,77391是服务的pid,type包含api、engine、executor三类。
原理也很简单,Mistral服务会每隔heartbeat_interval调用heartbeat方法发送心跳,如果backend是memcached,则会设置一个key-value,key为group id,value为”It’s alive!”,ttl为30s,实现代码如下:
<span style="color:#00000a"><code><span style="color:#1a1a1a">@_translate_failures</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a"><strong>def</strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#f1403c"><strong>heartbeat</strong></span></code><code><span style="color:#1a1a1a">(</span></code><code><span style="color:#999999">self</span></code><code><span style="color:#1a1a1a">):</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#999999">self</span></code><code><span style="color:#1a1a1a"><strong>.</strong></span></code><code><span style="color:#1a1a1a">client</span></code><code><span style="color:#1a1a1a"><strong>.</strong></span></code><code><span style="color:#0084ff">set</span></code><code><span style="color:#1a1a1a">(</span></code><code><span style="color:#999999">self</span></code><code><span style="color:#1a1a1a"><strong>.</strong></span></code><code><span style="color:#1a1a1a">_encode_member_id(</span></code><code><span style="color:#999999">self</span></code><code><span style="color:#1a1a1a"><strong>.</strong></span></code><code><span style="color:#1a1a1a">_member_id),</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#999999">self</span></code><code><span style="color:#1a1a1a"><strong>.</strong></span></code><code><span style="color:#1a1a1a">STILL_ALIVE,</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">expire</span></code><code><span style="color:#1a1a1a"><strong>=</strong></span></code><code><span style="color:#999999">self</span></code><code><span style="color:#1a1a1a"><strong>.</strong></span></code><code><span style="color:#1a1a1a">membership_timeout)</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#999999"><em># Reset the acquired locks</em></span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a"><strong>for</strong></span></code><code><span style="color:#1a1a1a"> lock </span></code><code><span style="color:#1a1a1a"><strong>in</strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#999999">self</span></code><code><span style="color:#1a1a1a"><strong>.</strong></span></code><code><span style="color:#1a1a1a">_acquired_locks:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">lock</span></code><code><span style="color:#1a1a1a"><strong>.</strong></span></code><code><span style="color:#1a1a1a">heartbeat()</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a"><strong>return</strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#0084ff">min</span></code><code><span style="color:#1a1a1a">(</span></code><code><span style="color:#999999">self</span></code><code><span style="color:#1a1a1a"><strong>.</strong></span></code><code><span style="color:#1a1a1a">membership_timeout,</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#999999">self</span></code><code><span style="color:#1a1a1a"><strong>.</strong></span></code><code><span style="color:#1a1a1a">leader_timeout,</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#999999">self</span></code><code><span style="color:#1a1a1a"><strong>.</strong></span></code><code><span style="color:#1a1a1a">lock_timeout)</span></code></span>
可以查看memcached值:
<span style="color:#00000a"><code><span style="color:#1a1a1a">$ telnet localhost </span></code><code><span style="color:#0084ff">11211</span></code>
<code><span style="color:#1a1a1a">Trying </span></code><code><span style="color:#0084ff">127</span></code><code><span style="color:#1a1a1a">.0.0.1...</span></code>
<code><span style="color:#1a1a1a">Connected to localhost.</span></code>
<code><span style="color:#1a1a1a">Escape character is </span></code><code><span style="color:#f1403c">'^]'</span></code><code><span style="color:#1a1a1a">.</span></code>
<code><span style="color:#1a1a1a">stats cachedump </span></code><code><span style="color:#0084ff">2</span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#0084ff">0</span></code>
<code><span style="color:#1a1a1a">ITEM _TOOZ_MEMBER_controller_77494 </span></code><code><span style="color:#1a1a1a"><strong>[</strong></span></code><code><span style="color:#0084ff">11</span></code><code><span style="color:#1a1a1a"> b; </span></code><code><span style="color:#0084ff">1502183642</span></code><code><span style="color:#1a1a1a"> s</span></code><code><span style="color:#1a1a1a"><strong>]</strong></span></code>
<code><span style="color:#1a1a1a">ITEM _TOOZ_MEMBER_controller_80355 </span></code><code><span style="color:#1a1a1a"><strong>[</strong></span></code><code><span style="color:#0084ff">11</span></code><code><span style="color:#1a1a1a"> b; </span></code><code><span style="color:#0084ff">1502183640</span></code><code><span style="color:#1a1a1a"> s</span></code><code><span style="color:#1a1a1a"><strong>]</strong></span></code>
<code><span style="color:#1a1a1a">ITEM _TOOZ_MEMBER_controller_77391 </span></code><code><span style="color:#1a1a1a"><strong>[</strong></span></code><code><span style="color:#0084ff">11</span></code><code><span style="color:#1a1a1a"> b; </span></code><code><span style="color:#0084ff">1502183640</span></code><code><span style="color:#1a1a1a"> s</span></code><code><span style="color:#1a1a1a"><strong>]</strong></span></code>
<code><span style="color:#1a1a1a">END</span></code>
<code><span style="color:#1a1a1a">get _TOOZ_MEMBER_controller_77391</span></code>
<code><span style="color:#1a1a1a">VALUE _TOOZ_MEMBER_controller_77391 </span></code><code><span style="color:#0084ff">1</span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#0084ff">11</span></code>
<code><span style="color:#1a1a1a">It</span></code><code><span style="color:#f1403c">'</span></code><code><span style="color:#1a1a1a">s alive!</span></code>
<code><span style="color:#1a1a1a">END</span></code></span>
3.2 开始使用Mistral
3.2.1 创建workflow
以一个官方的简单workflow为例,yaml文件为my_workflow.yaml,
<span style="color:#00000a"><code><span style="color:#1a1a1a">---</span></code>
<code><span style="color:#1a1a1a">version: "2.0"</span></code><code><span style="color:#1a1a1a">my_workflow:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">type: direct</span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">input:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">- names</span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">tasks:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">task1:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">with-items: name in <% $.names %></span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">action: std.echo output=<% $.name %></span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">on-success: task2</span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">task2:</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">action: std.echo output="Done"</span></code></span>
注意以上names参数是一个数组,with-times也是workflow的一个指令,它会遍历数组的所有元素,action为std.echo,即打印name参数,执行成功后执行task2,输出"Done"。
使用mistral workflow-create子命令创建workflow:
<span style="color:#00000a"><code><span style="color:#1a1a1a">$ mistral workflow-create my_workflow.yaml</span></code>
<code><span style="color:#1a1a1a">+------------------------------------+-------------+--------+---------+---------------------+------------+</span></code>
<code><span style="color:#1a1a1a">|ID | Name | Tags | Input | Created at | Updated at |</span></code>
<code><span style="color:#1a1a1a">+------------------------------------+-------------+--------+---------+---------------------+------------+</span></code>
<code><span style="color:#1a1a1a">|9b719d62-2ced-47d3-b500-73261bb0b2ad| my_workflow | <none> | names | 2017-04-13 08:44:49 | None |</span></code>
<code><span style="color:#1a1a1a">+------------------------------------+-------------+--------+---------+---------------------+------------+</span></code></span>
3.2.2 执行workflow
创建workflow相当于创建了一个任务模板,并没有实际执行,我们需要通过execution-create子命令触发执行,执行时需要传递参数,参数以json格式传递:
<span style="color:#00000a"><code><span style="color:#1a1a1a">$ mistral execution-create my_workflow '{"names": ["John", "Mistral", "Ivan", "Crystal"]}'</span></code>
<code><span style="color:#1a1a1a">+-------------------+--------------------------------------+</span></code>
<code><span style="color:#1a1a1a">| Field | Value |</span></code>
<code><span style="color:#1a1a1a">+-------------------+--------------------------------------+</span></code>
<code><span style="color:#1a1a1a">| ID | 49213eb5-196c-421f-b436-775849b55040 |</span></code>
<code><span style="color:#1a1a1a">| Workflow ID | 9b719d62-2ced-47d3-b500-73261bb0b2ad |</span></code>
<code><span style="color:#1a1a1a">| Workflow name | my_workflow |</span></code>
<code><span style="color:#1a1a1a">| Description | |</span></code>
<code><span style="color:#1a1a1a">| Task Execution ID | <none> |</span></code>
<code><span style="color:#1a1a1a">| State | RUNNING |</span></code>
<code><span style="color:#1a1a1a">| State info | None |</span></code>
<code><span style="color:#1a1a1a">| Created at | 2017-03-06 11:24:10 |</span></code>
<code><span style="color:#1a1a1a">| Updated at | 2017-03-06 11:24:10 |</span></code>
<code><span style="color:#1a1a1a">+-------------------+--------------------------------------+</span></code></span>
执行后,可以通过execution-list查看执行状态。
3.2.3 查看task执行状态
除了使用execution-list查看整个workflow执行的结果,还可以通过task-list查看其所有的task执行状态:
<span style="color:#00000a"><code><span style="color:#1a1a1a">$ mistral task-list 49213eb5-196c-421f-b436-775849b55040</span></code>
<code><span style="color:#1a1a1a">+--------------------------------------+-------+---------------+--------------------------------------+---------+------------+---------------------+---------------------+</span></code>
<code><span style="color:#1a1a1a">| ID | Name | Workflow name | Execution ID | State | State info | Created at | Updated at |</span></code>
<code><span style="color:#1a1a1a">+--------------------------------------+-------+---------------+--------------------------------------+---------+------------+---------------------+---------------------+</span></code>
<code><span style="color:#1a1a1a">| f639e7a9-9609-468e-aa08-7650e1472efe | task1 | my_workflow | 49213eb5-196c-421f-b436-775849b55040 | SUCCESS | None | 2017-03-06 11:24:11 | 2017-03-06 11:24:17 |</span></code>
<code><span style="color:#1a1a1a">| d565c5a0-f46f-4ebe-8655-9eb6796307a3 | task2 | my_workflow | 49213eb5-196c-421f-b436-775849b55040 | SUCCESS | None | 2017-03-06 11:24:17 | 2017-03-06 11:24:18 |</span></code>
<code><span style="color:#1a1a1a">+--------------------------------------+-------+---------------+--------------------------------------+---------+------------+---------------------+---------------------+</span></code></span>
通过task-get-result查看task的输出,即std.echo结果:
<span style="color:#00000a"><code><span style="color:#1a1a1a">$ mistral task-get-result f639e7a9-9609-468e-aa08-7650e1472efe</span></code>
<code><span style="color:#1a1a1a">[</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">"John",</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">"Mistral",</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">"Ivan",</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">"Crystal"</span></code>
<code><span style="color:#1a1a1a">]</span></code></span>
3.2.4 查看action执行状态
以上通过task已经获取了执行结果,可以进一步获取每个action的执行情况:
<span style="color:#00000a"><code><span style="color:#1a1a1a">$ mistral action-execution-list f639e7a9-9609-468e-aa08-7650e1472efe</span></code>
<code><span style="color:#1a1a1a">+--------------------------------------+----------+---------------+-----------+--------------------------------------+---------+----------+---------------------+---------------------+</span></code>
<code><span style="color:#1a1a1a">| ID | Name | Workflow name | Task name | Task ID | State | Accepted | Created at | Updated at |</span></code>
<code><span style="color:#1a1a1a">+--------------------------------------+----------+---------------+-----------+--------------------------------------+---------+----------+---------------------+---------------------+</span></code>
<code><span style="color:#1a1a1a">| 4e0a60be-04df-42d7-aa59-5107e599d079 | std.echo | my_workflow | task1 | f639e7a9-9609-468e-aa08-7650e1472efe | SUCCESS | True | 2017-03-06 11:24:12 | 2017-03-06 11:24:16 |</span></code>
<code><span style="color:#1a1a1a">| 5bd95da4-9b29-4a79-bcb1-298abd659bd6 | std.echo | my_workflow | task1 | f639e7a9-9609-468e-aa08-7650e1472efe | SUCCESS | True | 2017-03-06 11:24:12 | 2017-03-06 11:24:16 |</span></code>
<code><span style="color:#1a1a1a">| 6ae6c19e-b51b-4910-9e0e-96c788093715 | std.echo | my_workflow | task1 | f639e7a9-9609-468e-aa08-7650e1472efe | SUCCESS | True | 2017-03-06 11:24:12 | 2017-03-06 11:24:16 |</span></code>
<code><span style="color:#1a1a1a">| bed5a6a2-c1d8-460f-a2a5-b36f72f85e19 | std.echo | my_workflow | task1 | f639e7a9-9609-468e-aa08-7650e1472efe | SUCCESS | True | 2017-03-06 11:24:12 | 2017-03-06 11:24:17 |</span></code>
<code><span style="color:#1a1a1a">+--------------------------------------+----------+---------------+-----------+--------------------------------------+---------+----------+---------------------+---------------------+</span></code></span>
以上由于task1使用了with-items循环,因此会对应多个actions,你也可以获取其中一个action的结果:
<span style="color:#00000a"><code><span style="color:#1a1a1a">$ mistral action-execution-get-output 4e0a60be-04df-42d7-aa59-5107e599d079</span></code>
<code><span style="color:#1a1a1a">{ "result": "John" } </span></code></span>
4 定时任务
4.1 cron介绍
cron是一个在类Unix操作系统上的任务计划程序。它可以让用户在指定时间段周期性地运行命令或者shell脚本,通常被用在系统的自动化维护或者管理。
crontab 的基本格式是:
<span style="color:#00000a"><code><span style="color:#1a1a1a">┌───────────── </span></code><code><span style="color:#1a1a1a">minute (0 - 59)</span></code>
<code><span style="color:#1a1a1a"> │ ┌───────────── </span></code><code><span style="color:#1a1a1a">hour (0 - 23)</span></code>
<code><span style="color:#1a1a1a"> │ │ ┌───────────── </span></code><code><span style="color:#1a1a1a">day of month (1 - 31)</span></code>
<code><span style="color:#1a1a1a"> │ │ │ ┌───────────── </span></code><code><span style="color:#1a1a1a">month (1 - 12)</span></code>
<code><span style="color:#1a1a1a"> │ │ │ │ ┌───────────── </span></code><code><span style="color:#1a1a1a">day of week (0 - 6) (Sunday to Saturday;</span></code>
<code><span style="color:#1a1a1a"> │ │ │ │ │ </span></code><code><span style="color:#1a1a1a">7 is also Sunday)</span></code>
<code><span style="color:#1a1a1a"> │ │ │ │ │</span></code>
<code><span style="color:#1a1a1a"> │ │ │ │ │</span></code>
<code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a">* * * * * command to execute</span></code></span>
更详细的cron语法介绍可以参考维基百科–Cron。
Mistral使用了Python的crontiner库解析crontab,这个库封装得特别好,我们只需要会用两个方法即可,一个是构造方法__init__,另一个是获取下一次执行时间方法get_next()。构造方法签名如下:
<span style="color:#00000a"><code><span style="color:#1a1a1a"><strong>def</strong></span></code><code><span style="color:#1a1a1a"> __init__(</span></code><code><span style="color:#999999">self</span></code><code><span style="color:#1a1a1a">, cron_format, start_time</span></code><code><span style="color:#1a1a1a"><strong>=</strong></span></code><code><span style="color:#1a1a1a">time</span></code><code><span style="color:#1a1a1a"><strong>.</strong></span></code><code><span style="color:#1a1a1a">time(), day_or</span></code><code><span style="color:#1a1a1a"><strong>=</strong></span></code><code><span style="color:#999999">True</span></code><code><span style="color:#1a1a1a">) </span></code></span>
其中cron_format就是标准的cron格式,start_time是开始执行时间,默认从当前时间开始,day_or是处理day和week冲突情况下的处理办法,day_or默认为true,day和week满足其中一个条件就会执行,比如* * 1 * 1,则每个月的1号或者周一都会执行。
get_next方法签名如下:
<span style="color:#00000a"><code><span style="color:#1a1a1a"><strong>def</strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#f1403c"><strong>get_next</strong></span></code><code><span style="color:#1a1a1a">(</span></code><code><span style="color:#999999">self</span></code><code><span style="color:#1a1a1a">, ret_type</span></code><code><span style="color:#1a1a1a"><strong>=</strong></span></code><code><span style="color:#0084ff">float</span></code><code><span style="color:#1a1a1a">) </span></code></span>
其中ret_type指定返回类型,默认为浮点数,可以指定为datetime类型。
<span style="color:#00000a"><code><span style="color:#1a1a1a"><strong>>>></strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a"><strong>from</strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#646464">croniter</span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a"><strong>import</strong></span></code><code><span style="color:#1a1a1a"> croniter</span></code>
<code><span style="color:#1a1a1a"><strong>>>></strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a"><strong>from</strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#646464">datetime</span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a"><strong>import</strong></span></code><code><span style="color:#1a1a1a"> datetime</span></code>
<code><span style="color:#1a1a1a"><strong>>>></strong></span></code><code><span style="color:#1a1a1a"> base </span></code><code><span style="color:#1a1a1a"><strong>=</strong></span></code><code><span style="color:#1a1a1a"> datetime(</span></code><code><span style="color:#0084ff">2010</span></code><code><span style="color:#1a1a1a">, </span></code><code><span style="color:#0084ff">1</span></code><code><span style="color:#1a1a1a">, </span></code><code><span style="color:#0084ff">25</span></code><code><span style="color:#1a1a1a">, </span></code><code><span style="color:#0084ff">4</span></code><code><span style="color:#1a1a1a">, </span></code><code><span style="color:#0084ff">46</span></code><code><span style="color:#1a1a1a">)</span></code>
<code><span style="color:#1a1a1a"><strong>>>></strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#0084ff">iter</span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a"><strong>=</strong></span></code><code><span style="color:#1a1a1a"> croniter(</span></code><code><span style="color:#f1403c">'*/5 * * * *'</span></code><code><span style="color:#1a1a1a">, base) </span></code><code><span style="color:#999999"><em># every 5 minutes</em></span></code>
<code><span style="color:#1a1a1a"><strong>>>></strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a"><strong>print</strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#0084ff">iter</span></code><code><span style="color:#1a1a1a"><strong>.</strong></span></code><code><span style="color:#1a1a1a">get_next(datetime) </span></code><code><span style="color:#999999"><em># 2010-01-25 04:50:00</em></span></code>
<code><span style="color:#1a1a1a"><strong>>>></strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a"><strong>print</strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#0084ff">iter</span></code><code><span style="color:#1a1a1a"><strong>.</strong></span></code><code><span style="color:#1a1a1a">get_next(datetime) </span></code><code><span style="color:#999999"><em># 2010-01-25 04:55:00</em></span></code>
<code><span style="color:#1a1a1a"><strong>>>></strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a"><strong>print</strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#0084ff">iter</span></code><code><span style="color:#1a1a1a"><strong>.</strong></span></code><code><span style="color:#1a1a1a">get_next(datetime) </span></code><code><span style="color:#999999"><em># 2010-01-25 05:00:00</em></span></code>
<code><span style="color:#1a1a1a"><strong>>>></strong></span></code>
<code><span style="color:#1a1a1a"><strong>>>></strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#0084ff">iter</span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a"><strong>=</strong></span></code><code><span style="color:#1a1a1a"> croniter(</span></code><code><span style="color:#f1403c">'2 4 * * mon,fri'</span></code><code><span style="color:#1a1a1a">, base) </span></code><code><span style="color:#999999"><em># 04:02 on every Monday and Friday</em></span></code>
<code><span style="color:#1a1a1a"><strong>>>></strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a"><strong>print</strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#0084ff">iter</span></code><code><span style="color:#1a1a1a"><strong>.</strong></span></code><code><span style="color:#1a1a1a">get_next(datetime) </span></code><code><span style="color:#999999"><em># 2010-01-26 04:02:00</em></span></code>
<code><span style="color:#1a1a1a"><strong>>>></strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a"><strong>print</strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#0084ff">iter</span></code><code><span style="color:#1a1a1a"><strong>.</strong></span></code><code><span style="color:#1a1a1a">get_next(datetime) </span></code><code><span style="color:#999999"><em># 2010-01-30 04:02:00</em></span></code>
<code><span style="color:#1a1a1a"><strong>>>></strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a"><strong>print</strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#0084ff">iter</span></code><code><span style="color:#1a1a1a"><strong>.</strong></span></code><code><span style="color:#1a1a1a">get_next(datetime) </span></code><code><span style="color:#999999"><em># 2010-02-02 04:02:00</em></span></code>
<code><span style="color:#1a1a1a"><strong>>>></strong></span></code>
<code><span style="color:#1a1a1a"><strong>>>></strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#0084ff">iter</span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a"><strong>=</strong></span></code><code><span style="color:#1a1a1a"> croniter(</span></code><code><span style="color:#f1403c">'2 4 1 * wed'</span></code><code><span style="color:#1a1a1a">, base) </span></code><code><span style="color:#999999"><em># 04:02 on every Wednesday OR on 1st day of month</em></span></code>
<code><span style="color:#1a1a1a"><strong>>>></strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a"><strong>print</strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#0084ff">iter</span></code><code><span style="color:#1a1a1a"><strong>.</strong></span></code><code><span style="color:#1a1a1a">get_next(datetime) </span></code><code><span style="color:#999999"><em># 2010-01-27 04:02:00</em></span></code>
<code><span style="color:#1a1a1a"><strong>>>></strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a"><strong>print</strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#0084ff">iter</span></code><code><span style="color:#1a1a1a"><strong>.</strong></span></code><code><span style="color:#1a1a1a">get_next(datetime) </span></code><code><span style="color:#999999"><em># 2010-02-01 04:02:00</em></span></code>
<code><span style="color:#1a1a1a"><strong>>>></strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a"><strong>print</strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#0084ff">iter</span></code><code><span style="color:#1a1a1a"><strong>.</strong></span></code><code><span style="color:#1a1a1a">get_next(datetime) </span></code><code><span style="color:#999999"><em># 2010-02-03 04:02:00</em></span></code>
<code><span style="color:#1a1a1a"><strong>>>></strong></span></code>
<code><span style="color:#1a1a1a"><strong>>>></strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#0084ff">iter</span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a"><strong>=</strong></span></code><code><span style="color:#1a1a1a"> croniter(</span></code><code><span style="color:#f1403c">'2 4 1 * wed'</span></code><code><span style="color:#1a1a1a">, base, day_or</span></code><code><span style="color:#1a1a1a"><strong>=</strong></span></code><code><span style="color:#999999">False</span></code><code><span style="color:#1a1a1a">) </span></code><code><span style="color:#999999"><em># 04:02 on every 1st day of the month if it is a Wednesday</em></span></code>
<code><span style="color:#1a1a1a"><strong>>>></strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a"><strong>print</strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#0084ff">iter</span></code><code><span style="color:#1a1a1a"><strong>.</strong></span></code><code><span style="color:#1a1a1a">get_next(datetime) </span></code><code><span style="color:#999999"><em># 2010-09-01 04:02:00</em></span></code>
<code><span style="color:#1a1a1a"><strong>>>></strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a"><strong>print</strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#0084ff">iter</span></code><code><span style="color:#1a1a1a"><strong>.</strong></span></code><code><span style="color:#1a1a1a">get_next(datetime) </span></code><code><span style="color:#999999"><em># 2010-12-01 04:02:00</em></span></code>
<code><span style="color:#1a1a1a"><strong>>>></strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#1a1a1a"><strong>print</strong></span></code><code><span style="color:#1a1a1a"> </span></code><code><span style="color:#0084ff">iter</span></code><code><span style="color:#1a1a1a"><strong>.</strong></span></code><code><span style="color:#1a1a1a">get_next(datetime) </span></code><code><span style="color:#999999"><em># 2011-06-01 04:02:00</em></span></code></span>
当然,还有一个get_prev方法,获取上一次执行的时间,用法和get_next()一样。
4.2 创建定时任务
Mistral支持cloud cron功能,即创建定时任务,其定义语法和linux crontab基本一致,前面已经介绍过。
mistral还支持定义开始执行时间、执行次数等:
<span style="color:#00000a"><code><span style="color:#1a1a1a">int32bit $ mistral cron-trigger-create --pattern '* * * * *' --count 5 test-hello-world hello-world</span></code>
<code><span style="color:#1a1a1a">+----------------------+--------------------------------------+</span></code>
<code><span style="color:#1a1a1a">| Field | Value |</span></code>
<code><span style="color:#1a1a1a">+----------------------+--------------------------------------+</span></code>
<code><span style="color:#1a1a1a">| ID | a3a0ed3f-a5ef-4416-af9f-33cef498bbb6 |</span></code>
<code><span style="color:#1a1a1a">| Name | test-hello-world |</span></code>
<code><span style="color:#1a1a1a">| Workflow | hello-world |</span></code>
<code><span style="color:#1a1a1a">| Params | {} |</span></code>
<code><span style="color:#1a1a1a">| Pattern | * * * * * |</span></code>
<code><span style="color:#1a1a1a">| Next execution time | 2017-08-28 02:36:00 |</span></code>
<code><span style="color:#1a1a1a">| Remaining executions | 1 |</span></code>
<code><span style="color:#1a1a1a">| Status | READY |</span></code>
<code><span style="color:#1a1a1a">| Created at | 2017-08-28 02:35:08 |</span></code>
<code><span style="color:#1a1a1a">| Updated at | None |</span></code>
<code><span style="color:#1a1a1a">+----------------------+--------------------------------------+</span></code></span>
以上任务会每分钟执行一次,执行5次后结束。
查看cron任务列表:
<span style="color:#00000a"><code><span style="color:#1a1a1a">int32bit $ mistral cron-trigger-list</span></code>
<code><span style="color:#1a1a1a">+--------------------------------------+------------------+-------------+--------+-------------+---------------------+----------------------+-----------+---------------------+---------------------+</span></code>
<code><span style="color:#1a1a1a">| ID | Name | Workflow | Params | Pattern | Next execution time | Remaining executions | Status | Created at | Updated at |</span></code>
<code><span style="color:#1a1a1a">+--------------------------------------+------------------+-------------+--------+-------------+---------------------+----------------------+-----------+---------------------+---------------------+</span></code>
<code><span style="color:#1a1a1a">| 88fd87ba-2429-4995-abba-54bfff91ba13 | int32bit-test-1 | hello-world | {} | */1 * * * * | 2017-08-17 08:47:00 | 0 | COMPLETED | 2017-08-17 08:41:49 | 2017-08-17 08:46:58 |</span></code>
<code><span style="color:#1a1a1a">| a3a0ed3f-a5ef-4416-af9f-33cef498bbb6 | test-hello-world | hello-world | {} | * * * * * | 2017-08-28 02:36:00 | 4 | READY | 2017-08-28 02:35:08 | None |</span></code>
<code><span style="color:#1a1a1a">+--------------------------------------+------------------+-------------+--------+-------------+---------------------+----------------------+-----------+---------------------+---------------------+</span></code></span>
通过execution-list查看执行结果,其中cron id为关联的cron任务:
<span style="color:#00000a"><code><span style="color:#1a1a1a">int32bit $ mistral execution-list</span></code>
<code><span style="color:#1a1a1a">+--------------------------------------+--------------------------------------+---------------+--------------------------------------+-------------------+---------+------------+---------------------+---------------------+</span></code>
<code><span style="color:#1a1a1a">| ID | Workflow ID | Workflow name | Cron ID | Task Execution ID | State | State info | Created at | Updated at |</span></code>
<code><span style="color:#1a1a1a">+--------------------------------------+--------------------------------------+---------------+--------------------------------------+-------------------+---------+------------+---------------------+---------------------+</span></code>
<code><span style="color:#1a1a1a">| fe8d752e-8a96-45d3-a13c-0fdca58951cc | 86c581b9-e08d-46a0-ad0d-cf3f1d30bf4d | hello-world | None | <none> | SUCCESS | None | 2017-08-28 02:32:23 | 2017-08-28 02:32:24 |</span></code>
<code><span style="color:#1a1a1a">| 039af1e2-177e-4905-9c29-ccfbcdfedbff | 86c581b9-e08d-46a0-ad0d-cf3f1d30bf4d | hello-world | a3a0ed3f-a5ef-4416-af9f-33cef498bbb6 | <none> | SUCCESS | None | 2017-08-28 02:35:58 | 2017-08-28 02:35:59 |</span></code>
<code><span style="color:#1a1a1a">+--------------------------------------+--------------------------------------+---------------+--------------------------------------+-------------------+---------+---------</span></code></span>
注意:
社区版本定时任务没有State字段,执行完后会自动删除,因此不会记录已经完成的定时任务。
社区版本的execution没有cron id关联。
5 社区最新进展
OpenStack在8月30日发布了最新版本Pike,其中比较重要的几个新特性如下:
action支持多region了,用户可以通过action_region参数指定region。
workflow可以指定namespace,不同的namespace可以使用相同的名字。
支持使用 <% execution().created_at %>获取workflow的执行时间。
mistral-engine可以配置为local模式,action直接在本地执行而不需要通过RPC发给executor执行。
参考文档
https://docs.openstack.org/developer/mistral/
https://en.wikipedia.org/wiki/Cron
以上内容转裁自:
https://zhuanlan.zhihu.com/p/29078865
Mistral : 1 Mistral基础相关推荐
- Mistral : 2 Mistral表结构分析
目标: 1 弄清楚如何操作postgresql 2 弄清楚mistral中表结构 1 postgresql 1.1 登录 psql 报错: psql: FATAL: role "root& ...
- OpenStack工作流服务Mistral简介
1 Mistral背景 Mistral是一个OpenStack生态圈中比较新的项目,该项目的目标是: The project is to provide capability to define, e ...
- stackstorm 14.编写stackstorm的mistral
1 mistral动作流基础 因为workflows也是属于action,因此workflows在actions下面 因为mistral工作流比较特殊,它和之前执行的actions中对应的shell脚 ...
- openstack环境中安装mistral
确认keystone版本是v3,必须是v3: . admin-openrc.sh openstack endpoint list |grep keystone 确认git客户端是否安装,如果没有先安装 ...
- CentOS OpenStack Pike tacker 之 mistral 安装实录
格式有点乱有空再整理 一.安装mistral组件(官网手册为Ubuntu版,操作有点坑) "For information on how to install and configure t ...
- stackstorm 6. 工作流之Mistral
1 Mistral Mistral是一个用于管理和执行动作流的Openstack项目.Mistral是可以作为一个单独的 mistral服务在StackStorm中安装.一个Mistral工作流可以通 ...
- openstack mistral的安装
说明:本文基于使用rdo安装的allinone的opentack的Pika版本进行安装 mistral Mistral提供Workflow As a Service.典型的应用场景包括任务计划服务Cl ...
- OpenStack Pike 版本的 Mistral 安装
OpenStack Pike 版本的 Mistral 安装部署 # 安装环境使用的centos 7.3 1. 安装 Mistral 安装包. # yum -y install openstack-m ...
- 【Mistral】 workflow实例一, yaml文件里的变量定义,action调用等
以下实例引用自https://github.com/openstack/mistral-extra/blob/master/examples/v2/calculator/calculator.yaml ...
最新文章
- JavaScript - 数据类型和变量
- 进程间通信————有名管道
- PHP 从结果集中取得一行作为关联数组:
- Linux如何查看所有用户和用户组信息(cat groups whoami)
- python unittest断言_python unittest之断言及示例
- 之江天枢正式开源!一文详解天枢核心优势
- 将文本文件内容存储在DataSet中的方法总结
- 【毕设】JAVA+SQL办公自动化系统(源代码+论文+外文翻译)
- android自定义view案例,Android自定义View,你摸的透透的了?
- WCDMA功率控制与BER/BLER
- Excel键盘快捷键大全(二)
- GEE:LandTrendr时间序列曲线拟合
- JZOJ5401. 【NOIP2017提高A组模拟10.8】Star Way To Heaven
- ブリアー / 三星枪
- UHS-II文档学习
- 定时任务的10种写法,长见识了
- Luogu_P4140 奇数国
- 如何快速把Excel数据导入SQL数据库
- C++模板类的运算符重载
- 计算机排序操作步骤,win7电脑更改磁盘卷标排列顺序的操作步骤-电脑自学网