内部負荷下でのRTLSサーバーの安定性テストの自動化の経験

はじめに



この記事では、RTL-Service品質管理部門が、多数のモバイルロケーションデバイスにサービスを提供しながら、RealTracサーバーの安定性の自動テストを実行する方法について説明します。 さらに理解するために、有用な用語を理解することをお勧めします。

RealTrac RTLSサーバー(サーバー) -システムハードウェアと対話してデバイスの位置を計算するRealTracサーバーソフトウェア。



RealTracアプリケーションサーバー(アプリケーションサーバー)-Webアプリケーションの操作に必要なサーバーソフトウェア。システムの基本機能にアクセスするためのプログラムインターフェイスを提供します。



RealTracアクセスポイント (以降AP と呼びます )は、モバイルネットワークデバイスとシステムサーバー間でデータを転送するように設計されたデバイスです。 アクセスポイントはオブジェクトに永続的にインストールされ、その座標はクライアントソフトウェアカードに入力され、システムサーバーのデータベースに記録されます。 APは、ゲートウェイモードまたはリレーモードで動作できます。 モードは、ネットワークへの有線イーサネット接続の存在(ゲートウェイアクセスポイント、ShTD)とそのような不在(リピーターアクセスポイント、RTD)によって決定されます。 サーバーとのデータ交換は、ゲートウェイによってのみ実行されます。 アクセスポイントの例を図1に示します。



図 1.アクセスポイントの例。



モバイルデバイス (MU)は、モバイル無線ノードであるデバイスであり、ユーザーが接続している人物または他のオブジェクトの位置をリアルタイムで判断できます。 デバイスのタイプに応じて、音声送信などの追加機能を実行できます。 モバイルの例を図2に示します。



図 2.モバイルデバイスの例。

画像



生存サイクル(生存サイクル) -デバイスがそのステータスに関するデータをブロードキャストする期間。

図3は、RTLSシステムのアーキテクチャを示しています。 黒い線で示されている領域には、このタスクの一部としてテストが必要なコンポーネントがあります。



図 3.高レベルアーキテクチャRealTrac。

画像

サーバーの内部負荷は、サーバーと対話するRealTracワイヤレスセグメントのデバイス、データ交換の数と強度によって特徴付けられます。 データは内部プロトコル-INCPを介して送信されます。

外部負荷は、パブリックAPI要求-RTLSCPです。



問題の声明。



タスクは、2秒間のポーリングサイクルで2000台のモバイルデバイスの長い負荷の間、サーバーソフトウェアの安定性を判断することです。つまり、ソフトウェアにフリーズ、障害、またはエラーがあるかどうかを確認することです。 また、プロセッサリソースとメモリの消費量(最大、最小、平均)を判断する必要がありました。



当初、このタスクは手動で解決されましたが、このタスクはできるだけ早く自動化する必要があることにすぐに気付きました。

部門内では、問題は次のサブタスクに分割されました。

1.テストおよびツールへのアプローチを定義します。

2.内部Load Generatorの構成ファイルを作成します。

3.テストシステムの実装。

4.実行中のテストの自動化。



テストアプローチ。



問題の詳細のため、自動安定性テスト用に独自の小さなシステムを実装することが決定されました。 テストシステムの中核は、特定の時点でsshを介して「下位」マシンにスクリプトを送信および実行するコントローラーアプリケーションです。 スクリプトは、リモートマシンへのシステムの展開とリソースの監視という2つの主要な機能を実行します。 図4に相互作用図を示します。



図 4.テストサーバーの相互作用のスキーム。

画像

次のタスクは、テストサイクルを自動化することです。 それを解決するために、我々は車輪を再発明せず、ローカルビルドサーバーを使用しました。 つまり、アセンブリ中にテストプロセス自体が実行され、指定されたスケジュールに従ってアセンブリが実行されます。



内部負荷の生成。



テストに常に実際のデバイスを使用せず、任意の数のデバイスをサーバーにロードできるようにするために、ワイヤレスセグメントデバイス(TDおよびMU)をエミュレートするように設計されたincptesterアプリケーションが内部で開発されました。

テスターの観点からは、構成ファイルを使用してincptesterが構成されていることを知るだけで十分です。 ファイルは次のようになります。



