Merge "Additional port-related RBAC tests"
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 595730a..243a9c0 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -1,14 +1,14 @@
 If you would like to contribute to the development of OpenStack, you must
 follow the steps in this page:
 
-   http://docs.openstack.org/infra/manual/developers.html
+   https://docs.openstack.org/infra/manual/developers.html
 
 If you already have a good understanding of how the system works and your
 OpenStack accounts are set up, you can skip to the development workflow
 section of this documentation to learn how changes to OpenStack should be
 submitted for review via the Gerrit tool:
 
-   http://docs.openstack.org/infra/manual/developers.html#development-workflow
+   https://docs.openstack.org/infra/manual/developers.html#development-workflow
 
 Pull requests submitted through GitHub will be ignored.
 
diff --git a/HACKING.rst b/HACKING.rst
index 178e538..5281b53 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -1,7 +1,7 @@
 Patrole Style Commandments
 ==========================
 
-- Step 1: Read the OpenStack Style Commandments: `<http://docs.openstack.org/developer/hacking/>`__
+- Step 1: Read the OpenStack Style Commandments: `<https://docs.openstack.org/developer/hacking/>`__
 - Step 2: Review Tempest's Style Commandments: `<https://docs.openstack.org/developer/tempest/HACKING.html>`__
 - Step 3: Read on
 
diff --git a/README.rst b/README.rst
index ad8add0..51940d7 100644
--- a/README.rst
+++ b/README.rst
@@ -11,9 +11,9 @@
 custom roles.
 
 * Free software: Apache license
-* Documentation: http://docs.openstack.org/developer/patrole
-* Source: http://git.openstack.org/cgit/openstack/patrole
-* Bugs: http://bugs.launchpad.net/patrole
+* Documentation: https://docs.openstack.org/developer/patrole
+* Source: https://git.openstack.org/cgit/openstack/patrole
+* Bugs: https://bugs.launchpad.net/patrole
 
 Features
 ========
diff --git a/patrole_tempest_plugin/rbac_rule_validation.py b/patrole_tempest_plugin/rbac_rule_validation.py
index c63ef90..ba04a30 100644
--- a/patrole_tempest_plugin/rbac_rule_validation.py
+++ b/patrole_tempest_plugin/rbac_rule_validation.py
@@ -81,7 +81,7 @@
                 LOG.info("As admin_only is True, only admin role should be "
                          "allowed to perform the API. Skipping oslo.policy "
                          "check for policy action {0}.".format(rule))
-                allowed = CONF.rbac.rbac_test_role == CONF.identity.admin_role
+                allowed = test_obj.rbac_utils.is_admin
             else:
                 allowed = _is_authorized(test_obj, service, rule,
                                          extra_target_data)
diff --git a/patrole_tempest_plugin/rbac_utils.py b/patrole_tempest_plugin/rbac_utils.py
index fe2d99f..3bb2cbd 100644
--- a/patrole_tempest_plugin/rbac_utils.py
+++ b/patrole_tempest_plugin/rbac_utils.py
@@ -162,3 +162,11 @@
 
         self.admin_role_id = admin_role_id
         self.rbac_role_id = rbac_role_id
+
+    @property
+    def is_admin(self):
+        """Verifies whether the current test role equals the admin role.
+
+        :returns: True if ``rbac_test_role`` is the admin role.
+        """
+        return CONF.rbac.rbac_test_role == CONF.identity.admin_role
diff --git a/patrole_tempest_plugin/tests/api/compute/test_server_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_server_rbac.py
index 3613a25..3c8ef69 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_server_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_server_rbac.py
@@ -127,8 +127,9 @@
         # We just need any host out of the hosts list to build the
         # availability_zone attribute. So, picking the first one is fine.
         # The first key of the dictionary specifies the host name.
-        host = hosts[0].keys()[0]
+        host = list(hosts[0].keys())[0]
         availability_zone = 'nova:' + host
+
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         self.create_test_server(wait_until='ACTIVE',
                                 availability_zone=availability_zone)
