Add standalone tests using direct HTTP links

Adds two tests that uses direct HTTP link instead of glance:
  * pxe_ipmitool + wholedisk on HTTP server
  * agent_ipmitool + wholedisk on HTTP server

Change-Id: I8f1a5b0ecb3d53ffdefd7018c1b9700210572ffc
diff --git a/ironic_tempest_plugin/config.py b/ironic_tempest_plugin/config.py
index a7afcd6..661b89c 100644
--- a/ironic_tempest_plugin/config.py
+++ b/ironic_tempest_plugin/config.py
@@ -34,6 +34,10 @@
                                     'live_migration, pause, rescue, resize, '
                                     'shelve, snapshot, and suspend')
 
+baremetal_features_group = cfg.OptGroup(
+    name='baremetal_feature_enabled',
+    title="Enabled Baremetal Service Features")
+
 BaremetalGroup = [
     cfg.StrOpt('catalog_type',
                default='baremetal',
@@ -86,6 +90,11 @@
                 help="Whether the Ironic/Neutron tenant isolation is enabled"),
     cfg.StrOpt('whole_disk_image_ref',
                help="UUID of the wholedisk image to use in the tests."),
+    cfg.StrOpt('whole_disk_image_url',
+               help="An http link to the wholedisk image to use in the "
+                    "tests."),
+    cfg.StrOpt('whole_disk_image_checksum',
+               help="An MD5 checksum of the image."),
     cfg.StrOpt('partition_image_ref',
                help="UUID of the partitioned image to use in the tests."),
     cfg.ListOpt('enabled_drivers',
@@ -99,3 +108,9 @@
                help="Ironic adjusted disk size to use in the standalone tests "
                     "as instance_info/root_gb value."),
 ]
+
+BaremetalFeaturesGroup = [
+    cfg.BoolOpt('ipxe_enabled',
+                default=True,
+                help="Defines if IPXE is enabled"),
+]
diff --git a/ironic_tempest_plugin/plugin.py b/ironic_tempest_plugin/plugin.py
index dbe0f0b..9e9c175 100644
--- a/ironic_tempest_plugin/plugin.py
+++ b/ironic_tempest_plugin/plugin.py
@@ -21,6 +21,12 @@
 
 from ironic_tempest_plugin import config as project_config
 
+_opts = [
+    (project_config.baremetal_group, project_config.BaremetalGroup),
+    (project_config.baremetal_features_group,
+     project_config.BaremetalFeaturesGroup)
+]
+
 
 class IronicTempestPlugin(plugins.TempestPlugin):
     def load_tests(self):
@@ -33,9 +39,8 @@
     def register_opts(self, conf):
         conf.register_opt(project_config.service_option,
                           group='service_available')
-        config.register_opt_group(conf, project_config.baremetal_group,
-                                  project_config.BaremetalGroup)
+        for group, option in _opts:
+            config.register_opt_group(conf, group, option)
 
     def get_opt_lists(self):
-        return [(project_config.baremetal_group.name,
-                 project_config.BaremetalGroup)]
+        return [(group.name, option) for group, option in _opts]
diff --git a/ironic_tempest_plugin/tests/scenario/baremetal_standalone_manager.py b/ironic_tempest_plugin/tests/scenario/baremetal_standalone_manager.py
index 8415a48..507513b 100644
--- a/ironic_tempest_plugin/tests/scenario/baremetal_standalone_manager.py
+++ b/ironic_tempest_plugin/tests/scenario/baremetal_standalone_manager.py
@@ -206,7 +206,7 @@
         return nodes[0]
 
     @classmethod
-    def boot_node(cls, driver, image_ref):
+    def boot_node(cls, driver, image_ref, image_checksum=None):
         """Boot ironic node.
 
         The following actions are executed:
@@ -220,6 +220,8 @@
 
         :param driver: Node driver to use.
         :param image_ref: Reference to user image to boot node with.
+        :param image_checksum: md5sum of image specified in image_ref.
+                               Needed only when direct HTTP link is provided.
         :returns: Ironic node.
         """
         node = cls.get_and_reserve_node()
@@ -230,6 +232,10 @@
         patch = [{'path': '/instance_info/image_source',
                   'op': 'add',
                   'value': image_ref}]