[general]

geo_lat=61.786838

geo_lng=34.353548

geo_alt=1



[tracks]

id=1,type=POLY,TF=0,VRT=(20:0:0)(21:0:30)(60)(40:0:0)(60)



[devices]

mac=CF0000000000,devtype=1,ip=127.0.1.1,cycle=30000,x=0.0,y=0.0,z=0

mac=C00000000001,devtype=2,ip=127.0.0.2,cycle=30000,x=5.0,y=0.0,z=0

mac=000000BAD001,devtype=4,cycle=2000,track=1

mac=000000BAD002,devtype=6,cycle=2000,track=1









[一般]ブロックでは、中心点の地理座標が指定され、そこからローカル座標(x、y、z)がカウントされます。 [tracks]ブロックは、モバイルデバイスが移動するパスを記述するために必要です。 軌跡は、一連の点によって記述されます。 [devices]ブロックでは、デバイス自体が登録され、実際のデバイスの動作をエミュレートします。



MACアドレスに加えて、各デバイスには、デバイスのタイプを決定するパラメーターであるdevtypeがあります(1、2-固定APおよび3-6-MU)。 また、デバイスのポーリングサイクルを定義するサイクルパラメーターがあります。 固定アクセスポイントの場合、座標を介して特定の位置を指定する必要があります。 モバイルデバイスの場合、移動するパスを指定できます。



その結果、必要な負荷を生成するには、incptesterに必要な数のデバイス用の構成ファイルを作成する必要があります。

構成に2000台のデバイスを手動で登録しないようにするために、指定した行数をベースファイルに追加する小さなbashスクリプトが作成されました。

add_devices.sh
 #!/bin/bash set -e if [ "$#" -ne 2 ] then echo "Usage: ./${0} <base_config> <dev_num>" exit 1 fi INCPTESTER_CONF_PATH=./${1}.conf if [ ! -f ${INCPTESTER_CONF_PATH} ]; then cat ./incptester_geo-base.conf > ${INCPTESTER_CONF_PATH} fi alias get_next_mac='python -c "import sys; print '{:012x}'.format(int(sys.argv[1], 16)+int(sys.argv[2], 16)).upper()"' last_mac=$(tac ${INCPTESTER_CONF_PATH} | grep -m 1 . | grep -o -P "[A-z0-9]{12}") for count in $(seq ${2}); do next_mac=$(get_next_mac "0x${last_mac}" "0x1") echo "mac=${next_mac},devtype=6,cycle=2000,track=2" >> ${INCPTESTER_CONF_PATH} last_mac=${next_mac} done
      
      







使用済みソフトウェア。



コントローラーアプリケーションは、javaで実装することが決定されました。 プログラムのテストシナリオを記述するために、キュウリライブラリを使用し、それに応じてガーキン言語を使用しました。 ビルドツールとしてgradleを使用しました。 アセンブリ自体は、ローカルのHudsonサーバーに組み込まれました。



システムはDebian Linuxで実行されるため、bashでOSと対話するためのスクリプトを実装することをお勧めします。 これには、debパッケージと構成のインストール/削除が含まれます。 現在のプロセスを監視するために、Pythonのpsutilパッケージを使用し、csvの消費リソースの値を定期的にアップロードしました。



主なシナリオ。



スクリプトは、次の手順に分かれています。

1.テストサーバーから以前のパッケージを削除します。

2. debパッケージの構成ファイルを準備します。

3.構成ファイルとスクリプトをテストサーバーにコピーします。

4.テストサーバーにシステムを展開します。

5. slave1上の2000台のモバイルデバイスの構成でincptesterを実行します。

6.リソースを並列監視しながら、slave1で一定時間、内部負荷でサーバーを起動します。

7.スレーブ上にあるメインサーバーで構成されたスレーブ2でアプリケーションサーバーを実行し、リソースを並列監視します。



実装。



このセクションでは、テストシステムの実装の主なポイントについて簡単に説明します。

テストスクリプトは、ガーキン機能に簡単に変換できます。 実行できるステップで、実行可能メソッドに渡されるパラメーターを設定できます。 次のようになります。

@Load_stability_geo

