Deleting VM ports on cluster delete flow
- modified delete port task to take a list of ports and delete all specified ports as well as taking a single port - delete port task also does not throw an exception upon failure to delete a port, it instead logs a warning - modified delete cluster flow to retrieve a list of ports for a VM and attempt to delete each port after deleting the VM Change-Id: Ib311b23fdf3a58bc4b79257ed412e9d53d039013 closes-bug: 1448140
This commit is contained in:
parent
440bbf9eac
commit
0dc7db6e80
|
@ -19,6 +19,7 @@ import cue.client as client
|
|||
from cue.db.sqlalchemy import models
|
||||
import cue.taskflow.task as cue_tasks
|
||||
import os_tasklib.common as os_common
|
||||
import os_tasklib.neutron as neutron
|
||||
import os_tasklib.nova as nova
|
||||
|
||||
|
||||
|
@ -40,6 +41,7 @@ def delete_cluster_node(cluster_id, node_number, node_id):
|
|||
node_name = "cluster[%s].node[%d]" % (cluster_id, node_number)
|
||||
|
||||
extract_vm_id = lambda node: node['instance_id']
|
||||
extract_port_ids = lambda interfaces: [i['port_id'] for i in interfaces]
|
||||
|
||||
deleted_node_values = {'status': models.Status.DELETED,
|
||||
'deleted': True}
|
||||
|
@ -57,10 +59,24 @@ def delete_cluster_node(cluster_id, node_number, node_id):
|
|||
name="extract vm id %s" % node_name,
|
||||
rebind={'node': "node_%d" % node_number},
|
||||
provides="vm_id_%d" % node_number),
|
||||
nova.ListVmInterfaces(
|
||||
os_client=client.nova_client(),
|
||||
name="list vm interfaces %s" % node_name,
|
||||
rebind={'server': "vm_id_%d" % node_number},
|
||||
provides="vm_interfaces_%d" % node_number),
|
||||
os_common.Lambda(
|
||||
extract_port_ids,
|
||||
name="extract port ids %s" % node_name,
|
||||
rebind={'interfaces': "vm_interfaces_%d" % node_number},
|
||||
provides="vm_port_list_%d" % node_number),
|
||||
nova.DeleteVm(
|
||||
os_client=client.nova_client(),
|
||||
name="delete vm %s" % node_name,
|
||||
rebind={'server': "vm_id_%d" % node_number}),
|
||||
neutron.DeletePorts(
|
||||
os_client=client.neutron_client(),
|
||||
name="delete vm %s ports" % node_name,
|
||||
rebind={'port_ids': "vm_port_list_%d" % node_number}),
|
||||
cue_tasks.UpdateNode(
|
||||
name="update node %s" % node_name,
|
||||
inject={'node_id': node_id,
|
||||
|
|
|
@ -96,7 +96,7 @@ class NeutronClient(base.BaseFixture):
|
|||
body['port']['id'] = port_id
|
||||
body['port']['fixed_ips'] = []
|
||||
body['port']['fixed_ips'].append({'ip_address': '0.0.0.0'})
|
||||
self._port_list[port_id] = body['port']
|
||||
self._port_list[port_id] = body['port'].copy()
|
||||
return body
|
||||
|
||||
def create_network(self, body=None):
|
||||
|
|
|
@ -23,12 +23,14 @@ import cue.tests.functional.fixtures.base as base
|
|||
|
||||
|
||||
class VmDetails(object):
|
||||
def __init__(self, vm_id, name, flavor, image, status=None):
|
||||
def __init__(self, vm_id, name, flavor, image,
|
||||
port_list=None, status=None):
|
||||
self.id = vm_id
|
||||
self.name = name
|
||||
self.flavor = flavor
|
||||
self.image = image
|
||||
self.status = status if status else 'ACTIVE'
|
||||
self.port_list = port_list
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
|
@ -69,6 +71,12 @@ class FlavorDetails(object):
|
|||
self.vcpus = vcpus or 2
|
||||
|
||||
|
||||
class InterfaceDetails(object):
|
||||
def __init__(self, net_id=None, port_id=None):
|
||||
self.net_id = net_id
|
||||
self.port_id = port_id
|
||||
|
||||
|
||||
class VmStatusDetails(object):
|
||||
vm_status_list = []
|
||||
|
||||
|
@ -146,6 +154,7 @@ class NovaClient(base.BaseFixture):
|
|||
v2_client.servers.delete = self.delete_vm
|
||||
v2_client.servers.get = self.get_vm
|
||||
v2_client.servers.list = self.list_vms
|
||||
v2_client.servers.interface_list = self.list_interfaces
|
||||
v2_client.images.find = self.find_images
|
||||
v2_client.images.list = self.list_images
|
||||
v2_client.flavors.find = self.find_flavors
|
||||
|
@ -178,6 +187,7 @@ class NovaClient(base.BaseFixture):
|
|||
if image_id not in self._image_list:
|
||||
raise nova_exc.BadRequest(400)
|
||||
|
||||
port_list = list()
|
||||
if nics is not None:
|
||||
neutron_client = client.neutron_client()
|
||||
for nic in nics:
|
||||
|
@ -188,8 +198,22 @@ class NovaClient(base.BaseFixture):
|
|||
'networks' not in network_list or
|
||||
len(network_list['networks']) == 0):
|
||||
raise nova_exc.BadRequest(400)
|
||||
|
||||
else:
|
||||
body_value = {
|
||||
"port": {
|
||||
"admin_state_up": True,
|
||||
"name": "test port",
|
||||
"network_id": nic['net-id'],
|
||||
}
|
||||
}
|
||||
port_info = neutron_client.create_port(
|
||||
body=body_value)
|
||||
port_id = port_info['port']['id']
|
||||
port_list.append(InterfaceDetails(net_id=nic['net-id'],
|
||||
port_id=port_id))
|
||||
if 'port-id' in nic:
|
||||
pass
|
||||
port_list.append(InterfaceDetails(port_id=nic['port-id']))
|
||||
|
||||
if security_groups is not None:
|
||||
missing = set(security_groups) - set(self._security_group_list)
|
||||
|
@ -198,6 +222,7 @@ class NovaClient(base.BaseFixture):
|
|||
|
||||
newVm = VmDetails(vm_id=uuid.uuid4(), name=name,
|
||||
flavor=flavor, image=image,
|
||||
port_list=port_list,
|
||||
status='BUILDING')
|
||||
|
||||
self._vm_list[str(newVm.id)] = newVm
|
||||
|
@ -241,7 +266,7 @@ class NovaClient(base.BaseFixture):
|
|||
|
||||
return server
|
||||
|
||||
def list_vms(self, retrieve_all=True, **_params):
|
||||
def list_vms(self, retrieve_all=True, **kwargs):
|
||||
"""Mock'd version of novaclient...list_vms().
|
||||
|
||||
List available vms.
|
||||
|
@ -263,7 +288,7 @@ class NovaClient(base.BaseFixture):
|
|||
if image_detail.name == name:
|
||||
return image_detail
|
||||
|
||||
def list_images(self, retrieve_all=True, **_params):
|
||||
def list_images(self, retrieve_all=True, **kwargs):
|
||||
"""Mock'd version of novaclient...list_images().
|
||||
|
||||
List available images.
|
||||
|
@ -283,4 +308,25 @@ class NovaClient(base.BaseFixture):
|
|||
"""
|
||||
for flavor_detail in self._flavor_list.itervalues():
|
||||
if flavor_detail.name == name:
|
||||
return flavor_detail
|
||||
return flavor_detail
|
||||
|
||||
def list_interfaces(self, server, **kwargs):
|
||||
"""Mock'd version of novaclient...interface_list().
|
||||
|
||||
Lists all interfaces attached to the specified Nova VM.
|
||||
|
||||
:param server:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
server_id = server.id
|
||||
except AttributeError:
|
||||
server_id = server
|
||||
|
||||
try:
|
||||
server = self._vm_list[str(server_id)]
|
||||
except KeyError:
|
||||
raise nova_exc.NotFound(404)
|
||||
|
||||
return server.port_list
|
||||
|
|
|
@ -53,23 +53,23 @@ class DeletePortTests(base.FunctionalTestCase):
|
|||
|
||||
# Delete port using DeletePort task
|
||||
self.flow = linear_flow.Flow('create port').add(
|
||||
neutron_task.DeletePort(os_client=self.neutron_client))
|
||||
neutron_task.DeletePorts(os_client=self.neutron_client))
|
||||
|
||||
def test_delete_existing_port(self):
|
||||
def test_delete_existing_port_single(self):
|
||||
# create port
|
||||
body_value = {
|
||||
"port": {
|
||||
"admin_state_up": True,
|
||||
"name": "test port",
|
||||
"network_id": self.valid_network['id'],
|
||||
}
|
||||
}
|
||||
}
|
||||
port_info = self.neutron_client.create_port(body=body_value)
|
||||
port_id = port_info['port']['id']
|
||||
|
||||
# populate task_store with port-id of port created for this test
|
||||
task_store = {
|
||||
'port_id': port_id
|
||||
'port_ids': port_id
|
||||
}
|
||||
|
||||
# retrieve port list prior to delete
|
||||
|
@ -89,12 +89,50 @@ class DeletePortTests(base.FunctionalTestCase):
|
|||
"port-id %s found in neutron port after "
|
||||
"delete" % port_id)
|
||||
|
||||
def test_delete_existing_port_multi(self):
|
||||
# create port
|
||||
body_value = {
|
||||
"port": {
|
||||
"admin_state_up": True,
|
||||
"name": "test port",
|
||||
"network_id": self.valid_network['id'],
|
||||
}
|
||||
}
|
||||
port_id_list = list()
|
||||
for i in range(0, 10):
|
||||
port_info = self.neutron_client.create_port(body=body_value)
|
||||
port_id = port_info['port']['id']
|
||||
port_id_list.append(port_id)
|
||||
|
||||
# populate task_store with port-id of port created for this test
|
||||
task_store = {
|
||||
'port_ids': port_id_list
|
||||
}
|
||||
|
||||
# retrieve port list prior to delete
|
||||
pre_port_list = self.neutron_client.list_ports()
|
||||
|
||||
# search for created port in port list
|
||||
for port_id in port_id_list:
|
||||
self.assertEqual(True, _find_port(port_id, pre_port_list),
|
||||
"port-id %s not found" % port_id)
|
||||
|
||||
engines.run(self.flow, store=task_store)
|
||||
|
||||
# retrieve port list after delete
|
||||
post_port_list = self.neutron_client.list_ports()
|
||||
|
||||
# search for deleted port in port list
|
||||
self.assertEqual(False, _find_port(port_id, post_port_list),
|
||||
"port-id %s found in neutron port after "
|
||||
"delete" % port_id)
|
||||
|
||||
def test_delete_nonexistent_port(self):
|
||||
# generate random port_id
|
||||
port_id = uuid.uuid4().hex
|
||||
|
||||
task_store = {
|
||||
'port_id': port_id
|
||||
'port_ids': port_id
|
||||
}
|
||||
|
||||
# retrieve current port list
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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 uuid
|
||||
|
||||
from cue import client
|
||||
from cue.tests.functional import base
|
||||
from cue.tests.functional.fixtures import neutron
|
||||
from cue.tests.functional.fixtures import nova
|
||||
import os_tasklib.nova.list_vm_interfaces as list_vm_interfaces
|
||||
|
||||
from taskflow import engines
|
||||
from taskflow.patterns import linear_flow
|
||||
|
||||
|
||||
class GetVmInterfacesTests(base.FunctionalTestCase):
|
||||
additional_fixtures = [
|
||||
neutron.NeutronClient,
|
||||
nova.NovaClient,
|
||||
]
|
||||
|
||||
def setUp(self):
|
||||
super(GetVmInterfacesTests, self).setUp()
|
||||
|
||||
flavor_name = "m1.tiny"
|
||||
|
||||
self.nova_client = client.nova_client()
|
||||
self.neutron_client = client.neutron_client()
|
||||
|
||||
self.valid_vm_name = str(uuid.uuid4())
|
||||
|
||||
image_list = self.nova_client.images.list()
|
||||
for image in image_list:
|
||||
if (image.name.startswith("cirros")) and (
|
||||
image.name.endswith("kernel")):
|
||||
break
|
||||
valid_image = image
|
||||
|
||||
valid_flavor = self.nova_client.flavors.find(name=flavor_name)
|
||||
|
||||
network_name = "private"
|
||||
networks = self.neutron_client.list_networks(name=network_name)
|
||||
network = networks['networks'][0]
|
||||
self.valid_net_id = network['id']
|
||||
nics = [{'net-id': self.valid_net_id}]
|
||||
|
||||
new_vm = self.nova_client.servers.create(name=self.valid_vm_name,
|
||||
image=valid_image,
|
||||
flavor=valid_flavor,
|
||||
nics=nics)
|
||||
self.valid_vm_id = new_vm.id
|
||||
|
||||
self.flow = linear_flow.Flow("create vm flow")
|
||||
self.flow.add(
|
||||
list_vm_interfaces.ListVmInterfaces(
|
||||
os_client=self.nova_client,
|
||||
requires=('server',),
|
||||
provides=('interface_list')
|
||||
)
|
||||
)
|
||||
|
||||
def test_get_valid_vm_interfaces(self):
|
||||
flow_store = {
|
||||
'server': self.valid_vm_id
|
||||
}
|
||||
|
||||
result = engines.run(self.flow, store=flow_store)
|
||||
interface_list = result['interface_list']
|
||||
self.assertEqual(self.valid_net_id, interface_list[0]['net_id'])
|
||||
|
||||
def tearDown(self):
|
||||
if self.valid_vm_id is not None:
|
||||
self.nova_client.servers.delete(self.valid_vm_id)
|
||||
super(GetVmInterfacesTests, self).tearDown()
|
|
@ -15,4 +15,4 @@
|
|||
|
||||
|
||||
from create_port import CreatePort # noqa
|
||||
from delete_port import DeletePort # noqa
|
||||
from delete_ports import DeletePorts # noqa
|
||||
|
|
|
@ -13,7 +13,13 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from cue.common.i18n import _LW # noqa
|
||||
|
||||
import collections
|
||||
|
||||
import neutronclient.common.exceptions as neutron_exc
|
||||
from oslo_log import log as logging
|
||||
import six
|
||||
|
||||
import os_tasklib
|
||||
|
||||
|
@ -21,19 +27,31 @@ import os_tasklib
|
|||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DeletePort(os_tasklib.BaseTask):
|
||||
class DeletePorts(os_tasklib.BaseTask):
|
||||
"""DeletePort Task
|
||||
|
||||
This task interfaces with Neutron API and creates a port based on the
|
||||
This task interfaces with Neutron API and deletes a port based on the
|
||||
parameters provided to the Task.
|
||||
|
||||
"""
|
||||
|
||||
def execute(self, port_id, **kwargs):
|
||||
def execute(self, port_ids, **kwargs):
|
||||
"""Main execute method
|
||||
|
||||
:param port_id: Port ID of the port being deleted
|
||||
:type port_id: string
|
||||
:param port_ids: Port ID of the port being deleted
|
||||
:type port_id: string or list
|
||||
"""
|
||||
|
||||
self.os_client.delete_port(port=port_id)
|
||||
if (not isinstance(port_ids, six.string_types) and
|
||||
isinstance(port_ids, collections.Iterable)):
|
||||
for port_id in port_ids:
|
||||
self._delete_port(port_id)
|
||||
|
||||
else:
|
||||
self._delete_port(port_ids)
|
||||
|
||||
def _delete_port(self, port_id):
|
||||
try:
|
||||
self.os_client.delete_port(port=port_id)
|
||||
except neutron_exc.NotFound:
|
||||
LOG.warning(_LW("Port was not found %s") % port_id)
|
|
@ -13,7 +13,8 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from create_vm import CreateVm # noqa
|
||||
from delete_vm import DeleteVm # noqa
|
||||
from get_vm import GetVm # noqa
|
||||
from get_vm_status import GetVmStatus # noqa
|
||||
from create_vm import CreateVm # noqa
|
||||
from delete_vm import DeleteVm # noqa
|
||||
from get_vm import GetVm # noqa
|
||||
from get_vm_status import GetVmStatus # noqa
|
||||
from list_vm_interfaces import ListVmInterfaces # noqa
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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 os_tasklib
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
_INTERFACE_ATTRIBUTES = [
|
||||
'fixed_ips',
|
||||
'mac_addr',
|
||||
'net_id',
|
||||
'port_id',
|
||||
'port_state'
|
||||
]
|
||||
|
||||
|
||||
class ListVmInterfaces(os_tasklib.BaseTask):
|
||||
"""ListVmInterfaces Task
|
||||
|
||||
This task interfaces with Nova API and retrieves the list of interfaces
|
||||
attached to the indicated VM.
|
||||
|
||||
"""
|
||||
def execute(self, server, **kwargs):
|
||||
"""Main execute method
|
||||
|
||||
:param server: vm id to get list of attached interfaces to
|
||||
:type server: string
|
||||
:return: list of interfaces
|
||||
"""
|
||||
raw_list = self.os_client.servers.interface_list(server=server)
|
||||
|
||||
# The interface returned by Nova API has circular references which
|
||||
# break serialization of the list to storage, so a subset of the data
|
||||
# is being extracted and returned.
|
||||
|
||||
interface_list = list()
|
||||
for interface in raw_list:
|
||||
interface_entry = {k: getattr(interface, k)
|
||||
for k in _INTERFACE_ATTRIBUTES
|
||||
if hasattr(interface, k)}
|
||||
interface_list.append(interface_entry)
|
||||
|
||||
return interface_list
|
Loading…
Reference in New Issue