diff --git a/cfme/cloud/provider/__init__.py b/cfme/cloud/provider/__init__.py index b85c616d00..33e065cf4e 100644 --- a/cfme/cloud/provider/__init__.py +++ b/cfme/cloud/provider/__init__.py @@ -139,6 +139,8 @@ class CloudProvider(BaseProvider, CloudInfraProviderMixin, Pretty, PolicyProfile db_types = ["CloudManager"] template_class = Image collection_name = 'cloud_providers' + provisioning_dialog_widget_names = BaseProvider.provisioning_dialog_widget_names.union({ + 'properties', 'volumes', 'customize'}) name = attr.ib(default=None) key = attr.ib(default=None) diff --git a/cfme/cloud/provider/ec2.py b/cfme/cloud/provider/ec2.py index aee3b51b7d..4fa91d2239 100644 --- a/cfme/cloud/provider/ec2.py +++ b/cfme/cloud/provider/ec2.py @@ -93,6 +93,9 @@ class EC2Provider(CloudProvider): region = attr.ib(default=None) region_name = attr.ib(default=None) + provisioning_dialog_widget_names = CloudProvider.provisioning_dialog_widget_names.difference( + {'volumes'}) + @property def view_value_mapping(self): """Maps values to view attrs""" diff --git a/cfme/common/provider.py b/cfme/common/provider.py index 608bd5ed25..557803c534 100644 --- a/cfme/common/provider.py +++ b/cfme/common/provider.py @@ -101,6 +101,7 @@ class BaseProvider(Taggable, Updateable, Navigatable, BaseEntity, CustomButtonEv _param_name = ParamClassName('name') STATS_TO_MATCH = [] db_types = ["Providers"] + provisioning_dialog_widget_names = {'request', 'purpose', 'catalog', 'environment', 'schedule'} ems_events = [] settings_key = None vm_class = None # Set on type specific provider classes for VM/instance class diff --git a/cfme/common/vm.py b/cfme/common/vm.py index 714c69d041..327e7cfa03 100644 --- a/cfme/common/vm.py +++ b/cfme/common/vm.py @@ -4,11 +4,13 @@ from datetime import datetime import attr +import fauxfactory from cached_property import cached_property from manageiq_client.filters import Q from navmazing import NavigateToSibling from riggerlib import recursive_update from widgetastic.exceptions import NoSuchElementException +from widgetastic.utils import partial_match from cfme.common import BaseLoggedInPage from cfme.common import CustomButtonEventsMixin @@ -33,6 +35,7 @@ from cfme.utils.appliance.implementations.ui import navigate_to from cfme.utils.appliance.implementations.ui import navigator from cfme.utils.blockers import BZ +from cfme.utils.conf import cfme_data from cfme.utils.log import logger from cfme.utils.net import find_pingable from cfme.utils.pretty import Pretty @@ -274,6 +277,29 @@ def delete(self, cancel=False, from_details=False): self.find_quadicon().ensure_checked() view.toolbar.configuration.item_select(self.REMOVE_SELECTED, handle_alert=not cancel) + def _fill_clone_form(self, view, email=None, first_name=None, last_name=None, + new_name=None, provision_type=None): + first_name = first_name or fauxfactory.gen_alphanumeric() + last_name = last_name or fauxfactory.gen_alphanumeric() + email = email or f"{first_name}@{last_name}.test" + try: + prov_data = cfme_data["management_systems"][self.provider.key]["provisioning"] + except (KeyError, IndexError): + raise ValueError("You have to specify the correct options in cfme_data.yaml") + + provisioning_data = { + 'catalog': {'vm_name': new_name, + 'provision_type': provision_type}, + 'request': { + 'email': email, + 'first_name': first_name, + 'last_name': last_name}, + 'environment': {"host_name": {'name': prov_data.get("host")}, + "datastore_name": {"name": prov_data.get("datastore")}}, + 'network': {'vlan': partial_match(prov_data.get("vlan"))}, + } + view.form.fill_with(provisioning_data, on_change=view.form.submit_button) + @property def ip_address(self): """Fetches IP Address of VM @@ -1074,6 +1100,11 @@ def mgmt(self): def exists_on_provider(self): return self.provider.mgmt.does_template_exist(self.name) + def clone_template(self, email=None, first_name=None, last_name=None, + new_name=None, provision_type=None): + view = navigate_to(self, 'CloneTemplate') + self._fill_clone_form(view, email, first_name, last_name, new_name, provision_type) + @attr.s class TemplateCollection(BaseVMCollection): diff --git a/cfme/infrastructure/provider/__init__.py b/cfme/infrastructure/provider/__init__.py index fbe8333eeb..c4a4f4bf59 100644 --- a/cfme/infrastructure/provider/__init__.py +++ b/cfme/infrastructure/provider/__init__.py @@ -96,6 +96,8 @@ class InfraProvider(BaseProvider, CloudInfraProviderMixin, Pretty, Fillable, hosts_menu_item = "Hosts" vm_name = "Virtual Machines" collection_name = 'infra_providers' + provisioning_dialog_widget_names = BaseProvider.provisioning_dialog_widget_names.union( + {'hardware', 'network', 'customize'}) name = attr.ib(default=None) key = attr.ib(default=None) diff --git a/cfme/infrastructure/provider/scvmm.py b/cfme/infrastructure/provider/scvmm.py index 14d1412963..f9e83fcab5 100644 --- a/cfme/infrastructure/provider/scvmm.py +++ b/cfme/infrastructure/provider/scvmm.py @@ -40,6 +40,8 @@ class SCVMMProvider(InfraProvider): settings_key = 'ems_scvmm' ui_prov_type = 'Microsoft System Center VMM' log_name = 'scvmm' + provisioning_dialog_widget_names = (InfraProvider.provisioning_dialog_widget_names + - {'environment'} | {'customize'}) @property def view_value_mapping(self): diff --git a/cfme/infrastructure/virtual_machines.py b/cfme/infrastructure/virtual_machines.py index 6c1ff2f3b3..c95d3e5c76 100644 --- a/cfme/infrastructure/virtual_machines.py +++ b/cfme/infrastructure/virtual_machines.py @@ -948,26 +948,7 @@ def migrate_vm(self, email=None, first_name=None, last_name=None, def clone_vm(self, email=None, first_name=None, last_name=None, vm_name=None, provision_type=None): view = navigate_to(self, 'Clone') - first_name = first_name or fauxfactory.gen_alphanumeric() - last_name = last_name or fauxfactory.gen_alphanumeric() - email = email or f"{first_name}@{last_name}.test" - try: - prov_data = cfme_data["management_systems"][self.provider.key]["provisioning"] - except (KeyError, IndexError): - raise ValueError("You have to specify the correct options in cfme_data.yaml") - - provisioning_data = { - 'catalog': {'vm_name': vm_name, - 'provision_type': provision_type}, - 'request': { - 'email': email, - 'first_name': first_name, - 'last_name': last_name}, - 'environment': {"host_name": {'name': prov_data.get("host")}, - "datastore_name": {"name": prov_data.get("datastore")}}, - 'network': {'vlan': partial_match(prov_data.get("vlan"))}, - } - view.form.fill_with(provisioning_data, on_change=view.form.submit_button) + self._fill_clone_form(view, email, first_name, last_name, vm_name, provision_type) def publish_to_template(self, template_name, email=None, first_name=None, last_name=None): view = navigate_to(self, 'Publish') @@ -1741,6 +1722,15 @@ def step(self, *args, **kwargs): self.prerequisite_view.toolbar.configuration.item_select('Set Ownership') +@navigator.register(InfraTemplate, 'CloneTemplate') +class TemplateClone(CFMENavigateStep): + VIEW = CloneVmView + prerequisite = NavigateToSibling('Details') + + def step(self, *args, **kwargs): + self.prerequisite_view.toolbar.lifecycle.item_select("Clone this Template") + + @navigator.register(InfraVm, 'Rename') class Rename(CFMENavigateStep): VIEW = RenameVmView diff --git a/cfme/services/requests.py b/cfme/services/requests.py index 792c228400..780a65432f 100644 --- a/cfme/services/requests.py +++ b/cfme/services/requests.py @@ -431,6 +431,12 @@ class schedule(WaitTab): # noqa retirement = SummaryFormItem('Lifespan', 'Time until Retirement') retirement_warning = SummaryFormItem('Lifespan', 'Retirement Warning') + @View.nested + class volumes(WaitTab): # noqa + volume_name = SummaryFormItem('Volumes', 'Volume Name') + volume_size = SummaryFormItem('Volumes', 'Size (gigabytes)') + delete_on_terminate = Checkbox(name='volumes__delete_on_terminate_1') + @property def is_displayed(self): expected_description = self.context['object'].rest.description diff --git a/cfme/tests/cloud_infra_common/test_cloud_init_provisioning.py b/cfme/tests/cloud_infra_common/test_cloud_init_provisioning.py index de8fcf699d..1041038572 100644 --- a/cfme/tests/cloud_infra_common/test_cloud_init_provisioning.py +++ b/cfme/tests/cloud_infra_common/test_cloud_init_provisioning.py @@ -12,7 +12,9 @@ from cfme.infrastructure.provider.scvmm import SCVMMProvider from cfme.infrastructure.pxe import get_template_from_config from cfme.markers.env_markers.provider import providers +from cfme.tests.infrastructure.test_provisioning_dialog import check_all_tabs from cfme.utils import ssh +from cfme.utils.blockers import BZ from cfme.utils.generators import random_vm_name from cfme.utils.log import logger from cfme.utils.providers import ProviderFilter @@ -59,6 +61,7 @@ def vm_name(): @pytest.mark.tier(3) @test_requirements.provision +@pytest.mark.meta(automates=[BZ(1797706)]) def test_provision_cloud_init(appliance, request, setup_provider, provider, provisioning, setup_ci_template, vm_name): """ Tests provisioning from a template with cloud_init @@ -68,6 +71,7 @@ def test_provision_cloud_init(appliance, request, setup_provider, provider, prov Bugzilla: 1619744 + 1797706 Polarion: assignee: jhenner @@ -88,6 +92,7 @@ def test_provision_cloud_init(appliance, request, setup_provider, provider, prov # for image selection in before_fill inst_args['template_name'] = image + # TODO Perhaps merge this with stuff in test_provisioning_dialog.prov_data if provider.one_of(AzureProvider): inst_args['environment'] = {'public_ip_address': "New"} if provider.one_of(OpenStackProvider): @@ -97,6 +102,7 @@ def test_provision_cloud_init(appliance, request, setup_provider, provider, prov inst_args['environment'] = {'public_ip_address': floating_ip} inst_arg_props = inst_args.setdefault('properties', {}) inst_arg_props['instance_type'] = partial_match(provisioning['ci-flavor-name']) + if provider.one_of(InfraProvider) and appliance.version > '5.9': inst_args['customize']['customize_type'] = 'Specification' @@ -104,9 +110,11 @@ def test_provision_cloud_init(appliance, request, setup_provider, provider, prov collection = appliance.provider_based_collection(provider) instance = collection.create(vm_name, provider, form_values=inst_args) + request.addfinalizer(instance.cleanup_on_provider) provision_request = provider.appliance.collections.requests.instantiate(vm_name, partial_check=True) + check_all_tabs(provision_request, provider) provision_request.wait_for_request() wait_for(lambda: instance.ip_address is not None, num_sec=600) connect_ip = instance.ip_address @@ -122,6 +130,7 @@ def test_provision_cloud_init(appliance, request, setup_provider, provider, prov @test_requirements.provision @pytest.mark.provider([RHEVMProvider]) +@pytest.mark.meta(automates=[1797706]) def test_provision_cloud_init_payload(appliance, request, setup_provider, provider, provisioning, vm_name): """ @@ -170,6 +179,7 @@ def test_provision_cloud_init_payload(appliance, request, setup_provider, provid request.addfinalizer(instance.cleanup_on_provider) provision_request = provider.appliance.collections.requests.instantiate(vm_name, partial_check=True) + check_all_tabs(provision_request, provider) provision_request.wait_for_request() connect_ip = wait_for(find_global_ipv6, func_args=[instance], num_sec=600, delay=20).out diff --git a/cfme/tests/infrastructure/test_provisioning_dialog.py b/cfme/tests/infrastructure/test_provisioning_dialog.py index 247d2c7ca6..4095e2f7ba 100644 --- a/cfme/tests/infrastructure/test_provisioning_dialog.py +++ b/cfme/tests/infrastructure/test_provisioning_dialog.py @@ -9,6 +9,9 @@ from widgetastic_patternfly import CheckableBootstrapTreeview as CbTree from cfme import test_requirements +from cfme.cloud.provider import CloudProvider +from cfme.cloud.provider.azure import AzureProvider +from cfme.cloud.provider.openstack import OpenStackProvider from cfme.common import BaseLoggedInPage from cfme.infrastructure.provider import InfraProvider from cfme.infrastructure.provider.rhevm import RHEVMProvider @@ -23,6 +26,7 @@ from cfme.utils.wait import TimedOutError from cfme.utils.wait import wait_for + not_scvmm = ProviderFilter(classes=[SCVMMProvider], inverted=True) all_infra = ProviderFilter(classes=[InfraProvider], required_fields=[['provisioning', 'template'], @@ -35,10 +39,17 @@ pytest.mark.long_running, test_requirements.provision, pytest.mark.tier(3), - pytest.mark.provider(gen_func=providers, filters=[all_infra], scope="module"), + pytest.mark.provider(gen_func=providers, scope="module"), ] +def prov_source(provider): + if provider.one_of(CloudProvider): + return provider.data['provisioning']['image']['name'] + else: + return provider.data['provisioning']['template'] + + @pytest.fixture(scope="function") def vm_name(): vm_name = random_vm_name('provd') @@ -47,20 +58,36 @@ def vm_name(): @pytest.fixture(scope="function") def prov_data(provisioning, provider): - data = { + # TODO Perhaps merge this with stuff with test_cloud_init_provisioning.test_provision_cloud_init + data = dict(provisioning, **{ 'request': { 'email': fauxfactory.gen_email(), 'first_name': fauxfactory.gen_alphanumeric(), 'last_name': fauxfactory.gen_alphanumeric(), 'manager_name': fauxfactory.gen_alphanumeric(20, start="manager ")}, - 'network': {'vlan': partial_match(provisioning.get('vlan'))}, - 'environment': {'datastore_name': {'name': provisioning['datastore']}, - 'host_name': {'name': provisioning['host']}}, 'catalog': {}, 'hardware': {}, 'schedule': {}, 'purpose': {}, - } + }) + + mgmt_system = provider.mgmt + + if provider.one_of(InfraProvider): + data['network'] = {'vlan': partial_match(provisioning.get('vlan'))} + data['environment'] = { + 'datastore_name': {'name': provisioning['datastore']}, + 'host_name': {'name': provisioning['host']}} + elif provider.one_of(AzureProvider): + data['environment'] = {'public_ip_address': "New"} + elif provider.one_of(OpenStackProvider): + ip_pool = provider.data['public_network'] + floating_ip = mgmt_system.get_first_floating_ip(pool=ip_pool) + provider.refresh_provider_relationships() + data['environment'] = {'public_ip_address': floating_ip} + props = data.setdefault('properties', {}) + props['instance_type'] = partial_match(provisioning['ci-flavor-name']) + if provider.one_of(RHEVMProvider): data['catalog']['provision_type'] = 'Native Clone' elif provider.one_of(VMwareProvider): @@ -73,22 +100,21 @@ def prov_data(provisioning, provider): def provisioner(appliance, request, setup_provider, provider, vm_name): def _provisioner(template, provisioning_data, delayed=None): - vm = appliance.collections.infra_vms.instantiate(name=vm_name, - provider=provider, - template_name=template) + collection = appliance.provider_based_collection(provider) provisioning_data['template_name'] = template provisioning_data['provider_name'] = provider.name - view = navigate_to(vm.parent, 'Provision') - view.form.fill_with(provisioning_data, on_change=view.form.submit_button) + vm = collection.create(vm_name, provider, form_values=provisioning_data) + base_view = vm.appliance.browser.create_view(BaseLoggedInPage) base_view.flash.assert_no_error() request.addfinalizer( - lambda: appliance.collections.infra_vms.instantiate(vm_name, provider) - .cleanup_on_provider()) + lambda: vm.cleanup_on_provider()) request_description = f'Provision from [{template}] to [{vm_name}]' provision_request = appliance.collections.requests.instantiate( description=request_description) + check_all_tabs(provision_request, provider) + if delayed is not None: total_seconds = (delayed - datetime.utcnow()).total_seconds() try: @@ -115,7 +141,19 @@ def _provisioner(template, provisioning_data, delayed=None): return _provisioner +def check_all_tabs(provision_request, provider): + view = navigate_to(provision_request, "Details") + + for name in provider.provisioning_dialog_widget_names: + widget = getattr(view, name) + widget.click() + if BZ(1797706).blocks and provider.one_of(RHEVMProvider): + pytest.skip('Skipping as this fails due to BZ 1797706') + assert widget.is_displayed + + @pytest.mark.meta(blockers=[BZ(1627673, forced_streams=['5.10'])]) +@pytest.mark.provider(gen_func=providers, filters=[all_infra], scope="module") def test_change_cpu_ram(provisioner, soft_assert, provider, prov_data, vm_name): """ Tests change RAM and CPU in provisioning dialog. @@ -141,8 +179,8 @@ def test_change_cpu_ram(provisioner, soft_assert, provider, prov_data, vm_name): prov_data['hardware']["num_sockets"] = "4" prov_data['hardware']["cores_per_socket"] = "1" if not provider.one_of(SCVMMProvider) else None prov_data['hardware']["memory"] = "2048" - template_name = provider.data['provisioning']['template'] - vm = provisioner(template_name, prov_data) + + vm = provisioner(prov_source(provider), prov_data) # Go to the VM info view = navigate_to(vm, "Details") @@ -212,9 +250,8 @@ def test_disk_format_select(provisioner, disk_format, provider, prov_data, vm_na prov_data['catalog']['vm_name'] = vm_name prov_data['hardware']["disk_format"] = disk_format - template_name = provider.data['provisioning']['template'] - vm = provisioner(template_name, prov_data) + vm = provisioner(prov_source(provider), prov_data) # Go to the VM info view = navigate_to(vm, 'Details') @@ -228,6 +265,8 @@ def test_disk_format_select(provisioner, disk_format, provider, prov_data, vm_na @pytest.mark.parametrize("started", [True, False]) +@pytest.mark.meta(automates=[1797706]) +@pytest.mark.provider(gen_func=providers, filters=[all_infra], scope="module") def test_power_on_or_off_after_provision(provisioner, prov_data, provider, started, vm_name): """ Tests setting the desired power state after provisioning. @@ -252,9 +291,8 @@ def test_power_on_or_off_after_provision(provisioner, prov_data, provider, start """ prov_data['catalog']['vm_name'] = vm_name prov_data['schedule']["power_on"] = started - template_name = provider.data['provisioning']['template'] - vm = provisioner(template_name, prov_data) + vm = provisioner(prov_source(provider), prov_data) wait_for( lambda: vm.exists_on_provider and @@ -287,9 +325,8 @@ def test_tag(provisioner, prov_data, provider, vm_name): """ prov_data['catalog']['vm_name'] = vm_name prov_data['purpose']["apply_tags"] = CbTree.CheckNode(path=("Service Level *", "Gold")) - template_name = provider.data['provisioning']['template'] - vm = provisioner(template_name, prov_data) + vm = provisioner(prov_source(provider), prov_data) tags = vm.get_tags() assert any( @@ -333,9 +370,7 @@ def test_provisioning_schedule(provisioner, provider, prov_data, vm_name): prov_data['schedule']["provision_start_hour"] = str(provision_time.hour) prov_data['schedule']["provision_start_min"] = str(provision_time.minute) - template_name = provider.data['provisioning']['template'] - - provisioner(template_name, prov_data, delayed=provision_time) + provisioner(prov_source(provider), prov_data, delayed=provision_time) @pytest.mark.provider([RHEVMProvider], @@ -369,9 +404,8 @@ def test_provisioning_vnic_profiles(provisioner, provider, prov_data, vm_name, v """ prov_data['catalog']['vm_name'] = vm_name prov_data['network'] = {'vlan': vnic_profile} - template_name = provider.data['provisioning']['template'] - vm = provisioner(template_name, prov_data) + vm = provisioner(prov_source(provider), prov_data) wait_for( lambda: vm.exists_on_provider, @@ -451,11 +485,10 @@ def test_vmware_default_placement(provisioner, prov_data, provider, setup_provid casecomponent: Provisioning initialEstimate: 1/4h """ - template_name = provider.data['provisioning']['template'] prov_data['catalog']['vm_name'] = vm_name prov_data['environment'] = {'automatic_placement': True} - vm = provisioner(template_name, prov_data) + vm = provisioner(prov_source(provider), prov_data) wait_for( lambda: vm.exists_on_provider, diff --git a/cfme/tests/infrastructure/test_vm_clone.py b/cfme/tests/infrastructure/test_vm_clone.py index 875619e398..09760524ce 100644 --- a/cfme/tests/infrastructure/test_vm_clone.py +++ b/cfme/tests/infrastructure/test_vm_clone.py @@ -4,8 +4,11 @@ from cfme import test_requirements from cfme.infrastructure.provider import InfraProvider +from cfme.infrastructure.provider.rhevm import RHEVMProvider +from cfme.infrastructure.provider.scvmm import SCVMMProvider from cfme.infrastructure.provider.virtualcenter import VMwareProvider from cfme.markers.env_markers.provider import providers +from cfme.tests.infrastructure.test_provisioning_dialog import check_all_tabs from cfme.utils.blockers import BZ from cfme.utils.providers import ProviderFilter @@ -33,7 +36,7 @@ def clone_vm_name(): @pytest.mark.provider([VMwareProvider], **filter_fields) -@pytest.mark.meta(blockers=[BZ(1685201)]) +@pytest.mark.meta(automates=[BZ(1685201)]) @test_requirements.provision def test_vm_clone(appliance, provider, clone_vm_name, create_vm): """ @@ -47,6 +50,45 @@ def test_vm_clone(appliance, provider, clone_vm_name, create_vm): request_description = clone_vm_name request_row = appliance.collections.requests.instantiate(request_description, partial_check=True) + check_all_tabs(request_row, provider) + request_row.wait_for_request(method='ui') + msg = f"Request failed with the message {request_row.row.last_message.text}" + assert request_row.is_succeeded(method='ui'), msg + + +@pytest.mark.provider([VMwareProvider, RHEVMProvider, SCVMMProvider], **filter_fields) +@test_requirements.provision +@pytest.mark.meta(automates=[BZ(1797733), BZ(1797706)]) +def test_template_clone(request, appliance, provider, clone_vm_name): + """ + Polarion: + assignee: jhenner + casecomponent: Provisioning + initialEstimate: 1/6h + caseimportance: high + """ + cloned_template_name = provider.data['provisioning']['template'] + vm = appliance.collections.infra_templates.instantiate(cloned_template_name, provider) + + if provider.one_of(VMwareProvider): + provision_type = 'VMware' + else: + provision_type = 'Native Clone' + + @request.addfinalizer + def template_clone_cleanup(): + collections = appliance.collections + if BZ(1797733).blocks: + cloned_template = collections.infra_vms.instantiate(clone_vm_name, provider) + else: + cloned_template = collections.infra_templates.instantiate(clone_vm_name, provider) + cloned_template.delete() + + vm.clone_template("email@xyz.com", "first", "last", clone_vm_name, provision_type) + request_row = appliance.collections.requests.instantiate(clone_vm_name, partial_check=True) + + if not BZ(1797706).blocks and provider.one_of(RHEVMProvider): + check_all_tabs(request_row, provider) request_row.wait_for_request(method='ui') msg = f"Request failed with the message {request_row.row.last_message.text}" assert request_row.is_succeeded(method='ui'), msg