Feature: Load_stability_geo

This test starts the large number of the devices and monitors the system resources



@Install

Scenario: Instalation of RealTrac system in geo mode

Given I delete the previous Realtrac-server from the both test-servers

And I prepare all deb configs

And I copy all configs and scripts to the test servers

And I install the main Realtrac-server on the test-server and incptester and stop service rtlserm for geo configuration

Then Run first part of the test with the inside load for 11520 steps

Given I install the app server

Then Run second part of the test with the inside load for 11520 steps









スクリプトについては、別のクラスLoadStabilityGeoを作成します。 クラスには、機能からのステップを実行するメソッドが含まれます。 メソッドにパラメーターを渡す例。 パラメーターは正規表現によって解析されます。



 import rtls.test.utils.RTLSUtils; import cucumber.api.java.en.And; import cucumber.api.java.en.Then; import cucumber.api.java.en.When; public class LoadStabilityGeo { // some other methods @Then("^Run first part of the test with the inside load for (\\d+) steps") public void First_Monitoring(int count_time) throws ScriptFaildException, Exception { int times=0; double minutes; int check_time_min=10; //each 10 minutes the resources is verified monitoring_file=NameFileDataFormat("monitoring", "csv"); path_monitoring_file=" /home/"+user+"/TestResult/"; path_monitoring_file=path_monitoring_file+monitoring_file; minutes=0.5; while (times <= count_time) { run_ssh_cmd("resource_get.py "+path_monitoring_file+" rtls", "main_server"); If (((times%check_time_min)==0) && times!=0){ checkResource("rtls", rtlscp_port, rtlscpip, check_time_min); times=times+1; }else{ Sleep_time(minutes); times=times+1; } } System.out.println("The first part of the stress test in geo-mode is successfully finished"); } }
      
      







別のRTLSUtilsクラスも作成され、スクリプトを操作するための静的メソッド(結果の実行/検証)およびその他の一般的なメソッドが含まれています。 OSでコマンドを実行する方法の例:



 public class RTLSUtils { // some other methods public static void executeCommand(String command) throws ScriptFaildException { try { String line; System.out.println("Excecute " + command); String[] env = new String[]{"DEBIAN_FRONTEND=noninteractive"}; Process p = Runtime.getRuntime().exec(command, env); BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream())); BufferedReader error = new BufferedReader(new InputStreamReader(p.getErrorStream())); while ((line = input.readLine()) != null) { System.out.println(line); } input.close(); while ((line = error.readLine()) != null) { System.out.println(line); } error.close(); try { if (p.exitValue() != 0) { throw new ScriptFaildException(new Exception("error to execute command " + command)); } } catch (IllegalThreadStateException ex) { } } catch (IOException ex) { throw new ScriptFaildException(ex); } } }
      
      







それでは、スクリプトに移りましょう。 Bashスクリプトは、debパッケージを削除およびインストールするという単純な機能を実行します。 apt-getを介してリモートマシンにパッケージをインストールする例。

install_deb.sh
 #!/bin/bash set -e set -x #     SCRIPT_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #  (     ) TEST_USER=user TEST_SERVER_IP=192.168.1.2 #     #    ,     if [ "${TEST_SERVER_IP}" == "127.0.0.1" ]; then COMMAND_PREFIX= else COMMAND_PREFIX="ssh ${TEST_USER}@${TEST_SERVER_IP}" fi APT_CONFIG_DIR="/home/${TEST_USER}/apt-get/" VERSION=$1 #  deb   if [ "${TEST_SERVER_IP}" == "127.0.0.1" ]; then if [ ! -d ${APT_CONFIG_DIR} ]; then mkdir ${APT_CONFIG_DIR} fi cp ${SCRIPT_PATH}/debconf.dat $APT_CONFIG_DIR else scp ${SCRIPT_PATH}/debconf.dat ${TEST_USER}@${TEST_SERVER_IP}:${APT_CONFIG_DIR} fi #   ${COMMAND_PREFIX} sudo debconf-set-selections ${APT_CONFIG_DIR}debconf.dat ${COMMAND_PREFIX} sudo apt-get update && true ${COMMAND_PREFIX} sudo DEBIAN_FRONTEND=noninteractive apt-get install --force-yes -y some-package-${VERSION}
      
      







プロセスデータをcsvファイルにアップロードするPythonスクリプト。

resources.sh
 #!/usr/bin/python # -*- coding: utf-8 -*- # depends on; # sudo pip install psutil # Usage: python resources.py </path/to/file> <proc_name> import time import datetime import psutil import sys import csv def convert_bytes(bytes): '''         :param bytes: :return: ''' bytes = float(bytes) if bytes >= 1099511627776: terabytes = bytes / 1099511627776 size = '%.2fT' % terabytes elif bytes >= 1073741824: gigabytes = bytes / 1073741824 size = '%.2fG' % gigabytes elif bytes >= 1048576: megabytes = bytes / 1048576 size = '%.2fM' % megabytes elif bytes >= 1024: kilobytes = bytes / 1024 size = '%.2fK' % kilobytes else: size = '%.2fb' % bytes return size def get_string(proc, proc_name): '''    . :param proc:   :param proc_name:   :return:  ''' data_time = datetime.datetime.fromtimestamp(time.time()).strftime("%Y-%m-%d %H:%M:%S") ram = convert_bytes(proc.memory_info().rss) ram_percent = round(proc.memory_percent(),2) cpu = proc.cpu_percent(interval=1) wrt_str = ("{0} {1} {2} {3} {4} {5}".format(data_time, proc_name, proc.pid, ram, ram_percent, cpu)) return wrt_str if __name__ == "__main__": if (len(sys.argv) == 3): pathresult_log = sys.argv[1] target_proc = sys.argv[2] #     if (target_proc=="rtls"): res_log_rtls = open(pathresult_log, 'a') writer_rtls_log = csv.writer(res_log_rtls) elif (target_proc == "rtlsapp"): res_log_app = open(pathresult_log, 'a') writer_app_log = csv.writer(res_log_app) elif (target_proc == "all"): res_log_rtls = open(pathresult_log, 'a') res_log_app = open(pathresult_log, 'a') writer_rtls_log = csv.writer(res_log_rtls) writer_app_log = csv.writer(res_log_app) else: print ("Types of the test are not correct") sys.exit(1) proc_rtls = 0 proc_rtlsapp = 0 try: #     procs = [p for p in psutil.process_iter()] for proc in procs: if (proc.name() == 'java' and proc.username() == 'rtlsadmin'): proc_rtls = proc elif (proc.name() == 'node' and proc.username() == 'rtlsapp'): proc_rtlsapp = proc except psutil.NoSuchProcess: pass else: #     if (target_proc == "rtls" or target_proc == "all"): if (proc_rtls != 0): proc_name = "rtls-server" str_rtls = get_string(proc_rtls, proc_name) writer_rtls_log.writerow(str_rtls.split()) elif (target_proc == "rtlsapp" or target_proc == "all"): if (proc_rtlsapp != 0): proc_name = "rtls-app" str_rtlsapp = get_string(proc_rtlsapp, proc_name) writer_app_log.writerow(str_rtlsapp.split()) finally: #   if (target_proc == "rtls" or target_proc == "all"): res_log_rtls.close() elif (target_proc == "rtlsapp" or target_proc == "all"): res_log_app.close() else: print("Input parameters are not correct") sys.exit(1)
      
      









結果



テスト結果はcsvファイルに反映されます。 例:



2016-03-04,11:03:55、rtls-server、30237、1.29G、32.71、167.8

2016-03-04,11:04:27、rtls-server、30237、1.33G、33.63、166.9

2016-03-04,11:04:59、rtls-server、30237、1.34G、34.0、172.8



ここで、各行には日付、時刻、プロセス名、プロセス識別子、使用メモリの割合、プロセッサ使用率の割合が含まれています。

このデータを使用して、グラフを分析および構築することがすでに可能です。 さらに、グラフィックダッシュボードを使用して、たとえばGrafanaなどの監視サービスに受信データを送信すると便利です。



以上です。 将来的には、外部負荷プロセスがどのように実装されるか、および内部負荷と外部負荷の合計でサーバーをテストする方法について、より詳細に伝える予定です。

著者:ニキータ・ダヴィドフスキー



All Articles