diff --git a/patrole_tempest_plugin/tests/api/identity/v2/test_projects_rbac.py b/patrole_tempest_plugin/tests/api/identity/v2/test_projects_rbac.py
index 9a4363d..784045a 100644
--- a/patrole_tempest_plugin/tests/api/identity/v2/test_projects_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v2/test_projects_rbac.py
@@ -112,8 +112,7 @@
         admin-scoped tenants, raise ``RbacActionFailed`` exception otherwise.
         """
         tenants_client = self.os_admin.tenants_client if \
-            CONF.identity.admin_role == CONF.rbac.rbac_test_role else \
-            self.os_primary.tenants_client
+            self.rbac_utils.is_admin else self.os_primary.tenants_client
         admin_tenant_id = self.os_admin.auth_provider.credentials.project_id
         non_admin_tenant_id = self.auth_provider.credentials.project_id
 
diff --git a/patrole_tempest_plugin/tests/api/image/v1/test_images_rbac.py b/patrole_tempest_plugin/tests/api/image/v1/test_images_rbac.py
index 7010baa..a90790b 100644
--- a/patrole_tempest_plugin/tests/api/image/v1/test_images_rbac.py
+++ b/patrole_tempest_plugin/tests/api/image/v1/test_images_rbac.py
@@ -13,7 +13,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from six import moves
+import six
 
 from tempest import config
 from tempest.lib.common.utils import data_utils
@@ -77,7 +77,7 @@
                                  properties=properties)
         image_id = body['id']
         # Now try uploading an image file
-        image_file = moves.cStringIO(data_utils.random_bytes())
+        image_file = six.BytesIO(data_utils.random_bytes())
         self.client.update_image(image_id, data=image_file)
         # Toggle role and get created image
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
@@ -99,7 +99,7 @@
                                  properties=properties)
         image_id = body['id']
         # Now try uploading an image file
-        image_file = moves.cStringIO(data_utils.random_bytes())
+        image_file = six.BytesIO(data_utils.random_bytes())
         self.client.update_image(image_id, data=image_file)
         # Toggle role and get created image
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
diff --git a/patrole_tempest_plugin/tests/api/image/v2/test_images_rbac.py b/patrole_tempest_plugin/tests/api/image/v2/test_images_rbac.py
index 5b7c823..78ba9e5 100644
--- a/patrole_tempest_plugin/tests/api/image/v2/test_images_rbac.py
+++ b/patrole_tempest_plugin/tests/api/image/v2/test_images_rbac.py
@@ -13,7 +13,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from six import moves
+import six
 
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
@@ -38,7 +38,7 @@
         return image
 
     def _upload_image(self, image_id):
-        image_file = moves.cStringIO(data_utils.random_bytes())
+        image_file = six.BytesIO(data_utils.random_bytes())
         return self.client.store_image_file(image_id, image_file)
 
     @rbac_rule_validation.action(service="glance",
@@ -184,6 +184,18 @@
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         self._create_image(visibility='public')
 
+    @decorators.idempotent_id('0f2d8427-134a-4d3c-a102-5fcdf5443d09')
+    @rbac_rule_validation.action(service="glance",
+                                 rule="communitize_image")
+    def test_communitize_image(self):
+
+        """Communitize Image Test
+
+        RBAC test for the glance communitize_image policy
+        """
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self._create_image(visibility='community')
+
     @rbac_rule_validation.action(service="glance",
                                  rule="deactivate")
     @decorators.idempotent_id('b488458c-65df-11e6-9947-080027824017')
diff --git a/patrole_tempest_plugin/tests/api/network/test_networks_rbac.py b/patrole_tempest_plugin/tests/api/network/test_networks_rbac.py
index 8208fde..f72fe75 100644
--- a/patrole_tempest_plugin/tests/api/network/test_networks_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_networks_rbac.py
@@ -17,8 +17,8 @@
 
 from tempest import config
 from tempest.lib.common.utils import data_utils
-from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
+from tempest import test
 
 from patrole_tempest_plugin import rbac_exceptions
 from patrole_tempest_plugin import rbac_rule_validation
@@ -27,68 +27,37 @@
 CONF = config.CONF
 
 
-class RbacNetworksTest(base.BaseNetworkRbacTest):
+class NetworksRbacTest(base.BaseNetworkRbacTest):
 
     @classmethod
     def resource_setup(cls):
-        super(RbacNetworksTest, cls).resource_setup()
-
-        network_name = data_utils.rand_name(
-            cls.__name__ + '-rbac-admin-network-')
-
-        post_body = {'name': network_name}
-        body = cls.networks_client.create_network(**post_body)
-        cls.admin_network = body['network']
-        cls.networks.append(cls.admin_network)
-
-        # Create a subnet by admin user
+        super(NetworksRbacTest, cls).resource_setup()
+        cls.network = cls.create_network()
         cls.cidr = netaddr.IPNetwork(CONF.network.project_network_cidr)
-
-        cls.admin_subnet = cls.create_subnet(cls.admin_network,
-                                             cidr=cls.cidr,
-                                             mask_bits=24,
-                                             enable_dhcp=False)
-
-    def _delete_network(self, network):
-        # Deleting network also deletes its subnets if exists
-        self.networks_client.delete_network(network['id'])
-        if network in self.networks:
-            self.networks.remove(network)
-        for subnet in self.subnets:
-            if subnet['network_id'] == network['id']:
-                self.subnets.remove(subnet)
+        cls.subnet = cls.create_subnet(
+            cls.network, cidr=cls.cidr, mask_bits=24, enable_dhcp=False)
 
     def _create_network(self,
-                        shared=None,
                         router_external=None,
                         router_private=None,
                         provider_network_type=None,
                         provider_physical_network=None,
-                        provider_segmentation_id=None):
+                        provider_segmentation_id=None,
+                        **kwargs):
+        if router_external is not None:
+            kwargs['router:external'] = router_external
+        if router_private is not None:
+            kwargs['router:private'] = router_private
+        if provider_network_type is not None:
+            kwargs['provider:network_type'] = provider_network_type
+        if provider_physical_network is not None:
+            kwargs['provider:physical_network'] = provider_physical_network
+        if provider_segmentation_id is not None:
+            kwargs['provider:segmentation_id'] = provider_segmentation_id
 
         network_name = data_utils.rand_name(
-            self.__class__.__name__ + '-test-network-')
-        post_body = {'name': network_name}
-
-        if shared is not None:
-            post_body['shared'] = shared
-        if router_external is not None:
-            post_body['router:external'] = router_external
-        if router_private is not None:
-            post_body['router:private'] = router_private
-        if provider_network_type is not None:
-            post_body['provider:network_type'] = provider_network_type
-        if provider_physical_network is not None:
-            post_body['provider:physical_network'] = provider_physical_network
-        if provider_segmentation_id is not None:
-            post_body['provider:segmentation_id'] = provider_segmentation_id
-
-        body = self.networks_client.create_network(**post_body)
-        network = body['network']
-
-        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
-                        self._delete_network, network)
-        self.assertEqual('ACTIVE', network['status'])
+            self.__class__.__name__ + '-Network')
+        network = self.create_network(network_name=network_name, **kwargs)
         return network
 
     def _update_network(self,
@@ -97,30 +66,24 @@
                         shared_network=None,
                         router_external=None,
                         router_private=None,
-                        segments=None):
-
-        # update a network that has been created during class setup
+                        segments=None,
+                        **kwargs):
         if not net_id:
-            net_id = self.admin_network['id']
-
-        post_body = {}
-        updated_network = None
+            net_id = self.network['id']
 
         if admin is not None:
-            post_body['admin_state_up'] = admin
+            kwargs['admin_state_up'] = admin
         elif shared_network is not None:
-            post_body['shared'] = shared_network
+            kwargs['shared'] = shared_network
         elif router_external is not None:
-            post_body['router:external'] = router_external
+            kwargs['router:external'] = router_external
         elif router_private is not None:
-            post_body['router:private'] = router_private
+            kwargs['router:private'] = router_private
         elif segments is not None:
-            post_body['segments'] = segments
-        else:
-            return updated_network
+            kwargs['segments'] = segments
 
-        body = self.networks_client.update_network(net_id, **post_body)
-        updated_network = body['network']
+        updated_network = self.networks_client.update_network(
+            net_id, **kwargs)['network']
         return updated_network
 
     @rbac_rule_validation.action(service="neutron",
@@ -194,13 +157,11 @@
 
         RBAC test for the neutron update_network policy
         """
