[信息速递] vRealize Orchestrator(7.3)对接iMaster NCE-Fabric开发指导

vRealize Orchestrator(7.3)对接iMaster NCE-Fabric开发指导

0 前置条件

配置身份验证提供程序

VRAVRO环境已部署,并配置了单点登录功能。登录VRO的控制中心,默认端口5480,在控制中心首页点击Configure authentication provider按钮,配置身份验证提供程序,选择vRealize Automation作为身份验证模式,并填写VRA的主机相关信息进行注册。

1-改.PNG

配置数据库

为了保证数据的一致性,需要配置数据库连接对数据进行持久化。这里我们使用VRO内置的数据库实现PostgreSQL,在控制中心首页点击Configuration database按钮,配置数据库实例名称、端口号、用户名及密码(默认为vmware)。

2-改.PNG

导入AC证书

Orchestrator 控制中心(默认端口8283)首页点击certificate按钮,然后点击导入,选择从URL导入,在URL处输入AC的北向URL,导入成功后可以在证书列表中看到AC的证书。

3-改.PNG


验证配置

配置完成后,可在首页点击Verify configuration按钮,跳转至验证配置页面查看当前的各个组件配置信息以及Orchestrator 服务器的服务状态。也可以在此页面进入到Orchestrator的服务管理页面,对Orchestrator服务进行停止和重启操作。

4-改.PNG

1 使用Orchestrator客户端进行开发

Orchestrator控制中心下载Windows版的Windows客户端应用程序,安装后双击打开客户端登录页面,也可以点击控制中心中的Start the Orchestrator client快速启动本地的客户端应用程序。

5-改.PNG

初始化配置

数据库添加及表的设计

a)         前边我们已经使用内嵌的PostgreSQL配置了数据库连接,这里我们只需要运行客户端集成的工作流即可实现数据库的添加以及后续工作流的设计与数据库的交互。

选择Design模式,然后点击最左侧的工作流菜单,在SQL文件夹下展开configuration子文件夹,在Add a database工作流上右键单击,然后选择Start workflow,弹出数据库添加工作流所必填的连接配置参数。

6.png


参数填写完后点击submit提交,运行工作流,添加数据库实例,工作流运行成功后,结束图标会变为绿色失败为灰色,且工作流实例的左侧有一个绿色的。如果运行失败可切到logs标签查看报错堆栈。

7.png


 

8.png


 

b)         数据库添加完成后可以在清单(inventory)中,展开SQL Plug-in可以看到我们添加的数据库。

9.png


c)         数据库表添加

-- -------------------------------
-- Table structure for ac_port
-- -------------------------------
DROP TABLE IF EXISTS "public"."ac_port";
CREATE TABLE "public"."ac_port" (
  "id" int2 NOT NULL,
  "uuid" varchar(128) COLLATE "pg_catalog"."default",
  "name" varchar(128) COLLATE "pg_catalog"."default",
  "description" varchar(255) COLLATE "pg_catalog"."default",
  "type" char(16) COLLATE "pg_catalog"."default",
  "vlan" varchar(8) COLLATE "pg_catalog"."default",
  "mode" char(16) COLLATE "pg_catalog"."default",
  "portName" varchar(128) COLLATE "pg_catalog"."default",
  "deviceId" varchar(128) COLLATE "pg_catalog"."default",
  "time_create" varchar(128) COLLATE "pg_catalog"."default"
);
ALTER TABLE "public"."ac_port" ADD CONSTRAINT "ac_port_pkey" PRIMARY KEY ("id");

CREATE SEQUENCE ac_port_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;
alter table ac_port alter column id set default nextval('ac_port_id_seq');

-- ---------------------------------
-- Table structure for ac_router
-- ---------------------------------

