跳转到: 导航, 搜索

Murano/UnifiedAgent

统一 Murano Agent vNext 架构

简介

本文档定义了 Murano Agent 必须满足的架构规范和要求。它不强制执行任何特定的 Agent 实现。只要它们符合此规范,就可以用不同的编程语言为不同的操作系统和平台编写多个 Agent 实现。

与 vCurrent 相比,Murano Agent vNext 架构有两项主要改进

  1. vNext Agent 可以执行针对不同部署平台的执行计划。vCurrent Agent 仅设计用于使用 PowerShell 部署平台。这使得开发针对 Microsoft Windows 以外的操作系统 Agent 成为可能
  2. vNext Agent 支持构成执行计划的脚本和函数的非线性执行。虽然 vCurrent Agent 逐个顺序执行它们,但 vNext Agent 能够使用条件分支和循环来控制执行顺序。它还使执行计划能够将数据在脚本和函数之间传递(例如,将函数 1 的输出传递到函数 2 的输入)

虽然 vNext Agent 的执行计划格式与 vCurrent 格式不兼容,但它的设计方式使得从 vCurrent 格式到 vNext 格式的自动转换成为可能。因此,只要它们支持 PowerShell 执行,vNext Agent 就需要支持 vCurrent 执行计划。

Murano Agent vNext

Murano Agent 是一个应用程序,负责接收和执行来自远程源(通常来自 Murano Conductor)的命令,这些命令称为执行计划。

Murano Agent vNext 负责

  1. 侦听一个或多个通信通道,等待执行计划的到来
  2. 验证收到的执行计划。Agent 必须检查它是否具有所有提及的脚本类型的执行器,并在开始执行计划之前验证执行计划的所有必需部分都存在。如果验证失败,则必须将错误结果返回给计划发起者。
  3. 将 vCurrent 执行计划转换为 vNext 计划
  4. 准备环境并执行执行计划脚本。Agent 负责向执行计划脚本提供 API,以调用执行计划中提到的其他脚本以及 Agent 自身的服务命令。
  5. 管理和协调执行器的工作,执行器执行实际的脚本执行
  6. 处理来自执行计划脚本的 API 调用。其中一些调用可能需要向执行计划发起者发送消息
  7. 将计划执行结果发送回发起者


Murano Agent vNext 需要

  1. 支持至少一个通信通道。参考 Agent 实现必须支持至少 AMQP(S) 通信。所有连接和协议参数必须可通过 Agent 的配置文件进行配置。
  2. 跟踪执行计划发起者和接收计划的通道。计划执行结果需要通过接收它的相同通道发送给计划发起者。Agent 负责响应消息 ID 与请求消息 ID 相同。
  3. 处理通信和数据错误。在连接和/或协议错误(包括错误的执行计划格式)的情况下,Agent 绝不能崩溃或退出
  4. 具有容错能力。如果由于任何原因,Agent 在执行计划脚本完成后但在其结果发送回之前退出,则必须在下次启动时发送它们,然后再处理任何新的计划。理想的 Agent 实现能够在执行计划脚本运行时为突然关闭做好准备,并在上次执行脚本中的点从那里恢复。不太理想的实现将重新启动脚本执行。无论如何,Agent 都需要防止因意外应用程序关闭而丢失收到的执行计划。

部署平台

