Merge "Add some missing implemented microversion in doc"
diff --git a/.zuul.yaml b/.zuul.yaml
index 0588bc9..1c34d4a 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -16,13 +16,15 @@
- job:
name: tempest-full
parent: devstack-tempest
+ # This currently only works on the master branch.
+ branches: master
description: |
Base integration test with Neutron networking and py27.
Former names for this job where:
* legacy-tempest-dsvm-neutron-full
* gate-tempest-dsvm-neutron-full-ubuntu-xenial
vars:
- tox_venvlist: full
+ tox_envlist: full
devstack_localrc:
ENABLE_FILE_INJECTION: True
diff --git a/doc/requirements.txt b/doc/requirements.txt
new file mode 100644
index 0000000..b666029
--- /dev/null
+++ b/doc/requirements.txt
@@ -0,0 +1,6 @@
+# The order of packages is significant, because pip processes them in the order
+# of appearance. Changing the order has an impact on the overall integration
+# process, which may cause wedges in the gate later.
+openstackdocstheme>=1.17.0 # Apache-2.0
+reno>=2.5.0 # Apache-2.0
+sphinx>=1.6.2 # BSD
diff --git a/releasenotes/notes/cli-tests-v3fixes-fb38189cefd64213.yaml b/releasenotes/notes/cli-tests-v3fixes-fb38189cefd64213.yaml
new file mode 100644
index 0000000..e3443c8
--- /dev/null
+++ b/releasenotes/notes/cli-tests-v3fixes-fb38189cefd64213.yaml
@@ -0,0 +1,9 @@
+---
+other:
+ - |
+ The CLIClient class, when it calls a command line client, uses
+ --os-project-name instead of --os-tenant-name for the project, and
+ passes --os-identity-api-version (default empty).
+ All CLI clients still available in supported releases of OpenStack
+ which are wrapped by the cmd_with_auth() method support those
+ switches.
diff --git a/requirements.txt b/requirements.txt
index cd74449..c02cd05 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -8,9 +8,9 @@
paramiko>=2.0.0 # LGPLv2.1+
netaddr>=0.7.18 # BSD
testrepository>=0.0.18 # Apache-2.0/BSD
-oslo.concurrency>=3.20.0 # Apache-2.0
+oslo.concurrency>=3.25.0 # Apache-2.0
oslo.config>=5.1.0 # Apache-2.0
-oslo.log>=3.30.0 # Apache-2.0
+oslo.log>=3.36.0 # Apache-2.0
oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0
oslo.utils>=3.33.0 # Apache-2.0
six>=1.10.0 # MIT
diff --git a/roles/run-tempest/README.rst b/roles/run-tempest/README.rst
index b5defb7..33dcce9 100644
--- a/roles/run-tempest/README.rst
+++ b/roles/run-tempest/README.rst
@@ -29,7 +29,7 @@
# Line with only a comment.
(tempest\.(api|scenario|thirdparty)).*$ # Run only api scenario and third party
-.. zuul:rolevar:: tox_venvlist
+.. zuul:rolevar:: tox_envlist
:default: smoke
The Tempest tox environment to run.
diff --git a/roles/run-tempest/defaults/main.yaml b/roles/run-tempest/defaults/main.yaml
index 3e57511..85e94f2 100644
--- a/roles/run-tempest/defaults/main.yaml
+++ b/roles/run-tempest/defaults/main.yaml
@@ -1,3 +1,3 @@
devstack_base_dir: /opt/stack
tempest_test_regex: ''
-tox_venvlist: smoke
+tox_envlist: smoke
diff --git a/roles/run-tempest/tasks/main.yaml b/roles/run-tempest/tasks/main.yaml
index 297cd72..87898db 100644
--- a/roles/run-tempest/tasks/main.yaml
+++ b/roles/run-tempest/tasks/main.yaml
@@ -21,7 +21,7 @@
when: num_cores|int > 3
- name: Run Tempest
- command: tox -e {{tox_venvlist}} -- {{tempest_test_regex|quote}} --concurrency={{tempest_concurrency|default(default_concurrency)}}
+ command: tox -e {{tox_envlist}} -- {{tempest_test_regex|quote}} --concurrency={{tempest_concurrency|default(default_concurrency)}}
args:
chdir: "{{devstack_base_dir}}/tempest"
become: true
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index ac03cdc..a5d5c57 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -99,6 +99,15 @@
cls.versions_client = cls.os_primary.compute_versions_client
if CONF.service_available.cinder:
cls.volumes_client = cls.os_primary.volumes_client_latest
+ if CONF.service_available.glance:
+ if CONF.image_feature_enabled.api_v1:
+ cls.images_client = cls.os_primary.image_client
+ elif CONF.image_feature_enabled.api_v2:
+ cls.images_client = cls.os_primary.image_client_v2
+ else:
+ raise lib_exc.InvalidConfiguration(
+ 'Either api_v1 or api_v2 must be True in '
+ '[image-feature-enabled].')
@classmethod
def resource_setup(cls):
@@ -254,7 +263,11 @@
@classmethod
def create_image_from_server(cls, server_id, **kwargs):
- """Wrapper utility that returns an image created from the server."""
+ """Wrapper utility that returns an image created from the server.
+
+ If compute microversion >= 2.36, the returned image response will
+ be from the image service API rather than the compute image proxy API.
+ """
name = kwargs.pop('name',
data_utils.rand_name(cls.__name__ + "-image"))
wait_until = kwargs.pop('wait_until', None)
@@ -267,14 +280,21 @@
image_id = image['image_id']
else:
image_id = data_utils.parse_image_id(image.response['location'])
+
+ # The compute image proxy APIs were deprecated in 2.35 so
+ # use the images client directly if the API microversion being
+ # used is >=2.36.
+ if api_version_utils.compare_version_header_to_response(
+ "OpenStack-API-Version", "compute 2.36", image.response, "lt"):
+ client = cls.images_client
+ else:
+ client = cls.compute_images_client
cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
- cls.compute_images_client.delete_image,
- image_id)
+ client.delete_image, image_id)
if wait_until is not None:
try:
- waiters.wait_for_image_status(cls.compute_images_client,
- image_id, wait_until)
+ waiters.wait_for_image_status(client, image_id, wait_until)
except lib_exc.NotFound:
if wait_until.upper() == 'ACTIVE':
# If the image is not found after create_image returned
@@ -292,7 +312,11 @@
image_id=image_id)
else:
raise
- image = cls.compute_images_client.show_image(image_id)['image']
+ image = client.show_image(image_id)
+ # Compute image client returns response wrapped in 'image' element
+ # which is not the case with Glance image client.
+ if 'image' in image:
+ image = image['image']
if wait_until.upper() == 'ACTIVE':
if wait_for_server:
@@ -352,6 +376,11 @@
'VERIFY_RESIZE')
cls.servers_client.confirm_resize_server(server_id)
waiters.wait_for_server_status(cls.servers_client, server_id, 'ACTIVE')
+ server = cls.servers_client.show_server(server_id)['server']
+ if new_flavor_id != server['flavor']['id']:
+ msg = ('Flavor id of %s is not equal to new_flavor_id.'
+ % server_id)
+ raise lib_exc.TempestException(msg)
@classmethod
def delete_volume(cls, volume_id):
diff --git a/tempest/api/compute/flavors/test_flavors_negative.py b/tempest/api/compute/flavors/test_flavors_negative.py
index efd4f0e..3a474e6 100644
--- a/tempest/api/compute/flavors/test_flavors_negative.py
+++ b/tempest/api/compute/flavors/test_flavors_negative.py
@@ -30,18 +30,6 @@
class FlavorsV2NegativeTest(base.BaseV2ComputeTest):
- @classmethod
- def setup_clients(cls):
- super(FlavorsV2NegativeTest, cls).setup_clients()
- if CONF.image_feature_enabled.api_v1:
- cls.images_client = cls.os_primary.image_client
- elif CONF.image_feature_enabled.api_v2:
- cls.images_client = cls.os_primary.image_client_v2
- else:
- raise lib_exc.InvalidConfiguration(
- 'Either api_v1 or api_v2 must be True in '
- '[image-feature-enabled].')
-
@decorators.attr(type=['negative'])
@utils.services('image')
@decorators.idempotent_id('90f0d93a-91c1-450c-91e6-07d18172cefe')
diff --git a/tempest/api/compute/servers/test_device_tagging.py b/tempest/api/compute/servers/test_device_tagging.py
index a126fd6..d857fcb 100644
--- a/tempest/api/compute/servers/test_device_tagging.py
+++ b/tempest/api/compute/servers/test_device_tagging.py
@@ -139,6 +139,7 @@
server = self.create_test_server(
validatable=True,
+ wait_until='ACTIVE',
validation_resources=validation_resources,
config_drive=config_drive_enabled,
adminPass=admin_pass,
@@ -205,6 +206,7 @@
self.addCleanup(self.delete_server, server['id'])
+ server = self.servers_client.show_server(server['id'])['server']
self.ssh_client = remote_client.RemoteClient(
self.get_server_ip(server, validation_resources),
CONF.validation.image_ssh_user,
diff --git a/tempest/api/compute/servers/test_list_servers_negative.py b/tempest/api/compute/servers/test_list_servers_negative.py
index 6c9b287..393e68f 100644
--- a/tempest/api/compute/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/servers/test_list_servers_negative.py
@@ -119,8 +119,12 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('74745ad8-b346-45b5-b9b8-509d7447fc1f')
def test_list_servers_by_changes_since_future_date(self):
- # Return an empty list when a date in the future is passed
- changes_since = {'changes-since': '2051-01-01T12:34:00Z'}
+ # Return an empty list when a date in the future is passed.
+ # updated_at field may haven't been set at the point in the boot
+ # process where build_request still exists, so add
+ # {'status': 'ACTIVE'} along with changes-since as filter.
+ changes_since = {'changes-since': '2051-01-01T12:34:00Z',
+ 'status': 'ACTIVE'}
body = self.client.list_servers(**changes_since)
self.assertEmpty(body['servers'])
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index 297e8a8..e6184b7 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -23,12 +23,12 @@
CONF = config.CONF
-class AttachVolumeTestJSON(base.BaseV2ComputeTest):
- max_microversion = '2.19'
+class BaseAttachVolumeTest(base.BaseV2ComputeTest):
+ """Base class for the attach volume tests in this module."""
@classmethod
def skip_checks(cls):
- super(AttachVolumeTestJSON, cls).skip_checks()
+ super(BaseAttachVolumeTest, cls).skip_checks()
if not CONF.service_available.cinder:
skip_msg = ("%s skipped as Cinder is not available" % cls.__name__)
raise cls.skipException(skip_msg)
@@ -36,11 +36,11 @@
@classmethod
def setup_credentials(cls):
cls.prepare_instance_network()
- super(AttachVolumeTestJSON, cls).setup_credentials()
+ super(BaseAttachVolumeTest, cls).setup_credentials()
@classmethod
def resource_setup(cls):
- super(AttachVolumeTestJSON, cls).resource_setup()
+ super(BaseAttachVolumeTest, cls).resource_setup()
cls.device = CONF.compute.volume_device_name
def _create_server(self):
@@ -58,6 +58,9 @@
server['id'])['addresses']
return server, validation_resources
+
+class AttachVolumeTestJSON(BaseAttachVolumeTest):
+
@decorators.idempotent_id('52e9045a-e90d-4c0d-9087-79d657faffff')
def test_attach_detach_volume(self):
# Stop and Start a server with an attached volume, ensuring that
@@ -149,7 +152,7 @@
self.volumes_client, attachment['volumeId'], 'available')
-class AttachVolumeShelveTestJSON(AttachVolumeTestJSON):
+class AttachVolumeShelveTestJSON(BaseAttachVolumeTest):
"""Testing volume with shelved instance.
This test checks the attaching and detaching volumes from
diff --git a/tempest/api/object_storage/test_container_sync.py b/tempest/api/object_storage/test_container_sync.py
index 2e979bc..322579c 100644
--- a/tempest/api/object_storage/test_container_sync.py
+++ b/tempest/api/object_storage/test_container_sync.py
@@ -71,14 +71,12 @@
(cls.container_client_alt, cls.object_client_alt)
for cont_name, client in cls.clients.items():
client[0].create_container(cont_name)
+ cls.addClassResourceCleanup(base.delete_containers,
+ cont_name,
+ client[0],
+ client[1])
cls.containers.append(cont_name)
- @classmethod
- def resource_cleanup(cls):
- for client in cls.clients.values():
- cls.delete_containers(client[0], client[1])
- super(ContainerSyncTest, cls).resource_cleanup()
-
def _test_container_synchronization(self, make_headers):
# container to container synchronization
# to allow/accept sync requests to/from other accounts
diff --git a/tempest/lib/api_schema/response/compute/v2_45/__init__.py b/tempest/lib/api_schema/response/compute/v2_45/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_45/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_45/images.py b/tempest/lib/api_schema/response/compute/v2_45/images.py
new file mode 100644
index 0000000..8a48f36
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_45/images.py
@@ -0,0 +1,32 @@
+# 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.
+
+# The 2.45 microversion removes the "location" header and adds "image_id"
+# to the response body.
+create_image = {
+ 'status_code': [202],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'image_id': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['image_id']
+ }
+}
+
+# NOTE(mriedem): The compute proxy APIs for showing/listing and deleting
+# images were deprecated in microversion 2.35, and the compute proxy APIs for
+# working with image metadata were deprecated in microversion 2.39. Therefore,
+# client-side code shouldn't rely on those APIs in the compute images client
+# past those microversions and should instead use the Glance images client
+# directly.
diff --git a/tempest/lib/cli/base.py b/tempest/lib/cli/base.py
index f39ecbc..3a97801 100644
--- a/tempest/lib/cli/base.py
+++ b/tempest/lib/cli/base.py
@@ -101,12 +101,15 @@
:type project_domain_name: string
:param project_domain_id: Project's domain ID
:type project_domain_id: string
+ :param identity_api_version: Version of the Identity API
+ :type identity_api_version: string
"""
def __init__(self, username='', password='', tenant_name='', uri='',
cli_dir='', insecure=False, prefix='', user_domain_name=None,
user_domain_id=None, project_domain_name=None,
- project_domain_id=None, *args, **kwargs):
+ project_domain_id=None, identity_api_version=None, *args,
+ **kwargs):
"""Initialize a new CLIClient object."""
super(CLIClient, self).__init__()
self.cli_dir = cli_dir if cli_dir else '/usr/bin'
@@ -120,6 +123,7 @@
self.user_domain_id = user_domain_id
self.project_domain_name = project_domain_name
self.project_domain_id = project_domain_id
+ self.identity_api_version = identity_api_version
def nova(self, action, flags='', params='', fail_ok=False,
endpoint_type='publicURL', merge_stderr=False):
@@ -374,12 +378,15 @@
:param merge_stderr: if True the stderr buffer is merged into stdout
:type merge_stderr: boolean
"""
- creds = ('--os-username %s --os-tenant-name %s --os-password %s '
+ creds = ('--os-username %s --os-project-name %s --os-password %s '
'--os-auth-url %s' %
(self.username,
self.tenant_name,
self.password,
self.uri))
+ if self.identity_api_version:
+ creds += ' --os-identity-api-version %s' % (
+ self.identity_api_version)
if self.user_domain_name is not None:
creds += ' --os-user-domain-name %s' % self.user_domain_name
if self.user_domain_id is not None:
diff --git a/tempest/lib/common/utils/linux/remote_client.py b/tempest/lib/common/utils/linux/remote_client.py
index 1676a28..94fab00 100644
--- a/tempest/lib/common/utils/linux/remote_client.py
+++ b/tempest/lib/common/utils/linux/remote_client.py
@@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import functools
import sys
import netaddr
@@ -25,6 +26,7 @@
def debug_ssh(function):
"""Decorator to generate extra debug info in case off SSH failure"""
+ @functools.wraps(function)
def wrapper(self, *args, **kwargs):
try:
return function(self, *args, **kwargs)
diff --git a/tempest/lib/services/compute/images_client.py b/tempest/lib/services/compute/images_client.py
index 86bea9e..0f4eb42 100644
--- a/tempest/lib/services/compute/images_client.py
+++ b/tempest/lib/services/compute/images_client.py
@@ -17,6 +17,7 @@
from six.moves.urllib import parse as urllib
from tempest.lib.api_schema.response.compute.v2_1 import images as schema
+from tempest.lib.api_schema.response.compute.v2_45 import images as schemav245
from tempest.lib.common import rest_client
from tempest.lib import exceptions as lib_exc
from tempest.lib.services.compute import base_compute_client
@@ -24,6 +25,10 @@
class ImagesClient(base_compute_client.BaseComputeClient):
+ schema_versions_info = [
+ {'min': None, 'max': '2.44', 'schema': schema},
+ {'min': '2.45', 'max': None, 'schema': schemav245}]
+
def create_image(self, server_id, **kwargs):
"""Create an image of the original server.
@@ -36,7 +41,10 @@
post_body = json.dumps(post_body)
resp, body = self.post('servers/%s/action' % server_id,
post_body)
- self.validate_response(schema.create_image, resp, body)
+ _schema = self.get_schema(self.schema_versions_info)
+ if body:
+ body = json.loads(body)
+ self.validate_response(_schema.create_image, resp, body)
return rest_client.ResponseBody(resp, body)
def list_images(self, detail=False, **params):
diff --git a/tempest/scenario/test_network_advanced_server_ops.py b/tempest/scenario/test_network_advanced_server_ops.py
index 7c404ad..e4ab11c 100644
--- a/tempest/scenario/test_network_advanced_server_ops.py
+++ b/tempest/scenario/test_network_advanced_server_ops.py
@@ -195,6 +195,8 @@
waiters.wait_for_server_status(self.servers_client, server['id'],
'VERIFY_RESIZE')
self.servers_client.confirm_resize_server(server['id'])
+ server = self.servers_client.show_server(server['id'])['server']
+ self.assertEqual(resize_flavor, server['flavor']['id'])
self._wait_server_status_and_check_network_connectivity(
server, keypair, floating_ip)
diff --git a/tempest/scenario/test_volume_migrate_attached.py b/tempest/scenario/test_volume_migrate_attached.py
index cd10bbd..ff7996a 100644
--- a/tempest/scenario/test_volume_migrate_attached.py
+++ b/tempest/scenario/test_volume_migrate_attached.py
@@ -38,6 +38,11 @@
credentials = ['primary', 'admin']
@classmethod
+ def setup_clients(cls):
+ super(TestVolumeMigrateRetypeAttached, cls).setup_clients()
+ cls.admin_volumes_client = cls.os_admin.volumes_v2_client
+
+ @classmethod
def skip_checks(cls):
super(TestVolumeMigrateRetypeAttached, cls).skip_checks()
if not CONF.volume_feature_enabled.multi_backend:
@@ -76,8 +81,10 @@
return source_body['name'], dest_body['name']
def _volume_retype_with_migration(self, volume_id, new_volume_type):
+ # NOTE: The 'on-demand' migration requires admin operation, so
+ # admin_volumes_client() should be used here.
migration_policy = 'on-demand'
- self.volumes_client.retype_volume(
+ self.admin_volumes_client.retype_volume(
volume_id, new_type=new_volume_type,
migration_policy=migration_policy)
waiters.wait_for_volume_retype(self.volumes_client,
diff --git a/tempest/tests/lib/cli/test_execute.py b/tempest/tests/lib/cli/test_execute.py
index c276386..c069af5 100644
--- a/tempest/tests/lib/cli/test_execute.py
+++ b/tempest/tests/lib/cli/test_execute.py
@@ -125,3 +125,27 @@
mock_execute.call_args[0][2])
self.assertNotIn('--os-project-domain-name',
mock_execute.call_args[0][2])
+
+ @mock.patch.object(cli_base, 'execute')
+ def test_execute_with_default_api_version(self, mock_execute):
+ cli = cli_base.CLIClient()
+ cli.openstack('action')
+ self.assertEqual(mock_execute.call_count, 1)
+ self.assertNotIn('--os-identity-api-version ',
+ mock_execute.call_args[0][2])
+
+ @mock.patch.object(cli_base, 'execute')
+ def test_execute_with_empty_api_version(self, mock_execute):
+ cli = cli_base.CLIClient(identity_api_version='')
+ cli.openstack('action')
+ self.assertEqual(mock_execute.call_count, 1)
+ self.assertNotIn('--os-identity-api-version ',
+ mock_execute.call_args[0][2])
+
+ @mock.patch.object(cli_base, 'execute')
+ def test_execute_with_explicit_api_version(self, mock_execute):
+ cli = cli_base.CLIClient(identity_api_version='0.0')
+ cli.openstack('action')
+ self.assertEqual(mock_execute.call_count, 1)
+ self.assertIn('--os-identity-api-version 0.0 ',
+ mock_execute.call_args[0][2])
diff --git a/test-requirements.txt b/test-requirements.txt
index 37644d0..e33f207 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -2,11 +2,7 @@
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
-# needed for doc build
-sphinx>=1.6.2 # BSD
-openstackdocstheme>=1.17.0 # Apache-2.0
-reno>=2.5.0 # Apache-2.0
mock>=2.0.0 # BSD
coverage!=4.4,>=4.0 # Apache-2.0
-oslotest>=1.10.0 # Apache-2.0
+oslotest>=3.2.0 # Apache-2.0
flake8-import-order==0.11 # LGPLv3
diff --git a/tox.ini b/tox.ini
index e7ea1e2..c0df8a6 100644
--- a/tox.ini
+++ b/tox.ini
@@ -133,9 +133,14 @@
commands = {posargs}
[testenv:docs]
+deps =
+ -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt}
+ -r{toxinidir}/requirements.txt
+ -r{toxinidir}/doc/requirements.txt
commands =
- rm -rf doc/build
- python setup.py build_sphinx {posargs}
+ rm -rf doc/build
+ sphinx-build -b html doc/source doc/build/html
+whitelist_externals = rm
[testenv:pep8]
commands =
@@ -161,9 +166,15 @@
import-order-style = pep8
[testenv:releasenotes]
+deps =
+ -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt}
+ -r{toxinidir}/requirements.txt
+ -r{toxinidir}/doc/requirements.txt
commands =
- rm -rf releasenotes/build
- sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
+ rm -rf releasenotes/build
+ sphinx-build -a -E -W -d releasenotes/build/doctrees \
+ -b html releasenotes/source releasenotes/build/html
+whitelist_externals = rm
[testenv:pip-check-reqs]
# Do not install test-requirements as that will pollute the virtualenv for