-        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        updated_network = self._update_network(admin=False)
-        self.assertEqual(updated_network['admin_state_up'], False)
+        updated_name = data_utils.rand_name(
+            self.__class__.__name__ + '-Network')
 
-        # Revert back to True
-        updated_network = self._update_network(admin=True)
-        self.assertEqual(updated_network['admin_state_up'], True)
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self._update_network(name=updated_name)
 
     @rbac_rule_validation.action(service="neutron",
                                  rule="update_network:shared")
@@ -212,12 +173,8 @@
         RBAC test for the neutron update_network:shared policy
         """
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        updated_network = self._update_network(shared_network=True)
-        self.assertEqual(updated_network['shared'], True)
-
-        # Revert back to False
-        updated_network = self._update_network(shared_network=False)
-        self.assertEqual(updated_network['shared'], False)
+        self._update_network(shared_network=True)
+        self.addCleanup(self._update_network, shared_network=False)
 
     @rbac_rule_validation.action(service="neutron",
                                  rule="update_network:router:external")
@@ -242,8 +199,7 @@
         RBAC test for the neutron get_network policy
         """
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        # show a network that has been created during class setup
-        self.networks_client.show_network(self.admin_network['id'])
+        self.networks_client.show_network(self.network['id'])
 
     @rbac_rule_validation.action(service="neutron",
                                  rule="get_network:router:external")