部署平台是 Murano 用于指代各种脚本编程语言、配置管理系统和管理工具的术语,这些工具可以从执行计划中调用。可能的部署平台列表是开放的,并且可以随着时间的推移而增长。Agent 不需要支持所有这些平台,但它们支持的平台越多,它们就越强大。至少 Agent 需要支持“Application”平台。以下是可能的部署平台(部分)列表

  • Application。 调用外部可执行文件(脚本)。输入参数使用简单的 DSL 转换为命令行字符串。这种 DSL 最简单的例子是 printf (C/C++) 或 String.Format (C#) 格式字符串)。执行结果是应用程序的退出代码。还可能存在相同平台的其他变体,例如运行应用程序并使用正则表达式从其 stdout 提取一些数据。
  • PowerShell。 执行 PowerShell 脚本/函数。
  • Python
  • Bash
  • Bat/CMD
  • Puppet
  • Cheff
  • SaltStack
  • VBScript
  • CShell

执行计划

执行计划是 Murano 工作流中可以触发的执行的最小单元。每个执行计划最初属于某个 Murano 服务(它可能是抽象服务 Mixin)。

执行计划不是低级命令(例如,将文件从 A 复制到 B),而是一个对服务用户有意义的语义的脚本。但是,执行计划包含各种执行一些低级操作的脚本。这些脚本可以在多个执行计划中重用。

Agent 以 JSON 编码的文档的形式接收执行计划,该文档以字典作为其根元素。在尝试执行任何操作之前,Agent 必须验证该语句的正确性。

以下是该字典中可以存在的键的列表。它们都是可选的。

  • FormatVersion。 当前规范定义 FormatVersion 2.0.0。vCurrent 格式的版本为 1.0.0。Agent 使用 SemVer 约定比较版本。如果此 FormatVersion 属性不存在或值小于 2.0.0,则 Agent 必须假定 ExecutionPlan 处于 vCurrent 格式,并在进一步处理之前将其转换为 vNext 格式。根据 SemVer 主要版本号更改意味着不兼容的破坏性更改。因此,vNext (v2.0) Agent 绝不能尝试执行 FormatVersion >= 3.0.0 的计划。
  • Action。 按照此规范,所有传入消息都必须将此属性设置为“Execute”或完全省略。未来的版本可能有其他操作(“Cconfigure”是一个可能的例子)。响应消息将 Action 属性设置为“Execute:Result”。
  • Service。 这是执行计划所属服务的 ID。这用于状态 API 的设置命名空间隔离。如果未提供服务 ID,则状态 API 不可用,并且如果它使用状态 API,则执行计划将失败。
  • ID。 这是执行计划 ID。此 ID 由发送者生成,必须是全局唯一的字符串。具有相同 ID 的两条消息被认为是相等的(因此是重复的)
  • Name。 执行计划的可读名称,用于记录(ThisIsMyExecutionPlanName)。
  • Version 执行计划的 SemVer 版本(默认 =“0.0.0”)。这是执行计划本身的版本。每次执行计划内容发生更改(主脚本、附加脚本、属性等)时,都应增加版本。版本属性用于记录和跟踪。这与 FormatVersion 形成对比,后者用于区分执行计划格式(vCurrent、vNext、未来格式)
  • Body。 这是以纯文本 Python 形式的执行计划脚本的字符串主体。
  • Parameters。 字典,类型为 String->JsonObject,将参数名称映射到其值。
  • Scripts。 字典,将脚本名称映射到脚本定义。有关确切格式规范,请参见下文。
  • Files。 字典,将文件 ID 映射到文件信息结构。有关确切格式规范,请参见下文。

强烈建议执行计划是幂等的(即,可以重复任意次数,系统处于完全相同状态且每次结果相同)。开发人员可以为此使用 States API。

脚本

脚本是执行计划的构建块。顾名思义,这些是用于不同部署平台的脚本。

每个脚本可以包含一个或多个文件。这些文件是脚本的程序模块、资源文件、配置文件、证书等。

脚本可以作为一个整体执行(例如,作为一段代码),公开可以在执行计划脚本中独立调用的某些函数,或者两者兼而有之。这取决于部署平台和执行器的功能。

脚本使用执行计划的“Scripts”属性指定。此属性将脚本名称映射到描述脚本的结构(文档)。它具有以下属性

  • Type: 脚本针对的部署平台名称。
  • Version: 脚本所需的部署平台/执行器的可选最低版本。
  • EntryPoint: 包含脚本入口点的文件的 ID(例如,主文件)。
  • Files。 这是脚本所需的其他文件的可选数组(ID)
  • Options: 脚本执行器的附加选项的字典(参见下文)。如果未提供,则假定为空字典。

Type 和 EntryPoints 属性是必需的。如果执行计划包含任何缺少这些属性的脚本,则必须立即失败。如果执行计划的 Files 条目不包含上述脚本文件(入口点或附加文件),则也是如此。

执行器

执行器是负责执行特定部署平台脚本的程序模块。此规范不强制执行执行器实现的任何特定方式。它们可以实现为内置类/模块/包等,动态加载的插件,甚至是进程外服务。无论如何,所有执行器都必须具有兼容的 API,以便 Agent 可以使用相同的协议与所有执行器通信。

脚本的执行方式如下

  1. Agent 为脚本文件准备一个文件夹,并将脚本入口点文件和所有提及的附加文件放入该文件夹(可能是指向文件的符号链接)
  2. Agent 根据脚本的 Type 属性选择适当的执行器
  3. Agent 请求执行器加载脚本文件,提供其入口点路径。即使在执行计划脚本主体中有多次调用该脚本,也仅发生一次。
  4. 如果需要将脚本文件作为一个整体或其中包含的一些函数执行,则 Agent 会请求执行器执行它,传递从执行计划脚本获得的功能名称(如果作为整体执行脚本则为 None)和参数
  5. 执行器执行脚本(或函数)并将结果返回给 Agent。Agent 在执行期间被阻止(等待结果)。
  6. 如果执行导致错误,执行器必须将其转换为引发的异常

执行器可以使用以下几种方法来执行

  • 嵌入脚本引擎并在自己的进程中执行脚本
  • 调用一些外部 RPC API
  • 将提供的脚本包装在一些服务代码中,该代码将使用某种 IPC(例如命名管道)与执行器通信,并将包装器作为独立进程执行

最佳执行方法取决于特定的部署平台。

建议将脚本执行超时配置为脚本的 Options 条目的一部分。


文件

Files 是执行计划中的一个条目,描述了作为执行计划的一部分传递的文件。这是一个将文件 ID 映射到描述文件的文档的字典。它具有以下属性

  • Name。 文件名。可以包含斜杠以表示嵌套文件夹中的文件。
  • BodyType。 以下之一
   “Text”. Body attribute contains string content of the file
   “Base64”. Body attribute contains base64 encoded string content of the (binary) file
   “ID”. Body attribute is a file ID in Murano Metadata Service
   “URI”. Body attribute is an absolute file URI
  • Body。 包含文件数据或有效的文件引用

智能 Agent 实现将(提取并)将文件存储在某种缓存中,并在首次访问时使用符号链接来引用来自多个脚本文件夹的文件。

执行计划脚本

这是一个简单的 Python 脚本,用于编排脚本的执行方式。由于 Python 是一种动态编程语言,Agent 可以将函数发布到 Python 脚本引擎,以便执行计划的脚本表示为 Python 函数。

例如,如果执行计划中有 3 个脚本,名为“script1”、“script2”和“script3”(它们都可以是不同类型的!),则以下是执行计划脚本的示例

 result = script1(
 ‘foo’, 
 args.argument1, 
 args[‘argument2’], 
 named_parameter=args.bar)
 if result:
   for i in range(0, result):
      t = script2(i)
      script3(t)

这演示了执行计划脚本的非常高级的功能。通常,这更像是一个线性脚本执行,一个接一个地执行。

Python 脚本引擎的功能可能受到限制。Python 脚本不应假定它可以导入其他模块,尤其是那些不是 Python 发行版的一部分的模块。它不应依赖于在主机上可以安装的任何 Python 版本上执行,因此必须仅使用保证存在于任何 Python 版本中的最简单的 Python 语句。

如果底层脚本执行器支持调用脚本内的单个函数,则使用脚本对象来访问它们

 script4.scriptFunction()

有两个预定义的对象名称不能用作脚本名称:args,它保存执行计划参数(那些在执行计划的 Parameters 条目中)。可以通过属性或索引器语法按其名称访问它们 api 用于访问 Agent 自身公开的 API 函数(参见下文)

执行结果

执行结束时,Agent 必须将执行结果发送给执行计划发起者,其中包含结果。它是一个 JSON 编码的文档,具有以下属性

  • FormatVersion - 执行结果格式版本。如果此属性等于“1.0.0”或不存在,则假定文档的其余部分处于 vCurrent 格式。必须使用版本 2.0.0 来表示符合此规范的格式
  • ID。 Agent 生成的全局唯一消息 ID
  • SourceID。 我们正在发送结果的执行计划属性的 ID。
  • Action。 执行结果的“Execute:Result”
  • ErrorCode。
 0 = no error
 1 = unknown/internal/generic error, error during script execution
 2 = incorrect input (Execution Plan is badly formatted)
 3 = unsupported Deployment Platform - Execution Plan has some scripts of unsupported type
 4 = SyntaxError, TabError, ImportError, SyntaxError etc. in Execution Plan script
 5 = Invalid set of options for one of the scripts
 6 = Attempt to access non-existing Execution Plan parameter
 7 = Some required files are missing in the Files entry
 8 = Error fetching file, IOError while storing the file
 9 = Unsupported FormatVersion
 10 = timeout occured
 100 + X = user error X
  • Body。 包含从执行计划脚本返回的值或异常详细信息(错误消息、堆栈跟踪、嵌套异常等)的 JsonObject
  • Time - ISO-8601 时间戳字符串,包含生成结果的日期/时间(使用客户端时钟)

API

Agent 通过 api 对象向执行计划脚本公开其他 API,以便脚本使用 api.methodCall(...) 代码调用 API 方法

重启 API

  • api.reboot(). 在执行计划完成后但在将结果发送回计划发起者之前安排重启。
  • api.waitReboot(timeout=0). 阻塞脚本执行,直到重启发生(假设最后调用的脚本启动了重启)。重启后,脚本将从该点继续(或者如果 Agent 实现不支持继续,则重新启动)。
  • api.expectReboots(count). 告知 Agent 在脚本执行期间可能发生的重启的最大次数,超过此次数则认为陷入死循环。


状态 API

状态 API 是对本地 Agent 存储的接口。它是一种通用的键值持久化存储,其中键是字符串,值是任何与 JSON 兼容的对象。值会立即持久化并保留在数据库中,直到显式删除。键与服务 ID 绑定,以便属于不同服务的执行计划不会发生键冲突。

状态 API 帮助开发人员持久化系统状态,当脚本无法做到幂等时。

  • api.setState(key, value) - 设置状态
  • api.getState(key) - 获取状态,如果不存在(或等于 None)则返回 None
  • api.removeState(key) - 如果状态存在则删除状态

文件 API

  • api.putFile(file_id path) - 从 Files 条目中获取文件(如果需要),并将其存储在指定路径中(或在指定路径中放置符号链接)。可以使用相对路径相对于执行计划的工作目录存储它(工作目录将在完成后被擦除)。
  • file_id api.addFile(fileInfo) - 将另一个条目添加到执行计划的 Files 字典中

杂项函数

  • api.version(). 返回 Agent 版本
  • api.setExitCode(code). 设置用户退出代码 (X in 100 + X),该代码将代替通用错误代码返回
  • api.logInfo(object, exception_info=False, notify=True), api.logDebug(object, exception_info=False, notify=True), api.logWarning(object, exception_info=False, notify=True), api.logError(object, exception_info=False, notify=True), api.logFatalError(object, exception_info=True, notify=True) - 将记录写入本地日志文件,并(如果 notify==True)将日志记录发送到执行计划发起者。

日志消息

日志消息是 Agent 作为对 api.logXXX 调用响应而发送到(或排队等待,具体取决于通信通道)客户端的消息。其格式如下

{
 “FormatVersion”: “2.0.0”,
 “ID”: “globally-unique message ID”,
 “SourceID”: “ID of an Execution Plan (optional)”,
 “Level”: “debug|info|warning|error|fatal”,
 “Body”: “message text, exception string etc”,
 “Action”: “log”,
 “Time”: “ISO-8601 client time of the message/exception”,
 “Tag”: “optional client tag if needed (contains in config file)”
}

向后兼容性

支持 PowerShell 部署平台和 AMQP 通信通道的 Agent 必须通过自动将其转换为 vNext 格式来支持 vCurrent 执行计划格式。此类执行计划的结果必须转换回 vCurrent 执行结果。转换方法如下

vCurrent 执行计划 -> vNext 执行计划

  1. FormatVersion = “2.0.0”, Action = “execute”, ID = amqp_message_id, Name = “Auto-converted”
  2. 遍历所有命令,并将它们的参数放入 args 对象中,使用 key = command_name + ‘_’ + argument_name
  3. 将 vCurrent 脚本转换为 Files 文档,每个文件的类型为“Base64”。使用类似“script{index}.ps1”的文件名
  4. 生成脚本入口点,只需点引用其他生成的脚本文件即可。将其添加到 Files 文档中,类型为“Text”
  5. 生成单个脚本条目(假设命名为“ps”),其中包含生成的 EntryPoint 文件和 Files 属性中提到的所有其他文件
  6. 将执行计划脚本生成如下:对于每个函数名,生成类似 ps.functionName(arg1 = args[‘functionName_arg1’], arg2 = args[‘functionName_arg2’]) 的语句。将它们放入 try-except 块中
  7. 将 Reboot 标志转换为 api.reboot() 调用

vNext 执行结果 -> vCurrent 执行结果

如果 1 < ErrorCode < 100 则结果将是

{
 “IsException”: true,
 “Result”: result[‘Body’]
}

否则,如果 ErrorCode == 1 或 ErrorCode >= 100

{
 “IsException”: false,
 “Result”: {
                “IsException”: true,
                “Result”: result[‘Body’]
                }
}

否则

{
 “IsException”: false,
 “Result”: {
               “IsException”: false,
               “Result”: result[‘Body’]
               }
}