SDN學習之Opendaylight淺析(二)

 上一篇講了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來構建回調函數。

                                                                          

                                                                           

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章