В этой статье я расскажу об инфраструктуре нашего проекта для запуска REST API автотестов для сервисов Open Telekom Cloud. Акцент делается на Python и Robot Framework составляющей, с небольшим обзором инфраструктуры проекта (Jenkins/Gitlab/InfluxDB/Grafana). Статья описывает один из многочисленных способов настройки окружения, на котором запускаются автотесты (внутри нашего проекта есть 3 инфраструктуры с разным способом запуска, обработки и выгрузки результатов тестов - одна из них описана в этой статье).

Информация, изложенная в этой статье, может быть полезна как для тестировщиков-автоматизаторов, так и для DevOps-инженеров, у которых есть необходимость в создании собственного окружения для автотестов. Если вам просто интересно, как работает автоматическая прогонка тестов в большом проекте - тогда вам тоже сюда. Сразу оговорюсь, что текущая инфраструктура справляется со своими задачами тестирования API запросов, но может не подойти для мобильного тестирования / проведения нагрузочных тестов или проч. Если у вас предложения, по улучшению - буду рад увидеть их в комментариях.

Open Telekom Cloud

Open Telekom Cloud (далее - OTC) - самый крупный европейский публичный облачный сервис основанный на Openstack. Платформа создана для компаний и стартапов, которые работают с европейскими пользователями, чьи данные должны храниться в пределах Евросоюза. Более подробную информацию об облаке можно найти по ссылкам:

По структуре сервисов и API запросам он больше всего похож на Amazon Web Service / Huawei Cloud / SberCloud.

Мое знакомство с облачным провайдером Open Telekom Cloud началось более года назад. До этого опыта в автомазированном тестировании я не имел, так что изначально все взаимодействие ограничивалось Robot Framework’ом и написанием keyword-driven API тестов с использованием кастомной библиотеки для Open Telekom Cloud. Со временем стали появляться новые задачи, которые требовали более глубокого понимания нашей среды, в первую очередь, касающиеся Python, настройки Jenkins и CI/CD Gitlab’а. 

Почему Robot Framework?

Robot framework (RF) – это open source keyword-driven фреймворк на основе Python, разработанный для автоматизации тестирования.

В контексте нашего проекта я хочу выделить три основные причины использования Robot Framework в качестве основного инструмента для написания автотестов:

Отчеты RF

Отчеты в Robot Framework генерируются автоматически в процессе работы. По итогам получаем 3 файла с результатами тестов – output.xml (сводка по тестам, которую в дальнейшем можно парсить командой rebot), report.html (общая сводка по тестам) и log.html (подробные результаты по каждому тесту).

Содержание log.html
Содержание log.html

Детальный отчет можно получить, добавив аргумент -b и название файла (в который должен сохраняться отчет) в команде запуска robot:

~$ robot -b debug.log test_suite.robot

Стандартные репорты robot’а достаточно ясные, и даже человек, не связанный с тестированием, поймет в какой момент в тест сьюте что-то сломалось. Также это наглядный инструмент для менеджмента, который позволяет сразу оценить общее количество тестов и процент успешного выполнения.   

Проблемы могут возникнуть на этапе воспроизведения неудавшихся тестов. Debug логи robot тестов не сохраняются на нодах Jenkins из-за соображения безопасности, а сам robot плохо оптимизирован для отладки. Но, как показывает практика, большая часть результатов тестов воспроизводится.

RF прост для понимания

Благодаря простому синтаксису ключевых слов в библиотеках RF большинство тестов можно понять без глубокого погружения в тест-кейс. Также для написания тестов на RF не требуется знание ООП и умения программирования на Python. Нужно лишь подключить необходимые библиотеки, для которых достаточно понимания базовых концептов типа списков, словарей, объектов. 

Пример теста с ключевыми словами встроенной библиотеки RF:

*** Settings *** 
Library    String
*** Variables *** 
${var}    world
*** Keywords ***
My log uppercase keyword     
    ${uvar} =    Convert to Uppercase    ${var}     
    Log To Console    Hello ${uvar}
*** Test Cases *** 
Sample Testcase     
    My log uppercase keyword

Синтаксис ключевых слов библиотек OTC тоже стараемся поддерживать максимально простым и понятным.