@@ -254,11 +210,11 @@
 
         RBAC test for the neutron get_network:router:external policy
         """
-        post_body = {'fields': 'router:external'}
+        kwargs = {'fields': 'router:external'}
 
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self.networks_client.show_network(self.admin_network['id'],
-                                          **post_body)
+        self.networks_client.show_network(self.network['id'],
+                                          **kwargs)
 
     @rbac_rule_validation.action(service="neutron",
                                  rule="get_network:provider:network_type")
@@ -269,14 +225,13 @@
 
         RBAC test for the neutron get_network:provider:network_type policy
         """
-        post_body = {'fields': 'provider:network_type'}
+        kwargs = {'fields': 'provider:network_type'}
 
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        body = self.networks_client.show_network(self.admin_network['id'],
-                                                 **post_body)
-        showed_net = body['network']
+        retrieved_network = self.networks_client.show_network(
+            self.network['id'], **kwargs)['network']
 
-        if len(showed_net) == 0:
+        if len(retrieved_network) == 0:
             raise rbac_exceptions.RbacActionFailed
 
     @rbac_rule_validation.action(service="neutron",
@@ -288,14 +243,13 @@
 
         RBAC test for the neutron get_network:provider:physical_network policy
         """
-        post_body = {'fields': 'provider:physical_network'}
+        kwargs = {'fields': 'provider:physical_network'}
 
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        body = self.networks_client.show_network(self.admin_network['id'],
-                                                 **post_body)
-        showed_net = body['network']
+        retrieved_network = self.networks_client.show_network(
+            self.network['id'], **kwargs)['network']
 
-        if len(showed_net) == 0:
+        if len(retrieved_network) == 0:
             raise rbac_exceptions.RbacActionFailed
 
     @rbac_rule_validation.action(service="neutron",
@@ -307,18 +261,17 @@
 
         RBAC test for the neutron get_network:provider:segmentation_id policy
         """
-        post_body = {'fields': 'provider:segmentation_id'}
+        kwargs = {'fields': 'provider:segmentation_id'}
 
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        body = self.networks_client.show_network(self.admin_network['id'],
-                                                 **post_body)
-        showed_net = body['network']
+        retrieved_network = self.networks_client.show_network(
+            self.network['id'], **kwargs)['network']
 
-        if len(showed_net) == 0:
+        if len(retrieved_network) == 0:
             raise rbac_exceptions.RbacActionFailed
 
-        key = showed_net.get('provider:segmentation_id', "NotFound")
-        self.assertIsNot(key, "NotFound")
+        key = retrieved_network.get('provider:segmentation_id', "NotFound")
+        self.assertNotEqual(key, "NotFound")
 
     @rbac_rule_validation.action(service="neutron",
                                  rule="delete_network")
@@ -343,10 +296,8 @@
         RBAC test for the neutron create_subnet policy
         """
         network = self._create_network()
-        self.assertEqual('ACTIVE', network['status'])
 
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        # Create a subnet
         self.create_subnet(network, enable_dhcp=False)
 
     @rbac_rule_validation.action(service="neutron",
@@ -359,7 +310,7 @@
         RBAC test for the neutron get_subnet policy
         """
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self.subnets_client.show_subnet(self.admin_subnet['id'])
+        self.subnets_client.show_subnet(self.subnet['id'])
 
     @rbac_rule_validation.action(service="neutron",
                                  rule="update_subnet")
@@ -370,9 +321,12 @@
 
         RBAC test for the neutron update_subnet policy
         """
+        updated_name = data_utils.rand_name(
+            self.__class__.__name__ + '-Network')
+
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self.subnets_client.update_subnet(self.admin_subnet['id'],
-                                          name="New_subnet")
+        self.subnets_client.update_subnet(self.subnet['id'],
+                                          name=updated_name)
 
     @rbac_rule_validation.action(service="neutron",
                                  rule="delete_subnet")
@@ -383,13 +337,22 @@
 
         RBAC test for the neutron delete_subnet policy
         """
-        # Create a network using admin privilege
         network = self._create_network()
-        self.assertEqual('ACTIVE', network['status'])
-
-        # Create a subnet using admin privilege
         subnet = self.create_subnet(network, enable_dhcp=False)
 
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        # Delete the subnet
         self.subnets_client.delete_subnet(subnet['id'])
+
+    @test.requires_ext(extension='dhcp_agent_scheduler', service='network')
+    @decorators.idempotent_id('b524f19f-fbb4-4d11-a85d-03bfae17bf0e')
+    @rbac_rule_validation.action(service="neutron",
+                                 rule="get_dhcp-agents")
+    def test_list_dhcp_agents_on_hosting_network(self):
+
+        """List DHCP Agents on Hosting Network Test
+
+        RBAC test for the neutron "get_dhcp-agents" policy
+        """
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.networks_client.list_dhcp_agents_on_hosting_network(
+            self.network['id'])
diff --git a/patrole_tempest_plugin/version.py b/patrole_tempest_plugin/version.py
new file mode 100644
index 0000000..5f8aaf6
--- /dev/null
+++ b/patrole_tempest_plugin/version.py
@@ -0,0 +1,18 @@
+# Copyright (c) 2017 AT&T Corporation.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+
+import pbr.version
+
+version_info = pbr.version.VersionInfo('patrole')
diff --git a/releasenotes/notes/communitize-image-rbac-test-bdf1109e58a6c2e0.yaml b/releasenotes/notes/communitize-image-rbac-test-bdf1109e58a6c2e0.yaml
new file mode 100644
index 0000000..200cc58
--- /dev/null
+++ b/releasenotes/notes/communitize-image-rbac-test-bdf1109e58a6c2e0.yaml
@@ -0,0 +1,5 @@
+---
+features:
+  - |
+    Add RBAC test for communitizing image, providing coverage for the policy
+    action "communitize_image".
diff --git a/releasenotes/notes/dhcp-agent-scheduler-test-842fc1df45799def.yaml b/releasenotes/notes/dhcp-agent-scheduler-test-842fc1df45799def.yaml
new file mode 100644
index 0000000..1afe82b
--- /dev/null
+++ b/releasenotes/notes/dhcp-agent-scheduler-test-842fc1df45799def.yaml
@@ -0,0 +1,6 @@
+---
+features:
+  - |
+    Added RBAC network test for listing dhcp agents on a hosting
+    network, providing coverage for the following Neutron policy action:
+    * "get_dhcp-agents"
diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py
index d86d91c..c8439f6 100644
--- a/releasenotes/source/conf.py
+++ b/releasenotes/source/conf.py
@@ -55,18 +55,18 @@
 master_doc = 'index'
 
 # General information about the project.
-project = u'patrole Release Notes'
+project = u'Patrole Release Notes'
 copyright = u'2017, Patrole Developers'
 
 # The version info for the project you're documenting, acts as replacement for
 # |version| and |release|, also used in various other places throughout the
 # built documents.
 #
-# The short X.Y version.
+from patrole_tempest_plugin.version import version_info as patrole_version
 # The full version, including alpha/beta/rc tags.
-release = ''
+release = patrole_version.version_string_with_vcs()
 # The short X.Y version.
-version = ''
+version = patrole_version.canonical_version_string()
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
@@ -256,7 +256,7 @@
 texinfo_documents = [
     ('index', 'PatroleReleaseNotes', u'Patrole Release Notes Documentation',
      u'Patrole Developers', 'PatroleReleaseNotes',
-     'One line description of project.',
+     'A Tempest plugin for performing RBAC testing.',
      'Miscellaneous'),
 ]
 
diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst
index b2e9377..11e1ac4 100644
--- a/releasenotes/source/index.rst
+++ b/releasenotes/source/index.rst
@@ -1,8 +1,9 @@
-============================================
- patrole Release Notes
-============================================
+======================
+ Patrole Release Notes
+======================
 
 .. toctree::
    :maxdepth: 1
 
    unreleased
+   v0.1.0
diff --git a/releasenotes/source/v0.1.0.rst b/releasenotes/source/v0.1.0.rst
new file mode 100644
index 0000000..5db583f
--- /dev/null
+++ b/releasenotes/source/v0.1.0.rst
@@ -0,0 +1,6 @@
+=====================
+ v0.1.0 Release Notes
+=====================
+
+.. release-notes:: 0.1.0 Release Notes
+   :version: 0.1.0
diff --git a/setup.cfg b/setup.cfg
index d8c9505..f76d172 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -5,7 +5,7 @@
     README.rst
 author = OpenStack
 author-email = openstack-dev@lists.openstack.org
-home-page = http://docs.openstack.org/developer/patrole/
+home-page = https://docs.openstack.org/developer/patrole/
 classifier =
     Environment :: OpenStack
     Intended Audience :: Information Technology