DROP TABLE IF EXISTS "public"."ac_router";
CREATE TABLE "public"."ac_router" (
  "id" int4 NOT NULL,
  "uuid" varchar(128) COLLATE "pg_catalog"."default" NOT NULL,
  "name" varchar(64) COLLATE "pg_catalog"."default" NOT NULL,
  "tenant_name" varchar(64) COLLATE "pg_catalog"."default" NOT NULL,
  "cloud_name" varchar(64) COLLATE "pg_catalog"."default",
  "tenant_id" varchar(128) COLLATE "pg_catalog"."default",
  "logicNetworkId" varchar(128) COLLATE "pg_catalog"."default",
  "producer" varchar(16) COLLATE "pg_catalog"."default",
  "description" varchar(255) COLLATE "pg_catalog"."default",
  "fabricId" varchar(128) COLLATE "pg_catalog"."default",
  "type" varchar(255) COLLATE "pg_catalog"."default",
  "time_create" varchar(255) COLLATE "pg_catalog"."default",
  "fabric_role" varchar(16) COLLATE "pg_catalog"."default"
);
ALTER TABLE "public"."ac_router" ADD CONSTRAINT "ac_router_pkey" PRIMARY KEY ("id");
CREATE SEQUENCE ac_router_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;
 
alter table ac_router alter column id set default nextval('ac_router_id_seq');

-- ------------------------------------------
-- Table structure for ac_router_interface
-- ------------------------------------------

DROP TABLE IF EXISTS "public"."ac_router_interface";
CREATE TABLE "public"."ac_router_interface" (
  "id" int2 NOT NULL,
  "uuid" varchar(128) COLLATE "pg_catalog"."default" NOT NULL,
  "name" varchar(32) COLLATE "pg_catalog"."default" NOT NULL,
  "logicRouterId" varchar(128) COLLATE "pg_catalog"."default",
  "interfaceType" varchar(16) COLLATE "pg_catalog"."default" NOT NULL,
  "logicSwitchId" varchar(128) COLLATE "pg_catalog"."default",
  "producer" varchar(16) COLLATE "pg_catalog"."default",
  "time_create" varchar(64) COLLATE "pg_catalog"."default"
);
ALTER TABLE "public"."ac_router_interface" ADD CONSTRAINT "ac_router_interface_pkey" PRIMARY KEY ("id");
 
CREATE SEQUENCE ac_router_interface_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;
 
alter table ac_router_interface alter column id set default nextval('ac_router_interface_id_seq');

-- ---------------------------------
-- Table structure for ac_subnet
-- ---------------------------------
DROP TABLE IF EXISTS "public"."ac_subnet";
CREATE TABLE "public"."ac_subnet" (
  "id" int2 NOT NULL,
  "uuid" varchar(128) COLLATE "pg_catalog"."default" NOT NULL,
  "logicRouterId" varchar(128) COLLATE "pg_catalog"."default" NOT NULL,
  "cidr" varchar(64) COLLATE "pg_catalog"."default",
  "gatewayIp" varchar(32) COLLATE "pg_catalog"."default",
  "producer" varchar(16) COLLATE "pg_catalog"."default",
  "time_create" varchar(64) COLLATE "pg_catalog"."default",
  "name" varchar(32) COLLATE "pg_catalog"."default"
)
;

-- ----------------------------------------------
-- Primary Key structure for table ac_subnet
-- ----------------------------------------------
ALTER TABLE "public"."ac_subnet" ADD CONSTRAINT "ac_subnet_pkey" PRIMARY KEY ("id");

CREATE SEQUENCE ac_subnet_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;

alter table ac_subnet alter column id set default nextval('ac_subnet_id_seq');

-- --------------------------------
-- Table structure for ac_switch
-- --------------------------------
DROP TABLE IF EXISTS "public"."ac_switch";
CREATE TABLE "public"."ac_switch" (
  "id" int2 NOT NULL,
  "uuid" varchar(128) COLLATE "pg_catalog"."default" NOT NULL,
  "name" varchar(64) COLLATE "pg_catalog"."default",
  "description" varchar(255) COLLATE "pg_catalog"."default",
  "tenantId" varchar(128) COLLATE "pg_catalog"."default" NOT NULL,
  "logicRouterId" varchar(128) COLLATE "pg_catalog"."default" NOT NULL,
  "logicNetworkId" varchar(128) COLLATE "pg_catalog"."default" NOT NULL,
  "bd" varchar(16) COLLATE "pg_catalog"."default" NOT NULL,
  "vni" varchar(16) COLLATE "pg_catalog"."default" NOT NULL,
  "producer" varchar(16) COLLATE "pg_catalog"."default" NOT NULL,
  "time_create" varchar(128) COLLATE "pg_catalog"."default"
)
;