+        if image_checksum is not None:
+            patch.append({'path': '/instance_info/image_checksum',
+                          'op': 'add',
+                          'value': image_checksum})
         patch.append({'path': '/instance_info/root_gb',
                       'op': 'add',
                       'value': CONF.baremetal.adjusted_root_disk_size_gb})
@@ -280,6 +286,9 @@
     # Boolean value specify if image is wholedisk or not.
     wholedisk_image = None
 
+    # Image checksum, required when image is stored on HTTP server.
+    image_checksum = None
+
     mandatory_attr = ['driver', 'image_ref', 'wholedisk_image']
 
     node = None
@@ -310,7 +319,11 @@
             if getattr(cls, v) is None:
                 raise lib_exc.InvalidConfiguration(
                     "Mandatory attribute %s not set." % v)
-        cls.node = cls.boot_node(cls.driver, cls.image_ref)
+        image_checksum = None
+        if not uuidutils.is_uuid_like(cls.image_ref):
+            image_checksum = cls.image_checksum
+        cls.node = cls.boot_node(cls.driver, cls.image_ref,
+                                 image_checksum=image_checksum)
         cls.node_ip = cls.add_floatingip_to_node(cls.node['uuid'])
 
     @classmethod
diff --git a/ironic_tempest_plugin/tests/scenario/ironic_standalone/test_basic_ops.py b/ironic_tempest_plugin/tests/scenario/ironic_standalone/test_basic_ops.py
index 509813b..453d749 100644
--- a/ironic_tempest_plugin/tests/scenario/ironic_standalone/test_basic_ops.py
+++ b/ironic_tempest_plugin/tests/scenario/ironic_standalone/test_basic_ops.py
@@ -34,6 +34,27 @@
         self.ping_ip_address(self.node_ip, should_succeed=True)
 
 
+class BaremetalAgentIpmitoolWholediskHttpLink(
+        bsm.BaremetalStandaloneScenarioTest):
+
+    driver = 'agent_ipmitool'
+    image_ref = CONF.baremetal.whole_disk_image_url
+    image_checksum = CONF.baremetal.whole_disk_image_checksum
+    wholedisk_image = True
+
+    @classmethod
+    def skip_checks(cls):
+        super(BaremetalAgentIpmitoolWholediskHttpLink, cls).skip_checks()
+        if not CONF.baremetal_feature_enabled.ipxe_enabled:
+            skip_msg = ("HTTP server is not available when ipxe is disabled.")
+            raise cls.skipException(skip_msg)
+
+    @test.idempotent_id('d926c683-1a32-44df-afd0-e60134346fd0')
+    @test.services('network')
+    def test_ip_access_to_server(self):
+        self.ping_ip_address(self.node_ip, should_succeed=True)
+
+
 class BaremetalAgentIpmitoolPartitioned(bsm.BaremetalStandaloneScenarioTest):
 
     driver = 'agent_ipmitool'
@@ -58,6 +79,27 @@
         self.ping_ip_address(self.node_ip, should_succeed=True)
 
 
+class BaremetalPxeIpmitoolWholediskHttpLink(
+        bsm.BaremetalStandaloneScenarioTest):
+
+    driver = 'pxe_ipmitool'
+    image_ref = CONF.baremetal.whole_disk_image_url
+    image_checksum = CONF.baremetal.whole_disk_image_checksum
+    wholedisk_image = True
+
+    @classmethod
+    def skip_checks(cls):
+        super(BaremetalPxeIpmitoolWholediskHttpLink, cls).skip_checks()
+        if not CONF.baremetal_feature_enabled.ipxe_enabled:
+            skip_msg = ("HTTP server is not available when ipxe is disabled.")
+            raise cls.skipException(skip_msg)
+
+    @test.idempotent_id('71ccf06f-6765-40fd-8252-1b1bfa423b9b')
+    @test.services('network')
+    def test_ip_access_to_server(self):
+        self.ping_ip_address(self.node_ip, should_succeed=True)
+
+
 class BaremetalPxeIpmitoolPartitioned(bsm.BaremetalStandaloneScenarioTest):
 
     driver = 'pxe_ipmitool'