В целом это упрощает порог вхождения для новых сотрудников, которые у нас регулярно появляются, так как команда активно растет и всегда есть открытые позиции в тест автоматизации. Новому сотруднику не обязательно иметь опыт в автоматизации и ООП. Для начала ему хватит RF, с помощью которого можно без проблем покрыть сервисы тестами. Если же ставятся более серьезные задачи, или тест сьюты в RF становятся слишком большими и сложными — значит пришло время углубиться в Python и дописать модуль / ключевые слова для своего сервиса.

RF поддерживает теги

Теги позволяют разбить запуск тестов для разных окружений. На практике это означает, что внутри одного тест сьюта с помощью тегов можно отметить, какие тесты должны запускаться на каком окружении.

Теги могут быть добавлены в разделе Settings robot framework’а:

*** Setting ***
Force Tags	    status:stable 
...           	release:OTC_2.1 	 
...           	importance:critical 	 
...           	frequency:hourly 	 
...             region:prod_eu-de

Такой вариант добавляет теги для всех тест-кейсов.

Либо можно отдельно добавить теги для тест-кейса:

*** Setting ***
*** Keywords ***
*** Variables ***
*** Test Cases ***
Example-Test-Case
[Tags]      status:stable 
...         release:OTC_2.1 
...         importance:critical 
...         frequency:hourly 
...         region:prod_eu-de

Далее запуская тесты для определенного региона или настраивая Jenkins/Gitlab, мы явно указываем тесты с каким тегом должны быть запущены:

~$ robot -i region:prod_eu-de test_suite.robot

Инфраструктура проекта

Запуск автотестов нашего проекта происходит на виртуальных машинах Open Telekom Cloud с помощью Jenkins. В нашей конфигурации Jenkins есть два вида задач – hourly и nightly – задачи запускаемые на нодах Jenkins каждый час/ночь, в зависимости от проставленного в Robot тега (если тест сьют требует более 5-10 минут на исполнение, то тег ставится nightly).

Ниже приведен процесс автоматизации наших тестов:

Перед запуском очередной задачи на ноде, Jenkins обновляет репозиторий Gitlab, в котором хранятся тесты Robot Framework. После чего происходит запуск тестов.

Часть результатов мы получаем во время исполнения тестов RF (обычно это тесты на производительность сервисов), они парсятся через RF и отправляется curl’ом в InfluxDB и Grafana. По завершению всех тестов результаты (а именно output.xml) парсятся уже через Jenkins и выгружаются в Grafana и Web Reports. В итоге имеем следующий результат:

Grafana
Grafana
Web reports
Web reports

Главная страничка Web reports отображает статистику по всем окружениям, в которых запускаются тесты. Нажав на одно из них, мы проваливаемся в report.html и можем посмотреть детальное исполнение тестов.

Автоматизированные тесты в OTC и задача QA

Мы занимаемся тестированием REST API и написанием соответствующих автотестов для сервисов Open Telekom Cloud. С точки зрения тестирования – это основная задача QA, хотя RF позволяет писать UI/UX тесты (с помощью Selenium), тесты на производительность сервисов. В основе RF лежит Python, поэтому возможности ограничены лишь теми библиотеками, которые вы подключаете к RF.

Для каждого сервиса OTC есть API документация, которая содержит в себе описание возможных REST запросов, связанных с сервисом. Это основной документ, с которым работают и на который ориентируются инженеры при создании автотестов:

Пример Get запроса для сервиса CBR (Cloud backup and recovery):

GET http://{cbr_endpoint_url}/v3/{project_id}/operation-logs/{operation_log_id}

Соответствующий запрос написанный в RF с использованием внутренних библиотек будет выглядеть следующим образом:

*** Test Cases***
Get operation log ID    
    Use service    cbr    
    ${resp} =    Get Request    /operation-logs/{operatiom_log_id}

Базовый тест сьют обычно имеет следующую структуру: создание объекта сервиса - настройка / изменение объекта сервиса - удаление объекта сервиса. Часто для создания какого-то сервиса требуется предварительное создание и настройка другого сервиса. Например, до создания виртуальной машины требуется создать виртуальную сеть и секьюрити группу.

Robot Framework

Для работы с RF не требуется знание Python, достаточно использовать заложенные в него ключевые слова и внешние библиотеки. В нашем проекте используется следующий набор библиотек для RF:

При подключении OTC Library в тест сьют и дальнейшем запуске в первую очередь через Openstack sdk и OpenStackConfig (openstack.config.loader) создается сессия. Авторизация сессии происходит двумя способами:

clouds.yaml

OpenStack sdk самостоятельно ищет файл для авторизации в следующих местах:

  1. system-wide (/etc/openstack/{clouds,secure}.yaml)

  2. Home directory / user space (~/.config/openstack/{clouds,secure}.yaml)

  3. Current directory (./{clouds,secure}.yaml)

clouds:  
  otc:    
    profile: otc    
    auth:      
      username: '<USER_NAME>'      
      password: ''      
      project_name: '<eu-de_project>'      
      # or project_id: '<123456_PROJECT_ID>'      
      user_domain_name: 'OTC00000000001000000xxx'      
      # or user_domain_id: '<123456_DOMAIN_ID>'    
    account_key: '<AK_VALUE>' # AK/SK pair for access to OBS    
    secret_key: '<SK_VALUE>

После того, как файл создан, нужно указать имя конфигурации для переменной окружения OS_CLOUD:

~$ export OS_CLOUD=otc

Переменные окружения

В случае, если OpenStack sdk не обнаруживает переменную OS_CLOUD и/или файл clouds.yaml, он проверяет переменные окружения. Авторизация через переменные окружение может осуществляться вручную или через файл (например.ostackrc):

# .ostackrc file
export OS_USERNAME="<USER_NAME>"
export OS_USER_DOMAIN_NAME=<OTC00000000001000000XYZ>
export OS_PASSWORD=<PASSWORD> # optional
export OS_TENANT_NAME=eu-de
export OS_PROJECT_NAME=<eu-de_PROJECT_NAME>
export OS_AUTH_URL=https://iam.eu-de.otc.t-systems.com:443/v3
export NOVA_ENDPOINT_TYPE=publicURL
export OS_ENDPOINT_TYPE=publicURL
export CINDER_ENDPOINT_TYPE=publicURL
export OS_VOLUME_API_VERSION=2
export OS_IDENTITY_API_VERSION=3
export OS_IMAGE_API_VERSION=2

Добавляем переменные в окружение с помощью

~$ source .ostackrc

После чего можно использовать OTC Library.

Далее есть два варианта построения тестов – с использованием модулей сервисов (библиотека ключевых слов, заранее написанного для сервиса на python, например OtcLibrary.evs_robot), либо прописывать вручную все API запросы и последующие проверки статус кода и тела ответа.

Базовый вариант (без модулей) тест сьюта выглядит следующим образом:

*** Settings ***
Library    OtcLibrary
*** Test Cases ***
Check EVS disk type
Use service    evs    
    ${resp} =   Get Request    /volumes/ 4d7c7b9d-1b70-40a8-8dd5-edd86eb08154    
    Should Be Equal As Strings	${resp.status_code}    200    
    Should Be True	“${resp.json()[“volume”][“volume_type”]}” == “SATA”

Команда “Use service” запрашивает endpoint сервиса, который далее подставляется в последующие запросы. Тело ответа и статус код содержится в переменной ${resp}, над которой дальше проводят всевозможные проверки.

Также OtcLibrary содержит в себе основные ключевые слова для работы с проектом и регионами – вот часть из них:

Get ProjectId
Get ProjectName
Get Region
Get DomainId

Основой OtcLibrary является Python библиотека RequestsLibrary, так что структура запросов и переменной с ответом сервера почти ничем не отличаются.

Также использование RequestsLibrary позволяет создание нескольких сессий с легким переключением между ними.

Для основных сервисов OTC написаны модули с набором ключевых слов. Как правило внутри ключевого слова уже совершаются проверки статус кода и полей в ответе запроса, итоговый вид тест сьюта менее загруженный:

*** Settings *** 
Library    OtcLibrary 
Library    OtcLibrary.evs_robot
*** Test Cases ***
Check EVS disk type    
    ${resp}=    Query EVS disk    diskname       
    Should Be True “${resp[“volume_type”]}” == “SATA”  

В данном примере метод Query EVS disk описан в модуле OtcLibrary.evs_robot и содержит отправку запроса, а также проверку кода статуса ответа следующего вида:

def query_evs_disk(self, name=None):    
    '''query details of an EVS disk'''    
    if name:        
        ...        
        response = self._session.get(self._url + '/cloudvolumes/detail', params=params)        
        status_code_should_be(response, 200)        
        return response.json()    
    else:        
        raise TypeError(name + '() needs name as an argument')

Далее работа идет с телом ответа в json формате.

Для робота создано большое количество комьюнити библиотек, что расширяет возможности тестирования и позволяет создавать не только функциональные тесты. Например, комбинация SSHLibrary и модулей сервиса ecs позволяет создать VM и подключиться через ssh с дальнейшим запуском команд на VM.

Таким образом у нас настроены тесты на производительность виртуальных жестких дисков (SATA, SSD и проч.)  через утилиту fio, после пробега команд и последующего парсинга, результат отправляется API запросами на Influx и отображается на Grafana.

Результаты тестов на производительность сервиса EVS
Результаты тестов на производительность сервиса EVS

В принципе, таким образом можно автоматизировать почти любой ручной тест. Например, рассмотрим тесты для сервиса s3, который используется для хранения и доступа данных в "бакетах" - виртуальных контейнерах облачного сервиса. В качестве инструментов тестирования используется утилита s3tester и obsutil. Все действия выполняются через ключевые слова RF, порядок работы в данном случае следующий:

  • создание VM и s3 контейнера;

  • удаленное подключение к VM;

  • загрузка s3tester/obsutil на VM, изменение прав доступа к файлам;

  • запуск команд s3tester/obsutil с дальнейшей выгрузкой на сервер с Grafana.

В данном случае, чтобы не загромождать тест-кейс, сделано разделение по ресурсам:

obs/    
    Resources/        
        keywords.robot        
        settings.robot        
        setup_and_teardown.robot        
        variables.robot   
    OBS_Performance_obsutil.robot    
    OBS_Performance_s3tester.robot

Файл OBS_Performance_obsutil.robot:

*** Settings ***
Documentation    OBS Performance Testing- Obsutil
Suite Setup     Setup Suite
Suite Teardown  Teardonw Suite
Resource    ${CURDIR}/Resources/settings.robot   
#путь к файлам, в которых находятся ключевые слова тест-кейса
*** Test Cases ***
Start test    
    Start Test Keyword    
    Set Suite Variable    ${ecs_type}    ${ecs_type2}
Create KeyPair    
    Create KeyPair Keyword
OBS Performance test - az1    
    Preparing testing ECS Keyword    ${AZ-1}    s3.2xlarge.1    
    	#создание VM    
    BuiltIn.Sleep    15    
    Get EIP Keyword    
    Open Connection And Login With Public Key Keyword  
    	#подключение к VM по ssh    
    Configure Ecs    
    	#загрузка obsutil на VM, изменение прав доступа к файлам    
    Set Up Configuration File    
    Execute Performance Tests Obsutil    ${AZ-1} 
    	# запуск тестов obsutil в выбранной availability zone, 
    	# парсинг и выгрузка в Grafana    
    Delete ECS              ${servername}    
    Wait for ECS job completion    
    Run Keyword and Ignore Error    
    Release EIP             
    ${hostip}    
    ...

Тесты, исполняемые на VM, подключаются через Resource и содержатся в keywords.robot. Они, так же как и основной тест-кейс, представляют из себя набор ключевых слов, в основе которого стоят команды типа:

${output}=	Execute Command  ./obsutil cp obs://${bucket_name}/upload ~/download

запускающие утилиту obsutil/s3tester непосредственно на VM через SSHLibarary robot framework’а.

После этого результаты парсятся и выгружаются в InfluxDB следующей командой:

${output}=  Execute Command  curl -XPOST -u "${login}:${password}" '${influx_url}?db=${database}' --data-binary 'storage,env=${env},az=${region}-${AZ},request_type=${upload_type},size=${size} download=${download},upload=${upload}

В итоге данные отображаются в Grafana:

Python

Хорошо, с роботом разобрались, но как реализована кастомная библиотека и модули?

Часть, связанная с python, включает в себя две основных библиотеки – OTCLibrary (конечной библиотекой, с которой работают QA) и otcmodules, подключаемой к OTCLibrary. Otcmodules представляют собой набор классов с низкоуровневой логикой отправки запросов для сервисов OTC.