-- ----------------------------------------------
-- Primary Key structure for table ac_switch
-- ----------------------------------------------
ALTER TABLE "public"."ac_switch" ADD CONSTRAINT "ac_switch_pkey" PRIMARY KEY ("id");

CREATE SEQUENCE ac_switch_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;

alter table ac_switch alter column id set default nextval('ac_switch_id_seq');

HTTP-REST主机添加

l  使用内置URL对象不能满足HTTP REST的所有使用场景,所以我们在这里使用HTTP-REST插件发起对控制器API的调用。

Library文件夹下找到HTTP-REST文件件,展开configuration子文件夹选择Add a REST host工作流,右键单机运行工作流添加REST host主机。

 12.png 

l  添加完成后可在清单中查看所添加的HTTP-REST HOST主机。

13.png


l  HTTP-REST插件API的用法

使用RESTHost对象可以创建http request请求,设置请求头、bodytoken信息等,点击左下角的搜索按钮可搜索所有的API

14.png

var request = restHost.createRequest(httpMethod, operationUrl, JSON.stringify(jsonPort));
request.contentType="application/json";
request.setHeader("X-ACCESS-TOKEN", TOKEN);

工作流设计

这里我们以创建logicport的工作流设计为例。

LogicPortCreateTask工作流的流程:创建流程共有五个步骤,首先查询数据库中是否存在同名的port,如果不存在查询数据库中是否存在与之关联的logicswitch,如果不存在获取Token,发送HTTP Post请求到AC创建logicport,创建成功后会同步保存port相关信息到本地库。任何一个步骤操作失败都会抛出自定义异常信息,工作流结束运行。

运行结束后可以切到logs查看运行日志。

一个完整的工作流除了必不可少的开始和结束图标,还包括必要的脚本组件、决策组件、异常组件以及嵌入的工作流组件。如果多个组件抛出相同的异常信息,可以多个组件共用一个异常组件。

15.png


 16.png


工作流概览

点击编辑按钮进入流程编辑设计模式。

17.png


General页签添加整个工作流公用的全局变量信息,可以添加变量值的初始值,在工作流中运行过程可以改变其值进行传递,通常我们把一些不变的量设置为全局属性,如REST:RESTHost对象(HTTP-REST),获取TOKENURL用户名和密码信息、数据库的连接信息及sql模板和异常信息等。

选择添加Attributes,选择类型后添加value信息。

Inputs页签的Parameters信息为工作流运行时需要输入的参数,可以设置为可选和必选。

Outputs页签的Parameters信息为整个工作流运行结束后输出的参数信息。

Schema页签可以查看整个流程信息,单击每个组件可以对每个组件的输入输出参数、绑定信息及脚本进行更改。

Presentation页签可以对输入参数进行分组、分页的顺序调整,并进行属性设置。

每次对schema做了更改后点击右下角的save and close按钮保存当前状态,保存可以增加版本号以区分。

18.png

点击Run/Debug按钮可以运行工作流,效果如下图

19.png

查询port(脚本组件)

查询port这个组件的作用是校验本地库中是否存在相同的port,如果存在抛出自定义异常结束工作流,否则进行下一步流程。

组件的输入参数中需要绑定输入参数switchName以及全局属性中的数据库连接参数和查询模板参数等。输出参数为数据库的查询结果portDBName,作为决策组件的输入参数决策工作流是否要继续运行。

20.png


输入输出参数绑定信息可以在ViSual Binding页签中查看

21.png

