diff --git a/cfme/containers/provider/openshift.py b/cfme/containers/provider/openshift.py index b1bca0ae50..c55393a2bf 100644 --- a/cfme/containers/provider/openshift.py +++ b/cfme/containers/provider/openshift.py @@ -87,7 +87,6 @@ class AlertsEndpoint(ServiceBasedEndpoint): @attr.s(eq=False) class OpenshiftProvider(ContainersProvider, ConsoleMixin, Taggable): - num_route = ['num_route'] STATS_TO_MATCH = ContainersProvider.STATS_TO_MATCH + num_route type_name = "openshift" @@ -351,7 +350,7 @@ def _copy_certificate(): provider_ssh = self.cli.ssh_client appliance_ssh = self.appliance.ssh_client() - # Connection to the applince in case of dead connection + # Connection to the appliance in case of dead connection if not appliance_ssh.connected: appliance_ssh.connect() @@ -371,7 +370,7 @@ def _copy_certificate(): message="Copy certificate from OCP to CFME") appliance_ssh.exec_command("update-ca-trust") - # restarting evemserverd to apply the new SSL certificate + # restarting evmserverd to apply the new SSL certificate self.appliance.evmserverd.restart() self.appliance.evmserverd.wait_for_running() self.appliance.wait_for_web_ui() diff --git a/cfme/fixtures/appliance.py b/cfme/fixtures/appliance.py index e1d3ce5ad2..836674a96f 100644 --- a/cfme/fixtures/appliance.py +++ b/cfme/fixtures/appliance.py @@ -11,12 +11,17 @@ :py:func:`temp_appliances_unconfig`. """ from contextlib import contextmanager +from typing import ContextManager +from typing import Iterable +from typing import Tuple import pytest from cfme.test_framework.sprout.client import SproutClient from cfme.utils import conf from cfme.utils import periodic_call +from cfme.utils import the_only_one_from +from cfme.utils.appliance import IPAppliance from cfme.utils.log import logger @@ -31,7 +36,7 @@ def sprout_appliances( provider_type=None, version=None, **kwargs -): +) -> ContextManager[Iterable[IPAppliance]]: """ Provisions one or more appliances for testing Args: @@ -89,21 +94,21 @@ def temp_appliance_preconfig(temp_appliance_preconfig_modscope): @pytest.fixture(scope="module") def temp_appliance_preconfig_modscope(request, appliance, pytestconfig): with sprout_appliances(appliance, config=pytestconfig, preconfigured=True) as appliances: - yield appliances[0] + yield the_only_one_from(appliances) _collect_logs(request.config, appliances) @pytest.fixture(scope="class") def temp_appliance_preconfig_clsscope(request, appliance, pytestconfig): with sprout_appliances(appliance, config=pytestconfig, preconfigured=True) as appliances: - yield appliances[0] + yield the_only_one_from(appliances) _collect_logs(request.config, appliances) @pytest.fixture(scope="function") def temp_appliance_preconfig_funcscope(request, appliance, pytestconfig): with sprout_appliances(appliance, config=pytestconfig, preconfigured=True) as appliances: - yield appliances[0] + yield the_only_one_from(appliances) _collect_logs(request.config, appliances) @@ -118,7 +123,7 @@ def temp_appliance_preconfig_funcscope_upgrade(request, appliance, pytestconfig) split_version[0], (int(split_version[1]) - 1) ) # n-1 stream for upgrade ) as appliances: - yield appliances[0] + yield the_only_one_from(appliances) _collect_logs(request.config, appliances) @@ -129,7 +134,7 @@ def temp_appliance_preconfig_long(request, appliance, pytestconfig): appliance, config=pytestconfig, preconfigured=True, lease_time=1440, provider_type='rhevm' ) as appliances: - yield appliances[0] + yield the_only_one_from(appliances) _collect_logs(request.config, appliances) @@ -140,7 +145,7 @@ def temp_appliance_preconfig_funcscope_rhevm(appliance, pytestconfig): config=pytestconfig, preconfigured=True, provider_type='rhevm' ) as appliances: - yield appliances[0] + yield the_only_one_from(appliances) # Single appliance, unconfigured @@ -152,21 +157,21 @@ def temp_appliance_unconfig(temp_appliance_unconfig_modscope): @pytest.fixture(scope="module") def temp_appliance_unconfig_modscope(request, appliance, pytestconfig): with sprout_appliances(appliance, config=pytestconfig, preconfigured=False) as appliances: - yield appliances[0] + yield the_only_one_from(appliances) _collect_logs(request.config, appliances) @pytest.fixture(scope="class") def temp_appliance_unconfig_clsscope(request, appliance, pytestconfig): with sprout_appliances(appliance, config=pytestconfig, preconfigured=False) as appliances: - yield appliances[0] + yield the_only_one_from(appliances) _collect_logs(request.config, appliances) @pytest.fixture(scope="function") def temp_appliance_unconfig_funcscope(request, appliance, pytestconfig): with sprout_appliances(appliance, config=pytestconfig, preconfigured=False) as appliances: - yield appliances[0] + yield the_only_one_from(appliances) _collect_logs(request.config, appliances) @@ -178,7 +183,7 @@ def temp_appliance_unconfig_funcscope_rhevm(request, appliance, pytestconfig): preconfigured=False, provider_type='rhevm' ) as appliances: - yield appliances[0] + yield the_only_one_from(appliances) _collect_logs(request.config, appliances) @@ -189,7 +194,8 @@ def temp_appliances_unconfig(temp_appliances_unconfig_modscope): @pytest.fixture(scope="module") -def temp_appliances_unconfig_modscope(request, appliance, pytestconfig): +def temp_appliances_unconfig_modscope(request, appliance, pytestconfig) \ + -> Tuple[IPAppliance, IPAppliance]: with sprout_appliances( appliance, config=pytestconfig, @@ -201,7 +207,8 @@ def temp_appliances_unconfig_modscope(request, appliance, pytestconfig): @pytest.fixture(scope="function") -def temp_appliances_unconfig_funcscope_rhevm(request, appliance, pytestconfig): +def temp_appliances_unconfig_funcscope_rhevm(request, appliance, pytestconfig) \ + -> Tuple[IPAppliance, IPAppliance]: with sprout_appliances( appliance, config=pytestconfig, @@ -227,7 +234,8 @@ def temp_appliances_unconfig_modscope_rhevm(request, appliance, pytestconfig): @pytest.fixture(scope="function") -def temp_appliances_preconfig_funcscope(request, appliance, pytestconfig): +def temp_appliances_preconfig_funcscope(request, appliance, pytestconfig) \ + -> Tuple[IPAppliance, IPAppliance]: with sprout_appliances( appliance, config=pytestconfig, @@ -239,7 +247,8 @@ def temp_appliances_preconfig_funcscope(request, appliance, pytestconfig): @pytest.fixture(scope="class") -def temp_appliances_unconfig_clsscope(request, appliance, pytestconfig): +def temp_appliances_unconfig_clsscope(request, appliance, pytestconfig) \ + -> Tuple[IPAppliance, IPAppliance]: with sprout_appliances( appliance, config=pytestconfig, @@ -251,7 +260,8 @@ def temp_appliances_unconfig_clsscope(request, appliance, pytestconfig): @pytest.fixture(scope="function") -def temp_appliances_unconfig_funcscope(request, appliance, pytestconfig): +def temp_appliances_unconfig_funcscope(request, appliance, pytestconfig) \ + -> Tuple[IPAppliance, IPAppliance]: with sprout_appliances( appliance, config=pytestconfig, diff --git a/cfme/fixtures/cli.py b/cfme/fixtures/cli.py index 4e47ec1f95..7abe0cd061 100644 --- a/cfme/fixtures/cli.py +++ b/cfme/fixtures/cli.py @@ -3,6 +3,9 @@ from configparser import ConfigParser from contextlib import contextmanager from io import StringIO +from typing import ContextManager +from typing import Iterable +from typing import Tuple import fauxfactory import pytest @@ -18,10 +21,12 @@ from cfme.test_framework.sprout.client import AuthException from cfme.test_framework.sprout.client import SproutClient from cfme.utils import conf -from cfme.utils.appliance.console import configure_appliances_ha +from cfme.utils import the_only_one_from +from cfme.utils.appliance import IPAppliance from cfme.utils.conf import auth_data from cfme.utils.conf import cfme_data from cfme.utils.conf import credentials +from cfme.utils.ha import configure_appliances_ha from cfme.utils.log import logger from cfme.utils.providers import list_providers_by_class from cfme.utils.version import Version @@ -39,7 +44,7 @@ def unconfigured_appliance(request, appliance, pytestconfig): config=pytestconfig, provider_type='rhevm', ) as apps: - yield apps[0] + yield the_only_one_from(apps) _collect_logs(request.config, apps) @@ -52,7 +57,7 @@ def unconfigured_appliance_secondary(request, appliance, pytestconfig): config=pytestconfig, provider_type='rhevm', ) as apps: - yield apps[0] + yield the_only_one_from(apps) _collect_logs(request.config, apps) @@ -78,7 +83,7 @@ def configured_appliance(request, appliance, pytestconfig): config=pytestconfig, provider_type='rhevm', ) as apps: - yield apps[0] + yield the_only_one_from(apps) _collect_logs(request.config, apps) @@ -190,7 +195,8 @@ def get_puddle_cfme_version(repo_file_path): @contextmanager -def get_apps(requests, appliance, old_version, count, preconfigured, pytest_config): +def get_apps(request, appliance, old_version, count, preconfigured, pytest_config) \ + -> ContextManager[Iterable[IPAppliance]]: """Requests appliance from sprout based on old_versions, edits partitions and adds repo file for update""" series = appliance.version.series() @@ -240,23 +246,25 @@ def appliance_preupdate(appliance, old_version, request): """Requests single appliance from sprout.""" with get_apps(request, appliance, old_version, count=1, preconfigured=True, pytest_config=request.config) as apps: - yield apps[0] + yield the_only_one_from(apps) @pytest.fixture -def multiple_preupdate_appliances(appliance, old_version, request): +def multiple_preupdate_appliances(appliance, old_version, request) \ + -> Tuple[IPAppliance, IPAppliance]: """Requests multiple appliances from sprout.""" with get_apps(request, appliance, old_version, count=2, preconfigured=False, pytest_config=request.config) as apps: - yield apps + yield tuple(apps) @pytest.fixture -def ha_multiple_preupdate_appliances(appliance, old_version, request): +def ha_multiple_preupdate_appliances(appliance, old_version, request) \ + -> Tuple[IPAppliance, IPAppliance, IPAppliance]: """Requests multiple appliances from sprout.""" with get_apps(request, appliance, old_version, count=3, preconfigured=False, pytest_config=request.config) as apps: - yield apps + yield tuple(apps) @pytest.fixture diff --git a/cfme/fixtures/multi_region.py b/cfme/fixtures/multi_region.py index 8bd00dcadb..7e4953fb05 100644 --- a/cfme/fixtures/multi_region.py +++ b/cfme/fixtures/multi_region.py @@ -1,6 +1,11 @@ +from typing import List +from typing import Optional + import attr import pytest +from cfme.utils.appliance import IPAppliance + @attr.s class ApplianceCluster: @@ -9,8 +14,8 @@ class ApplianceCluster: Appliance from global region is stored in global_appliance. Whereas remote region appliances are stored in remote_appliances property. """ - global_appliance = attr.ib(default=None) - remote_appliances = attr.ib(default=[]) + global_appliance: Optional[IPAppliance] = attr.ib(default=None) + remote_appliances: List[IPAppliance] = attr.ib(default=[]) @pytest.fixture(scope='module') diff --git a/cfme/fixtures/pytest_store.py b/cfme/fixtures/pytest_store.py index b38e16f06f..4c79a060f6 100644 --- a/cfme/fixtures/pytest_store.py +++ b/cfme/fixtures/pytest_store.py @@ -53,9 +53,9 @@ class Store: @property def current_appliance(self): - # layz import due to loops and loops and loops + # lazy import due to loops and loops and loops from cfme.utils import appliance - # TODO: concieve a better way to detect/log import-time missuse + # TODO: conceive a better way to detect/log import-time misuse # assert self.config is not None, 'current appliance not in scope' return appliance.current_appliance diff --git a/cfme/fixtures/v2v_fixtures.py b/cfme/fixtures/v2v_fixtures.py index 48d804afa2..1923eb918d 100644 --- a/cfme/fixtures/v2v_fixtures.py +++ b/cfme/fixtures/v2v_fixtures.py @@ -16,6 +16,7 @@ from cfme.infrastructure.provider.virtualcenter import VMwareProvider from cfme.utils import conf from cfme.utils import ssh +from cfme.utils.appliance import IPAppliance from cfme.utils.appliance.implementations.ui import navigate_to from cfme.utils.blockers import BZ from cfme.utils.generators import random_vm_name @@ -32,7 +33,7 @@ V2vProviders = namedtuple("V2vProviders", ["vmware_provider", "rhv_provider", "osp_provider"]) -def set_skip_event_history_flag(appliance): +def set_skip_event_history_flag(appliance: IPAppliance): """This flag is required for OSP to skip all old events and refresh inventory""" config = appliance.advanced_settings if not config['ems']['ems_openstack']['event_handling']['event_skip_history']: @@ -44,7 +45,7 @@ def set_skip_event_history_flag(appliance): appliance.wait_for_api_available() -def _start_event_workers_for_osp(appliance, provider): +def _start_event_workers_for_osp(appliance: IPAppliance, provider): """This is a workaround to start event catchers until BZ 1753364 is fixed""" provider_edit_view = navigate_to(provider, 'Edit', wait_for_view=30) endpoint_view = provider.endpoints_form(parent=provider_edit_view) @@ -65,7 +66,7 @@ def is_osp_worker_running(): @pytest.fixture(scope="module") -def v2v_provider_setup(request, appliance, source_provider, provider): +def v2v_provider_setup(request, appliance: IPAppliance, source_provider, provider): """ Fixture to setup providers """ vmware_provider, rhv_provider, osp_provider = None, None, None for v2v_provider in [source_provider, provider]: @@ -102,7 +103,7 @@ def v2v_provider_setup(request, appliance, source_provider, provider): v2v_provider.delete_if_exists(cancel=False) -def __host_credentials(appliance, transformation_method, v2v_providers): # noqa +def __host_credentials(appliance: IPAppliance, transformation_method, v2v_providers): # noqa """ Sets up host credentials for vmware and rhv providers for RHEV migration. For migration with OSP only vmware(source) provider @@ -182,7 +183,7 @@ def extract_tag(tag): return False -def create_tags(appliance, transformation_method): +def create_tags(appliance: IPAppliance, transformation_method): """ Create tags V2V - Transformation Host * and V2V - Transformation Method Args: @@ -252,7 +253,7 @@ def get_conversion_data(target_provider): def set_conversion_host_api( - appliance, transformation_method, source_provider, target_provider): + appliance: IPAppliance, transformation_method, source_provider, target_provider): """Setting conversion host for RHV and OSP providers via REST""" vmware_ssh_private_key = None vmware_vddk_package_url = None @@ -294,7 +295,7 @@ def set_conversion_host_api( @pytest.fixture(scope="function") -def delete_conversion_hosts(appliance): +def delete_conversion_hosts(appliance: IPAppliance): # Delete existing conversion host entries from CFME delete_hosts = appliance.ssh_client.run_rails_command( "'MiqTask.delete_all; ConversionHost.delete_all'") @@ -317,7 +318,7 @@ def cleanup_target(provider, migrated_vm): logger.warning(e) -def get_vm(request, appliance, source_provider, template_type, datastore='nfs'): +def get_vm(request, appliance: IPAppliance, source_provider, template_type, datastore='nfs'): """ Helper method that takes template , source provider and datastore and creates VM on source provider to migrate . @@ -404,7 +405,7 @@ def infra_mapping_default_data(source_provider, provider): @pytest.fixture(scope="function") -def mapping_data_vm_obj_mini(request, appliance, source_provider, provider): +def mapping_data_vm_obj_mini(request, appliance: IPAppliance, source_provider, provider): """Fixture to return minimal mapping data and vm object for migration plan""" infra_mapping_data = infra_mapping_default_data(source_provider, provider) vm_obj = get_vm(request, appliance, source_provider, Templates.RHEL7_MINIMAL) @@ -537,7 +538,7 @@ def mapping_data_dual_vm_obj_dual_datastore(request, appliance, source_provider, @pytest.fixture(scope="function") -def mapping_data_vm_obj_dual_nics(request, appliance, source_provider, provider): +def mapping_data_vm_obj_dual_nics(request, appliance: IPAppliance, source_provider, provider): source_type = ["VM Network", "DPortGroup"] dest_type = ["ovirtmgmt", "Storage - VLAN 33"] @@ -566,7 +567,7 @@ def mapping_data_vm_obj_dual_nics(request, appliance, source_provider, provider) @pytest.fixture(scope="function") -def mapping_data_vm_obj_single_datastore(request, appliance, source_provider, provider, +def mapping_data_vm_obj_single_datastore(request, appliance: IPAppliance, source_provider, provider, source_type, dest_type, template_type): """Return Infra Mapping form data and vm object""" infra_mapping_data = infra_mapping_default_data(source_provider, provider) @@ -588,7 +589,7 @@ def mapping_data_vm_obj_single_datastore(request, appliance, source_provider, pr @pytest.fixture(scope="function") -def mapping_data_vm_obj_single_network(request, appliance, source_provider, provider, +def mapping_data_vm_obj_single_network(request, appliance: IPAppliance, source_provider, provider, source_type, dest_type, template_type): infra_mapping_data = infra_mapping_default_data(source_provider, provider) recursive_update( diff --git a/cfme/test_framework/sprout/client.py b/cfme/test_framework/sprout/client.py index fec9b671c5..578a23b04b 100644 --- a/cfme/test_framework/sprout/client.py +++ b/cfme/test_framework/sprout/client.py @@ -1,5 +1,6 @@ import json import os +from typing import List import attr import requests @@ -130,7 +131,7 @@ def provision_appliances( message=f'provision {count} appliance(s) from sprout') data = self.call_method('request_check', str(request_id)) logger.debug(data) - appliances = [] + appliances: List[IPAppliance] = [] for appliance in data['appliances']: app_args = {'hostname': appliance['ip_address'], 'project': appliance['project'], diff --git a/cfme/tests/cli/test_appliance_cli.py b/cfme/tests/cli/test_appliance_cli.py index 5b46ac8f90..f93de2a734 100644 --- a/cfme/tests/cli/test_appliance_cli.py +++ b/cfme/tests/cli/test_appliance_cli.py @@ -2,7 +2,7 @@ from wait_for import wait_for from cfme import test_requirements -from cfme.utils.appliance.console import waiting_for_ha_monitor_started +from cfme.utils.ha import waiting_for_ha_monitor_started from cfme.utils.log_validator import LogValidator pytestmark = [ diff --git a/cfme/tests/cli/test_appliance_console.py b/cfme/tests/cli/test_appliance_console.py index 2149194815..f12cb74470 100644 --- a/cfme/tests/cli/test_appliance_console.py +++ b/cfme/tests/cli/test_appliance_console.py @@ -12,10 +12,10 @@ from cfme.exceptions import SSHExpectTimeoutError from cfme.tests.cli import app_con_menu from cfme.utils import conf -from cfme.utils.appliance.console import waiting_for_ha_monitor_started from cfme.utils.appliance.implementations.ui import navigate_to from cfme.utils.blockers import BZ from cfme.utils.conf import credentials +from cfme.utils.ha import waiting_for_ha_monitor_started from cfme.utils.log import logger from cfme.utils.log_validator import LogValidator from cfme.utils.net import net_check diff --git a/cfme/tests/cli/test_appliance_console_db_restore.py b/cfme/tests/cli/test_appliance_console_db_restore.py index c328418690..933ec8400f 100644 --- a/cfme/tests/cli/test_appliance_console_db_restore.py +++ b/cfme/tests/cli/test_appliance_console_db_restore.py @@ -9,12 +9,13 @@ from cfme.cloud.provider.openstack import OpenStackProvider from cfme.fixtures.cli import provider_app_crud from cfme.infrastructure.provider.virtualcenter import VMwareProvider -from cfme.utils.appliance.console import configure_appliances_ha -from cfme.utils.appliance.console import waiting_for_ha_monitor_started from cfme.utils.appliance.implementations.ui import navigate_to from cfme.utils.browser import manager from cfme.utils.conf import cfme_data from cfme.utils.conf import credentials +from cfme.utils.ha import configure_appliances_ha +from cfme.utils.ha import configure_automatic_failover +from cfme.utils.ha import waiting_for_ha_monitor_started from cfme.utils.log import logger from cfme.utils.log_validator import LogValidator from cfme.utils.ssh_expect import SSHExpect @@ -542,7 +543,7 @@ def test_appliance_console_restore_db_ha(request, unconfigured_appliances, app_c appl1.appliance_console.reconfigure_primary_replication_node(pwd) appl2.appliance_console.reconfigure_standby_replication_node(pwd, appl1.hostname) - appl3.appliance_console.configure_automatic_failover(primary_ip=appl1.hostname) + configure_automatic_failover(appl3, primary_ip=appl1.hostname) appl3.evm_failover_monitor.restart() appl3.evmserverd.start() diff --git a/cfme/utils/__init__.py b/cfme/utils/__init__.py index b1c8974122..fef42853ac 100644 --- a/cfme/utils/__init__.py +++ b/cfme/utils/__init__.py @@ -5,15 +5,19 @@ import threading from contextlib import contextmanager from functools import partial +from typing import Iterable +from typing import TypeVar -import diaper +import diaper # import diaper for backward compatibility from cached_property import cached_property from werkzeug.local import LocalProxy -# import diaper for backward compatibility on_rtd = os.environ.get('READTHEDOCS') == 'True' +T = TypeVar('T') + + class TriesExceeded(Exception): """Default exception raised when tries() method doesn't catch a func exception""" pass @@ -411,3 +415,11 @@ def reschedule(): yield finally: timer.cancel() + + +def the_only_one_from(iterable: Iterable[T]) -> T: + """ Returns the first element from iterable, preserving type, + checking whether it is the only one in it. + """ + obj, = iterable + return obj diff --git a/cfme/utils/appliance/__init__.py b/cfme/utils/appliance/__init__.py index df81de6fa0..16d63424c3 100644 --- a/cfme/utils/appliance/__init__.py +++ b/cfme/utils/appliance/__init__.py @@ -57,6 +57,7 @@ from cfme.utils.wait import TimedOutError from cfme.utils.wait import wait_for + RUNNING_UNDER_SPROUT = os.environ.get("RUNNING_UNDER_SPROUT", "false") != "false" # EMS types recognized by IP or credentials RECOGNIZED_BY_IP = [ @@ -2985,7 +2986,7 @@ def _get_latest_template(): class ApplianceStack(LocalStack): - def push(self, obj): + def push(self, obj: IPAppliance): stack_parent = self.top super().push(obj) @@ -2995,7 +2996,7 @@ def push(self, obj): from cfme.utils import browser browser.start() - def pop(self): + def pop(self) -> IPAppliance: stack_parent = super().pop() current = self.top logger.info(f"Popped appliance {getattr(stack_parent, 'hostname', 'empty')} from stack\n" @@ -3165,7 +3166,7 @@ class ApplianceSummoningWarning(PendingDeprecationWarning): """to ease filtering/erroring on magical appliance creation based on script vs code""" -def get_or_create_current_appliance(): +def get_or_create_current_appliance() -> IPAppliance: if stack.top is None: warnings.warn( "magical creation of appliance objects has been deprecated," diff --git a/cfme/utils/appliance/console.py b/cfme/utils/appliance/console.py index b8a0d08325..a019462000 100644 --- a/cfme/utils/appliance/console.py +++ b/cfme/utils/appliance/console.py @@ -1,20 +1,16 @@ import re import socket -from contextlib import contextmanager import lxml import yaml from cfme.utils.appliance.plugin import AppliancePlugin from cfme.utils.log import logger -from cfme.utils.log_validator import LogValidator from cfme.utils.path import scripts_path from cfme.utils.ssh_expect import SSHExpect from cfme.utils.version import LOWEST from cfme.utils.version import Version from cfme.utils.version import VersionPicker -from cfme.utils.wait import wait_for - AP_WELCOME_SCREEN_TIMEOUT = 30 @@ -248,150 +244,10 @@ def reconfigure_standby_replication_node(self, pwd, primary_ip, repmgr_reconfigu re.escape('Apply this Replication Server Configuration? (Y/N): '), 'y') interaction.answer('Press any key to continue.', '', timeout=AP_WELCOME_SCREEN_TIMEOUT) - def configure_automatic_failover(self, primary_ip): - # Configure automatic failover on EVM appliance - with SSHExpect(self.appliance) as interaction: - interaction.send('ap') - interaction.answer('Press any key to continue.', '', timeout=AP_WELCOME_SCREEN_TIMEOUT) - interaction.expect('Choose the advanced setting: ') - - with waiting_for_ha_monitor_started(self.appliance, primary_ip, timeout=300): - # Configure Application Database Failover Monitor - interaction.send(VersionPicker({ - LOWEST: 8, - '5.10': 10, - '5.11.2.1': 8 - })) - - interaction.answer('Choose the failover monitor configuration: ', '1') - # Failover Monitor Service configured successfully - interaction.answer('Press any key to continue.', '') - - -@contextmanager -def waiting_for_ha_monitor_started(appl, standby_server_ip, timeout): - if appl.version < '5.10': - with LogValidator( - "/var/www/miq/vmdb/config/failover_databases.yml", - matched_patterns=[standby_server_ip], - hostname=appl.hostname).waiting(timeout=timeout): - yield - else: - yield - wait_for(lambda: appl.evm_failover_monitor.running, timeout=300) - - -def configure_appliances_ha(appliances, pwd): - """Configure HA environment - - Appliance one configuring dedicated database, 'ap' launch appliance_console, - '' clear info screen, '5' setup db, '1' Creates v2_key, '1' selects internal db, - '1' use partition, 'y' create dedicated db, 'pwd' db password, 'pwd' confirm db password + wait - and '' finish. - - Appliance two creating region in dedicated database, 'ap' launch appliance_console, '' clear - info screen, '5' setup db, '2' fetch v2_key, 'app0_ip' appliance ip address, '' default user, - 'pwd' appliance password, '' default v2_key location, '2' create region in external db, '0' db - region number, 'y' confirm create region in external db 'app0_ip', '' ip and default port for - dedicated db, '' use default db name, '' default username, 'pwd' db password, 'pwd' confirm db - password + wait and '' finish. - - Appliance one configuring primary node for replication, 'ap' launch appliance_console, '' clear - info screen, '6' configure db replication, '1' configure node as primary, '1' cluster node - number set to 1, '' default dbname, '' default user, 'pwd' password, 'pwd' confirm password, - 'app0_ip' primary appliance ip, confirm settings and wait to configure, '' finish. - - - Appliance three configuring standby node for replication, 'ap' launch appliance_console, '' - clear info screen, '6' configure db replication, '2' configure node as standby, '2' cluster node - number set to 2, '' default dbname, '' default user, 'pwd' password, 'pwd' confirm password, - 'app0_ip' primary appliance ip, app1_ip standby appliance ip, confirm settings and wait - to configure finish, '' finish. - - - Appliance two configuring automatic failover of database nodes, 'ap' launch appliance_console, - '' clear info screen '9' configure application database failover monitor, '1' start failover - monitor. wait 30 seconds for service to start '' finish. - - """ - apps0, apps1, apps2 = appliances - app0_ip = apps0.hostname - - # Configure first appliance as dedicated database - with SSHExpect(apps0) as interaction: - interaction.send('ap') - interaction.answer('Press any key to continue.', '', timeout=AP_WELCOME_SCREEN_TIMEOUT) - interaction.answer('Choose the advanced setting: ', VersionPicker({ - LOWEST: 5, - '5.10': 7, - '5.11.2.1': 5 - })) # Configure Database - interaction.answer(re.escape('Choose the encryption key: |1| '), '1') - interaction.answer('Choose the database operation: ', '1') - # On 5.10, rhevm provider: - # - # database disk - # - # 1) /dev/sr0: 0 MB - # 2) /dev/vdb: 4768 MB - # 3) Don't partition the disk - if apps0.version < '5.11.2.0': - interaction.answer(re.escape('Choose the database disk: '), - '1' if apps0.version < '5.10' else '2') - else: - interaction.answer(re.escape('Choose the database disk: |1| '), '') - - # Should this appliance run as a standalone database server? - interaction.answer(re.escape('? (Y/N): |N| '), 'y') - interaction.answer('Enter the database password on localhost: ', pwd) - interaction.answer('Enter the database password again: ', pwd) - # Configuration activated successfully. - interaction.answer('Press any key to continue.', '', timeout=6 * 60) - - wait_for(lambda: apps0.db.is_dedicated_active, num_sec=4 * 60) - - # Configure EVM webui appliance with create region in dedicated database - with SSHExpect(apps2) as interaction: - interaction.send('ap') - interaction.answer('Press any key to continue.', '', timeout=AP_WELCOME_SCREEN_TIMEOUT) - interaction.answer('Choose the advanced setting: ', VersionPicker({ - LOWEST: 5, - '5.10': 7, - '5.11.2.1': 5 - })) # Configure Database - interaction.answer(re.escape('Choose the encryption key: |1| '), '2') - interaction.send(app0_ip) - interaction.answer(re.escape('Enter the appliance SSH login: |root| '), '') - interaction.answer('Enter the appliance SSH password: ', pwd) - interaction.answer( - re.escape('Enter the path of remote encryption key: |/var/www/miq/vmdb/certs/v2_key| '), - '') - interaction.answer('Choose the database operation: ', '2', timeout=30) - interaction.answer('Enter the database region number: ', '0') - # WARNING: Creating a database region will destroy any existing data and - # cannot be undone. - interaction.answer(re.escape('Are you sure you want to continue? (Y/N):'), 'y') - interaction.answer('Enter the database hostname or IP address: ', app0_ip) - interaction.answer(re.escape('Enter the port number: |5432| '), '') - interaction.answer(r'Enter the name of the database on .*: \|vmdb_production\| ', '') - interaction.answer(re.escape('Enter the username: |root| '), '') - interaction.answer('Enter the database password on .*: ', pwd) - # Configuration activated successfully. - interaction.answer('Press any key to continue.', '', timeout=360) - - apps2.evmserverd.wait_for_running() - apps2.wait_for_web_ui() - - apps0.appliance_console.configure_primary_replication_node(pwd) - apps1.appliance_console.configure_standby_replication_node(pwd, app0_ip) - - apps2.appliance_console.configure_automatic_failover(primary_ip=None) - return appliances - def answer_cluster_related_questions(interaction, node_uid, db_name, db_username, db_password): - # It seems like sometimes, the word "Enter " ... dosen't fit to the paramiko-expect buffer. + # It seems like sometimes, the word "Enter " ... doesn't fit to the paramiko-expect buffer. # This seems to happen when (re)configuring the standby replication node. interaction.answer('.* the number uniquely identifying ' 'this node in the replication cluster: ', node_uid) diff --git a/cfme/utils/appliance/implementations/__init__.py b/cfme/utils/appliance/implementations/__init__.py index 0ecce2d58b..12430be77e 100644 --- a/cfme/utils/appliance/implementations/__init__.py +++ b/cfme/utils/appliance/implementations/__init__.py @@ -1,13 +1,19 @@ +from typing import TypeVar + from cfme.utils.browser import manager from cfme.utils.log import logger +# A hack to workaround problem with circular import of modules +# for the IPAppliance class +T = TypeVar('T') + class Implementation: """UI implementation using the normal ux""" navigator = None - def __init__(self, owner): + def __init__(self, owner: T): self.owner = owner @property diff --git a/cfme/utils/appliance/plugin.py b/cfme/utils/appliance/plugin.py index 01fe8136ed..02c1d90a3e 100644 --- a/cfme/utils/appliance/plugin.py +++ b/cfme/utils/appliance/plugin.py @@ -1,9 +1,13 @@ +from typing import Type +from typing import TypeVar from weakref import proxy from weakref import WeakKeyDictionary import attr from cached_property import cached_property +T = TypeVar('T') + class AppliancePluginException(Exception): """Base class for all custom exceptions raised from plugins.""" @@ -21,7 +25,8 @@ def __get__(self, o, t=None): return self if o not in self.cache: - self.cache[o] = self.cls(o, *self.args, **self.kwargs) + self.cache[o] = new_instance = self.cls(o, *self.args, **self.kwargs) + return new_instance return self.cache[o] @@ -41,6 +46,7 @@ class IPAppliance(object): Instance of such plugin is then created upon first access. """ + # appliance: IPAppliance = attr.ib(repr=False, converter=proxy) appliance = attr.ib(repr=False, converter=proxy) @cached_property @@ -50,5 +56,5 @@ def logger(self): return logger @classmethod - def declare(cls, **kwargs): + def declare(cls: Type[T], **kwargs) -> T: return AppliancePluginDescriptor(cls, (), kwargs) diff --git a/cfme/utils/ha.py b/cfme/utils/ha.py new file mode 100644 index 0000000000..988cf6387b --- /dev/null +++ b/cfme/utils/ha.py @@ -0,0 +1,153 @@ +import re +from contextlib import contextmanager +from typing import Tuple + +from miq_version import LOWEST + +from cfme.utils.appliance import IPAppliance +from cfme.utils.appliance.console import AP_WELCOME_SCREEN_TIMEOUT +from cfme.utils.log_validator import LogValidator +from cfme.utils.ssh_expect import SSHExpect +from cfme.utils.version import VersionPicker +from cfme.utils.wait import wait_for + + +@contextmanager +def waiting_for_ha_monitor_started(appl, standby_server_ip, timeout): + if appl.version < '5.10': + with LogValidator( + "/var/www/miq/vmdb/config/failover_databases.yml", + matched_patterns=[standby_server_ip], + hostname=appl.hostname).waiting(timeout=timeout): + yield + else: + yield + wait_for(lambda: appl.evm_failover_monitor.running, timeout=300) + + +def configure_appliances_ha(appliances: Tuple[IPAppliance, IPAppliance, IPAppliance], pwd): + """Configure HA environment + + Appliance one configuring dedicated database, 'ap' launch appliance_console, + '' clear info screen, '5' setup db, '1' Creates v2_key, '1' selects internal db, + '1' use partition, 'y' create dedicated db, 'pwd' db password, 'pwd' confirm db password + wait + and '' finish. + + Appliance two creating region in dedicated database, 'ap' launch appliance_console, '' clear + info screen, '5' setup db, '2' fetch v2_key, 'app0_ip' appliance ip address, '' default user, + 'pwd' appliance password, '' default v2_key location, '2' create region in external db, '0' db + region number, 'y' confirm create region in external db 'app0_ip', '' ip and default port for + dedicated db, '' use default db name, '' default username, 'pwd' db password, 'pwd' confirm db + password + wait and '' finish. + + Appliance one configuring primary node for replication, 'ap' launch appliance_console, '' clear + info screen, '6' configure db replication, '1' configure node as primary, '1' cluster node + number set to 1, '' default dbname, '' default user, 'pwd' password, 'pwd' confirm password, + 'app0_ip' primary appliance ip, confirm settings and wait to configure, '' finish. + + + Appliance three configuring standby node for replication, 'ap' launch appliance_console, '' + clear info screen, '6' configure db replication, '2' configure node as standby, '2' cluster node + number set to 2, '' default dbname, '' default user, 'pwd' password, 'pwd' confirm password, + 'app0_ip' primary appliance ip, app1_ip standby appliance ip, confirm settings and wait + to configure finish, '' finish. + + + Appliance two configuring automatic failover of database nodes, 'ap' launch appliance_console, + '' clear info screen '9' configure application database failover monitor, '1' start failover + monitor. wait 30 seconds for service to start '' finish. + + """ + apps0, apps1, apps2 = appliances + app0_ip = apps0.hostname + + # Configure first appliance as dedicated database + with SSHExpect(apps0) as interaction: + interaction.send('ap') + interaction.answer('Press any key to continue.', '', timeout=AP_WELCOME_SCREEN_TIMEOUT) + interaction.answer('Choose the advanced setting: ', VersionPicker({ + LOWEST: 5, + '5.10': 7, + '5.11.2.1': 5 + })) # Configure Database + interaction.answer(re.escape('Choose the encryption key: |1| '), '1') + interaction.answer('Choose the database operation: ', '1') + # On 5.10, rhevm provider: + # + # database disk + # + # 1) /dev/sr0: 0 MB + # 2) /dev/vdb: 4768 MB + # 3) Don't partition the disk + if apps0.version < '5.11.2.0': + interaction.answer(re.escape('Choose the database disk: '), + '1' if apps0.version < '5.10' else '2') + else: + interaction.answer(re.escape('Choose the database disk: |1| '), '') + + # Should this appliance run as a standalone database server? + interaction.answer(re.escape('? (Y/N): |N| '), 'y') + interaction.answer('Enter the database password on localhost: ', pwd) + interaction.answer('Enter the database password again: ', pwd) + # Configuration activated successfully. + interaction.answer('Press any key to continue.', '', timeout=6 * 60) + + wait_for(lambda: apps0.db.is_dedicated_active, num_sec=4 * 60) + + # Configure EVM webui appliance with create region in dedicated database + with SSHExpect(apps2) as interaction: + interaction.send('ap') + interaction.answer('Press any key to continue.', '', timeout=AP_WELCOME_SCREEN_TIMEOUT) + interaction.answer('Choose the advanced setting: ', VersionPicker({ + LOWEST: 5, + '5.10': 7, + '5.11.2.1': 5 + })) # Configure Database + interaction.answer(re.escape('Choose the encryption key: |1| '), '2') + interaction.send(app0_ip) + interaction.answer(re.escape('Enter the appliance SSH login: |root| '), '') + interaction.answer('Enter the appliance SSH password: ', pwd) + interaction.answer( + re.escape('Enter the path of remote encryption key: |/var/www/miq/vmdb/certs/v2_key| '), + '') + interaction.answer('Choose the database operation: ', '2', timeout=30) + interaction.answer('Enter the database region number: ', '0') + # WARNING: Creating a database region will destroy any existing data and + # cannot be undone. + interaction.answer(re.escape('Are you sure you want to continue? (Y/N):'), 'y') + interaction.answer('Enter the database hostname or IP address: ', app0_ip) + interaction.answer(re.escape('Enter the port number: |5432| '), '') + interaction.answer(r'Enter the name of the database on .*: \|vmdb_production\| ', '') + interaction.answer(re.escape('Enter the username: |root| '), '') + interaction.answer('Enter the database password on .*: ', pwd) + # Configuration activated successfully. + interaction.answer('Press any key to continue.', '', timeout=360) + + apps2.evmserverd.wait_for_running() + apps2.wait_for_web_ui() + + apps0.appliance_console.configure_primary_replication_node(pwd) + apps1.appliance_console.configure_standby_replication_node(pwd, app0_ip) + + configure_automatic_failover(apps2, primary_ip=None) + return appliances + + +def configure_automatic_failover(appliance: IPAppliance, primary_ip): + # Configure automatic failover on EVM appliance + with SSHExpect(appliance) as interaction: + interaction.send('ap') + interaction.answer('Press any key to continue.', '', timeout=AP_WELCOME_SCREEN_TIMEOUT) + interaction.expect('Choose the advanced setting: ') + + with waiting_for_ha_monitor_started(appliance, primary_ip, timeout=300): + # Configure Application Database Failover Monitor + interaction.send(VersionPicker({ + LOWEST: 8, + '5.10': 10, + '5.11.2.1': 8 + })) + + interaction.answer('Choose the failover monitor configuration: ', '1') + # Failover Monitor Service configured successfully + interaction.answer('Press any key to continue.', '')