Remove dead neutron client code

The neutron client integration and related NovaHelper methods
(create_instance, get_security_group_id_from_name,
get_network_id_from_name) became dead code after the legacy workflow
removal in commit 4179c3527c.
These methods were originally used for instance creation in workflow
testing but are no longer called by any code paths.

This change removes:
- python-neutronclient dependency
- neutron_client configuration options
- NovaHelper dead methods: create_instance,
  get_security_group_id_from_name, get_network_id_from_name
- Related test coverage for removed methods
- OpenStackClients.neutron() client initialization

The removal eliminates an unnecessary runtime dependency and reduces
configuration complexity with zero impact on users.

Closes-Bug: #2126568
Generated-By: claude-code
Change-Id: I662d0db78fb96950239f1939a4d34c42f22b5ae1
Signed-off-by: Sean Mooney <work@seanmooney.info>
This commit is contained in:
Sean Mooney
2025-10-01 14:37:39 +01:00
parent 9b5c8c1c4f
commit 24afb27d3c
8 changed files with 30 additions and 321 deletions

View File

@@ -0,0 +1,9 @@
---
other:
- |
The experimental neutron client integration has been removed from Watcher.
The neutron client and related methods (create_instance,
get_security_group_id_from_name, get_network_id_from_name) became dead
code after the legacy workflow removal in commit 4179c3527c. This removal
eliminates the python-neutronclient dependency and neutron_client
configuration options with no user-facing impact.

View File

@@ -35,7 +35,6 @@ gnocchiclient>=7.0.1 # Apache-2.0
python-cinderclient>=3.5.0 # Apache-2.0
python-glanceclient>=2.9.1 # Apache-2.0
python-keystoneclient>=3.15.0 # Apache-2.0
python-neutronclient>=6.7.0 # Apache-2.0
python-novaclient>=14.1.0 # Apache-2.0
python-observabilityclient>=1.1.0 # Apache-2.0
python-openstackclient>=3.14.0 # Apache-2.0

View File

@@ -21,7 +21,6 @@ from ironicclient import client as irclient
from keystoneauth1 import adapter as ka_adapter
from keystoneauth1 import loading as ka_loading
from keystoneclient import client as keyclient
from neutronclient.neutron import client as netclient
from novaclient import api_versions as nova_api_versions
from novaclient import client as nvclient
@@ -74,7 +73,6 @@ class OpenStackClients:
self._gnocchi = None
self._cinder = None
self._monasca = None
self._neutron = None
self._ironic = None
self._maas = None
self._placement = None
@@ -235,32 +233,6 @@ class OpenStackClients:
return self._monasca
@exception.wrap_keystone_exception
def neutron(self):
if self._neutron:
return self._neutron
# NOTE(dviroel): This integration is classified as Experimental due to
# the lack of documentation and CI testing. It can be marked as
# supported or deprecated in future releases, based on improvements.
debtcollector.deprecate(
("Neutron is an experimental integration and may be "
"deprecated in future releases."),
version="2025.2", category=PendingDeprecationWarning)
neutronclient_version = self._get_client_option('neutron',
'api_version')
neutron_endpoint_type = self._get_client_option('neutron',
'endpoint_type')
neutron_region_name = self._get_client_option('neutron', 'region_name')
self._neutron = netclient.Client(neutronclient_version,
endpoint_type=neutron_endpoint_type,
region_name=neutron_region_name,
session=self.session)
self._neutron.format = 'json'
return self._neutron
@exception.wrap_keystone_exception
def ironic(self):
if self._ironic:

View File

