今天在YARN上提供新服务并非易事。现有框架的API级别太低(本机YARN),需要编写新代码(对于具有编程API的框架)或编写复杂的规范(对于声明性框架)。
此简化的REST API可用于创建和管理YARN服务的生命周期。在大多数情况下,应用程序所有者不会被迫对其应用程序进行任何更改。如果应用程序使用诸如Docker之类的容器化技术打包在一起,那么这首先是正确的。
本文档介绍了用于在YARN上部署/管理容器化服务的API规范(也称为YarnFile)。REST API和CLI可以使用相同的JSON规范来管理服务。
许可证:Apache 2.0许可证URL:http : //www.apache.org/licenses/LICENSE-2.0.html
POST / app / v1 / services
PUT / app / v1 / services / {service_name}
服务组件的工件。如果未指定,组件将仅运行光头启动命令,并且不会本地化任何工件。
名称 | 描述 | 需要 | 架构图 | 默认 |
---|---|---|---|---|
ID | 工件ID。示例包括用于基于tarball的服务的软件包位置uri,用于docker的映像名称,服务名称等。 | 真正 | 串 | |
类型 | 工件类型,例如docker,tarball等(可选)。对于TARBALL类型,指定的tarball将被本地化到名为lib的文件夹下的容器本地工作目录。对于SERVICE类型,将读取指定的服务,并将其组件添加到该服务中。工件类型为SERVICE的原始组件将被删除(原始组件中指定的任何属性都将被忽略)。 | 假 | 枚举(DOCKER,TARBALL,SERVICE) | DOCKER |
乌里 | 工件位置以支持多个工件存储(可选)。 | 假 | 串 |
服务的一个或多个组件。如果说服务是HBase,则该组件可以是简单角色,例如master或regionserver。如果该服务是复杂的业务Web应用程序,则组件可以是其他服务,例如Kafka或Storm。从而打开了对复杂和嵌套服务的支持。
名称 | 描述 | 需要 | 架构图 | 默认 |
---|---|---|---|---|
名称 | 服务组件的名称(强制性)。如果启用了注册表DNS,则最大长度为44个字符。 | 真正 | 串 | |
州 | 组件的状态 | 假 | ComponentState | |
依存关系 | 在可以启动此组件之前,应处于READY状态(由就绪检查定义)的服务组件的数组。服务所有组件之间的依赖关系应表示为DAG。 | 假 | 字符串数组 | |
准备情况检查 | 此组件的准备情况检查。 | 假 | 准备情况检查 | |
神器 | 组件的工件(可选)。如果未指定,则服务级别全局工件生效。 | 假 | 神器 | |
launch_command | 该组件的自定义启动命令(对于DOCKER组件是可选的,否则是必需的)。在组件级别指定时,它将覆盖在全局级别指定的值(如果有)。如果docker image支持ENTRYPOINT,则launch_command用逗号(,)而不是空格定界。 | 假 | 串 | |
资源 | 该组件的资源(可选)。如果未指定,则服务级别全局资源生效。 | 假 | 资源资源 | |
容器数 | 该组件的容器数(可选)。如果未指定,则服务级别全局number_of_containers生效。 | 假 | 整数(int64) | |
decommissioned_instances | 退役的组件实例列表。 | 假 | 字符串数组 | |
货柜 | 启动组件的容器。为POST有效负载指定此属性的值会引发验证错误。此Blob仅在已启动服务的GET响应中可用。 | 假 | 容器阵列 | |
run_privileged_container | 在特权模式(YARN-4262)中运行此组件的所有容器。 | 假 | 布尔值 | |
placement_policy | 此组件所有容器的高级计划和放置策略。 | 假 | 刊登位置政策 | |
组态 | 此组件的配置属性。 | 假 | 组态 | |
快速链接 | 在服务级别定义的快速链接键的列表,此组件将对其进行解析。 | 假 | 字符串数组 | |
restart_policy | 重新启动组件的策略。包括总是(总是重启 | |||
组件,即使实例退出代码= 0);ON_FAILURE(如果实例退出代码!= 0,则仅重新启动组件);永远不要(在任何情况下都不要重启)。具有restart_policy = ON_FAILURE / NEVER的组件不支持伸缩 | 假 | 串 | 总是 |
需要在服务组件容器中创建一个配置文件并将其作为卷使用。
名称 | 描述 | 需要 | 架构图 | 默认 |
---|---|---|---|---|
类型 | 标准格式的配置文件,例如xml,属性,json,yaml,模板或静态/归档资源文件。如果指定了静态/归档类型,则必须在启动作业之前将文件上传到远程文件系统,并且YARN服务框架将在启动容器之前对文件进行本地化。存档文件在本地化过程中被解包 | 假 | 枚举(XML,PROPERITY,JSON,YAML,TEMPLATE,HADOOP_XML,STATIC,ARCHIVE) | |
dest_file | 该配置文件的创建路径。如果它是绝对路径,它将被安装到DOCKER容器中。绝对路径仅适用于DOCKER容器。如果它是相对路径,则仅应提供文件名,并且将在容器本地工作目录中名为conf的文件夹下为静态/归档以外的所有类型创建文件。对于静态/归档资源类型,这些文件位于资源目录下。 | 假 | 串 | |
src_file | 这提供了配置文件的源位置,配置文件的内容以type中指定的格式转储到dest_file后属性替换中。通常,src_file指向由诸如puppet,chef或hdfs等工具维护的源代码控制的网络可访问文件。当前仅支持hdfs。 | 假 | 串 | |
属性 | 键值对的Blob,将以type中指定的格式转储到dest_file中。如果指定了src_file,则src_file内容将转储到dest_file中,并且这些属性将覆盖src_file中的现有属性(如果有),或作为src_file中的新属性添加。 | 假 | 宾语 |
可以通过env,文件和自定义可插拔助手docker容器注入服务组件的配置属性集。将支持多种标准格式的文件,例如xml,属性,json,yaml和模板。
名称 | 描述 | 需要 | 架构图 | 默认 |
---|---|---|---|---|
属性 | 键值对的Blob,用于配置YARN服务AM。 | 假 | 宾语 | |
环保 | 键值对的Blob,将附加到默认系统属性并在启动时移交给服务。在注入之前,将替换所有对属性的占位符。 | 假 | 宾语 | |
档案 | 需要创建的文件列表列表,这些文件列表可以作为卷在服务组件容器中使用。 | 假 | ConfigFile数组 |
正在运行的服务容器的实例。
名称 | 描述 | 需要 | 架构图 | 默认 |
---|---|---|---|---|
ID | 正在运行的服务的唯一容器ID,例如container_e3751_1458061340047_0008_01_000002。 | 假 | 串 | |
发射时间 | 容器创建的时间,例如2016-03-16T01:01:49.000Z。这很可能与群集启动时间不同。 | 假 | 字符串(日期) | |
ip | 正在运行的容器的IP地址,例如172.31.42.141。IP地址和主机名属性值取决于YARN-4007的群集/泊坞窗网络设置。 | 假 | 串 | |
主机名 | 正在运行的容器的标准主机名,例如ctr-e3751-1458061340047-0008-01-000002.examplestg.site。IP地址和主机名属性值取决于YARN-4007的群集/泊坞窗网络设置。 | 假 | 串 | |
主机 | 运行容器的裸节点或主机,例如cn008.example.com。 | 假 | 串 | |
州 | 服务容器的状态。 | 假 | 容器状态 | |
component_instance_name | 该容器实例所属的组件实例的名称。组件实例名称被命名为$ COMPONENT_NAME-i,其中i是单调递增的整数。例如,一个名为nginx的组件可以具有多个名为nginx-0,nginx-1等的组件实例。每个组件实例都由一个容器实例支持。 | 假 | 串 | |
资源 | 用于此容器的资源。 | 假 | 资源资源 | |
神器 | 用于此容器的工件。 | 假 | 神器 | |
privileged_container | 容器是否以特权模式运行。 | 假 | 布尔值 |
启动服务的用户的kerberos主体信息。
名称 | 描述 | 需要 | 架构图 | 默认 |
---|---|---|---|---|
委托人名称 | 启动服务的用户的主体名称。请注意,principal_name字段(例如testuser/_HOST@EXAMPLE.COM)中需要_HOST,因为Hadoop客户端会在与服务器通信时验证服务器(在这种情况下,AM的)主体具有主机名。 | 假 | 串 | |
键表 | kerberos keytab的URI。当前仅支持裸主机上存在的文件。URI以“ file://”开头-本地主机上存储密钥表的路径。假定管理员在AM启动之前将密钥表预先安装在本地主机上。 | 假 | 串 |
放置约束详细信息。
名称 | 描述 | 需要 | 架构图 | 默认 |
---|---|---|---|---|
名称 | 与此约束关联的可选名称。 | 假 | 串 | |
类型 | 展示位置的类型。 | 真正 | PlacementType | |
范围 | 安置范围。 | 真正 | 放置范围 | |
target_tags | 该组件的放置策略所依赖的组件的名称将作为目标标记添加。因此,出于亲和力,此组件的容器要求放置在运行目标标签组件的容器的主机上。目标标签也可以包含该组件的名称,在这种情况下,这意味着对于反亲和力,在主机上最多可以放置一个该组件的容器。类似地,对于基数,这意味着该组件的容器正在请求放置在至少运行minCardinality但不超过目标标签组件的maxCardinality容器的主机上。 | 假 | 字符串数组 | |
node_attributes | 节点属性是与节点关联的一组key:value对。 | 假 | 宾语 | |
node_partitions | 可以运行此组件的容器的节点分区。 | 假 | 字符串数组 | |
最小基数 | 当放置类型为基数时,主机应具有的依赖组件的最小容器数,可以在该容器上分配该组件的容器。 | 假 | 整数(int64) | |
max_cardinality | 当放置类型为基数时,主机应具有的依赖组件的最大容器数,可以在该容器上分配该组件的容器。 | 假 | 整数(int64) |
放置类型-与另一组件的容器或同一组件(自身)的容器的亲和力/反亲和力/基数亲和力。
名称 | 描述 | 需要 | 架构图 | 默认 |
---|---|---|---|---|
类型 | 假 | 枚举(AFFINITY,ANTI_AFFINITY,AFFINITY_WITH_CARDINALITY) |
要执行的检查以确定组件实例(容器)的准备情况。如果未指定准备情况检查,则将使用默认的准备情况检查,除非在组件或全局级别将yarn.service.default-readiness-check.enabled配置属性设置为false。当前不支持构件字段,但可能会在将来实现,从而使可插入的帮助容器能够支持高级用例。
名称 | 描述 | 需要 | 架构图 | 默认 |
---|---|---|---|---|
类型 | DEFAULT(AM检查容器是否具有IP并有选择地对容器主机名执行DNS查找),HTTP(AM执行默认检查并向容器发送REST调用并期望响应代码在200到299之间)或PORT (AM执行默认检查,并尝试在指定端口上打开与容器的套接字连接)。 | 真正 | 枚举(默认,HTTP,端口) | |
属性 | 键值对的Blob,将用于配置检查。 | 假 | 宾语 | |
神器 | 可插拔的就绪检查辅助容器的伪像(可选)。如果指定,则此帮助容器通常托管http uri并封装执行实际容器准备情况检查所需的复杂脚本。最终,像简化的用例一样,预期将响应204 No content。这个可插入的框架使服务所有者受益,他们可以在不进行任何包装修改的情况下运行服务。注意,目前仅支持类型为docker的工件。尚未实施 | 假 | 神器 |
资源确定容器可用的资源量(vcore,内存,网络等)。此字段确定要应用于组件或服务的所有容器的资源。在服务(或全局)级别指定的资源可以在组件级别覆盖。仅配置文件或CPU和内存之一。否则会引发验证异常。
名称 | 描述 | 需要 | 架构图 | 默认 |
---|---|---|---|---|
轮廓 | 每个资源配置文件都有一个唯一的ID,该ID与群集级别的预定义内存,CPU等相关联。 | 假 | 串 | |
中央处理器 | 分配给每个容器的vcore数量(可选,但如果指定,将覆盖配置文件中的cpus)。 | 假 | 整数(int32) | |
记忆 | 分配给每个容器的内存量(可选,但如果指定,将覆盖配置文件中的内存)。当前仅接受整数值,默认单位为MB。 | 假 | 串 | |
额外 | 资源类型名称到资源类型信息的映射。包括值(整数)和单位(字符串)。这将用于指定除cpu和内存以外的资源。请参考以下示例。 | 假 | 宾语 |
ResourceInformation除了确定内存和vcore之外,还确定资源类型的单位/值。它将是Resource对象的一部分。
名称 | 描述 | 需要 | 架构图 | 默认 |
---|---|---|---|---|
值 | 资源的整数值。 | 假 | 整数(int64) | |
单元 | 资源单位,可接受的值为-p / n / u / m / k / M / G / T / P / Ki / Mi / Gi / Ti / Pi。默认情况下为空意味着没有单位。 | 假 | 串 |
服务资源具有以下属性。
名称 | 描述 | 需要 | 架构图 | 默认 |
---|---|---|---|---|
名称 | 唯一的服务名称。如果启用了注册表DNS,则最大长度为63个字符。 | 真正 | 串 | |
版 | 服务的版本。 | 真正 | 串 | |
描述 | 服务说明。 | 假 | 串 | |
ID | 唯一的服务ID。 | 假 | 串 | |
神器 | 服务所有组件的默认工件,但工件类型设置为SERVICE的组件除外(可选)。 | 假 | 神器 | |
资源 | 服务所有组件的默认资源(可选)。 | 假 | 资源资源 | |
发射时间 | 创建服务的时间,例如2016-03-16T01:01:49.000Z。 | 假 | 字符串(日期) | |
number_of_running_containers | 在获取响应时,它提供了请求时此服务(所有组件)的运行容器总数。请注意,当分配了更多容器时,后续请求可以返回不同的数字,直到达到容器总数为止,或者两个请求之间都发出了弹性请求。 | 假 | 整数(int64) | |
一生 | 服务到达STARTED状态(此状态由YARN自动销毁)之后的生命周期(以秒为单位)。对于无限的生命周期,请勿设置生命周期值。 | 假 | 整数(int64) | |
组件 | 服务的组成部分。 | 假 | 组件数组 | |
组态 | 服务的配置属性。服务/全局级别提供的配置可用于所有组件。特定属性可以在组件级别覆盖。 | 假 | 组态 | |
州 | 服务状态。为PUT有效负载指定此属性的值意味着将服务更新到此所需状态。 | 假 | 服务状态 | |
快速链接 | 要为服务导出的快速链接的键值对的Blob。 | 假 | 宾语 | |
队列 | 该服务应提交到的YARN队列。 | 假 | 串 | |
kerberos_principal | 启动服务的用户的主要信息 | 假 | Kerberos主体 | |
docker_client_config | 包含Docker客户端配置的文件的URI(例如hdfs:///tmp/config.json) | 假 | 串 | |
依存关系 | 该服务所依赖的服务名称的列表。 | 假 | 字符串数组 |
POST URL-http:// localhost:8088 / app / v1 / services
{ “ name”:“ hello-world”, “ version”:“ 1.0.0”, “ description”:“ hello world example”, “组件” : [ { “ name”:“ hello”, “容器数”:2 “工件”:{ “ id”:“ nginx:latest”, “ type”:“ DOCKER” }, “ launch_command”:“ ./start_nginx.sh”, “资源”:{ “ cpus”:1 “内存”:“ 256” } } ] }
GET URL-http:// localhost:8088 / app / v1 / services / hello-world
注意,生存期值为-1表示无限的生存期。
{ “ name”:“ hello-world”, “ version”:“ 1.0.0”, “ description”:“ hello world example”, “ id”:“ application_1503963985568_0002”, “寿命”:-1, “ state”:“ STABLE”, “组件”: [ { “ name”:“ hello”, “ state”:“ STABLE”, “资源”:{ “ cpus”:1 “内存”:“ 256” }, “配置”:{ “属性”:{}, “ env”:{}, “文件”:[] }, “快速链接”: [], “容器”:[ { “ id”:“ container_e03_1503963985568_0002_01_000002”, “ ip”:“ 10.22.8.143”, “主机名”:“ ctr-e03-1503963985568-0002-01-000002.example.site”, “ state”:“ READY”, “启动时间”:1504051512412, “ bare_host”:“ host100.cloud.com”, “ component_instance_name”:“ hello-0” }, { “ id”:“ container_e03_1503963985568_0002_01_000003”, “ ip”:“ 10.22.8.144”, “主机名”:“ ctr-e03-1503963985568-0002-01-000003.example.site”, “ state”:“ READY”, “启动时间”:1504051536450, “ bare_host”:“ host100.cloud.com”, “ component_instance_name”:“ hello-1” } ], “ launch_command”:“ ./start_nginx.sh”, “容器数”:1 “ run_privileged_container”:false } ], “配置”:{ “属性”:{}, “ env”:{}, “文件”:[] }, “快速链接”: {} }
放置网址-http:// localhost:8088 / app / v1 / services / hello-world / components / hello
POST URL-http:// localhost:8088:/ app / v1 / services / hbase-app-1
{ “ name”:“ hbase-app-1”, “ version”:“ 1.0.0”, “ description”:“ hbase服务”, “寿命”:“ 3600”, “组件”: [ { “ name”:“ hbasemaster”, “容器数”:1 “工件”:{ “ id”:“ hbase:latest”, “ type”:“ DOCKER” }, “ launch_command”:“ / usr / hdp / current / hbase-master / bin / hbase主启动”, “资源”:{ “ cpus”:1 “内存”:“ 2048” }, “配置”:{ “ env”:{ “ HBASE_LOG_DIR”:“ <LOG_DIR>” }, “文件”:[ { “ type”:“ XML”, “ dest_file”:“ /etc/hadoop/conf/core-site.xml”, “属性”:{ “ fs.defaultFS”:“ $ {CLUSTER_FS_URI}” } }, { “ type”:“ XML”, “ dest_file”:“ /etc/hbase/conf/hbase-site.xml”, “属性”:{ “ hbase.cluster.distributed”:“ true”, “ hbase.zookeeper.quorum”:“ $ {CLUSTER_ZK_QUORUM}”, “ hbase.rootdir”:“ $ {SERVICE_HDFS_DIR} / hbase”, “ zookeeper.znode.parent”:“ $ {SERVICE_ZK_PATH}”, “ hbase.master.hostname”:“ hbasemaster。$ {SERVICE_NAME}。$ {USER}。$ {DOMAIN}”, “ hbase.master.info.port”:“ 16010” } } ] } }, { “ name”:“ regionserver”, “容器数”:3, “工件”:{ “ id”:“ hbase:latest”, “ type”:“ DOCKER” }, “ launch_command”:“ / usr / hdp / current / hbase-regionserver / bin / hbase regionserver start”, “资源”:{ “ cpus”:1 “内存”:“ 2048” }, “配置”:{ “ env”:{ “ HBASE_LOG_DIR”:“ <LOG_DIR>” }, “文件”:[ { “ type”:“ XML”, “ dest_file”:“ /etc/hadoop/conf/core-site.xml”, “属性”:{ “ fs.defaultFS”:“ $ {CLUSTER_FS_URI}” } }, { “ type”:“ XML”, “ dest_file”:“ /etc/hbase/conf/hbase-site.xml”, “属性”:{ “ hbase.cluster.distributed”:“ true”, “ hbase.zookeeper.quorum”:“ $ {CLUSTER_ZK_QUORUM}”, “ hbase.rootdir”:“ $ {SERVICE_HDFS_DIR} / hbase”, “ zookeeper.znode.parent”:“ $ {SERVICE_ZK_PATH}”, “ hbase.master.hostname”:“ hbasemaster。$ {SERVICE_NAME}。$ {USER}。$ {DOMAIN}”, “ hbase.master.info.port”:“ 16010”, “ hbase.regionserver.hostname”:“ $ {COMPONENT_INSTANCE_NAME}。$ {SERVICE_NAME}。$ {USER}。$ {DOMAIN}” } } ] } } ], “快速链接”: { “ HBase主状态UI”:“ http://hbasemaster0.$ {SERVICE_NAME}。$ {USER}。$ {DOMAIN}:16010 / master-status”, “代理的HBase主状态UI”:“ http:// app-proxy / $ {DOMAIN} / $ {USER} / $ {SERVICE_NAME} / hbasemaster / 16010 /” } }
POST URL-http:// localhost:8088 / app / v1 / services
{ “ name”:“ hello-world”, “ version”:“ 1.0.0”, “ description”:“带有GPU的Hello world示例”, “组件” : [ { “ name”:“ hello”, “容器数”:2 “工件”:{ “ id”:“ nginx:latest”, “ type”:“ DOCKER” }, “ launch_command”:“ ./start_nginx.sh”, “资源”:{ “ cpus”:1 “内存”:“ 256”, “其他”:{ “ yarn.io/gpu”:{ “值”:4 “ unit”:“” } } } } ] }
POST URL-http:// localhost:8088 / app / v1 / services
{ “ name”:“ hello-world”, “ version”:“ 1.0.0”, “ description”:“具有反亲和性的Hello世界示例”, “组件” : [ { “ name”:“ hello”, “容器数”:3, “工件”:{ “ id”:“ nginx:latest”, “ type”:“ DOCKER” }, “ launch_command”:“ ./start_nginx.sh”, “资源”:{ “ cpus”:1 “内存”:“ 256” }, “ placement_policy”:{ “约束”:[ { “ type”:“ ANTI_AFFINITY”, “ scope”:“ NODE”, “ node_attributes”:{ “ os”:[“ centos6”,“ centos7”], “ fault_domain”:[“ fd1”,“ fd2”] }, “ node_partitions”:[ “ gpu”, “快速磁盘” ], “ target_tags”:[ “你好” ] } ] } } ] }
GET URL-http:// localhost:8088 / app / v1 / services / hello-world
注意,对于反亲和性组件,在特定节点中最多分配1个容器。在此示例中,组件“ hello”已请求3个容器。因为群集具有3个或更多NM,所以分配了所有3个容器。如果群集的少于3个NM,则将分配少于3个容器。如果分配的容器数少于请求的容器数,则组件和服务将处于非稳定状态。
{ “ name”:“ hello-world”, “ version”:“ 1.0.0”, “ description”:“具有反亲和性的Hello世界示例”, “ id”:“ application_1503963985568_0003”, “寿命”:-1, “ state”:“ STABLE”, “组件”: [ { “ name”:“ hello”, “ state”:“ STABLE”, “资源”:{ “ cpus”:1 “内存”:“ 256” }, “ placement_policy”:{ “约束”:[ { “ type”:“ ANTI_AFFINITY”, “ scope”:“ NODE”, “ node_attributes”:{ “ os”:[“ centos6”,“ centos7”], “ fault_domain”:[“ fd1”,“ fd2”] }, “ node_partitions”:[ “ gpu”, “快速磁盘” ], “ target_tags”:[ “你好” ] } ] }, “配置”:{ “属性”:{}, “ env”:{}, “文件”:[] }, “快速链接”: [], “容器”:[ { “ id”:“ container_e03_1503963985568_0003_01_000002”, “ ip”:“ 10.22.8.143”, “主机名”:“ ctr-e03-1503963985568-0003-01-000002.example.site”, “ state”:“ READY”, “启动时间”:1504051512412, “ bare_host”:“ host100.cloud.com”, “ component_instance_name”:“ hello-0” }, { “ id”:“ container_e03_1503963985568_0003_01_000003”, “ ip”:“ 10.22.8.144”, “主机名”:“ ctr-e03-1503963985568-0003-01-000003.example.site”, “ state”:“ READY”, “启动时间”:1504051536450, “ bare_host”:“ host101.cloud.com”, “ component_instance_name”:“ hello-1” }, { “ id”:“ container_e03_1503963985568_0003_01_000004”, “ ip”:“ 10.22.8.145”, “主机名”:“ ctr-e03-1503963985568-0003-01-000004.example.site”, “ state”:“ READY”, “启动时间”:1504051536450, “ bare_host”:“ host102.cloud.com”, “ component_instance_name”:“ hello-2” } ], “ launch_command”:“ ./start_nginx.sh”, “容器数”:1 “ run_privileged_container”:false } ], “配置”:{ “属性”:{}, “ env”:{}, “文件”:[] }, “快速链接”: {} }
POST URL-http:// localhost:8088 / app / v1 / services
{ “ name”:“ hello-world”, “ version”:“ 1.0.0”, “ description”:“带有健康阈值监视器的Hello world示例”, “组件” : [ { “ name”:“ hello”, “容器数”:100, “工件”:{ “ id”:“ nginx:latest”, “ type”:“ DOCKER” }, “ launch_command”:“ ./start_nginx.sh”, “资源”:{ “ cpus”:1 “内存”:“ 256” }, “配置”:{ “属性”:{ “ yarn.service.container-health-threshold.percent”:“ 90”, “ yarn.service.container-health-threshold.window-secs”:“ 400”, “ yarn.service.container-health-threshold.init-delay-secs”:“ 800” } } } ] }