Otcmodules

Otcmodules были отделены от основной библиотеки, чтобы не загружать ее. Для основных сервисов прописаны классы с инициализацией сессии отдельного класса и соответствующие методы, каждый из которых представляет задокументированный API запрос. В методах происходит проверка передаваемых параметров запроса, логические действия и создание недостающих параметров перед отправкой самого запроса. Каждый метод класса заканчивается отправкой REST API и проверкой статус кода:

def getdisks(self, name=None, status=None):
    '''get details for EVS disks'''
	  params = {}
	  if name:
        params['name'] = name
	  if status:
    	  params['status'] = status
	  response = self._session.get(self._url + '/cloudvolumes/detail', params=params)
	  status_code_should_be(response, 200)
	  return response.json()

Для инициализации самого класса требуется передать объект авторизованной сессии, service endpoint и project id:

class EVS(OtcTags):
    '''EVS API'''

    def __init__(self, session, baseurl, projectid):
        '''save session and endpoint URL'''
        self._session = session
        self._session.headers.update({'Content-Type': 'application/json', 'X-Language': 'en-us'})
        self._url = baseurl+'/v2/'+projectid
        self._tagsurl = baseurl+'/v3/'+projectid
        ...

    def getavailabilityzones(self):
        '''Get List of Availability Zones'''
        response = self._session.get(self._url + '/os-availability-zone')
        status_code_should_be(response, 200)
        return response.json()

Для проверки работы классов и методов otcmodules созданы юнит-тесты. Не вдаваясь в подробности того, как происходит авторизация, привожу конечную реализацию:

import unittest
import os
import sys
import requests
import random

from requests.packages.urllib3.exceptions import InsecureRequestWarning

requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

path = os.path.dirname(os.path.dirname(__file__))
sys.path.append(path)

from otcmodules.utils.defaults.common import Common
from otcmodules.services.evs import EVS

SERVICENAME_EVS = 'volume'

class EVSTests(unittest.TestCase):
    '''EVS testcases'''

    def setUp(self):
        '''prepare EVS objects'''
        self.evs = Common._create_service_instance(EVS, SERVICENAME_EVS)

    def tearDown(self):
        '''remove EVS disk'''
        self.evsdisk.delete_no_wait()

    def testEVSObject(self):
        '''run basic tests on EVS object'''
        self.assertIsNotNone(self.evs._url)
        self.assertIsNotNone(self.evs._urlv1)

    def testDiskLifeCycle(self):
        '''test the disk life cycle (create, get, expand, delete)'''
        rid = format(random.randint(1, 2 ** 16), '04x')
        diskname = 'testdisk-' + rid
        # Create
        self.evs.createdisk(diskname, 'eu-de-01', '10')
        r = self.evs.waitforjob()
        self.assertTrue(r)
        job = self.evs.getjob()
        self.assertIn('volume_id', job['entities'])
        diskid = job['entities']['volume_id']
        # Get (by id)
        disk = self.evs.getdiskbyid(diskid)
        self.assertIsNotNone(disk)
        self.assertIn(disk['id'], diskid)
        self.assertEqual(disk['name'], diskname)
        # Expand
        self.evs.expanddisk(disk['id'], 20)
        r = self.evs.waitforjob()
        self.assertTrue(r)
        # Delete
        self.evs.deletedisk(disk['id'])
        r = self.evs.waitforjob()
        self.assertTrue(r)

if __name__ == '__main__':
    unittest.main()

OTC Library

OTC Library содержит в себе законченные модули с набором ключевых слов сервисов, данные модули уже готовы к инициализации и запуску в роботе QA-инженерами.

Структура OTCLibrary:

robot-otc/
    OTCLibrary/
    	    core/
        	    __init__.py
        	    cloudconnection.py
        	    OtcKeywords.py
        	    OtcRequestsLibrary.py
  	      __init__.py
    	    bms_robot.py
    	    cbr_robot_ext.py
    	    css_robot.py
    	    cts_robot.py
    	    dcaas_robot.py
    	    evs_robot.py
          ...
	   setup.py

Авторизация