@@ -21,7 +21,6 @@ import time
from novaclient import api_versions
from oslo_log import log
import glanceclient.exc as glexceptions
import novaclient.exceptions as nvexceptions
from watcher.common import clients
@@ -38,7 +37,6 @@ class NovaHelper:
def __init__(self, osc=None):
""":param osc: an OpenStackClients instance"""
self.osc = osc if osc else clients.OpenStackClients()
self.neutron = self.osc.neutron()
self.cinder = self.osc.cinder()
self.nova = self.osc.nova()
self.glance = self.osc.glance()
@@ -638,111 +636,6 @@ class NovaHelper:
LOG.debug("Current instance status: %s", instance.status)
return instance.status in status_list
def create_instance(self, node_id, inst_name="test", image_id=None,
flavor_name="m1.tiny",
sec_group_list=["default"],
network_names_list=["demo-net"], keypair_name="mykeys",
create_new_floating_ip=True,
block_device_mapping_v2=None):
"""This method creates a new instance
It also creates, if requested, a new floating IP and associates
it with the new instance
It returns the unique id of the created instance.
"""
LOG.debug(
"Trying to create new instance '%(inst)s' "
"from image '%(image)s' with flavor '%(flavor)s' ...",
{'inst': inst_name, 'image': image_id, 'flavor': flavor_name})
try:
self.nova.keypairs.findall(name=keypair_name)
except nvexceptions.NotFound:
LOG.debug("Key pair '%s' not found ", keypair_name)
return
try:
image = self.glance.images.get(image_id)
except glexceptions.NotFound:
LOG.debug("Image '%s' not found ", image_id)
return
try:
flavor = self.nova.flavors.find(name=flavor_name)
except nvexceptions.NotFound:
LOG.debug("Flavor '%s' not found ", flavor_name)
return
# Make sure all security groups exist
for sec_group_name in sec_group_list:
group_id = self.get_security_group_id_from_name(sec_group_name)
if not group_id:
LOG.debug("Security group '%s' not found ", sec_group_name)
return
net_list = list()
for network_name in network_names_list:
nic_id = self.get_network_id_from_name(network_name)
if not nic_id:
LOG.debug("Network '%s' not found ", network_name)
return
net_obj = {"net-id": nic_id}
net_list.append(net_obj)
# get availability zone of destination host
azone = self.nova.services.list(host=node_id,
binary='nova-compute')[0].zone
instance = self.nova.servers.create(
inst_name, image,
flavor=flavor,
key_name=keypair_name,
security_groups=sec_group_list,
nics=net_list,
block_device_mapping_v2=block_device_mapping_v2,
availability_zone=f"{azone}:{node_id}")
# Poll at 5 second intervals, until the status is no longer 'BUILD'
if instance:
if self.wait_for_instance_status(instance,
('ACTIVE', 'ERROR'), 5, 10):
instance = self.nova.servers.get(instance.id)
if create_new_floating_ip and instance.status == 'ACTIVE':
LOG.debug(
"Creating a new floating IP"
" for instance '%s'", instance.id)
# Creating floating IP for the new instance
floating_ip = self.nova.floating_ips.create()
instance.add_floating_ip(floating_ip)
LOG.debug(
"Instance %(instance)s associated to "
"Floating IP '%(ip)s'",
{'instance': instance.id, 'ip': floating_ip.ip})
return instance
def get_security_group_id_from_name(self, group_name="default"):
"""This method returns the security group of the provided group name"""
security_groups = self.neutron.list_security_groups(name=group_name)
security_group_id = security_groups['security_groups'][0]['id']
return security_group_id
def get_network_id_from_name(self, net_name="private"):
"""This method returns the unique id of the provided network name"""
networks = self.neutron.list_networks(name=net_name)
# LOG.debug(networks)
network_id = networks['networks'][0]['id']
return network_id
def get_hostname(self, instance):
return str(getattr(instance, 'OS-EXT-SRV-ATTR:host'))

View File

@@ -37,7 +37,6 @@ from watcher.conf import keystone_client
from watcher.conf import maas_client
from watcher.conf import models
from watcher.conf import monasca_client
from watcher.conf import neutron_client
from watcher.conf import nova_client
from watcher.conf import paths
from watcher.conf import placement_client
@@ -67,7 +66,6 @@ keystone_client.register_opts(CONF)
grafana_client.register_opts(CONF)
grafana_translators.register_opts(CONF)
cinder_client.register_opts(CONF)
neutron_client.register_opts(CONF)
clients_auth.register_opts(CONF)
ironic_client.register_opts(CONF)
collector.register_opts(CONF)

