Use SDK instead of neutronclient
The python-neutronclient has been deprecated for the CLI since Ocata and the python bindings "neutronclient" has been deprecated for removal as of the 2023.1 (Antelope) release[1] in favor of using openstacksdk. This patch migrates Designate from using the neutronclient to using the openstacksdk for communicating with neutron. [1] https://docs.openstack.org/releasenotes/python-neutronclient/2023.1.html Co-Authored-By: Michael Johnson <johnsomor@gmail.com> Change-Id: I0198f38afe3d5c32ea06d9e674ab0ff849f360e6 Related-Bug: #1999774
This commit is contained in:
parent
b8ec3b450b
commit
fd09a0cfc3
@ -24,25 +24,44 @@ NETWORK_API_NEUTRON_OPTS = [
|
|||||||
cfg.IntOpt('timeout',
|
cfg.IntOpt('timeout',
|
||||||
default=30,
|
default=30,
|
||||||
help='timeout value for connecting to neutron in seconds'),
|
help='timeout value for connecting to neutron in seconds'),
|
||||||
cfg.StrOpt('admin_username',
|
|
||||||
help='username for connecting to neutron in admin context'),
|
|
||||||
cfg.StrOpt('admin_password',
|
|
||||||
help='password for connecting to neutron in admin context',
|
|
||||||
secret=True),
|
|
||||||
cfg.StrOpt('admin_tenant_name',
|
|
||||||
help='tenant name for connecting to neutron in admin context'),
|
|
||||||
cfg.StrOpt('auth_url',
|
|
||||||
help='auth url for connecting to neutron in admin context'),
|
|
||||||
cfg.BoolOpt('insecure',
|
cfg.BoolOpt('insecure',
|
||||||
default=False,
|
default=False,
|
||||||
help='if set, ignore any SSL validation issues'),
|
help='if set, ignore any SSL validation issues'),
|
||||||
cfg.StrOpt('auth_strategy',
|
|
||||||
default='keystone',
|
|
||||||
help='auth strategy for connecting to '
|
|
||||||
'neutron in admin context'),
|
|
||||||
cfg.StrOpt('ca_certificates_file',
|
cfg.StrOpt('ca_certificates_file',
|
||||||
help='Location of ca certificates file to use for '
|
help='Location of ca certificates file to use for '
|
||||||
'neutron client requests.'),
|
'neutron client requests.'),
|
||||||
|
cfg.StrOpt('client_certificate_file',
|
||||||
|
help='Location of client certificate file to use for '
|
||||||
|
'neutron client requests.'),
|
||||||
|
|
||||||
|
|
||||||
|
cfg.StrOpt('admin_username',
|
||||||
|
help='username for connecting to neutron in admin context',
|
||||||
|
deprecated_for_removal=True,
|
||||||
|
deprecated_reason='This parameter is no longer used.',
|
||||||
|
deprecated_since='2023.2'),
|
||||||
|
cfg.StrOpt('admin_password',
|
||||||
|
help='password for connecting to neutron in admin context',
|
||||||
|
secret=True, deprecated_for_removal=True,
|
||||||
|
deprecated_reason='This parameter is no longer used.',
|
||||||
|
deprecated_since='2023.2'),
|
||||||
|
cfg.StrOpt('admin_tenant_name',
|
||||||
|
help='tenant name for connecting to neutron in admin context',
|
||||||
|
deprecated_for_removal=True,
|
||||||
|
deprecated_reason='This parameter is no longer used.',
|
||||||
|
deprecated_since='2023.2'),
|
||||||
|
cfg.StrOpt('auth_url',
|
||||||
|
help='auth url for connecting to neutron in admin context',
|
||||||
|
deprecated_for_removal=True,
|
||||||
|
deprecated_reason='This parameter is no longer used.',
|
||||||
|
deprecated_since='2023.2'),
|
||||||
|
cfg.StrOpt('auth_strategy',
|
||||||
|
default='keystone',
|
||||||
|
help='auth strategy for connecting to '
|
||||||
|
'neutron in admin context',
|
||||||
|
deprecated_for_removal=True,
|
||||||
|
deprecated_reason='This parameter is no longer used.',
|
||||||
|
deprecated_since='2023.2'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,36 +16,39 @@
|
|||||||
# Copied partially from nova
|
# Copied partially from nova
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
import futurist
|
import futurist
|
||||||
from neutronclient.common import exceptions as neutron_exceptions
|
from keystoneauth1 import session
|
||||||
from neutronclient.v2_0 import client as clientv20
|
from keystoneauth1 import token_endpoint
|
||||||
|
import openstack
|
||||||
|
from openstack import exceptions as sdk_exceptions
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
from designate import exceptions
|
from designate import exceptions
|
||||||
from designate.network_api import base
|
from designate.network_api import base
|
||||||
|
from designate import version
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def get_client(context, endpoint):
|
def get_client(context, endpoint):
|
||||||
params = {
|
verify = True
|
||||||
'endpoint_url': endpoint,
|
if CONF['network_api:neutron'].insecure:
|
||||||
'timeout': CONF['network_api:neutron'].timeout,
|
verify = False
|
||||||
'insecure': CONF['network_api:neutron'].insecure,
|
elif CONF['network_api:neutron'].ca_certificates_file:
|
||||||
'ca_cert': CONF['network_api:neutron'].ca_certificates_file,
|
verify = CONF['network_api:neutron'].ca_certificates_file
|
||||||
}
|
|
||||||
|
|
||||||
if context.auth_token:
|
auth_token = token_endpoint.Token(endpoint, context.auth_token)
|
||||||
params['token'] = context.auth_token
|
|
||||||
params['auth_strategy'] = None
|
user_session = session.Session(
|
||||||
elif CONF['network_api:neutron'].admin_username is not None:
|
auth=auth_token,
|
||||||
params['username'] = CONF['network_api:neutron'].admin_username
|
verify=verify,
|
||||||
params['project_name'] = CONF['network_api:neutron'].admin_tenant_name
|
cert=CONF['network_api:neutron'].client_certificate_file,
|
||||||
params['password'] = CONF['network_api:neutron'].admin_password
|
timeout=CONF['network_api:neutron'].timeout,
|
||||||
params['auth_url'] = CONF['network_api:neutron'].auth_url
|
app_name='designate',
|
||||||
params['auth_strategy'] = CONF['network_api:neutron'].auth_strategy
|
app_version=version.version_info.version_string())
|
||||||
return clientv20.Client(**params)
|
|
||||||
|
return openstack.connection.Connection(session=user_session)
|
||||||
|
|
||||||
|
|
||||||
class NeutronNetworkAPI(base.NetworkAPI):
|
class NeutronNetworkAPI(base.NetworkAPI):
|
||||||
@ -91,18 +94,20 @@ class NeutronNetworkAPI(base.NetworkAPI):
|
|||||||
{'region': region, 'endpoint': endpoint})
|
{'region': region, 'endpoint': endpoint})
|
||||||
client = get_client(context, endpoint=endpoint)
|
client = get_client(context, endpoint=endpoint)
|
||||||
try:
|
try:
|
||||||
fips = client.list_floatingips(project_id=project_id)
|
fips = client.network.ips(project_id=project_id)
|
||||||
for fip in fips['floatingips']:
|
for fip in fips:
|
||||||
yield {
|
yield {
|
||||||
'id': fip['id'],
|
'id': fip['id'],
|
||||||
'address': fip['floating_ip_address'],
|
'address': fip['floating_ip_address'],
|
||||||
'region': region
|
'region': region
|
||||||
}
|
}
|
||||||
except neutron_exceptions.Unauthorized:
|
except sdk_exceptions.HttpException as http_ex:
|
||||||
LOG.warning(
|
LOG.warning(
|
||||||
'Failed fetching floating ips from %(region)s @ %(endpoint)s'
|
'Failed fetching floating ips from %(region)s @ %(endpoint)s'
|
||||||
'due to an Unauthorized error',
|
'due to a %(cause)s error',
|
||||||
{'region': region, 'endpoint': endpoint}
|
{'region': region,
|
||||||
|
'endpoint': endpoint,
|
||||||
|
'cause': http_ex.message}
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
LOG.error(
|
LOG.error(
|
||||||
|
@ -15,8 +15,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from neutronclient.common import exceptions as neutron_exceptions
|
from openstack import exceptions as sdk_exceptions
|
||||||
from neutronclient.v2_0 import client as clientv20
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_config import fixture as cfg_fixture
|
from oslo_config import fixture as cfg_fixture
|
||||||
import oslotest.base
|
import oslotest.base
|
||||||
@ -25,6 +24,7 @@ from designate import context
|
|||||||
from designate import exceptions
|
from designate import exceptions
|
||||||
from designate.network_api import get_network_api
|
from designate.network_api import get_network_api
|
||||||
from designate.network_api import neutron
|
from designate.network_api import neutron
|
||||||
|
from designate import version
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
@ -35,75 +35,94 @@ class NeutronNetworkAPITest(oslotest.base.BaseTestCase):
|
|||||||
self.useFixture(cfg_fixture.Config(CONF))
|
self.useFixture(cfg_fixture.Config(CONF))
|
||||||
|
|
||||||
CONF.set_override(
|
CONF.set_override(
|
||||||
'endpoints', ['RegionOne|http://localhost:9696'],
|
'endpoints', ['RegionOne|http://192.0.2.5:9696'],
|
||||||
'network_api:neutron'
|
'network_api:neutron'
|
||||||
)
|
)
|
||||||
|
self.ca_certificates_file = 'fake_ca_cert_file'
|
||||||
|
self.client_certificate_file = 'fake_client_cert_file'
|
||||||
|
CONF.set_override('client_certificate_file',
|
||||||
|
self.client_certificate_file,
|
||||||
|
'network_api:neutron')
|
||||||
|
self.neutron_timeout = 100
|
||||||
|
CONF.set_override('timeout', self.neutron_timeout,
|
||||||
|
'network_api:neutron')
|
||||||
|
|
||||||
self.api = get_network_api('neutron')
|
self.api = get_network_api('neutron')
|
||||||
self.context = context.DesignateContext(
|
self.context = context.DesignateContext(
|
||||||
user_id='12345', project_id='54321',
|
user_id='12345', project_id='54321',
|
||||||
)
|
)
|
||||||
|
|
||||||
@mock.patch.object(clientv20, 'Client')
|
@mock.patch('keystoneauth1.token_endpoint.Token')
|
||||||
def test_get_client(self, mock_client):
|
@mock.patch('keystoneauth1.session.Session')
|
||||||
neutron.get_client(self.context, 'http://localhost:9696')
|
@mock.patch('openstack.connection.Connection')
|
||||||
|
def test_get_client(self, mock_client, mock_session, mock_token):
|
||||||
|
auth_token_mock = mock.MagicMock()
|
||||||
|
mock_token.return_value = auth_token_mock
|
||||||
|
|
||||||
_, kwargs = mock_client.call_args
|
user_session_mock = mock.MagicMock()
|
||||||
|
mock_session.return_value = user_session_mock
|
||||||
|
|
||||||
self.assertIn('endpoint_url', kwargs)
|
connection_mock = mock.MagicMock()
|
||||||
self.assertIn('timeout', kwargs)
|
mock_client.return_value = connection_mock
|
||||||
self.assertIn('insecure', kwargs)
|
|
||||||
self.assertIn('ca_cert', kwargs)
|
|
||||||
|
|
||||||
self.assertNotIn('token', kwargs)
|
|
||||||
self.assertNotIn('username', kwargs)
|
|
||||||
|
|
||||||
self.assertEqual('http://localhost:9696', kwargs['endpoint_url'])
|
|
||||||
|
|
||||||
@mock.patch.object(clientv20, 'Client')
|
|
||||||
def test_get_client_using_token(self, mock_client):
|
|
||||||
self.context = context.DesignateContext(
|
self.context = context.DesignateContext(
|
||||||
user_id='12345', project_id='54321', auth_token='token',
|
user_id='12345', project_id='54321', auth_token='token',
|
||||||
)
|
)
|
||||||
|
endpoint = 'http://192.0.2.5:9696'
|
||||||
|
|
||||||
neutron.get_client(self.context, 'http://localhost:9696')
|
result = neutron.get_client(self.context, endpoint)
|
||||||
|
|
||||||
_, kwargs = mock_client.call_args
|
mock_token.assert_called_once_with(endpoint, self.context.auth_token)
|
||||||
|
|
||||||
self.assertIn('token', kwargs)
|
mock_session.assert_called_once_with(
|
||||||
self.assertIn('auth_strategy', kwargs)
|
auth=auth_token_mock, verify=True,
|
||||||
self.assertNotIn('username', kwargs)
|
cert=self.client_certificate_file, timeout=self.neutron_timeout,
|
||||||
|
app_name='designate',
|
||||||
|
app_version=version.version_info.version_string())
|
||||||
|
|
||||||
self.assertEqual('http://localhost:9696', kwargs['endpoint_url'])
|
self.assertEqual(connection_mock, result)
|
||||||
self.assertEqual(self.context.auth_token, kwargs['token'])
|
|
||||||
|
|
||||||
@mock.patch.object(clientv20, 'Client')
|
# Test with CA certs file configuration
|
||||||
def test_get_client_using_admin(self, mock_client):
|
mock_token.reset_mock()
|
||||||
CONF.set_override(
|
mock_session.reset_mock()
|
||||||
'admin_username', 'test',
|
|
||||||
'network_api:neutron'
|
|
||||||
)
|
|
||||||
|
|
||||||
neutron.get_client(self.context, 'http://localhost:9696')
|
CONF.set_override('ca_certificates_file', self.ca_certificates_file,
|
||||||
|
'network_api:neutron')
|
||||||
|
|
||||||
_, kwargs = mock_client.call_args
|
result = neutron.get_client(self.context, endpoint)
|
||||||
|
|
||||||
self.assertIn('auth_strategy', kwargs)
|
mock_token.assert_called_once_with(endpoint, self.context.auth_token)
|
||||||
self.assertIn('username', kwargs)
|
|
||||||
self.assertIn('project_name', kwargs)
|
|
||||||
self.assertIn('password', kwargs)
|
|
||||||
self.assertIn('auth_url', kwargs)
|
|
||||||
self.assertNotIn('token', kwargs)
|
|
||||||
|
|
||||||
self.assertEqual('http://localhost:9696', kwargs['endpoint_url'])
|
mock_session.assert_called_once_with(
|
||||||
self.assertEqual(
|
auth=auth_token_mock, verify=self.ca_certificates_file,
|
||||||
kwargs['username'], CONF['network_api:neutron'].admin_username
|
cert=self.client_certificate_file, timeout=self.neutron_timeout,
|
||||||
)
|
app_name='designate',
|
||||||
|
app_version=version.version_info.version_string())
|
||||||
|
|
||||||
@mock.patch.object(neutron, 'get_client')
|
self.assertEqual(connection_mock, result)
|
||||||
|
|
||||||
|
# Test with insecure configuration
|
||||||
|
mock_token.reset_mock()
|
||||||
|
mock_session.reset_mock()
|
||||||
|
|
||||||
|
CONF.set_override('insecure', True, 'network_api:neutron')
|
||||||
|
|
||||||
|
result = neutron.get_client(self.context, endpoint)
|
||||||
|
|
||||||
|
mock_token.assert_called_once_with(endpoint, self.context.auth_token)
|
||||||
|
|
||||||
|
mock_session.assert_called_once_with(
|
||||||
|
auth=auth_token_mock, verify=False,
|
||||||
|
cert=self.client_certificate_file, timeout=self.neutron_timeout,
|
||||||
|
app_name='designate',
|
||||||
|
app_version=version.version_info.version_string())
|
||||||
|
|
||||||
|
self.assertEqual(connection_mock, result)
|
||||||
|
|
||||||
|
@mock.patch('designate.network_api.neutron.get_client')
|
||||||
def test_list_floatingips(self, get_client):
|
def test_list_floatingips(self, get_client):
|
||||||
driver = mock.Mock()
|
driver = mock.Mock()
|
||||||
driver.list_floatingips.return_value = {'floatingips': [
|
driver.network.ips.return_value = [
|
||||||
{
|
{
|
||||||
'id': '123',
|
'id': '123',
|
||||||
'floating_ip_address': '192.168.0.100',
|
'floating_ip_address': '192.168.0.100',
|
||||||
@ -114,24 +133,24 @@ class NeutronNetworkAPITest(oslotest.base.BaseTestCase):
|
|||||||
'floating_ip_address': '192.168.0.200',
|
'floating_ip_address': '192.168.0.200',
|
||||||
'region': 'RegionOne'
|
'region': 'RegionOne'
|
||||||
},
|
},
|
||||||
]}
|
]
|
||||||
get_client.return_value = driver
|
get_client.return_value = driver
|
||||||
|
|
||||||
self.assertEqual(2, len(self.api.list_floatingips(self.context)))
|
self.assertEqual(2, len(self.api.list_floatingips(self.context)))
|
||||||
|
|
||||||
@mock.patch.object(neutron, 'get_client')
|
@mock.patch('designate.network_api.neutron.get_client')
|
||||||
def test_list_floatingips_unauthorized(self, get_client):
|
def test_list_floatingips_unauthorized(self, get_client):
|
||||||
driver = mock.Mock()
|
driver = mock.Mock()
|
||||||
driver.list_floatingips.side_effect = neutron_exceptions.Unauthorized
|
driver.network.ips.side_effect = sdk_exceptions.HttpException
|
||||||
get_client.return_value = driver
|
get_client.return_value = driver
|
||||||
|
|
||||||
self.assertEqual(0, len(self.api.list_floatingips(self.context)))
|
self.assertEqual(0, len(self.api.list_floatingips(self.context)))
|
||||||
|
|
||||||
@mock.patch.object(neutron, 'get_client')
|
@mock.patch('designate.network_api.neutron.get_client')
|
||||||
def test_list_floatingips_communication_failure(self, get_client):
|
def test_list_floatingips_communication_failure(self, get_client):
|
||||||
driver = mock.Mock()
|
driver = mock.Mock()
|
||||||
driver.list_floatingips.side_effect = (
|
driver.network.ips.side_effect = (
|
||||||
neutron_exceptions.NeutronException
|
Exception
|
||||||
)
|
)
|
||||||
get_client.return_value = driver
|
get_client.return_value = driver
|
||||||
|
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
upgrade:
|
||||||
|
- |
|
||||||
|
Designate will now use the openstacksdk to access neutron instead of the
|
||||||
|
deprecated neutronclient. The python-neutronclient module requirement has
|
||||||
|
been replaced by the openstacksdk module.
|
||||||
|
other:
|
||||||
|
- |
|
||||||
|
Designate will now use the openstacksdk to access neutron instead of the
|
||||||
|
deprecated neutronclient.
|
@ -13,6 +13,7 @@ Jinja2>=2.10 # BSD License (3 clause)
|
|||||||
jsonschema>=3.2.0 # MIT
|
jsonschema>=3.2.0 # MIT
|
||||||
keystoneauth1>=3.4.0 # Apache-2.0
|
keystoneauth1>=3.4.0 # Apache-2.0
|
||||||
keystonemiddleware>=4.17.0 # Apache-2.0
|
keystonemiddleware>=4.17.0 # Apache-2.0
|
||||||
|
openstacksdk>=0.103.0 # Apache-2.0
|
||||||
oslo.config>=6.8.0 # Apache-2.0
|
oslo.config>=6.8.0 # Apache-2.0
|
||||||
oslo.concurrency>=4.2.0 # Apache-2.0
|
oslo.concurrency>=4.2.0 # Apache-2.0
|
||||||
oslo.messaging>=14.1.0 # Apache-2.0
|
oslo.messaging>=14.1.0 # Apache-2.0
|
||||||
@ -31,7 +32,6 @@ PasteDeploy>=1.5.0 # MIT
|
|||||||
pbr>=3.1.1 # Apache-2.0
|
pbr>=3.1.1 # Apache-2.0
|
||||||
pecan!=1.0.2,!=1.0.3,!=1.0.4,!=1.2,>=1.0.0 # BSD
|
pecan!=1.0.2,!=1.0.3,!=1.0.4,!=1.2,>=1.0.0 # BSD
|
||||||
python-designateclient>=2.12.0 # Apache-2.0
|
python-designateclient>=2.12.0 # Apache-2.0
|
||||||
python-neutronclient>=6.7.0 # Apache-2.0
|
|
||||||
requests>=2.23.0 # Apache-2.0
|
requests>=2.23.0 # Apache-2.0
|
||||||
tenacity>=6.0.0 # Apache-2.0
|
tenacity>=6.0.0 # Apache-2.0
|
||||||
SQLAlchemy>=1.4.41 # MIT
|
SQLAlchemy>=1.4.41 # MIT
|
||||||
|
Loading…
Reference in New Issue
Block a user