从头编写脚本: 为 IBM WebSphere Application Server 创建 Jython 管理脚本

从头开发 IBM® WebSphere® Application Server 管理脚本并不困难,但是很难找到指导您逐步完成这个过程的实用信息。本文帮助您实现这个目标,首先讨论希望脚本完成什么任务,然后带领您完成迭代式开发步骤,最终完成一个完整的示例脚本,包括注释、用法信息和命令行参数处理。

Bob Gibson, 咨询软件工程师, EMC

Bob Gibson 是一名咨询软件工程师,在 IBM 的多个软件相关职位上拥有超过 25 年的工作经验,包括应用程序程序员、架构师、开发人员、教员、技术支持分析师和测试人员。目前他是技术支持部门的一个团队负责人,负责 IBM 的 IBM WebSphere Distributed Application Server。他拥有 University of Virginia 的工程学学士学位和计算机科学硕士学位。



2010 年 6 月 24 日

简介

早在撰写 WebSphere Application Server Administration Using Jython 这本书之前,我已经发现描述如何创建完整脚本的示例非常少。因此我认为,如果介绍我创建管理脚本的常用过程,可能对管理脚本的编写者有帮助。

一般来说,从头创建 wsadmin 脚本需要完成以下步骤:

  1. 决定希望完成什么管理任务。
  2. 查阅文档,了解这个任务和可以使用的各种方法。
  3. 了解方法参数及如何使用它们。
  4. 使用交互式的 wsadmin 会话检验这个方法、它的用法和参数值。
  5. 通过使用脚本模板,判断向脚本提供方法参数值的最佳技术(例如,用属性文件还是通过命令行参数)。
  6. 在脚本中添加参数处理代码和用法详细信息。
  7. 添加执行实际脚本方法的代码以执行所需任务。

本文描述构建一个脚本所需的步骤,这个脚本用于创建集群成员。这是一个相当简单的任务,但是与大多数任务一样,有一些因素会导致这个任务复杂化。例如,这个脚本应该只能创建单个集群成员,还是希望能够用它同时创建多个成员?

按照本文描述的步骤,我们将从易到难,最终完成一个可以创建单个集群成员的示例脚本。

本文适用于 IBM WebSphere Application Server V7.0 和 V6.1。


决定方式

首先,应该寻找您感兴趣的任务的相关信息,以便了解涉及的过程和参数。这让您创建的脚本能够以适当且高效的方式执行此任务。

例如,在 Web 浏览器中访问 IBM WebSphere Application Server V7 Network Deployment 信息中心,搜索 create cluster member。搜索结果的顶部显示 Creating cluster members using scripting,这正是我们需要的。单击这个链接,显示相关的文档:

  • Before you begin 部分指出可以以多种方式执行这个任务。例如,要想创建新的集群成员,可以
  • About this task 部分提出一些有意思的问题,比如:
    • 您希望(或需要)脚本的一般化程度有多大?
    • 希望让脚本能够创建集群的第一个成员、集群的后续成员还是都可以?
    • 在执行脚本之前集群是否必须已经存在,还是希望(或需要)脚本能够创建新集群?

我们先讨论这些方式,然后再决定如何处理。

使用 AdminConfig 脚本对象

显然,对于这个任务,要使用某种形式的 AdminConfig 脚本对象,所以最好先在信息中心搜索相关信息。

搜索 AdminConfig 会产生几个结果,包括结果列表顶部的 Commands for the AdminConfig object using wsadmin scripting。通过查阅这一项了解到 AdminConfig 脚本对象上有一个 createClusterMember 方法。还发现这个方法有一些必需的参数。快速浏览一下页面上的示例,更好地了解如何使用它们。有意思的是,必需参数的列表(见表 1)与示例代码并不一致。

表 1. AdminConfig.createClusterMember() 必需的参数
参数名说明
clusterID要添加成员的集群的配置 ID。
nodeID要创建成员的节点的配置 ID。
memberAttributes指定用来创建新成员的属性。
templateID成员创建过程使用的模板的配置 ID。

如果 templateID 参数确实是必需的,那么示例代码中为什么没有呢?templateID 参数实际上只能用于集群的第一个成员。在创建后续集群成员时,使用第一个成员作为模板。

关于提供的示例脚本
使用提供的脚本让您能够把注意力集中于创建脚本的过程,而不是关注创建包含单个成员的集群的机制。本文假设之前已经创建了一个集群 (Cluster1) 和它的第一个成员 (Member1)。另外,这些脚本是按本文描述的过程创建的,因而也可以作为学习示例使用。

但是,为了创建集群成员,必须先创建集群以及第一个集群成员。可以使用本文的 下载 部分提供的脚本帮助完成这个任务(这些脚本也是用这里描述的技术创建的):

  • createCluster.py:创建一个空的集群。
  • createFirstClusterMember.py:使用用户指定的模板名创建第一个集群成员。

现在,我们通过一个交互式的 wsadmin 会话看看使用 AdminConfig.createClusterMember() 方法创建新集群成员所需的步骤。清单 1 给出这个交互式会话,表 2 详细解释此会话。

清单 1. 使用 AdminConfig.createClusterMember()
 1|[root@ragdoll bin]#./wsadmin.sh -lang jython
 2|WASX7209I: Connected to process "dmgr" on node ragdollCellManager02 using SOAP
 3|connector; The type of process is: DeploymentManager
 4|WASX7031I: For help, enter: "print Help.help()"
 5|
 6|wsadmin>print AdminConfig.list( 'ClusterMember' )
 7|Member1(cells/ragdollCell02/clusters/Cluster1|cluster.xml#ClusterMember_1261105354124)
 8|
 9|wsadmin>clusterID = AdminConfig.getid('/ServerCluster:Cluster1/' )
10|wsadmin>nodeID = AdminConfig.getid( '/Node:ragdollNode03/' );
11|wsadmin>memberID = AdminConfig.createClusterMember(clusterID, nodeID, '[[memberName
   Member2]]' )