View File

@@ -1,43 +0,0 @@
# Copyright (c) 2016 Intel Corp
#
# Authors: Prudhvi Rao Shedimbi <prudhvi.rao.shedimbi@intel.com>
#
# 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.
from oslo_config import cfg
neutron_client = cfg.OptGroup(name='neutron_client',
title='Configuration Options for Neutron')
NEUTRON_CLIENT_OPTS = [
cfg.StrOpt('api_version',
default='2.0',
help='Version of Neutron API to use in neutronclient.'),
cfg.StrOpt('endpoint_type',
default='publicURL',
choices=['public', 'internal', 'admin',
'publicURL', 'internalURL', 'adminURL'],
help='Type of endpoint to use in neutronclient.'),
cfg.StrOpt('region_name',
help='Region in Identity service catalog to use for '
'communication with the OpenStack service.')]
def register_opts(conf):
conf.register_group(neutron_client)
conf.register_opts(NEUTRON_CLIENT_OPTS, group=neutron_client)
def list_opts():
return [(neutron_client, NEUTRON_CLIENT_OPTS)]

View File

@@ -28,8 +28,6 @@ try:
MONASCA_INSTALLED = True
except Exception: # ImportError or others
MONASCA_INSTALLED = False
from neutronclient.neutron import client as netclient
from neutronclient.v2_0 import client as netclient_v2
from novaclient import client as nvclient
from watcher.common import clients
@@ -269,47 +267,6 @@ class TestClients(base.TestCase):
cinder_cached = osc.cinder()
self.assertEqual(cinder, cinder_cached)
@mock.patch.object(netclient, 'Client')
@mock.patch.object(clients.OpenStackClients, 'session')
def test_clients_neutron(self, mock_session, mock_call):
osc = clients.OpenStackClients()
osc._neutron = None
osc.neutron()
mock_call.assert_called_once_with(
CONF.neutron_client.api_version,
endpoint_type=CONF.neutron_client.endpoint_type,
region_name=CONF.neutron_client.region_name,
session=mock_session)
@mock.patch.object(clients.OpenStackClients, 'session')
def test_clients_neutron_diff_vers(self, mock_session):
'''neutronclient currently only has one version (v2)'''
CONF.set_override('api_version', '2.0',
group='neutron_client')
osc = clients.OpenStackClients()
osc._neutron = None
osc.neutron()
self.assertEqual(netclient_v2.Client,
type(osc.neutron()))
@mock.patch.object(clients.OpenStackClients, 'session')
def test_clients_neutron_diff_endpoint(self, mock_session):
'''neutronclient currently only has one version (v2)'''
CONF.set_override('endpoint_type', 'internalURL',
group='neutron_client')
osc = clients.OpenStackClients()
osc._neutron = None
osc.neutron()
self.assertEqual('internalURL', osc.neutron().httpclient.interface)
@mock.patch.object(clients.OpenStackClients, 'session')
def test_clients_neutron_cached(self, mock_session):
osc = clients.OpenStackClients()
osc._neutron = None
neutron = osc.neutron()
neutron_cached = osc.neutron()
self.assertEqual(neutron, neutron_cached)
@unittest.skipUnless(MONASCA_INSTALLED, "requires python-monascaclient")
@mock.patch('monascaclient.client.Client')
@mock.patch.object(ka_loading, 'load_session_from_conf_options')

View File