var main = new JDBCConnection();
var con;
var routers = [];
try  {
	con = main.getConnection(psqlURL, psqlUser, psqlPassword);
	System.log("Connection to database successful");
	var stat = con.createStatement();
	var queryString = queryPortNameStatement + " where name='"+ name + "'";
	System.log(queryString);
	var rs = stat.executeQuery(queryString);
	var uuid = "uuid";
	var _name = "name";

	if (rs.next()){
	  var portUUID = rs.getString(uuid);
	    var portName = rs.getString(_name);
	    portDBName = portName;
	    System.error("Port " + portName + " already exists!"); 
	} 
	rs.close();
	stat.close();
} catch(ex)  {
	throw "Connection to database failed (Reason: " + ex + ")";
} finally {
	if (con) {
	  con.close();
	}
}

查询switch(工作流组件)

这个组件是一个独立的工作流,作用是查询数据库中是否存在创建port工作流中输入的switch,如果存在抛出自定义异常结束工作流,不存在则进入下一步流程。
点击该组件,可以看到组件输入参数绑定了工作流输入参数switchName,输出为组件的查询输出参数信息switchUUID

var main = new JDBCConnection();
var con;
var switches = [];
try  {
	con = main.getConnection( psqlURL, psqlUser, psqlPassword );
	System.log( "Connection to database successful" );
	var stat = con.createStatement();
	var queryString = sqlStatement + " where name='"+ switchName + "'";
	System.log(queryString);
	var rs = stat.executeQuery( queryString );
	var uuid = "uuid";
	var _name = "name";
	var i = 0;
	System.log(rs);
	while (rs.next()){
		var _switchUUID = rs.getString(uuid);
		var _switchName = rs.getString(_name);
		System.log(_switchUUID+"====="+_switchName);
		if (_switchName ==  switchName) {
		    switches.push(_switchName);
		    switchUUID = _switchUUID;
		    switches = _switchUUID;
		    break;
		}
		i++;
	}
	
	System.log("query uuid from db:" + switchUUID);
	if ( i == 0 )  {
	    System.log( "No rows or first row!" );
	}
	if (switchUUID == null) {
	    System.log("no switch exists!");
	}
	rs.close();
	stat.close();
} catch(ex)  {
	throw "Connection to database failed (Reason: " + ex + ")";
} finally {
	if (con) {
		con.close();
	}
}


获取令牌token(脚本组件)

三方对接时,访问控制器的openAPI需要携带接口鉴权token,获取tokenURLhttps://{ip}:{port}/controller/v2/tokenstoken获取失败结束工作流,获取成功解析出token_id,token_id传给下一个组件,继续下一步流程。

22.png

var jsonObj = '{"userName":"' + userName + '", "password":"'+ password + '"}'
var urlObject = new URL(authUrl + "/controller/v2/tokens");
urlObject.contentType = "application/json";
var resultString = urlObject.postContent(jsonObj) ;
result = JSON.parse(resultString).data.token_id
resmsg = JSON.parse(resultString).errmsg
TOKEN = result;
System.log(result);

发起创建port请求(脚本组件)

该组件主要是组装port数据、带上接口鉴权token_id,用HTTP-REST插件创建POST请求调用AC的北向API/controller/dc/v3/logicnetwork/ports,发起创建port 的流程,创建成功后将port参数信息传递到下一个组件进行存库操作。创建失败,结束工作流,在logs标签页可查看控制器侧返回的错误信息。

23.png

var operationUrl =  "/controller/dc/v3/logicnetwork/ports";
var UUID = System.nextUUID();
System.log("Host: " + restHost + ", operation: " + operationUrl);
var jsonPort = {
	"port" : [
		{
		"id" : UUID.toUpperCase(),
		"name" : name,
		"description" : description,
		"logicSwitchId" : logicSwitchId,
		"accessInfo" : {
		    "mode" : mode,
		    "type" : type,
		    "vlan" : vlan,
		    "location" : [{
			"deviceId" : deviceId,
			"portName" : portName
		    }],
		},
		"additional" : {
		    "producer" : producer
		}
		}
	] 
};
pName = name;
pDescription = description;
pMode = mode;
pType = type;
pVlan = vlan;
pPortName = portName;
logicPortId = UUID.toUpperCase();
logic_port_uuid = logicPortId;