Основная сессия создается с помощью OpenStackConfig Openstack SDK (openstack.config.loader) и метода get_one(). По факту информация считывается из переменных окружения или файла clouds.yaml:

from openstack.config.loader import OpenStackConfig
...
def applyConfig(func):
    config = OpenStackConfig()
    s_data = config.get_one(cloud=cloud) 
    
    authdata = s_data.get_auth_args()
    for key, value in authdata.items():
        if value:
            kwargs.setdefault(key, value)
    kwargs.setdefault('domain_name', authdata.get('user_domain_name'))
    kwargs.setdefault('region_name', config.region_name)
    ...

cloud – конфигурация в файле clouds.yaml. Если cloud указан, то OpenStackConfig будет загружать сессию указанной конфигурации. В случае, если cloud не указан или clouds.yaml не найден, будут подгружены переменные окружения.

Далее данные авторизации передаются через декоратор @applyconfig в init_cloud_session(), используемый при инициации библиотеки. Создается объект с авторизованной сессией:

class OtcKeywords(OtcRequestsLibrary):

    def __init__(self,
                 initsession=True,
                 debug=0,
                 scope='project',
                 **kwargs):
        super(OtcKeywords, self).__init__()
        self.ROBOT_LIBRARY_LISTENER = self
        self.lasthttperror=None
        self.sessions={}
        self.auth_url=None
        self.project_name=None
        self.https_insecure=None
        if initsession:
            self.init_cloud_session(
                debug=debug,
                scope=scope,
                **kwargs)

    @applyConfig
    def init_cloud_session(self,
                           alias='default',
                           scope='project',
                           debug=0,
                           username=None,
                           password=None,
                           domain_name=None,
                           region_name=None,
                           auth_url=None,
                           project_name=None,
                           https_insecure=None,
                           proxies=None,
                           **kwargs):
        """Create a ready-to-use, configured and authenticated session"""

        if username is None:
            raise TypeError('No username was set!)
        if password is None:
            raise TypeError('No password was set!)

        s = self.create_session(alias=alias,proxies=proxies,
                                verify=not https_insecure,
                                debug=debug)

        project_name = project_name or region_name
        iamsession = iam(s, auth_url)
        authdata = {
            'username': username,
            'password': password,
            'domain_name': domain_name,
            'region_name': region_name,
            'project_name': project_name,
            'scope': scope
        }
        self.auth_url = auth_url
        self.project_name = project_name
        self.https_insecure = https_insecure

        iamsession.authenticate(**authdata)
        dnsdomain = ".".join(urlparse(auth_url).netloc.split(".")[1:])
        self.dnsdomain = dnsdomain
        self.sessions[alias]={'iam': iamsession, 'dnsdomain': dnsdomain}

При этом объект сессии будет храниться в OtcKeywords.sessions.

OtcRequestsLibrary, от которого наследует OtcKeywords – это класс, который в свою очередь наследован от стандартной библиотеки RequestsLibrary с небольшими изменениями под наш облачный сервис. Благодаря этому и методу use_service в OtcKeywords (который просто переключает url сессии на endpoint сервиса, переданный методу) при подключении OtcLibrary endpoint можно явно не указывать.

Помимо этого, класс OtcKeywords содержит методы для работы с сессией, получения service endpoint и project id – для дальнейшего подключения модулей сервисов из otcmodules.

OTCLibrary написана достаточно гибко и позволяет помимо подключения otcmodules использовать модули, написанные на Openstack SDK, например otcextensions (расширение Openstack SDK, включающие сервисы, предоставляемые OTC). Чтобы не запутаться в созданных сессиях и подключенных модулях, проверки инициализованных сессий и действия с сессией вынесены в файл cloudconnection.py с классом cloudconnection:

class cloudconnection(object):
    '''Provides the basic information for cloud requests'''

    __instance = None

    def __new__(cls):
        if cloudconnection.__instance is None:
            cloudconnection.__instance = object.__new__(cls)
        return cloudconnection.__instance

    def __init__(self):
        try:
            self.otclib = BuiltIn().get_library_instance(OTCROBOTLIB)
        except RobotNotRunningError:
            from OtcLibrary import OtcLibrary
            self.otclib = OtcLibrary()

    def getSession(self):
        '''get the session object for requests'''
        try:
            return self.otclib.getSession()
        except:
            self.otclib.init_cloud_session()
        return self.otclib.getSession()

    def getEndpointURL(self, service):
        '''get the endpoint URL for a cloud service'''
        return self.otclib.get_endpoint_url(service)

    def getProjectId(self):
        '''get the project id from the cloud connection'''
        return self.otclib.get_projectid()
        
    ...

    def getOpenstackConnection(self):
        '''Get Openstack Connection object'''
        return self.otclib._openstack_connection()

    def getOtcextensionsConnection(self, **kwargs):
        '''Get Openstack Connection object'''
        return self.otclib._otcextensions_connection(**kwargs)

В итоге сами модули робота сервисов инициализируются через cloudconnection() и выглядят следующим образом:

Модули OTCLibrary основанные на otcmodules

evs_robot.py

from otcmodules.services.evs import EVS
from OtcLibrary.core.cloudconnection import cloudconnection
...
class evs_robot():
    ''' robot library to access the OTC elastic volume service'''
    ROBOT_LIBRARY_SCOPE = 'TEST SUITE'
    SERVICENAME = 'evs'
    default_availability_zone = property(lambda self: 'eu-de-01', None)

    def __init__(self):
        '''setup session, base URL, and project Id for EVS'''
        self.evs = EVS(cloudconnection().getSession(), cloudconnection().getBaseEndpointURL(evs_robot.SERVICENAME),
                       cloudconnection().getProjectId())


    def create_evs_disk(self, name, availability_zone=None, size=None, backupid=None,
                        volume_type=None, description=None, imageRef=None,
                        count=1, encryption=False, cmkid=None, snapshotid=None, multiattach=False, hwpassthrough=None):
        '''create an EVS disk'''
        if volume_type is None:
            volume_type = self._get_default_volume_type()
        if backupid is None and size is None:
            size = 10
        # Forcing to Set AZ - Used for testing services on specific AZ
        if 'SET_AZ' in os.environ:
            availability_zone = os.environ['SET_AZ']
        if availability_zone is None:
            availability_zone = self._get_default_availability_zone(volume_type)
        return self.evs.createdisk(name, availability_zone, size, backupid, volume_type, description, imageRef, count,
                                   encryption, cmkid, snapshotid, multiattach, hwpassthrough)

    def _get_default_availability_zone(self, volume_type):
        ...

    def _get_default_volume_type(self):
        ...

Модули OTCLibrary основанные на otcextensions

cbr_robot_ext.py

from OtcLibrary.core.cloudconnection import cloudconnection
class cbr_robot_ext():
    """ Robot Library to access the OTC Cloud Backup and recovery service """

    def __init__(self, api_version='3'):
        self.api_version = api_version
        self.region = cloudconnection().getregion()
        self.cbr = cloudconnection().getOtcextensionsConnection().cbr

    def query_cbr_backups(self, **query):
        ...

В будущем планируется полный переход с otcmodules на otcextensions. В использовании otcextensions есть несколько плюсов:

  • при использовании otcextensions можно полностью отказаться от otcmodules

  • otcextensions – open-source проект. Для QA это означает большую вероятность выявления багов в процессе использования не только самими QA, но так же пользователями и коллегами из других команд. Текущий же проект является внутренним и используется только QA

  • в основе otcextensions используется Openstack SDK, для построения запросов используются базовые методы openstack типо _find(), _get(), _delete() и проч. Разрабатывая модули по новой документации с использованием базовых методов openstack, QA легче находить ошибки документации, ошибки в логике API и тем самым делать облачный сервис совместимым с openstack

Итоги

Текущая инфраструктура позволяет максимально быстро вовлечь нового QA инженера в работу. В начале «трудовой деятельности» каждого QA-инженера создается VM с предустановленными библиотеками Otcmodules и OTC Library. Деплой и настройка происходит с помощью ansible playbook, в основе которого используется коллекция openstack.cloud. Далее настраивается синхронизация тестов с VM, в нашем случае на Pycharm. Осталось заполнить переменные окружения – и все готово! Можно приступать к написанию и запуску тестов.

Таким образом robot framework подходит для большого проекта, в котором нужно быстро ввести в работу QA инженера - независимо от его предыдущего опыта в автоматизации.


Ссылки

Комментарии (0)