@@ -21,7 +21,6 @@ from unittest import mock
from novaclient import api_versions
import glanceclient.exc as glexceptions
import novaclient.exceptions as nvexceptions
from watcher.common import clients
@@ -35,7 +34,6 @@ CONF = conf.CONF
@mock.patch.object(clients.OpenStackClients, 'nova')
@mock.patch.object(clients.OpenStackClients, 'neutron')
@mock.patch.object(clients.OpenStackClients, 'cinder')
@mock.patch.object(clients.OpenStackClients, 'glance')
class TestNovaHelper(base.TestCase):
@@ -118,7 +116,7 @@ class TestNovaHelper(base.TestCase):
server.migrate.side_effect = side_effect
def test_get_compute_node_by_hostname(
self, mock_glance, mock_cinder, mock_neutron, mock_nova):
self, mock_glance, mock_cinder, mock_nova):
nova_util = nova_helper.NovaHelper()
hypervisor_id = utils.generate_uuid()
hypervisor_name = "fake_hypervisor_1"
@@ -164,7 +162,7 @@ class TestNovaHelper(base.TestCase):
self.assertIs(nodes[index], result)
def test_get_compute_node_by_uuid(
self, mock_glance, mock_cinder, mock_neutron, mock_nova):
self, mock_glance, mock_cinder, mock_nova):
nova_util = nova_helper.NovaHelper()
hypervisor_id = utils.generate_uuid()
hypervisor_name = "fake_hypervisor_1"
@@ -192,8 +190,7 @@ class TestNovaHelper(base.TestCase):
self.assertIs(result, nova_mock.servers.list.return_value)
@mock.patch.object(time, 'sleep', mock.Mock())
def test_stop_instance(self, mock_glance, mock_cinder, mock_neutron,
mock_nova):
def test_stop_instance(self, mock_glance, mock_cinder, mock_nova):
nova_util = nova_helper.NovaHelper()
instance_id = utils.generate_uuid()
server = self.fake_server(instance_id)
@@ -238,8 +235,7 @@ class TestNovaHelper(base.TestCase):
self.assertFalse(result)
@mock.patch.object(time, 'sleep', mock.Mock())
def test_start_instance(self, mock_glance, mock_cinder, mock_neutron,
mock_nova):
def test_start_instance(self, mock_glance, mock_cinder, mock_nova):
nova_util = nova_helper.NovaHelper()
instance_id = utils.generate_uuid()
server = self.fake_server(instance_id)
@@ -284,8 +280,7 @@ class TestNovaHelper(base.TestCase):
self.assertFalse(result)
@mock.patch.object(time, 'sleep', mock.Mock())
def test_delete_instance(self, mock_glance, mock_cinder, mock_neutron,
mock_nova):
def test_delete_instance(self, mock_glance, mock_cinder, mock_nova):
nova_util = nova_helper.NovaHelper()
instance_id = utils.generate_uuid()
@@ -303,7 +298,7 @@ class TestNovaHelper(base.TestCase):
@mock.patch.object(time, 'sleep', mock.Mock())
def test_resize_instance(self, mock_glance, mock_cinder,
mock_neutron, mock_nova):
mock_nova):
nova_util = nova_helper.NovaHelper()
server = self.fake_server(self.instance_uuid)
setattr(server, 'status', 'VERIFY_RESIZE')
@@ -322,7 +317,7 @@ class TestNovaHelper(base.TestCase):
@mock.patch.object(time, 'sleep', mock.Mock())
def test_live_migrate_instance(self, mock_glance, mock_cinder,
mock_neutron, mock_nova):
mock_nova):
nova_util = nova_helper.NovaHelper()
server = self.fake_server(self.instance_uuid)
setattr(server, 'OS-EXT-SRV-ATTR:host',
@@ -365,7 +360,7 @@ class TestNovaHelper(base.TestCase):
@mock.patch.object(time, 'sleep', mock.Mock())
def test_live_migrate_instance_with_task_state(
self, mock_glance, mock_cinder, mock_neutron, mock_nova):
self, mock_glance, mock_cinder, mock_nova):
nova_util = nova_helper.NovaHelper()
server = self.fake_server(self.instance_uuid)
setattr(server, 'OS-EXT-SRV-ATTR:host',
@@ -389,7 +384,7 @@ class TestNovaHelper(base.TestCase):
@mock.patch.object(time, 'sleep', mock.Mock())
def test_live_migrate_instance_no_destination_node(
self, mock_glance, mock_cinder, mock_neutron, mock_nova):
self, mock_glance, mock_cinder, mock_nova):
nova_util = nova_helper.NovaHelper()
server = self.fake_server(self.instance_uuid)
self.destination_node = None
@@ -404,7 +399,7 @@ class TestNovaHelper(base.TestCase):
self.assertTrue(is_success)
def test_watcher_non_live_migrate_instance_not_found(
self, mock_glance, mock_cinder, mock_neutron, mock_nova):
self, mock_glance, mock_cinder, mock_nova):
nova_util = nova_helper.NovaHelper()
self.fake_nova_find_list(nova_util, fake_find=None, fake_list=None)
@@ -416,7 +411,7 @@ class TestNovaHelper(base.TestCase):
@mock.patch.object(time, 'sleep', mock.Mock())
def test_abort_live_migrate_instance(self, mock_glance, mock_cinder,
mock_neutron, mock_nova):
mock_nova):
nova_util = nova_helper.NovaHelper()
server = self.fake_server(self.instance_uuid)
setattr(server, 'OS-EXT-SRV-ATTR:host',
@@ -455,7 +450,7 @@ class TestNovaHelper(base.TestCase):
self.instance_uuid, self.source_node, self.destination_node))
def test_non_live_migrate_instance_no_destination_node(
self, mock_glance, mock_cinder, mock_neutron, mock_nova):
self, mock_glance, mock_cinder, mock_nova):
nova_util = nova_helper.NovaHelper()
server = self.fake_server(self.instance_uuid)
setattr(server, 'OS-EXT-SRV-ATTR:host',
@@ -471,7 +466,7 @@ class TestNovaHelper(base.TestCase):
@mock.patch.object(time, 'sleep', mock.Mock())
def test_create_image_from_instance(self, mock_glance, mock_cinder,
mock_neutron, mock_nova):
mock_nova):
nova_util = nova_helper.NovaHelper()
instance = self.fake_server(self.instance_uuid)
image = mock.MagicMock()
@@ -500,7 +495,7 @@ class TestNovaHelper(base.TestCase):
self.assertIsNone(instance)
def test_enable_service_nova_compute(self, mock_glance, mock_cinder,
mock_neutron, mock_nova):
mock_nova):
nova_util = nova_helper.NovaHelper()
nova_services = nova_util.nova.services
nova_services.enable.return_value = mock.MagicMock(
@@ -526,7 +521,7 @@ class TestNovaHelper(base.TestCase):
service_uuid=mock.ANY)
def test_disable_service_nova_compute(self, mock_glance, mock_cinder,
mock_neutron, mock_nova):
mock_nova):
nova_util = nova_helper.NovaHelper()
nova_services = nova_util.nova.services
nova_services.disable_log_reason.return_value = mock.MagicMock(
@@ -553,77 +548,6 @@ class TestNovaHelper(base.TestCase):
nova_util.nova.services.disable_log_reason.assert_called_with(
service_uuid=mock.ANY, reason='test2')
@mock.patch.object(time, 'sleep', mock.Mock())
def test_create_instance(self, mock_glance, mock_cinder,
mock_neutron, mock_nova):
nova_util = nova_helper.NovaHelper()
instance = self.fake_server(self.instance_uuid)
nova_util.nova.servers.create.return_value = instance
nova_util.nova.servers.get.return_value = instance
create_instance = nova_util.create_instance(self.source_node)
self.assertIsNotNone(create_instance)
self.assertEqual(create_instance, instance)
# verify that the method create_instance will return None when
# the method findall raises exception.
nova_util.nova.keypairs.findall.side_effect = nvexceptions.NotFound(
404)
instance = nova_util.create_instance(self.source_node)
self.assertIsNone(instance)
nova_util.nova.keypairs.findall.side_effect = None
# verify that the method create_instance will return None when
# the method get raises exception.
nova_util.glance.images.get.side_effect = glexceptions.NotFound(404)
instance = nova_util.create_instance(self.source_node)
self.assertIsNone(instance)
nova_util.glance.images.get.side_effect = None
# verify that the method create_instance will return None when
# the method find raises exception.
nova_util.nova.flavors.find.side_effect = nvexceptions.NotFound(404)
instance = nova_util.create_instance(self.source_node)
self.assertIsNone(instance)
nova_util.nova.flavors.find.side_effect = None
# verify that the method create_instance will return None when
# the method get_security_group_id_from_name return None.
with mock.patch.object(
nova_util,
'get_security_group_id_from_name',
return_value=None
) as mock_security_group_id:
instance = nova_util.create_instance(self.source_node)
self.assertIsNone(instance)
mock_security_group_id.assert_called_once_with("default")
# verify that the method create_instance will return None when
# the method get_network_id_from_name return None.
with mock.patch.object(
nova_util,
'get_network_id_from_name',
return_value=None
) as mock_get_network_id:
instance = nova_util.create_instance(self.source_node)
self.assertIsNone(instance)
mock_get_network_id.assert_called_once_with("demo-net")
# verify that the method create_instance will not return None when
# the method wait_for_instance_status return True.
with mock.patch.object(
nova_util,
'wait_for_instance_status',
return_value=True
) as mock_instance_status:
instance = nova_util.create_instance(self.source_node)
self.assertIsNotNone(instance)
mock_instance_status.assert_called_once_with(
mock.ANY,
('ACTIVE', 'ERROR'),
5,
10)
@staticmethod
def fake_volume(**kwargs):
volume = mock.MagicMock()
@@ -636,7 +560,7 @@ class TestNovaHelper(base.TestCase):
@mock.patch.object(time, 'sleep', mock.Mock())
def test_swap_volume(self, mock_glance, mock_cinder,
mock_neutron, mock_nova):
mock_nova):
nova_util = nova_helper.NovaHelper()
server = self.fake_server(self.instance_uuid)
self.fake_nova_find_list(nova_util, fake_find=server, fake_list=server)
@@ -658,7 +582,7 @@ class TestNovaHelper(base.TestCase):
@mock.patch.object(time, 'sleep', mock.Mock())
def test_wait_for_volume_status(self, mock_glance, mock_cinder,
mock_neutron, mock_nova):
mock_nova):
nova_util = nova_helper.NovaHelper()
# verify that the method will return True when the status of volume
@@ -684,7 +608,7 @@ class TestNovaHelper(base.TestCase):
@mock.patch.object(api_versions, 'APIVersion', mock.MagicMock())
def test_check_nova_api_version(self, mock_glance, mock_cinder,
mock_neutron, mock_nova):
mock_nova):
nova_util = nova_helper.NovaHelper()
# verify that the method will return True when the version of nova_api
@@ -702,7 +626,7 @@ class TestNovaHelper(base.TestCase):
@mock.patch.object(time, 'sleep', mock.Mock())
def test_wait_for_instance_status(self, mock_glance, mock_cinder,
mock_neutron, mock_nova):
mock_nova):
nova_util = nova_helper.NovaHelper()
instance = self.fake_server(self.instance_uuid)
@@ -735,7 +659,7 @@ class TestNovaHelper(base.TestCase):
@mock.patch.object(time, 'sleep', mock.Mock())
def test_confirm_resize(self, mock_glance, mock_cinder,
mock_neutron, mock_nova):
mock_nova):
nova_util = nova_helper.NovaHelper()
instance = self.fake_server(self.instance_uuid)
self.fake_nova_find_list(nova_util, fake_find=instance, fake_list=None)
@@ -751,7 +675,7 @@ class TestNovaHelper(base.TestCase):
self.assertFalse(result)
def test_get_compute_node_list(
self, mock_glance, mock_cinder, mock_neutron, mock_nova):
self, mock_glance, mock_cinder, mock_nova):
nova_util = nova_helper.NovaHelper()
hypervisor1_id = utils.generate_uuid()
hypervisor1_name = "fake_hypervisor_1"