12|wsadmin>print AdminConfig.list( 'ClusterMember' )
13|Member1(cells/ragdollCell02/clusters/Cluster1|cluster.xml#ClusterMember_1261105354124)
14|Member2(cells/ragdollCell02/clusters/Cluster1|cluster.xml#ClusterMember_1261145939139)
表 2. AdminConfig.createClusterMember() 使用方法说明
行号说明
1启动 wsadmin 并指定语言是 Jython。
2-4wsadmin 生成的信息说明已经成功地连接到部署管理器。
5为了方便阅读添加的空行。
6-7调用 AdminConfig.list() 以显示现有集群成员的配置 ID。
8为了方便阅读添加的空行。
9-10使用赋值语句获得要添加成员的集群以及要创建成员的节点的配置 ID。
11调用 AdminConfig.createClusterMember() 方法创建新的集群成员。
12-13调用 AdminConfig.list() 以确认新创建的集群成员 (Member2) 确实存在。

下面是使用这种技术编写脚本来创建单个集群成员时需要完成的步骤:

  1. 检查命令行参数。
  2. 根据命令行参数 clusterName 查找集群的配置 ID。(脚本应该要求用户指定集群和节点名称,而不是指定配置 ID。)
  3. 根据命令行参数 nodeName 查找节点的配置 ID。
  4. 检查集群中是否已经存在指定的 memberName。
  5. 调用 createClusterMember 方法以执行所需的操作。
  6. 检查请求是成功还是失败。

当然,这只是对过程的粗略描述,但是可以看出使用这种技术所需的编程类型。但是,在花时间和精力编写使用这种方式的脚本之前,我们看看其他方式有哪些差异,以及哪种方式可能更好或更容易实现。

使用 AdminTask 脚本对象

再次搜索信息中心,这一次搜索 AdminTask。搜索产生 200 多个结果,向下滚动一点儿,找到 ClusterConfigCommands command group for the AdminTask object。选择这一项会提供 createClusterMember 方法的链接,页面下面进一步说明了这个方法。本节帮助您更好地了解如何使用这个方法:

  • 为了创建第一个集群成员,需要:
    • 指定应用服务器模板名称(如果指定模板名称,就不需要查找配置 ID,因为 AdminTask 脚本对象会替您完成这一步)。
    • 或者指定一个现有的应用服务器作为模板。
  • 可以指定:
    • 集群名称。
    • 或要添加成员的集群的配置 ID。
  • 集群成员可以使用不同的 WebSphere Application Server 版本(V6.1 和 V7.0),但是这个场景超出了本文的范围。

为了确保有效地对比不同的方式,要确保在所有方式中执行的实际任务相同,在这里要对比的任务是创建一个可以创建后续集群成员的脚本。这意味着可以忽略与模板有关的参数,只关注与创建后续集群成员有关的参数。

如果想让脚本使用脚本对象方法创建集群成员,就需要了解可用的参数。因为文档在这方面没有解释清楚,应该使用交互式 wsadmin 会话帮助理解。

AdminTask 脚本对象真正强大、有用的特点之一是,几乎所有方法都提供研究参数定义的方法。只需调用方法并指定 -interactive 作为惟一的参数即可。现在用这种技术了解集群配置 ID 与集群名称之间的差异,因为它们都与 createClusterMember() 方法有关。

首先,像清单 2a 的第 1 行那样调用这个方法。(因为行号是在这个 wsadmin 会话期间生成的,所以这里的清单划分为清单 2a 到清单 2e。)

清单 2a. 交互式地创建集群成员
 1|wsadmin>M1 = AdminTask.createClusterMember( '-interactive' )
 2|Create Cluster Member
 3|
 4|Creates a new member of an application server cluster.
 5|
 6|Cluster Object ID: Cluster1(cells/ragdollCell02/clusters/Cluster1|cluster.xml#Server
   Cluster_1261065198076)

交互式调用的结果赋值给一个变量(清单 2a 的第 1 行),因为在以交互方式指定参数时,产生的 AdminTask.createClusterMember() 调用就是要执行的实际调用,其中包含最终指定的所有参数。这个调用的结果是新创建的集群成员的配置 ID。

在第 6 行上,AdminTask 脚本对象提示输入信息,指出它需要 Cluster Object ID 的值(即要添加新成员的集群的配置 ID)。因为我们希望了解当指定配置 ID 时这个命令是什么样的,所以复制并粘贴集群的完整配置 ID 以响应提示。(如果指定无效或未知的配置 ID,就会发生异常,控制会返回到 wsadmin> 提示。AdminTask 交互模式不支持或允许使用变量名,所以必须像这里一样显式地指定配置 ID 的值。)

继续这个交互式会话(见清单 2b),提示输入要创建成员的集群的名称。因为已经使用配置 ID 指定了集群,所以不需要指定集群名称。因此,只需按 Enter(清单 2b,第 1 行)。

清单 2b. 交互式地创建集群成员
 1|Cluster Name (clusterName):
 2|Create Cluster Member
 3|
 4|Creates a new member of an application server cluster.
 5|
 6|-> *1. Member Configuration (memberConfig)
 7|    2. First Member Configuration (firstMember)
 8|    3. Promote Proxy Server Settings To Cluster (promoteProxyServer)
 9|    4. Configure the event service during cluster member creation. (eventServiceConfig)
10|
11|S (Select)
12|N (Next)
13|C (Cancel)
14|H (Help)
15|
16|
17|Select [S, N, C, H]: [S]

这时会以子菜单的形式显示可用的步骤(第 6-9 行)、可用的输入选项(第 11-14 行)和要求输入的命令提示(第 17 行,默认选择显示在方括号中)。

步骤 1 旁边的星号 * 表示需要输入。注意,默认命令是 S(表示选择)。如果按 Enter 或输入字母 S 并按 Enter,就会执行指定的步骤,会看到清单 2c 所示的信息。

清单 2c. 交互式地创建集群成员
 1|Member Configuration (memberConfig)
 2|
 3|*Node Name (memberNode):
 4|*Member Name (memberName):
 5|Member Weight (memberWeight):
 6|Member UUID (memberUUID):
 7|Generate Unique HTTP Ports (genUniquePorts): [true]
 8|enable data replication (replicatorEntry): [false]
 9|Specific short name of cluster member (specificShortName):
10|
11|
12|Select [C (Cancel), E (Edit)]: [E]

在清单 2c 中,第 1 行显示所选步骤的说明和步骤名称。第 3-9 行显示当前步骤属性的当前值。同样,必需的值前面有星号。因为还没有定义一些必需属性的值,默认命令是 E (Edit)(第 12 行)。如果在这里按 Enter,会提示输入缺少的必需值。清单 2d 显示这些提示和提供的响应(第 1-8 行)。在这里,只输入必需属性的值(第 1 和 2 行)。

清单 2d. 交互式地创建集群成员
 1|*Node Name (memberNode): ragdollNode03
 2|*Member Name (memberName): Member2
 3|Member Weight (memberWeight):
 4|Member UUID (memberUUID):
 5|Generate Unique HTTP Ports (genUniquePorts): [true]
 6|enable data replication (replicatorEntry): [false]
 7|Specific short name of cluster member (specificShortName):
 8|Create Cluster Member
 9|
10|Creates a new member of an application server cluster.
11|
12|    1. Member Configuration (memberConfig)
13|->  2. First Member Configuration (firstMember)
14|    3. Promote Proxy Server Settings To Cluster (promoteProxyServer)
15|    4. Configure the event service during cluster member creation. (eventServiceConfig)
16|
17|S (Select)
18|N (Next)
19|P (Previous)
20|F (Finish)
21|C (Cancel)
22|H (Help)
23|
24|
25|Select [S, N, P, F, C, H]: [F]

输入最后的响应之后,再次显示 Create Cluster Member 子菜单,这一次箭头 (->) 指向下一步;在这里是步骤 2(第 13 行)。一定要注意,因为已经提供了所有必需的属性值,现在出现了一个新命令 F (Finish),它也是默认命令(第 25 行)。

选择 Finish 命令就到了交互式方法的最后阶段,最终显示生成的命令。结果见清单 2e。

清单 2e. 生成的 createClusterMember 命令
WASX7278I: Generated command line: AdminTask.createClusterMember('Cluster1(cells/ragdoll
Cell02/clusters/Cluster1|cluster.xml#ServerCluster_1261065198076)', '[-memberConfig
[-memberNode ragdollNode03 -memberName Member2 -genUniquePorts true -replicatorEntry
false]]')

这是一个相当长的命令,为了便于解释,把它拆分开。这个命令的基本形式是:

AdminTask.createClusterMember( TargetObject, Parameters )

您肯定可以看出 TargetObject 是要添加新成员的目标集群的配置 ID。Parameters 值比较复杂,它是以下形式的字符串:

‘[-memberConfig [values]]’

您可能记得在前面见过 memberConfig;它出现在清单 2b(第 6 行)、清单 2c(第 1 行)和信息中心文档的 Steps 部分中。现在可以把文档与交互式 createClusterMember 会话联系起来,了解各个部分如何组合在一起。还可以看到生成的命令包含一些默认值(例如 genUniquePorts 和 replicatorEntry)。因为它们是默认的,可以以后再决定脚本是否需要提供这些值。

现在,已经使用 createClusterMember() 方法的交互形式了解了在使用集群配置 ID 时这个命令究竟是什么样的。可以用同样的方法了解在使用 clusterName 参数时命令是什么样的。只需使用 AdminConfig.reset() 方法删除刚才创建的集群成员,再次运行 AdminTask.createClusterMember( '-interactive' ) 方法,但是这一次指定 clusterName 参数而不是集群配置 ID。这么做的结果见清单 3。

清单 3. 另一个等效的 createClusterMember 命令
WASX7278I: Generated command line: AdminTask.createClusterMember('[-clusterName Cluster1
-memberConfig [-memberNode ragdollNode03 -memberName Member1 -genUniquePorts true
-replicatorEntry false]]')

这个命令的基本形式是:

AdminTask.createClusterMember( Parameters )

Parameters 参数的形式是:

‘[-clusterName value -memberConfig [values]]’

实际上,-memberConfig 值与前面看到的相同。这可能有助于理解在线文档中需要指定 -clusterName 参数的地方。(对 –clusterName 参数的说明指出,应该指定 TargetObject 或 –clusterName 值。如果同时提供这两者,会抛出异常,不执行命令。)

以上练习帮助您了解了 createClusterMember() 方法、它的交互式执行和支持它的文档。可以使用相同的技术创建第二个集群成员;与刚才运行的命令的惟一差异是要创建的成员的名称。

现在,您了解了在使用 AdminTask.createClusterMember() 方法创建后续集群成员时脚本需要处理的参数:

  • 要修改的集群的名称。
  • 要创建成员的节点的名称。
  • 要创建的成员的名称。

根据本文的意图,为了保持简单,所有其他参数都接受默认值。

您现在应该相当好地理解了使用这种方式创建脚本需要哪些步骤。

使用脚本库

完成创建集群成员这个任务的第三种方式是,使用 AdminClusterManagement 脚本库中的 createClusterMember 方法。因为 WebSphere Application Server V7 中已经有这些脚本库,这种方式可以以最小的工作量满足您的需求。我们看看这种方式是否有效。

在信息中心搜索 AdminClusterManagement,会找到 Jython script library: Cluster configuration scripts using wsadmin scripting,它描述库模块中的一些方法,包括 createClusterMember 方法,正好提供我们要找的信息。

为了测试这种方式,需要一个部署管理器配置,它应该有至少一个集群,集群有至少一个成员。因为这个配置已经有了,我们可以试试这种方式。清单 4 显示用来测试这种方式的交互式 wsadmin 会话,表 3 详细说明这些步骤。

清单 4. 对脚本库方法进行交互式测试
 1|C:\IBM\WebSphere\AppServer70\bin>wsadmin –lang jython
 2|WASX7209I: Connected to process "dmgr" on node ragweedCellManager02 using SOAP
 3| connector;  The type of process is: DeploymentManager
 4|WASX7031I: For help, enter: "print Help.help()"
 5|wsadmin>cluster, node, member = 'Cluster1', 'ragweedNode03', 'Member2'
 6|wsadmin>AdminClusterManagement.createClusterMember( cluster, node, member )
 7|----------------------------------------------------------------------
 8| AdminClusterManagement:         Create a new cluster server member
 9| Cluster name:                   Cluster1
10| Member node name:               ragweedNode03
11| New member name:                Member2
12| Usage: AdminClusterManagement.createClusterMember("Cluster1", "ragweedNode03", ...
13| Return: The configuration ID of the new cluster member.
14|-----------------------------------------------------------------------
15|
16|
17|'Member2(cells/ragweedCell02/clusters/Cluster1|cluster.xml#ClusterMember_...)'
18|wsadmin>
表 3. 交互式 wsadmin 会话说明
行号说明
1启动 wsadmin 并指定语言是 Jython。
2-4wsadmin 生成的信息说明已经成功地连接到部署管理器。
5局部变量赋值,让下一行不至于太长。
6调用 createClusterMember() 方法以创建新成员。
7-16方法生成的信息详细说明正在做什么。(这里的第 12 行已经截短。)
17createClusterMember() 方法调用返回的结果(已经截短)。

这个方法既有优点,也有缺点:

  • 最主要的优点是接口简单。只使用三个参数,可以快速轻松地为现有的集群创建新成员。实际上,这些参数与前面看到的 AdminTask.createClusterMember() 方法需要的参数相同。
  • 缺点是生成很多不必要的信息(清单 4 的第 7-16 行),会让人分心或厌烦。尽管这在脚本开发期间可能是可以接受的,但是没有禁止生成这些行的简便方法。如果这个缺点让您不愿意经常使用这种方法,可以考虑使用的另一种方法是使用源代码,这是脚本库的优点之一。

使用源代码

找到特定方法的源代码相当容易。在使用库模块时,可以在命令中添加 __file__ 属性,这会查明这个库是从哪个文件装载的。清单 5 给出使用这个属性的示例。

清单 5. 寻找库的源代码文件
 1|...>wsadmin -lang jython -conntype none -c "print AdminClusterManagement.__file__"
 2|WASX7357I: By request, this scripting client is not connected to any server process.
 3|Certain configuration and application operations will be available in local mode.
 4|C:\IBM\WebSphere\AppServer70\scriptLibraries\servers\V70\AdminClusterManagement.py

当然,不应该直接修改这些脚本库文件。应该复制希望查看的库文件,然后只编辑自己的拷贝。

通过研究脚本源代码,可以学到许多东西,比如:

  • 如何检查参数值。
  • 可以使用哪些 WebSphere Application Server 脚本对象方法调用。
  • 如何向这些脚本对象方法提供参数。
  • 如何建立错误处理器。

检查参数

仔细看看 AdminClusterManagement.createClusterMember() 方法,可以了解如何检查参数值。例如,没有为前三个参数(clusterName、nodeName 和 newMember)提供默认值。因此,在调用这个函数时必须指定这些参数的值。在查看代码时很容易看出这一点,因为方法信息部分(显示方法及其调用方式的相关信息)说明了这一要求,而且代码检查这些参数的值是否包含空字符串。清单 6 给出部分代码。(提供给 AdminUtilities._formatNLS() 方法的具体参数值只用来识别哪个参数值出现了错误,对于理解参数检查方法并不是必需的。)

清单 6. 参数检查
 1|# check  the required arguments
 2|if (clusterName == ""):
 3|  raise AttributeError(AdminUtilities._formatNLS(...))
 4|
 5|if (nodeName == ""):
 6|  raise AttributeError(AdminUtilities._formatNLS(...))
 7|
 8|if (newMember == ""):
 9|  raise AttributeError(AdminUtilities._formatNLS(...))
10|
11|# check if cluster exists
12|cluster = AdminConfig.getid("/ServerCluster:" +clusterName+"/")
13|if (len(cluster) == 0):
14|  raise AttributeError(AdminUtilities._formatNLS(...))
15|#endIf
16|
17|# check if node exists
18|node = AdminConfig.getid("/Node:"+nodeName+"/")
19|if (len(node) == 0):
20|  raise AttributeError(AdminUtilities._formatNLS(...))
21|#endIf

这些检查对于您的环境是否必要(或足够)?这取决于您的需求和需要的检查彻底程度;例如,可能需要检查以下方面:

  • 值为 None。
  • 数据类型不是字符串。
  • 只包含空白的值。

当出现参数错误时,脚本库模块抛出 ScriptLibraryException。如果不希望发生这种情况,就必须决定当探测到错误时希望怎么处理。

代码判断指定的值是否有效。但是,可以对清单 6 所示的代码做一些改进。例如,第 2 行检查提供的是否是空字符串。如果不是空字符串,第 12 和 13 行检查指定的集群是否存在。当指定的 clusterName 包含一个空格时,这段代码会出问题;第 2 行的空字符串检查会返回 false,调用 AdminConfig.getid( "/ServerCluster: /" ) 方法的结果是所有集群配置 ID 的列表。第 13 行中的检查也无法探测到错误,因为调用的结果不是空字符串。

发出调用

完成参数检查之后,就要实际调用 AdminTask.createClusterMember() 方法了。由于前面研究过这个调用,应该很容易理解这个语句本身:

clusterMember = AdminTask.createClusterMember(['-clusterName', clusterName, '-memberConfig', ['-memberNode', nodeName, '-memberName', newMember]])

Jython wsadmin 脚本对象方法可以传递字符串列表(这里看到的情况),也可以传递列表的串(前面看到的情况)。区别在于语法。如果仔细看看上面这个语句,会看出所有值都是直接字符串(由单引号包围)或变量名,它们都由逗号分隔。因此,在希望使用变量值的地方,可以使用变量名(比如 clusterName、nodeName 和 newMember)。

Parameters = '[-clusterName ' + clusterName + ' –memberConfig [-memberNode ' + nodeName + ' –memberName ' + newMember + ']]’

如果不喜欢使用这么长的字符串连接语句,另一种方法是使用字符串格式操作符,可以使用下面这样的表达式:

FormatString % ( values )

字符串格式操作涉及处理 FormatString、寻找格式定义序列和使用这些序列对右边的操作数中的相关数据应用格式。对于格式字符串中的任何其他文本,数据按原样复制到结果字符串中。对于这个简单的示例,可以在 FormatString 中使用格式定义序列 %s,表示应该进行字符串替换。这些字符被替换为元组(包围在圆括号中并由逗号分隔的有序的值序列)中下一个值的字符串表示。如果使用字符串格式化而不是字符串连接来构建相同的参数字符串,那么代码会是下面这样:

Parameters = '[-clusterName %s –memberConfig [-memberNode %s –memberName %s]]’ % ( clusterName, nodeName, newMember )

但是,这些解释都只是背景知识。构建参数字符串的作用是把实际的方法调用简化为下面这样:

memberID = AdminTask.createClusterMember( Parameters )

这是要创建的脚本真正的核心之处。


选择一种技术

既然已经研究了在脚本中执行所需任务的各种方式,现在就该做出决定了。您希望使用哪个方法:

  • AdminConfig.createClusterMember() 方法
  • AdminTask.createClusterMember() 方法
  • AdminClusterManagement.createClusterMember() 脚本库方法

如果选择第一种技术,那么需要向方法提供集群和节点的配置 ID。如果选择第三种技术,那么在调用方法时会显示许多脚本库信息。看起来更好的选择是第二种技术。

一般情况下,在 AdminTask 方法与执行特定操作的其他方法之间做选择时,AdminTask 方法常常更好,因为 AdminTask 方法往往会替脚本编写者完成更多事情,因此脚本更容易编写、更健壮。

既然知道了执行所需操作要调用的实际命令(或方法),现在就可以创建一个脚本来使用此命令。还有一个必须决定的问题:您希望脚本的健壮性如何。换句话说,如果脚本只执行最基本的检查,您是否可以接受?还是必须全面彻底地检查所有东西?答案取决于您能够(或应该)接受多大的风险。

可以检查的项目之一是,是执行脚本还是导入脚本。在 Jython 中,可以通过检查特殊全局变量 __name__ 的值来做出判断。如果是执行脚本,这个变量的值是 __main__;如果是导入脚本,值是导入的文件的名称。因此,要想检查是否执行脚本,脚本文件应该包含与清单 7 相似的简单测试。

清单 7. 检查是执行脚本还是导入脚本
 1|if ( __name__ == '__main__' ) :
 2|  # The script was executed
 3|else :
 4|  # The script was imported

这里采用的方法是逐步构建脚本,所以需要决定脚本接下来应该做什么。在清单 7 所示的代码中添加以下内容:

  • 描述此脚本的注释块(序言)。
  • 一个空的函数,它作为实际执行所需工作的代码的占位代码。
  • Usage() 函数,应该首先编写这个函数。

结果见清单 8。

清单 8. 第一次迭代
 1|#---------------------------------------------------------------------
 2|#       Name: createClusterMember.01.py
 3|#       Role: Example script, created from scratch.
 4|#     Author: Robert A. (Bob) Gibson
 5|#  Iteration: 1
 6|# What's new? Verify that the script was executed, not imported, and
 7|#             start populating the Usage information
 8|#---------------------------------------------------------------------
 9|import sys
10|
11|#---------------------------------------------------------------------
12|# Name: createClusterMember()
13|# Role: Placeholder for routine to perform the desired action
14|#---------------------------------------------------------------------
15|def createClusterMember() :
16|  print 'createClusterMember() - iteration 1'
17|
18|#---------------------------------------------------------------------
19|# Name: Usage()
20|# Role: Routine used to provide user with information necessary to
21|#       use the script.
22|#---------------------------------------------------------------------
23|def Usage( cmdName = None ) :
24|  if not cmdName :
25|    cmdName = 'createClusterMember'
26|
27|  print '''
28|Command: %(cmdName)s\n
29|Purpose: wsadmin script used to create an additional member to an
30|         existing cluster.\n
31|  Usage: %(cmdName)s [options]\n
32|Example: ./wsadmin.sh -lang jython -f %(cmdName)s.py ...''' % locals();
33|
34|#---------------------------------------------------------------------
35|# This is the point at which execution begins
36|#---------------------------------------------------------------------
37|if ( __name__ == '__main__' ) :
38|  createClusterMember();
39|else :
40|  print 'Error: this script must be executed, not imported.';
41|  Usage(__name__);

保存脚本以便测试它;把它命名为 createClusterMember.py。(UNIX® 系统有符号链接机制,所以文件名实际上可以指向当前的迭代;例如 createClusterMember.01.py。)

测试见清单 9。测试表明脚本成功地判断出是执行还是导入,还显示说明一切正常的消息(第 3 行)或用法信息(第 11-18 行)。

清单 9. 测试第一次迭代
 1|[root@ragdoll bin]# ./wsadmin.sh -lang jython -f createClusterMember.py
 2|WASX7209I: Connected to process "dmgr" ...
 3|createClusterMember() - iteration 1
 4|
 5|[root@ragdoll bin]# ./wsadmin.sh -lang jython
 6|WASX7209I: Connected to process "dmgr" ...
 7|
 8|wsadmin>import createClusterMember
 9|Error: this script must be executed, not imported.
10|
11|Command: createClusterMember
12|
13|Purpose: wsadmin script used to create an additional member to an
14|         existing cluster.
15|
16|  Usage: createClusterMember [optios]
17|
18|Example: ./wsadmin.sh -lang jython -f createClusterMember.py ...
19|wsadmin>

处理命令行参数和选项

在编写脚本时,比较有难度的方面之一是处理命令行参数。一种常用的技术是让命令行参数依赖于位置。但是,这意味着用户需要知道第一个参数应该代表 clusterName,第二个参数应该代表 nodeName,第三个参数应该代表要创建的成员的名称,以此类推。

Jython 的优点之一是,它附带的库例程可以简化脚本编程,让脚本更好、更便于用户使用。这种库之一是 getopt 库,它基于很久以前让 C 语言程序轻松地处理命令行参数的技术。

要想使用 getopt 库,需要做几件事。首先,必须为每个参数选择短形式参数字母长形式参数名。如果使用短形式(单个字母)的选项,可以这样执行脚本:

... –f createCluseterMember.py –X clusterName –Y nodeName –Z memberName

在上面的命令中,X、Y 和 Z 作为占位符。指定方便的字母有助于识别命令中的参数。例如,-c 代表 clusterName,-n 代表 nodeName,-m 代表 memberName。但是,在这里不能使用 -c,因为这是为 wsadmin 实用程序保留的命令行选项,脚本不能使用它。可以使用 -L 作为指定 clusterName 参数值的短形式命令行选项。(使用大写是为了避免与数字 1 混淆。)

接下来,需要让 getopt 实用程序例程寻找这些选项。步骤如下:

  1. 导入 getopt 库模块。
  2. 调用 getopt.getopt() 函数并把必需的参数传递给它。

getopt() 函数需要三个参数:

  • 用户指定的命令行参数的列表。
  • 指定短形式选项的字符串。
  • 包含长形式选项的列表。

短形式字符串应该包含每个有效的选项字母,每个字母后面是一个冒号 (:),这表示命令行选项后面应该是一个值。请记住,getopt 库例程本质上是通用的,而且并非每个命令行选项都有相关联的值。考虑一下某个命令行选项不需要值的情况(例如启用调试的选项)。这可以由 ‘-X’ 这样的选项参数表示。

希望您能够看出它的意义。在这里,决定使用三个短形式选项字母,每个选项后面应该有一个值。下面是我们需要的短形式选项字符串:

shortForm = 'L:m:n:';

在这个示例中,希望让 getopt 函数检查命令行中是否有任何选项字母,而且每个选项应该有一个值。不需要对这些参数出现的次序做特殊限制,也不需要限制每个选项可以出现的次数。稍后讨论如何决定这对于您的脚本是否合理。

现在,考虑长形式命令行选项。每个短形式命令行选项都由单个字符表示,前面加上一个连字符 (-),后面加上可选的冒号 (:),这表示这个命令行选项后面应该有一个值。长形式命令行选项采用相似但略有差异的格式。所有长形式选项都以两个连字符 (-) 开头,由数量可变的字母数字字符表示。在这个示例中,使用下面的长形式选项标识符是合理的:

  • clusterName
  • nodeName
  • memberName

还需要告诉 getopt() 函数每个选项后面应该有一个值。方法是提供一个字符串值列表,每个字符串表示一个选项标识符,后面加上一个等号。这个示例的长形式选项列表如下:

longForm = [ ‘clusterName=’, ‘nodeName=’, ‘memberName=’ ];

另一种更紧凑的表达方式是使用下面这样的赋值语句:

longForm = 'clusterName=,memberName=,nodeName='.split( ',' );

这里的 split() 方法处理包含指定分隔字符(在这里是逗号)的输入字符串(字符串类型),返回字符串的列表。因此,上面两个赋值语句创建相同的字符串值列表。可以选用您更容易阅读和理解的形式。

最后,对 getopt 例程的实际调用如下所示:

getopt.getopt( sys.argv, shortForm, longForm );

这相当简洁,但是这个函数如何告诉您遇到了哪些选项呢?它会返回两个东西:

  • 有效选项的名/值对列表。
  • 其他(未处理的)选项的列表。

清单 10 说明处理函数返回值的代码应该是什么样的。

清单 10. 使用 getopt() 函数
 1|try :
 2|  opts, args = getopt.getopt( sys.argv, shortForm, longForm );
 3|except getopt.GetoptError :
 4|  # code to handle the exception situation
 5|
 6|for name, value in opts :
 7|  # code to handle each value option letter/identifier
 8|
 9|if ( args != [] ) :
10|  # code to handle “extra” stuff on the command line

这看起来相当容易,但是一些地方的代码还没有实现(第 4、7 和 10 行):

  • 如果遇到不认识的选项,或者选项没有包含必需的值,getopt() 函数会抛出异常。异常处理器(第 4 行)中的代码应该显示适当的消息并终止脚本。
  • for 循环(第 7 行)中的代码处理支持的每个选项。如果需要检查命令行选项是否多次出现,应该在这里检查。
  • 在最后一部分中(第 10 行),脚本可以检查并处理命令行中不与任何有效选项相关联的额外信息。

这些信息应该有助于您理解清单 11 中的 parseOpts() 例程。在调用这个例程时,返回一个词典,它反映用户指定的命令行选项。

清单 11. 完整的 parseOpts() 例程
 1|#---------------------------------------------------------------------
 2|#    Name: parseOpts()
 3|#    Role: Process the user specified (command line) options
 4|# Returns: A dictionary containing the user specified values
 5|#---------------------------------------------------------------------
 6|def parseOpts( cmdName ) :
 7|  shortForm = 'L:m:n:';
 8|  longForm  = 'clusterName=,memberName=,nodeName='.split( ',' );
 9|  badOpt    = '%(cmdName)s: Unknown/unrecognized parameter%(plural)s: %(argStr)s';
10|  optErr    = '%(cmdName)s: Error encountered processing: %(argStr)s';
11|
12|  try :
13|    opts, args = getopt.getopt( sys.argv, shortForm, longForm );
14|  except getopt.GetoptError :
15|    argStr = ' '.join( sys.argv );
16|    print optErr % locals();
17|    Usage( cmdName );
18|
19|  #-------------------------------------------------------------------
20|  # Initialize the Opts dictionary using the longForm key identifiers
21|  #-------------------------------------------------------------------
22|  Opts = {};
23|  for name in longForm :
24|    if name[ -1 ] == '=' :
25|      name = name[ :-1 ]
26|    Opts[ name ] = None;
27|
28|  #-------------------------------------------------------------------
29|  # Process the list of options returned by getopt()
30|  #-------------------------------------------------------------------
31|  for opt, val in opts :
32|    if opt in   ( '-L', '--clusterName' ) : Opts[ 'clusterName' ] = val
33|    elif opt in ( '-m', '--memberName' )  : Opts[ 'memberName' ] = val
34|    elif opt in ( '-n', '--nodeName' )    : Opts[ 'nodeName' ] = val
35|
36|  #-------------------------------------------------------------------
37|  # Check for unhandled/unrecognized options
38|  #-------------------------------------------------------------------
39|  if ( args != [] ) :        # If any unhandled parms exist => error
40|    argStr = ' '.join( args );
41|    plural = '';
42|    if ( len( args ) > 1 ) : plural = 's';
43|    print badOpt % locals();
44|    Usage( cmdName );
45| 
46|  #-------------------------------------------------------------------
47|  # Return a dictionary of the user specified command line options
48|  #-------------------------------------------------------------------
49|  return Opts;

“下载” 中的 createClusterMember.02.py 文件包含完整的脚本,其中包含上面的 parseOpts() 例程。


检查和使用命令行选项

现在,有了一个简洁的函数,它检查用户指定的命令行参数并在词典中返回结果。但是,现在用它做什么呢?

首先要理解一点:只有在没有发现错误的情况下,parseOpts() 函数才会返回词典结果;也就是说,parseOpts() 例程对于它检查的条件没有遇到任何问题,但是这不一定意味着确实没有问题。

清单 12 演示这个脚本的第二次迭代如何处理这些参数。

清单 12. 第二次迭代:调用 parseOpts() 例程
 1|missingParms = '%(cmdName)s: Insufficient parameters provided.';
 2|
 3|#-------------------------------------------------------------------
 4|# How many user command line parameters were specified?
 5|#-------------------------------------------------------------------
 6|argc = len( sys.argv );                   # Number of arguments
 7|if ( argc < 3 ) :                         # If too few are present,
 8|  print missingParms % locals();          #   tell the user, and
 9|  Usage( cmdName );                       #   provide the Usage info
10|else :                                    # otherwise
11|  Opts = parseOpts( cmdName );            #   parse the command line

在清单 12 中,只有在指定了三个或更多参数的情况下,才会调用 parseOpts() 例程;getopt() 例程应该处理的最低参数数量是三个:

[ ‘-LclusterName’, ‘-nNodeName’, ‘-mMemberName’ ]

第 8 行中的打印语句也是前面讨论过的字符串格式操作。这里的差异是,格式字符串必须为每个格式定义指定使用哪个词典条目提供替换。因此,包含 %(cmdName)s 的格式字符串会替换为局部变量 cmdName,以字符串形式显示它的值。

这是一种非常强大的习惯用法,Usage() 例程也使用这种方法。例如,如果看一下清单 8 的第 27-32 行,就会找到这样的消息。对于这个示例中的格式字符串,有意思的一点是:同一个局部变量 (cmdName) 在同一个字符串中出现了多次。

可以使用 parseOpts() 例程返回的词典做什么?可以使用相应的索引访问各个词典值,但是把词典值赋值给局部变量会方便得多。对比清单 13 中的两个语句,看看哪种形式更容易理解:

清单 13. 哪种形式更容易阅读和理解?
 1|if  ( not Opts[ ‘clusterName’ ] ) :
 2|  print ‘Required option missing: clusterName’
 3|
 4|if ( not clusterName ) :
 5|  print ‘Required option missing: clusterName’

把 parseOpts() 例程返回的词典转换为相应的局部变量的一种简便方法是,使用一组赋值语句(清单 14)。

清单 14. 词典条目到局部变量的简单转换
 1|clusterName = Opts[ ‘clusterName’ ];
 2|nodeName    = Opts[ ‘nodeName’ ];
 3|memberName  = Opts[ ‘memberName’ ];

如果有效命令行(长形式)选项标识符的数量不大,这种方式很合适。另一种方式是使用词典中的标识符,见清单 15。

清单 15. 词典条目到局部变量的通用转换
 1|#-------------------------------------------------------------------
 2|# Assign values from the user Options dictionary, to make value
 3|# access simplier, and easier.  For example, instead of using:
 4|#   Opts[ 'clusterName' ]
 5|# we will be able to simply use:
 6|#   clusterName
 7|# to access the value.  Additionally, this allows us to make use of
 8|# mapped error messages that reference local variable values.
 9|#-------------------------------------------------------------------
10|for key in Opts.keys() :
11|  cmd = '%s=Opts["%s"]' % ( key, key );
12|  exec( cmd );

这段代码的关键是,创建一个与清单 14 中的赋值语句相似的字符串,用 exec() 例程执行赋值语句,执行实际的变量赋值。

到目前为止,脚本的这一次迭代只是通过显示消息显示用户指定的值。可以使用脚本的这个版本测试和检查命令行处理是否符合期望。


执行所需的操作

余下的工作是添加代码以检查用户指定的值,然后使用这些值执行所需的操作。

如果必需的值之一是 None,就意味着没有指定一个命令行选项。怎么会发生这种情况?parseOpts() 函数中处理命令行参数的代码没有检查重复的选项,所以如果用户指定下面的命令行参数:

--clusterName=C1 –L C2 –L C3

那么 nodeName 和 memberName 选项的值就是 None。可以修改 parseOpts() 函数,让它检查同一个命令行选项多次出现的情况,但是您必须判断添加这种检查有多大价值。

您还可能会考虑检查空的字符串值。用户可以指定包围在双引号中的空值:

./wsadmin.sh -f createClusterMember.py -L " " -n " " -m " "

如果用脚本的第二次迭代进行测试,输出如下:

createClusterMember: --clusterName --nodeName --memberName

因为所有值都是必需的,应该对每个值执行相同的检查。(甚至可以检查指定的 clusterName 和 nodeName 是否已经存在,以及指定的 memberName 是否不存在。这些检查超出了本文的范围,但是如果您觉得值得,以后随时可以添加它们。)

还要认识到一点:可以将检查添加到把 Opts 词典条目转换为局部变量的循环中。清单 16 给出修改后的代码。

清单 16. 检查无效的必需值
 1|badReqdParam = '%(cmdName)s: Invalid required parameter: %(key)s.\n';
 2|
 3|#-------------------------------------------------------------------
 4|# Assign values from the user Options dictionary, to make value
 5|# access simplier, and easier.  For example, instead of using:
 6|#   Opts[ 'clusterName' ]
 7|# we will be able to simply use:
 8|#   clusterName
 9|# to access the value.  Additionally, this allows us to make use of
10|# mapped error messages that reference local variable values.
11|#-------------------------------------------------------------------
12|for key in Opts.keys() :
13|  val = Opts[ key ];
14|  if ( not val ) or ( not val.strip() ) :
15|    print badReqdParam % locals();
16|    Usage();
17|  cmd = '%s=Opts["%s"]' % ( key, key );
18|  exec( cmd );

在清单 16 中,第 1 行创建一个新的错误消息,当遇到 None 值(或空字符串)时显示它。修改的代码在第 13-16 行,这里获取指定的标识符的值。

第 14 行中表达式的第一部分用来检查 None 值。如果变量 val 的值是 None,那么 ( not val ) 的结果是 true,就会显示错误消息。表达式的其余部分用来检查值(去掉头尾的空格之后)是否是空字符串。如果是,显示错误消息,调用 Usage() 例程,然后脚本终止运行。


调用 AdminTask.createClusterMember()

既然已经检查了用户为所有必需参数提供的值,现在只需在 AdminTask.createClusterMember() 方法调用中使用它们。在前面已经见过如何为这个方法构建参数字符串,所以看起来不缺什么了。

实际上,是几乎不缺什么了。要确保脚本能够应对错误;如果方法调用失败,它会抛出异常。清单 17 给出脚本中实际调用 AdmingTask.createClusterMember() 方法的部分。

清单 17. 调用 AdmingTask.createClusterMember()
 1|#-------------------------------------------------------------------
 2|# Call the AdminTask.createClusterMember() method using the command
 3|# line parameter values.
 4|#-------------------------------------------------------------------
 5|parms = '%(cmdName)s --clusterName %(clusterName)s --nodeName %(nodeName)s
   --memberName %(memberName)s';
 6|print parms % locals();
 7|try :
 8|  Parms = '[-clusterName %s -memberConfig [-memberNode %s -memberName %s]]' %
   ( clusterName, nodeName, memberName )
 9|  AdminTask.createClusterMember( Parms );
10|  AdminConfig.save();
11|  print '%(cmdName)s success. Member %(memberName)s created successfully.' % locals();
12|except :
13|  #-----------------------------------------------------------------
14|  # An exception occurred. Convert the exception value to a string
15|  # using the backtic operator, then look for the presence of a
16|  # WebSphere message, which start with 'ADMG'.  If one is found,
17|  # only display the last part of the value string.
18|  #-----------------------------------------------------------------
19|  val = `sys.exc_info()[ 1 ]`;
20|  pos = val.rfind( 'ADMG' )
21|  if pos > -1 :
22|    val = val[ pos: ]
23|  print '%(cmdName)s Error. %(val)s' % locals();

表 4 解释清单 17 中的代码段,帮助您更好地理解代码的作用及其实现方式。

表 4. 对 AdmingTask.createClusterMember() 调用的说明
行号说明
1-4解释要执行的操作的注释块。
5用来显示执行的操作和用户指定的值的消息。
6使用局部变量值显示消息。
7-23在异常处理器(例如 try/except 块)的保护下调用 createClusterMember() 方法。
8用来构建方法参数字符串的格式字符串。
9对 createClusterMember() 方法的实际调用。
10如果 createClusterMember() 成功,需要调用 AdminConfig.save() 以保存修改后的配置。
11输出消息,指出请求的操作成功完成了。
12-18如果出现异常,最可能失败的语句是对 createClusterMember() 例程的调用。
19调用 sys.exc_info() 以查明发生的情况。在这里,只有第二个值(错误值)是真正重要的,所以只保存它。使用斜引号操作符把返回的值转换为字符串。
20调用 rfind() 寻找 ADMG 消息号前缀在字符串中最后出现的位置。
21-22如果存在消息前缀,就丢弃它前面的所有字符,只保留由异常生成的重要错误消息。
23显示错误消息的文本。

结束语

本文讲解了一种通过创建 wsadmin Jython 脚本执行特定 IBM WebSphere Application Server 管理任务的方法;在这里,任务是在现有的集群上创建后续成员。描述了如何使用信息中心文档了解执行任务的各种方式、如何通过对比决定最实用的方式以及分多个阶段逐步构建示例脚本。本文还介绍了常见的问题、最佳实践、如何测试脚本以及如何处理错误。

希望这些信息对您有帮助、有价值。

阅读作者的书:WebSphere Application Server Administration Using Jython
WebSphere Application Server Administration Using Jython

下载

描述名字大小
示例脚本createClusterMember.scripts.zip16 KB

参考资料

学习

获得产品和技术

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=WebSphere
ArticleID=497955
ArticleTitle=从头编写脚本: 为 IBM WebSphere Application Server 创建 Jython 管理脚本
publish-date=06242010