從頭編寫指令碼: 為 IBM WebSphere Application Server 建立 Jython 管理指令碼

CloudSpace發表於2010-08-30
Bob Gibson, 諮詢軟體工程師, IBM
簡介: 從頭開發 IBM® WebSphere® Application Server 管理指令碼並不困難,但是很難找到指導您逐步完成這個過程的實用資訊。本文幫助您實現這個目標,首先討論希望指令碼完成什麼任務,然後帶領您完成迭代式開發步驟,最終完成一個完整的示例指令碼,包括註釋、用法資訊和命令列引數處理。

簡介

早在撰寫 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-4 wsadmin 生成的資訊說明已經成功地連線到部署管理器。
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-4 wsadmin 生成的資訊說明已經成功地連線到部署管理器。
5 區域性變數賦值,讓下一行不至於太長。
6 呼叫 createClusterMember() 方法以建立新成員。
7-16 方法生成的資訊詳細說明正在做什麼。(這裡的第 12 行已經截短。)
17 createClusterMember() 方法呼叫返回的結果(已經截短)。

這個方法既有優點,也有缺點:

  • 最主要的優點是介面簡單。只使用三個引數,可以快速輕鬆地為現有的叢集建立新成員。實際上,這些引數與前面看到的 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|  pts = {};
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|  pts = 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() 方法呼叫中使用它們。在前面已經見過如何為這個方法構建引數字串,所以看起來不缺什麼了。

實際上,是幾乎不缺什麼了。要確保指令碼能夠應對錯誤;如果方法呼叫失敗,它會丟擲異常。清單 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 管理任務的方法;在這裡,任務是在現有的叢集上建立後續成員。描述瞭如何使用資訊中心文件瞭解執行任務的各種方式、如何通過對比決定最實用的方式以及分多個階段逐步構建示例指令碼。本文還介紹了常見的問題、最佳實踐、如何測試指令碼以及如何處理錯誤。

希望這些資訊對您有幫助、有價值。

原文連結:http://www.ibm.com/developerworks/cn/websphere/library/techarticles/1004_gibson/1004_gibson.html

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/14789789/viewspace-672142/,如需轉載,請註明出處,否則將追究法律責任。

相關文章