上一篇講了ODL的基本認識和安裝,這一篇主要講講ODL內部的使用,ODL控制器是MD-SAL機制,MD代表服務是以模型驅動,的,這個模型簡單來說就是yang模型,早在2003年,IETF成立了一個NETCONF工作組,提出一種基於XML的網絡配置管理協議,也就是NETCONF(Network Configuration Protocol),因爲該協議的配置功能非常強大,同時兼顧監控和故障管理,安全驗證和訪問控制,所以得到業界的一致認可,所以廣泛採用netconfig來配置網絡。NETCONF協議分爲傳輸層、RPC層、操作層和內容層。其中,內容層是唯一沒有標準化的層,於是一種新的建模語言YANG產生了,它的目標是對NETCONF數據模型、操作進行建模,覆蓋NETCONF協議的操作層和內容層,yang模型據我的理解可以看作是定義一個數據對象的,對象中包含各種結構的屬性,這一部分篇幅所限,我不對yang模型進行講解,以後可以專門寫一個文章進行講解。
要學習ODL的MD-SAL,有三個方面的內容需要掌握:
1.RPC
2.notification
3.datastore
其實這三點都與yang模型有一定的聯繫,下面結合代碼進行分析和講解。
1.RPC
RPC代表一個輸入輸出的函數調用過程,是有一個請求和返回的消息對,RPC首先要在yang文件中定義,例如:
rpc connect-device {
input {
leaf device-id {
description "the ip to execute mtr";
type string;
}
leaf device-ip {
description "the url of dst";
type string;
}
leaf username {
description "the url of dst";
type string;
}
leaf password {
description "the url of dst";
type string;
}
leaf device-port {
description "the uuid of device";
type string;
}
leaf connection-type {
description "Type of connection, true for tcp only";
type string;
default "false";
}
leaf protocol {
description "the uuid of device";
type string;
default "ssh";
}
leaf schemaless {
description "the uuid of device";
type string;
default "false";
}
leaf excluded-tls-versions {
description "the uuid of device";
type string;
default "";
}
}
output {
leaf result {
description "total result";
type string;
}
}
}
列如上面就定義了一個連接設備的RPC調用,這個yang模型會在編譯之後會生成binding接口,可以供我們提供實現。
對每個RPC Method解析Input入參,實現RPC Method的業務邏輯,構造RPC Output,封裝成future返回。我們可以自己書寫代碼實現接口
@Override
public ListenableFuture<RpcResult<ConnectDeviceOutput>> connectDevice(ConnectDeviceInput input) {
// TODO Auto-generated method stub
String deviceIp = input.getDeviceIp();
String devicePort = input.getDevicePort();
String connectionType = input.getConnectionType();
String schemaless = input.getSchemaless();
String protocol = input.getProtocol();
String username = input.getUsername();
String password = input.getPassword();
String excludedTlsVersions = input.getExcludedTlsVersions();
String deviceId = input.getDeviceId();
if (!NetconfCommandUtils.isIpValid(deviceIp) || !NetconfCommandUtils.isPortValid(devicePort)) {
return RpcResultBuilder.<ConnectDeviceOutput>failed().withResult(new ConnectDeviceOutputBuilder().setResult(
"\"Invalid IP:\" + deviceIp + \" or Port:\" + devicePort + \"Please enter a valid entry to proceed.\""))
.buildFuture();
}
final boolean isTcpOnly = connectionType.equals("true");
final boolean isSchemaless = schemaless.equals("true");
final NetconfNodeBuilder netconfNodeBuilder = new NetconfNodeBuilder();
netconfNodeBuilder.setHost(new Host(new IpAddress(new Ipv4Address(deviceIp))))
.setPort(new PortNumber(Integer.decode(devicePort))).setTcpOnly(isTcpOnly).setSchemaless(isSchemaless);
if (isTcpOnly || protocol.equalsIgnoreCase("ssh")) {
if (Strings.isNullOrEmpty(username) || Strings.isNullOrEmpty(password)) {
return RpcResultBuilder.<ConnectDeviceOutput>failed()
.withResult(new ConnectDeviceOutputBuilder()
.setResult("Empty Username:" + username + " or Password:" + password
+ ". In TCP or SSH mode, you must provide valid username and password."))
.buildFuture();
}
final Credentials credentials = new LoginPasswordBuilder().setPassword(password).setUsername(username)
.build();
netconfNodeBuilder.setCredentials(credentials);
if (!isTcpOnly) {
netconfNodeBuilder.setProtocol(new ProtocolBuilder().setName(Name.SSH).build());
}
} else if (protocol.equalsIgnoreCase("tls")) {
TlsCase tlsCase = null;
if (!Strings.isNullOrEmpty(excludedTlsVersions)) {
tlsCase = new TlsCaseBuilder().setTls(
new TlsBuilder().setExcludedVersions(Arrays.asList(excludedTlsVersions.split(","))).build())
.build();
}
netconfNodeBuilder.setProtocol(new ProtocolBuilder().setName(Name.TLS).setSpecification(tlsCase).build());
} else {
return RpcResultBuilder.<ConnectDeviceOutput>failed()
.withResult(new ConnectDeviceOutputBuilder()
.setResult("Invalid protocol: " + protocol + ". Only SSH and TLS are supported."))
.buildFuture();
}
try {
FluentFuture<? extends @NonNull CommitInfo> future = service.connectDeviceSync(netconfNodeBuilder.build(),
deviceId);
future.get(DELAYTIME, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
// TODO Auto-generated catch block
return RpcResultBuilder.<ConnectDeviceOutput>failed()
.withResult(new ConnectDeviceOutputBuilder().setResult(e.getMessage())).buildFuture();
} catch (Exception e) {
return RpcResultBuilder.<ConnectDeviceOutput>failed()
.withResult(new ConnectDeviceOutputBuilder().setResult(e.getMessage())).buildFuture();
}
final String message = "Netconf connector added succesfully";
return RpcResultBuilder.success(new ConnectDeviceOutputBuilder().setResult(message)).buildFuture();
}
同時在blueprint上註冊RPC實現。
<bean id="devicemanagementImpl"
class="com.ctg.netcontrol.impl.DevicemanagementImpl">
<argument ref="commanders" />
<argument ref="mountPointService" />
<argument ref="dataBroker" />
</bean>
<odl:rpc-implementation ref="devicemanagementImpl" />
這樣就可以提供RPC服務,假如要調用RPC服務,也可以在blueprint裏面上註冊RPC調用。
<odl:rpc-service id="virtdevmanagerService" interface="org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.virtdevmanager.rev190225.VirtdevmanagerService"/>
RPC調用還有一定的講究,例如採用同步,異步調用,這個以後有時間再分析。
2.Notifications
notifications是ODL另一種信息傳遞形式,採用發佈和訂閱的方式,其實還有另一種對datachange的訂閱監聽,這種使用到data broker傳遞給訂閱者。
首先依然是yang模型的定義,例如:
同樣經過編譯後會生成接口,主要是一個listener接口和notification的builder接口,用於監聽和構建notification:
例如,作爲發佈者的時候,需要構建出notification,利用notificationpublishservice的publish方法發佈出去
publishservice具有兩個方法,putNotification:將消息投遞到隊列後才返回。表明消息已經投遞成功了。這個是同步的方式。如果消息隊列滿了,則會一直阻塞直到消息隊列有空閒將消息投遞後才返回。
offerNotification:立即返回。後臺線程負責將消息投遞到消息隊列。投遞成功則返回設置成功。如果消息隊列滿,則會一直阻塞,直到投遞成功爲止。這個方法會立即返回ListenableFuture,可以通過callBack方式檢測其是否成功還是失敗。
offerNotifiation(xxx,int var2,…):這個方法也是立即返回。不同於第二個的是,如果消息隊列滿,不會一直阻塞,在超時時間到達後如果還沒投遞消息,則設置失敗。
而假如作爲訂閱者,則需要在blueprint裏面註冊listener:
然後在listener裏面實現監聽到通知後的處理邏輯:
訂閱者在處理消息時,如果處理消息時間過長,儘量開闢一個新的線程來處理消息。否則會卡住消息的發佈者,因爲消息發佈者是將消息投遞到一個消息隊列的。如果處理消息時間長,消息隊列容易滿,進而造成消息發佈者阻塞住。
消息投遞也是有順序的。不會因爲前一個消息沒投遞出去,後面的消息投遞出去了。而datastore的datachange notify則是沒有順序的。它會併發地發出多個消息,無任何順序。
3.DataStore
datastore是一個樹狀結構保存在內存中的數據,可以看作ODL的數據庫,計算變化集並且維護commit handlers,notification listeners和實際數據之間的關係。要獲取服務的話首先需要在blueprint裏面定義。
<reference id="dataBroker"
interface="org.opendaylight.controller.md.sal.binding.api.DataBroker"
odl:type="default" />
datastore是以事務進行操作,因爲其本身就相當於數據庫,所以進行事務操作很好理解,簡要說明一下。事務(transaction)在資料裏面說是整體數據樹的一致性快照,相當於將要應用於數據樹的一系列原子操作集,也就是這個事務同樣具有原子性,
例如創建事務
事務中可以存在多個子操作,最後利用submit操作一併提交。
Put操作:全新的操作。如果數據不存在,則會創建。如果數據已經存在了,則會完全替換掉。
Merge操作:如果之前沒有數據則會寫進去。如果之前有數據,新的也有數據,則會更新。如果之前有數據,新的沒有數據,則不會發生任何變化。
LogicalDatastoreType是區分操作的數據樹類型,主要分爲operational和configuration兩種數據樹,InstanceIdentifier是用來標識數據樹上面的要操作的數據,可以看作樹節點,而dataobject是節點的返回數據類型,一般事務操作後可以返回Optional類型的對象,利用get()方法獲取到返回數據。
可以利用get獲取dataobject,當然也能利用future來構建回調函數。