var request = restHost.createRequest(httpMethod, operationUrl, JSON.stringify(jsonPort));
request.contentType="application/json";
request.setHeader("X-ACCESS-TOKEN", TOKEN);

var response = request.execute();
port_create_res_code = response.statusCode;
var jsonString = response.contentAsString;
if (jsonString.length > 0) {
	var obj = JSON.parse(jsonString);
	error_msg = obj.errors.error[0]["error-message"]
}
result = new Properties();
result.put("statusCode", response.statusCode);
result.put("contentLength", response.contentLength);
result.put("headers", response.getAllHeaders());
result.put("contentAsString", response.contentAsString);


数据持久化操作(脚本组件)

该组件主要对创建成功后的port数据持久化到本地的PostgreSQL数据库中,数据库的连接地址为:jdbc:postgresql://localhost:5432/vmware,持久化数据操作时要注意在PostgreSQL的约束下,如果表的列存在大写字母,需要将字段名用双引号引起来,如

insert into ac_port(uuid,name,description,type,vlan,mode,"portName","deviceId",time_create) values (?,?,?,?,?,?,?,?,?),
根据持久化操作的结果返回决定是停止工作流还是继续进行下一个流程,在这个工作流中存库成功即走向end,失败则抛出自定义异常。

24.png

var main = new JDBCConnection();
var con;
try  {
	con = main.getConnection( psqlURL, psqlUser, psqlPassword );
	System.log( "Connection to database successful" );
	
	var stat = con.prepareStatement( portSqlStatement );
	var date = new Date();
	var timestamp = date.toGMTString();
	stat.setString( 1, logicPortId );
	stat.setString( 2, pName );
	stat.setString( 3, pDescription );
	stat.setString( 4, pType );
	stat.setString( 5, pVlan );
	stat.setString( 6, pMode );
	stat.setString( 7, pPortName );
	stat.setString( 8, deviceId );
	stat.setString( 9, timestamp );
	var result = stat.executeUpdate();
	sqlResultCode = result;
	stat.close();
	if ( result == 1 )  {
	    System.log( "Row ('logicPortId="+logicPortId+"', '"+timestamp+"') inserted in table successfully" );
	} else  {
	    System.error( "Row insertion in table failed" );
	}
	
}
catch( ex )  {
    throw "Connection to database failed (Reason: " + ex + ")";
} finally {
    if (con) {
	con.close();
    }
}


自定义抛出异常(异常组件)

这个组件主要是帮助我们定位在工作流的执行过程中哪一个环节出现问题,同时阻止工作流进行下一个流程。还可以在组件的属性中设置错误码。

25.png

下图从异常堆栈中可以看到在工作流执行到创建port任务的时候打印了控制器返回的异常信息以及抛出了我们自定义的异常信息。

item: 'logicPortCreateTask/item8', state: 'failed', business state: 'ERROR! Port create failure!', exception: '902'

item8唯一确定了工作流中哪个组件,ERROR! Port create failure! 是我们自定义的异常信息,902是我们自定义的错误码。

26.png


工作流运行

工作流设计完成后,选中工作流点击Run/Debug按钮即可运行工作流,工作流的状态是有记忆的,每运行一次工作流,不管成功或者失败都会生成一个taskId标识的任务存储到vmware库中,点击该任务即可查看本次运行的状态、起止时间以及log日志。在schema中会标识当前任务的结束位置。下图中PortCreateTask组件为红色,即表示工作流本次运行到该组件时报错,导致工作流执行失败。

27.png


 

下图中可以看到工作流运行成功,port创建成功。

28.png


 工作流的导入导出

在工作空间文件夹上,单击右键即可完成工作流的创建、移动和导入工作流,导入的工作流的后缀xxx.workflow。右键单击已经创建好的工作流,选择export workflow即可保存工作流文件到本地。

 29.PNG