Add support for network adapter hotplug.
This patch makes it possible to add/del instance interface other than booting time. Implement bp:network-adapter-hotplug Originally from change Ibee003a9ec6cc9b3fd275417caccd0c67f6c871f Co-authored-by: Yaguang Tang <heut2008@gmail.com> Co-authored-by: Édouard Thuleau <edouard.thuleau@orange.com> Change-Id: I4f8f677af58afcb928379e5cf859388d1da45d51
This commit is contained in:
parent
1b9b66ba21
commit
a9add7d35e
@ -96,6 +96,14 @@
|
||||
"namespace": "http://docs.openstack.org/compute/ext/aggregates/api/v1.1",
|
||||
"updated": "2012-01-12T00:00:00+00:00"
|
||||
},
|
||||
{
|
||||
"alias": "os-attach-interfaces",
|
||||
"description": "Attach interface support.",
|
||||
"links": [],
|
||||
"name": "AttachInterfaces",
|
||||
"namespace": "http://docs.openstack.org/compute/ext/interfaces/api/v1.1",
|
||||
"updated": "2012-07-22T00:00:00+00:00"
|
||||
},
|
||||
{
|
||||
"alias": "os-availability-zone",
|
||||
"description": "1. Add availability_zone to the Create Server v1.1 API.\n 2. Add availability zones describing.\n ",
|
||||
@ -194,11 +202,11 @@
|
||||
},
|
||||
{
|
||||
"alias": "os-evacuate",
|
||||
"description": "Enables server evacuation",
|
||||
"description": "Enables server evacuation.",
|
||||
"links": [],
|
||||
"name": "Evacuate",
|
||||
"namespace": "http://docs.openstack.org/compute/ext/evacuate/api/v2",
|
||||
"updated": "2012-12-05T00:00:00+00:00"
|
||||
"updated": "2013-01-06T00:00:00+00:00"
|
||||
},
|
||||
{
|
||||
"alias": "os-fixed-ips",
|
||||
|
@ -40,6 +40,9 @@
|
||||
<extension alias="os-aggregates" updated="2012-01-12T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/aggregates/api/v1.1" name="Aggregates">
|
||||
<description>Admin-only aggregate administration.</description>
|
||||
</extension>
|
||||
<extension alias="os-attach-interfaces" updated="2012-07-22T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/interfaces/api/v1.1" name="AttachInterfaces">
|
||||
<description>Attach interface support.</description>
|
||||
</extension>
|
||||
<extension alias="os-availability-zone" updated="2012-12-21T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/availabilityzone/api/v1.1" name="AvailabilityZone">
|
||||
<description>1. Add availability_zone to the Create Server v1.1 API.
|
||||
2. Add availability zones describing.
|
||||
@ -88,8 +91,8 @@
|
||||
<extension alias="os-deferred-delete" updated="2011-09-01T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/deferred-delete/api/v1.1" name="DeferredDelete">
|
||||
<description>Instance deferred delete.</description>
|
||||
</extension>
|
||||
<extension alias="os-evacuate" updated="2012-12-05T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/evacuate/api/v2" name="Evacuate">
|
||||
<description>Enables server evacuation</description>
|
||||
<extension alias="os-evacuate" updated="2013-01-06T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/evacuate/api/v2" name="Evacuate">
|
||||
<description>Enables server evacuation.</description>
|
||||
</extension>
|
||||
<extension alias="os-fixed-ips" updated="2012-10-18T13:25:27-06:00" namespace="http://docs.openstack.org/compute/ext/fixed_ips/api/v2" name="FixedIPs">
|
||||
<description>Fixed IPs support.</description>
|
||||
|
@ -0,0 +1,5 @@
|
||||
{
|
||||
"interfaceAttachment": {
|
||||
"port_id": "ce531f90-199f-48c0-816c-13e38010b442"
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interfaceAttachment>
|
||||
<port_id>ce531f90-199f-48c0-816c-13e38010b442</port_id>
|
||||
</interfaceAttachment>
|
@ -0,0 +1,12 @@
|
||||
{
|
||||
"interfaceAttachment": {
|
||||
"fixed_ips": [{
|
||||
"ip_address": "192.168.1.1",
|
||||
"subnet_id": "f8a6e8f8-c2ec-497c-9f23-da9616de54ef"
|
||||
}],
|
||||
"mac_addr": "fa:16:3e:4c:2c:30",
|
||||
"net_id": "3cb9bc59-5699-4588-a4b1-b87f96708bc6",
|
||||
"port_id": "ce531f90-199f-48c0-816c-13e38010b442",
|
||||
"port_state": "ACTIVE"
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<interfaceAttachment>
|
||||
<net_id>3cb9bc59-5699-4588-a4b1-b87f96708bc6</net_id>
|
||||
<port_id>ce531f90-199f-48c0-816c-13e38010b442</port_id>
|
||||
<fixed_ips>
|
||||
<fixed_ip>
|
||||
<subnet_id>f8a6e8f8-c2ec-497c-9f23-da9616de54ef</subnet_id>
|
||||
<ip_address>192.168.1.3</ip_address>
|
||||
</fixed_ip>
|
||||
</fixed_ips>
|
||||
<port_state>ACTIVE</port_state>
|
||||
<mac_addr>fa:16:3e:4c:2c:30</mac_addr>
|
||||
</interfaceAttachment>
|
@ -0,0 +1,16 @@
|
||||
{
|
||||
"interfaceAttachments": [
|
||||
{
|
||||
"port_state": "ACTIVE",
|
||||
"fixed_ips": [
|
||||
{
|
||||
"subnet_id": "f8a6e8f8-c2ec-497c-9f23-da9616de54ef",
|
||||
"ip_address": "192.168.1.3"
|
||||
}
|
||||
],
|
||||
"net_id": "3cb9bc59-5699-4588-a4b1-b87f96708bc6",
|
||||
"port_id": "ce531f90-199f-48c0-816c-13e38010b442",
|
||||
"mac_addr": "fa:16:3e:4c:2c:30"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<interfaceAttachments>
|
||||
<interfaceAttachment>
|
||||
<port_state>ACTIVE</port_state>
|
||||
<fixed_ips>
|
||||
<fixed_ip>
|
||||
<subnet_id>f8a6e8f8-c2ec-497c-9f23-da9616de54ef</subnet_id>
|
||||
<ip_address>192.168.1.3</ip_address>
|
||||
</fixed_ip>
|
||||
</fixed_ips>
|
||||
<port_id>ce531f90-199f-48c0-816c-13e38010b442</port_id>
|
||||
<net_id>3cb9bc59-5699-4588-a4b1-b87f96708bc6</net_id>
|
||||
<mac_addr>fa:16:3e:4c:2c:30</mac_addr>
|
||||
</interfaceAttachment>
|
||||
</interfaceAttachments>
|
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interfaceAttachments>
|
||||
<interfaceAttachment>
|
||||
<item>
|
||||
<port_state>ACTIVE</port_state>
|
||||
<fixed_ips>
|
||||
<fixed_ip>
|
||||
<subnet_id>f8a6e8f8-c2ec-497c-9f23-da9616de54ef</subnet_id>
|
||||
<ip_address>192.168.1.3</ip_address>
|
||||
</fixed_ip>
|
||||
</fixed_ips>
|
||||
<port_id>ce531f90-199f-48c0-816c-13e38010b442</port_id>
|
||||
<net_id>3cb9bc59-5699-4588-a4b1-b87f96708bc6</net_id>
|
||||
<mac_addr>fa:16:3e:4c:2c:30</mac_addr>
|
||||
</item>
|
||||
</interfaceAttachment>
|
||||
</interfaceAttachments>
|
@ -0,0 +1,14 @@
|
||||
{
|
||||
"interfaceAttachment": {
|
||||
"port_state": "ACTIVE",
|
||||
"fixed_ips": [
|
||||
{
|
||||
"subnet_id": "f8a6e8f8-c2ec-497c-9f23-da9616de54ef",
|
||||
"ip_address": "192.168.1.3"
|
||||
}
|
||||
],
|
||||
"net_id": "3cb9bc59-5699-4588-a4b1-b87f96708bc6",
|
||||
"port_id": "ce531f90-199f-48c0-816c-13e38010b442",
|
||||
"mac_addr": "fa:16:3e:4c:2c:30"
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interfaceAttachment>
|
||||
<port_state>ACTIVE</port_state>
|
||||
<fixed_ips>
|
||||
<fixed_ip>
|
||||
<subnet_id>b6e47749-6bf0-4d6e-ae4b-ba6b5e238510</subnet_id>
|
||||
<ip_address>192.168.123.131</ip_address>
|
||||
</fixed_ip>
|
||||
</fixed_ips>
|
||||
<port_id>89e64f2e-86bd-4c19-9155-4548b36fdcb2</port_id>
|
||||
<net_id>a9efd207-2c1a-4cdd-a296-d3c7c3211302</net_id>
|
||||
<mac_addr>fa:16:3e:a4:1c:12</mac_addr>
|
||||
</interfaceAttachment>
|
16
doc/api_samples/os-attach-interfaces/server-post-req.json
Normal file
16
doc/api_samples/os-attach-interfaces/server-post-req.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"server" : {
|
||||
"name" : "new-server-test",
|
||||
"imageRef" : "http://openstack.example.com/openstack/images/70a599e0-31e7-49b7-b260-868f441e862b",
|
||||
"flavorRef" : "http://openstack.example.com/openstack/flavors/1",
|
||||
"metadata" : {
|
||||
"My Server Name" : "Apache1"
|
||||
},
|
||||
"personality" : [
|
||||
{
|
||||
"path" : "/etc/banner.txt",
|
||||
"contents" : "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBpdCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5kIGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVsc2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4gQnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRoZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlvdSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vyc2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6b25zLiINCg0KLVJpY2hhcmQgQmFjaA=="
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
19
doc/api_samples/os-attach-interfaces/server-post-req.xml
Normal file
19
doc/api_samples/os-attach-interfaces/server-post-req.xml
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<server xmlns="http://docs.openstack.org/compute/api/v1.1" imageRef="http://openstack.example.com/openstack/images/70a599e0-31e7-49b7-b260-868f441e862b" flavorRef="http://openstack.example.com/openstack/flavors/1" name="new-server-test">
|
||||
<metadata>
|
||||
<meta key="My Server Name">Apache1</meta>
|
||||
</metadata>
|
||||
<personality>
|
||||
<file path="/etc/banner.txt">
|
||||
ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBp
|
||||
dCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5k
|
||||
IGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVs
|
||||
c2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4g
|
||||
QnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRo
|
||||
ZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlv
|
||||
dSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vy
|
||||
c2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6
|
||||
b25zLiINCg0KLVJpY2hhcmQgQmFjaA==
|
||||
</file>
|
||||
</personality>
|
||||
</server>
|
16
doc/api_samples/os-attach-interfaces/server-post-resp.json
Normal file
16
doc/api_samples/os-attach-interfaces/server-post-resp.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"server": {
|
||||
"adminPass": "N4Lxd6cMUXmE",
|
||||
"id": "4e44ac84-f3ed-4219-aa2e-b3d1477f0ac3",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://openstack.example.com/v2/openstack/servers/4e44ac84-f3ed-4219-aa2e-b3d1477f0ac3",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://openstack.example.com/openstack/servers/4e44ac84-f3ed-4219-aa2e-b3d1477f0ac3",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<server xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1" id="71f1047f-f5db-42f9-b43f-85767bcafda6" adminPass="XVCtnj5P2MnJ">
|
||||
<metadata/>
|
||||
<atom:link href="http://openstack.example.com/v2/openstack/servers/71f1047f-f5db-42f9-b43f-85767bcafda6" rel="self"/>
|
||||
<atom:link href="http://openstack.example.com/openstack/servers/71f1047f-f5db-42f9-b43f-85767bcafda6" rel="bookmark"/>
|
||||
</server>
|
@ -29,6 +29,7 @@
|
||||
"compute_extension:admin_actions:migrate": "rule:admin_api",
|
||||
"compute_extension:aggregates": "rule:admin_api",
|
||||
"compute_extension:agents": "rule:admin_api",
|
||||
"compute_extension:attach_interfaces": "",
|
||||
"compute_extension:baremetal_nodes": "rule:admin_api",
|
||||
"compute_extension:cells": "rule:admin_api",
|
||||
"compute_extension:certificates": "",
|
||||
|
192
nova/api/openstack/compute/contrib/attach_interfaces.py
Normal file
192
nova/api/openstack/compute/contrib/attach_interfaces.py
Normal file
@ -0,0 +1,192 @@
|
||||
# Copyright 2012 SINA Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 instance interfaces extension."""
|
||||
|
||||
import webob
|
||||
from webob import exc
|
||||
|
||||
from nova.api.openstack import extensions
|
||||
from nova import compute
|
||||
from nova import exception
|
||||
from nova import network
|
||||
from nova.openstack.common import log as logging
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
authorize = extensions.extension_authorizer('compute', 'attach_interfaces')
|
||||
|
||||
|
||||
def _translate_interface_attachment_view(port_info):
|
||||
"""Maps keys for interface attachment details view."""
|
||||
return {
|
||||
'net_id': port_info['network_id'],
|
||||
'port_id': port_info['id'],
|
||||
'mac_addr': port_info['mac_address'],
|
||||
'port_state': port_info['status'],
|
||||
'fixed_ips': port_info.get('fixed_ips', None),
|
||||
}
|
||||
|
||||
|
||||
class InterfaceAttachmentController(object):
|
||||
"""The interface attachment API controller for the OpenStack API."""
|
||||
|
||||
def __init__(self):
|
||||
self.compute_api = compute.API()
|
||||
self.network_api = network.API()
|
||||
super(InterfaceAttachmentController, self).__init__()
|
||||
|
||||
def index(self, req, server_id):
|
||||
"""Returns the list of interface attachments for a given instance."""
|
||||
return self._items(req, server_id,
|
||||
entity_maker=_translate_interface_attachment_view)
|
||||
|
||||
def show(self, req, server_id, id):
|
||||
"""Return data about the given interface attachment."""
|
||||
context = req.environ['nova.context']
|
||||
authorize(context)
|
||||
|
||||
port_id = id
|
||||
try:
|
||||
instance = self.compute_api.get(context, server_id)
|
||||
except exception.NotFound:
|
||||
raise exc.HTTPNotFound()
|
||||
|
||||
try:
|
||||
port_info = self.network_api.show_port(context, port_id)
|
||||
except exception.NotFound:
|
||||
raise exc.HTTPNotFound()
|
||||
|
||||
if port_info['port']['device_id'] != server_id:
|
||||
raise exc.HTTPNotFound()
|
||||
|
||||
return {'interfaceAttachment': _translate_interface_attachment_view(
|
||||
port_info['port'])}
|
||||
|
||||
def create(self, req, server_id, body):
|
||||
"""Attach an interface to an instance."""
|
||||
context = req.environ['nova.context']
|
||||
authorize(context)
|
||||
|
||||
network_id = None
|
||||
port_id = None
|
||||
req_ip = None
|
||||
if body:
|
||||
attachment = body['interfaceAttachment']
|
||||
network_id = attachment.get('net_id', None)
|
||||
port_id = attachment.get('port_id', None)
|
||||
try:
|
||||
req_ip = attachment['fixed_ips'][0]['ip_address']
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if network_id and port_id:
|
||||
raise exc.HTTPBadRequest()
|
||||
if req_ip and not network_id:
|
||||
raise exc.HTTPBadRequest()
|
||||
|
||||
try:
|
||||
instance = self.compute_api.get(context, server_id)
|
||||
LOG.audit(_("Attach interface"), instance=instance)
|
||||
network_info = self.compute_api.attach_interface(context,
|
||||
instance, network_id, port_id, req_ip)
|
||||
except exception.NotFound, e:
|
||||
LOG.exception(e)
|
||||
raise exc.HTTPNotFound()
|
||||
except NotImplementedError:
|
||||
msg = _("Network driver does not support this function.")
|
||||
raise webob.exc.HTTPNotImplemented(explanation=msg)
|
||||
except exception.InterfaceAttachFailed, e:
|
||||
LOG.exception(e)
|
||||
msg = _("Failed to attach interface")
|
||||
raise webob.exc.HTTPInternalServerError(explanation=msg)
|
||||
|
||||
network, mapping = network_info
|
||||
return self.show(req, server_id, mapping['vif_uuid'])
|
||||
|
||||
def update(self, req, server_id, id, body):
|
||||
"""Update a interface attachment. We don't currently support this."""
|
||||
msg = _("Attachments update is not supported")
|
||||
raise exc.HTTPNotImplemented(explanation=msg)
|
||||
|
||||
def delete(self, req, server_id, id):
|
||||
"""Detach an interface from an instance."""
|
||||
context = req.environ['nova.context']
|
||||
authorize(context)
|
||||
port_id = id
|
||||
|
||||
try:
|
||||
instance = self.compute_api.get(context, server_id)
|
||||
LOG.audit(_("Detach interface %s"), port_id, instance=instance)
|
||||
|
||||
except exception.NotFound:
|
||||
raise exc.HTTPNotFound()
|
||||
try:
|
||||
self.compute_api.detach_interface(context,
|
||||
instance, port_id=port_id)
|
||||
except exception.PortNotFound:
|
||||
raise exc.HTTPNotFound
|
||||
except NotImplementedError:
|
||||
msg = _("Network driver does not support this function.")
|
||||
raise webob.exc.HTTPNotImplemented(explanation=msg)
|
||||
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
def _items(self, req, server_id, entity_maker):
|
||||
"""Returns a list of attachments, transformed through entity_maker."""
|
||||
context = req.environ['nova.context']
|
||||
authorize(context)
|
||||
|
||||
try:
|
||||
instance = self.compute_api.get(context, server_id)
|
||||
except exception.NotFound:
|
||||
raise exc.HTTPNotFound()
|
||||
|
||||
results = []
|
||||
search_opts = {'device_id': instance['uuid']}
|
||||
|
||||
try:
|
||||
data = self.network_api.list_ports(context, **search_opts)
|
||||
except exception.NotFound:
|
||||
raise exc.HTTPNotFound()
|
||||
except NotImplementedError:
|
||||
msg = _("Network driver does not support this function.")
|
||||
raise webob.exc.HTTPNotImplemented(explanation=msg)
|
||||
|
||||
ports = data.get('ports', [])
|
||||
results = [entity_maker(port) for port in ports]
|
||||
|
||||
return {'interfaceAttachments': results}
|
||||
|
||||
|
||||
class Attach_interfaces(extensions.ExtensionDescriptor):
|
||||
"""Attach interface support."""
|
||||
|
||||
name = "AttachInterfaces"
|
||||
alias = "os-attach-interfaces"
|
||||
namespace = "http://docs.openstack.org/compute/ext/interfaces/api/v1.1"
|
||||
updated = "2012-07-22T00:00:00+00:00"
|
||||
|
||||
def get_resources(self):
|
||||
resources = []
|
||||
|
||||
res = extensions.ResourceExtension('os-interface',
|
||||
InterfaceAttachmentController(),
|
||||
parent=dict(
|
||||
member_name='server',
|
||||
collection_name='servers'))
|
||||
resources.append(res)
|
||||
|
||||
return resources
|
@ -2284,6 +2284,20 @@ class API(base.Base):
|
||||
raise exception.VolumeUnattached(volume_id=volume_id)
|
||||
self._detach_volume(context, instance, volume_id)
|
||||
|
||||
@wrap_check_policy
|
||||
def attach_interface(self, context, instance, network_id, port_id,
|
||||
requested_ip):
|
||||
"""Use hotplug to add an network adapter to an instance."""
|
||||
return self.compute_rpcapi.attach_interface(context,
|
||||
instance=instance, network_id=network_id, port_id=port_id,
|
||||
requested_ip=requested_ip)
|
||||
|
||||
@wrap_check_policy
|
||||
def detach_interface(self, context, instance, port_id):
|
||||
"""Detach an network adapter from an instance."""
|
||||
self.compute_rpcapi.detach_interface(context, instance=instance,
|
||||
port_id=port_id)
|
||||
|
||||
@wrap_check_policy
|
||||
def get_instance_metadata(self, context, instance):
|
||||
"""Get all metadata associated with an instance."""
|
||||
|
@ -315,7 +315,7 @@ class ComputeVirtAPI(virtapi.VirtAPI):
|
||||
class ComputeManager(manager.SchedulerDependentManager):
|
||||
"""Manages the running instances from creation to destruction."""
|
||||
|
||||
RPC_API_VERSION = '2.24'
|
||||
RPC_API_VERSION = '2.25'
|
||||
|
||||
def __init__(self, compute_driver=None, *args, **kwargs):
|
||||
"""Load configuration options and connect to the hypervisor."""
|
||||
@ -2691,6 +2691,39 @@ class ComputeManager(manager.SchedulerDependentManager):
|
||||
except exception.NotFound:
|
||||
pass
|
||||
|
||||
def attach_interface(self, context, instance, network_id, port_id,
|
||||
requested_ip=None):
|
||||
"""Use hotplug to add an network adapter to an instance."""
|
||||
network_info = self.network_api.allocate_port_for_instance(
|
||||
context, instance, port_id, network_id, requested_ip,
|
||||
self.conductor_api)
|
||||
image_meta = _get_image_meta(context, instance['image_ref'])
|
||||
legacy_net_info = self._legacy_nw_info(network_info)
|
||||
for (network, mapping) in legacy_net_info:
|
||||
if mapping['vif_uuid'] == port_id:
|
||||
self.driver.attach_interface(instance, image_meta,
|
||||
[(network, mapping)])
|
||||
return (network, mapping)
|
||||
|
||||
def detach_interface(self, context, instance, port_id):
|
||||
"""Detach an network adapter from an instance."""
|
||||
network_info = self.network_api.get_instance_nw_info(
|
||||
context.elevated(), instance, conductor_api=self.conductor_api)
|
||||
legacy_nwinfo = self._legacy_nw_info(network_info)
|
||||
condemned = None
|
||||
for (network, mapping) in legacy_nwinfo:
|
||||
if mapping['vif_uuid'] == port_id:
|
||||
condemned = (network, mapping)
|
||||
break
|
||||
if condemned is None:
|
||||
raise exception.PortNotFound(_("Port %(port_id)s is not "
|
||||
"attached") % locals())
|
||||
|
||||
self.network_api.deallocate_port_for_instance(context, instance,
|
||||
port_id,
|
||||
self.conductor_api)
|
||||
self.driver.detach_interface(instance, [condemned])
|
||||
|
||||
def _get_compute_info(self, context, host):
|
||||
compute_node_ref = self.conductor_api.service_get_by_compute_host(
|
||||
context, host)
|
||||
|
@ -159,6 +159,7 @@ class ComputeAPI(nova.openstack.common.rpc.proxy.RpcProxy):
|
||||
rebuild_instance()
|
||||
2.23 - Remove network_info from reboot_instance
|
||||
2.24 - Added get_spice_console method
|
||||
2.25 - Add attach_interface() and detach_interface()
|
||||
'''
|
||||
|
||||
#
|
||||
@ -200,6 +201,15 @@ class ComputeAPI(nova.openstack.common.rpc.proxy.RpcProxy):
|
||||
instance=instance_p, network_id=network_id),
|
||||
topic=_compute_topic(self.topic, ctxt, None, instance))
|
||||
|
||||
def attach_interface(self, ctxt, instance, network_id, port_id,
|
||||
requested_ip):
|
||||
instance_p = jsonutils.to_primitive(instance)
|
||||
return self.call(ctxt, self.make_msg('attach_interface',
|
||||
instance=instance_p, network_id=network_id,
|
||||
port_id=port_id, requested_ip=requested_ip),
|
||||
topic=_compute_topic(self.topic, ctxt, None, instance),
|
||||
version='2.25')
|
||||
|
||||
def attach_volume(self, ctxt, instance, volume_id, mountpoint):
|
||||
instance_p = jsonutils.to_primitive(instance)
|
||||
self.cast(ctxt, self.make_msg('attach_volume',
|
||||
@ -243,6 +253,13 @@ class ComputeAPI(nova.openstack.common.rpc.proxy.RpcProxy):
|
||||
topic=_compute_topic(self.topic, ctxt, host, instance),
|
||||
version='2.7')
|
||||
|
||||
def detach_interface(self, ctxt, instance, port_id):
|
||||
instance_p = jsonutils.to_primitive(instance)
|
||||
self.cast(ctxt, self.make_msg('detach_interface',
|
||||
instance=instance_p, port_id=port_id),
|
||||
topic=_compute_topic(self.topic, ctxt, None, instance),
|
||||
version='2.25')
|
||||
|
||||
def detach_volume(self, ctxt, instance, volume_id):
|
||||
instance_p = jsonutils.to_primitive(instance)
|
||||
self.cast(ctxt, self.make_msg('detach_volume',
|
||||
|
@ -496,6 +496,10 @@ class NetworkNotFound(NotFound):
|
||||
message = _("Network %(network_id)s could not be found.")
|
||||
|
||||
|
||||
class PortNotFound(NotFound):
|
||||
message = _("Port id %(port_id)s could not be found.")
|
||||
|
||||
|
||||
class NetworkNotFoundForBridge(NetworkNotFound):
|
||||
message = _("Network could not be found for bridge %(bridge)s")
|
||||
|
||||
@ -529,10 +533,6 @@ class PortInUse(NovaException):
|
||||
message = _("Port %(port_id)s is still in use.")
|
||||
|
||||
|
||||
class PortNotFound(NotFound):
|
||||
message = _("Port %(port_id)s could not be found.")
|
||||
|
||||
|
||||
class PortNotUsable(NovaException):
|
||||
message = _("Port %(port_id)s not usable for instance %(instance)s.")
|
||||
|
||||
@ -1076,6 +1076,14 @@ class ConfigDriveUnknownFormat(NovaException):
|
||||
"iso9660 or vfat.")
|
||||
|
||||
|
||||
class InterfaceAttachFailed(Invalid):
|
||||
message = _("Failed to attach network adapter device to %(instance)s")
|
||||
|
||||
|
||||
class InterfaceDetachFailed(Invalid):
|
||||
message = _("Failed to detach network adapter device from %(instance)s")
|
||||
|
||||
|
||||
class InstanceUserDataTooLarge(NovaException):
|
||||
message = _("User data too large. User data must be no larger than "
|
||||
"%(maxsize)s bytes once base64 encoded. Your data is "
|
||||
|
@ -282,6 +282,25 @@ class API(base.Base):
|
||||
args['host'] = instance['host']
|
||||
self.network_rpcapi.deallocate_for_instance(context, **args)
|
||||
|
||||
# NOTE(danms): Here for quantum compatibility
|
||||
def allocate_port_for_instance(self, context, instance, port_id,
|
||||
network_id=None, requested_ip=None,
|
||||
conductor_api=None):
|
||||
raise NotImplementedError()
|
||||
|
||||
# NOTE(danms): Here for quantum compatibility
|
||||
def deallocate_port_for_instance(self, context, instance, port_id,
|
||||
conductor_api=None):
|
||||
raise NotImplementedError()
|
||||
|
||||
# NOTE(danms): Here for quantum compatibility
|
||||
def list_ports(self, *args, **kwargs):
|
||||
raise NotImplementedError()
|
||||
|
||||
# NOTE(danms): Here for quantum compatibility
|
||||
def show_port(self, *args, **kwargs):
|
||||
raise NotImplementedError()
|
||||
|
||||
@wrap_check_policy
|
||||
@refresh_cache
|
||||
def add_fixed_ip_to_instance(self, context, instance, network_id,
|
||||
|
@ -110,7 +110,7 @@ class API(base.Base):
|
||||
return nets
|
||||
|
||||
def allocate_for_instance(self, context, instance, **kwargs):
|
||||
"""Allocate all network resources for the instance.
|
||||
"""Allocate network resources for the instance.
|
||||
|
||||
TODO(someone): document the rest of these parameters.
|
||||
|
||||
@ -230,6 +230,33 @@ class API(base.Base):
|
||||
self.trigger_security_group_members_refresh(context, instance)
|
||||
self.trigger_instance_remove_security_group_refresh(context, instance)
|
||||
|
||||
def allocate_port_for_instance(self, context, instance, port_id,
|
||||
network_id=None, requested_ip=None,
|
||||
conductor_api=None):
|
||||
return self.allocate_for_instance(context, instance,
|
||||
requested_networks=[(network_id, requested_ip, port_id)],
|
||||
conductor_api=conductor_api)
|
||||
|
||||
def deallocate_port_for_instance(self, context, instance, port_id,
|
||||
conductor_api=None):
|
||||
try:
|
||||
quantumv2.get_client(context).delete_port(port_id)
|
||||
except Exception as ex:
|
||||
LOG.exception(_("Failed to delete quantum port %(port_id)s ") %
|
||||
locals())
|
||||
|
||||
self.trigger_security_group_members_refresh(context, instance)
|
||||
self.trigger_instance_remove_security_group_refresh(context, instance)
|
||||
|
||||
return self.get_instance_nw_info(context, instance,
|
||||
conductor_api=conductor_api)
|
||||
|
||||
def list_ports(self, context, **search_opts):
|
||||
return quantumv2.get_client(context).list_ports(**search_opts)
|
||||
|
||||
def show_port(self, context, port_id):
|
||||
return quantumv2.get_client(context).show_port(port_id)
|
||||
|
||||
def get_instance_nw_info(self, context, instance, networks=None,
|
||||
conductor_api=None):
|
||||
result = self._get_instance_nw_info(context, instance, networks)
|
||||
@ -640,7 +667,7 @@ class API(base.Base):
|
||||
data = quantumv2.get_client(context,
|
||||
admin=True).list_ports(**search_opts)
|
||||
ports = data.get('ports', [])
|
||||
if not networks:
|
||||
if networks is None:
|
||||
networks = self._get_available_networks(context,
|
||||
instance['project_id'])
|
||||
else:
|
||||
|
@ -0,0 +1,244 @@
|
||||
# Copyright 2012 SINA Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 nova.api.openstack.compute.contrib import attach_interfaces
|
||||
from nova.compute import api as compute_api
|
||||
from nova import context
|
||||
from nova import exception
|
||||
from nova.network import api as network_api
|
||||
from nova.openstack.common import cfg
|
||||
from nova.openstack.common import jsonutils
|
||||
from nova import test
|
||||
|
||||
import webob
|
||||
from webob import exc
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
FAKE_UUID1 = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
|
||||
FAKE_UUID2 = 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb'
|
||||
|
||||
FAKE_PORT_ID1 = '11111111-1111-1111-1111-111111111111'
|
||||
FAKE_PORT_ID2 = '22222222-2222-2222-2222-222222222222'
|
||||
FAKE_PORT_ID3 = '33333333-3333-3333-3333-333333333333'
|
||||
|
||||
FAKE_NET_ID1 = '44444444-4444-4444-4444-444444444444'
|
||||
FAKE_NET_ID2 = '55555555-5555-5555-5555-555555555555'
|
||||
FAKE_NET_ID3 = '66666666-6666-6666-6666-666666666666'
|
||||
|
||||
port_data1 = {
|
||||
"id": FAKE_PORT_ID1,
|
||||
"network_id": FAKE_NET_ID1,
|
||||
"admin_state_up": True,
|
||||
"status": "ACTIVE",
|
||||
"mac_address": "aa:aa:aa:aa:aa:aa",
|
||||
"fixed_ips": ["10.0.1.2"],
|
||||
"device_id": FAKE_UUID1,
|
||||
}
|
||||
|
||||
port_data2 = {
|
||||
"id": FAKE_PORT_ID2,
|
||||
"network_id": FAKE_NET_ID2,
|
||||
"admin_state_up": True,
|
||||
"status": "ACTIVE",
|
||||
"mac_address": "bb:bb:bb:bb:bb:bb",
|
||||
"fixed_ips": ["10.0.2.2"],
|
||||
"device_id": FAKE_UUID1,
|
||||
}
|
||||
|
||||
port_data3 = {
|
||||
"id": FAKE_PORT_ID3,
|
||||
"network_id": FAKE_NET_ID3,
|
||||
"admin_state_up": True,
|
||||
"status": "ACTIVE",
|
||||
"mac_address": "bb:bb:bb:bb:bb:bb",
|
||||
"fixed_ips": ["10.0.2.2"],
|
||||
"device_id": '',
|
||||
}
|
||||
|
||||
fake_networks = [FAKE_NET_ID1, FAKE_NET_ID2]
|
||||
ports = [port_data1, port_data2, port_data3]
|
||||
|
||||
|
||||
def fake_list_ports(self, *args, **kwargs):
|
||||
result = []
|
||||
for port in ports:
|
||||
if port['device_id'] == kwargs['device_id']:
|
||||
result.append(port)
|
||||
return {'ports': result}
|
||||
|
||||
|
||||
def fake_show_port(self, context, port_id, **kwargs):
|
||||
for port in ports:
|
||||
if port['id'] == port_id:
|
||||
return {'port': port}
|
||||
|
||||
|
||||
def fake_attach_interface(self, context, instance, network_id, port_id,
|
||||
requested_ip='192.168.1.3'):
|
||||
if not network_id:
|
||||
# if no network_id is given when add a port to an instance, use the
|
||||
# first default network.
|
||||
network_id = fake_networks[0]
|
||||
if not port_id:
|
||||
port_id = ports[fake_networks.index(network_id)]['id']
|
||||
network_info = [
|
||||
{'bridge': 'br-100',
|
||||
'id': network_id,
|
||||
'cidr': '192.168.1.0/24',
|
||||
'vlan': '101',
|
||||
'injected': 'False',
|
||||
'multi_host': 'False',
|
||||
'bridge_interface': 'bridge_interface'
|
||||
},
|
||||
{'label': 'fake_network',
|
||||
'broadcast': '192.168.1.255',
|
||||
'mac': '11:22:33:11:22:33',
|
||||
'vif_uuid': port_id,
|
||||
'rxtx_cap': 0,
|
||||
'dns': '8.8.8.8',
|
||||
'dhcp_server': '192.168.1.1',
|
||||
'ips': {'ip': requested_ip,
|
||||
'enabled': 1,
|
||||
'netmask': '255.255.255.0',
|
||||
'gateway': '192.168.1.254'}
|
||||
}
|
||||
]
|
||||
return network_info
|
||||
|
||||
|
||||
def fake_detach_interface(self, context, instance, port_id):
|
||||
for port in ports:
|
||||
if port['id'] == port_id:
|
||||
return
|
||||
raise exception.PortNotFound(port_id=port_id)
|
||||
|
||||
|
||||
def fake_get_instance(self, context, intance_id):
|
||||
return {}
|
||||
|
||||
|
||||
class InterfaceAttachTests(test.TestCase):
|
||||
def setUp(self):
|
||||
super(InterfaceAttachTests, self).setUp()
|
||||
self.flags(quantum_auth_strategy=None)
|
||||
self.flags(quantum_url='http://anyhost/')
|
||||
self.flags(quantum_url_timeout=30)
|
||||
self.stubs.Set(network_api.API, 'show_port', fake_show_port)
|
||||
self.stubs.Set(network_api.API, 'list_ports', fake_list_ports)
|
||||
self.stubs.Set(compute_api.API, 'get', fake_get_instance)
|
||||
self.context = context.get_admin_context()
|
||||
self.expected_show = {'interfaceAttachment':
|
||||
{'net_id': FAKE_NET_ID1,
|
||||
'port_id': FAKE_PORT_ID1,
|
||||
'mac_addr': port_data1['mac_address'],
|
||||
'port_state': port_data1['status'],
|
||||
'fixed_ips': port_data1['fixed_ips'],
|
||||
}}
|
||||
|
||||
def test_show(self):
|
||||
attachments = attach_interfaces.InterfaceAttachmentController()
|
||||
req = webob.Request.blank('/v2/fake/os-interfaces/show')
|
||||
req.method = 'POST'
|
||||
req.body = jsonutils.dumps({})
|
||||
req.headers['content-type'] = 'application/json'
|
||||
req.environ['nova.context'] = self.context
|
||||
|
||||
result = attachments.show(req, FAKE_UUID1, FAKE_PORT_ID1)
|
||||
self.assertEqual(self.expected_show, result)
|
||||
|
||||
def test_show_invalid(self):
|
||||
attachments = attach_interfaces.InterfaceAttachmentController()
|
||||
req = webob.Request.blank('/v2/fake/os-interfaces/show')
|
||||
req.method = 'POST'
|
||||
req.body = jsonutils.dumps({})
|
||||
req.headers['content-type'] = 'application/json'
|
||||
req.environ['nova.context'] = self.context
|
||||
|
||||
self.assertRaises(exc.HTTPNotFound,
|
||||
attachments.show, req, FAKE_UUID2, FAKE_PORT_ID1)
|
||||
|
||||
def test_delete(self):
|
||||
self.stubs.Set(compute_api.API, 'detach_interface',
|
||||
fake_detach_interface)
|
||||
attachments = attach_interfaces.InterfaceAttachmentController()
|
||||
req = webob.Request.blank('/v2/fake/os-interfaces/delete')
|
||||
req.method = 'POST'
|
||||
req.body = jsonutils.dumps({})
|
||||
req.headers['content-type'] = 'application/json'
|
||||
req.environ['nova.context'] = self.context
|
||||
|
||||
result = attachments.delete(req, FAKE_UUID1, FAKE_PORT_ID1)
|
||||
self.assertEqual('202 Accepted', result.status)
|
||||
|
||||
def test_delete_interface_not_found(self):
|
||||
self.stubs.Set(compute_api.API, 'detach_interface',
|
||||
fake_detach_interface)
|
||||
attachments = attach_interfaces.InterfaceAttachmentController()
|
||||
req = webob.Request.blank('/v2/fake/os-interfaces/delete')
|
||||
req.method = 'POST'
|
||||
req.body = jsonutils.dumps({})
|
||||
req.headers['content-type'] = 'application/json'
|
||||
req.environ['nova.context'] = self.context
|
||||
|
||||
self.assertRaises(exc.HTTPNotFound,
|
||||
attachments.delete,
|
||||
req,
|
||||
FAKE_UUID1,
|
||||
'invaid-port-id')
|
||||
|
||||
def test_attach_interface_without_network_id(self):
|
||||
self.stubs.Set(compute_api.API, 'attach_interface',
|
||||
fake_attach_interface)
|
||||
attachments = attach_interfaces.InterfaceAttachmentController()
|
||||
req = webob.Request.blank('/v2/fake/os-interfaces/attach')
|
||||
req.method = 'POST'
|
||||
body = jsonutils.dumps({'port_id': FAKE_PORT_ID1})
|
||||
req.body = jsonutils.dumps({})
|
||||
req.headers['content-type'] = 'application/json'
|
||||
req.environ['nova.context'] = self.context
|
||||
result = attachments.create(req, FAKE_UUID1, jsonutils.loads(req.body))
|
||||
self.assertEqual(result['interfaceAttachment']['net_id'],
|
||||
FAKE_NET_ID1)
|
||||
|
||||
def test_attach_interface_with_network_id(self):
|
||||
self.stubs.Set(compute_api.API, 'attach_interface',
|
||||
fake_attach_interface)
|
||||
attachments = attach_interfaces.InterfaceAttachmentController()
|
||||
req = webob.Request.blank('/v2/fake/os-interfaces/attach')
|
||||
req.method = 'POST'
|
||||
req.body = jsonutils.dumps({'interfaceAttachment':
|
||||
{'net_id': FAKE_NET_ID2}})
|
||||
req.headers['content-type'] = 'application/json'
|
||||
req.environ['nova.context'] = self.context
|
||||
result = attachments.create(req, FAKE_UUID1, jsonutils.loads(req.body))
|
||||
self.assertEqual(result['interfaceAttachment']['net_id'],
|
||||
FAKE_NET_ID2)
|
||||
|
||||
def test_attach_interface_with_port_and_network_id(self):
|
||||
self.stubs.Set(compute_api.API, 'attach_interface',
|
||||
fake_attach_interface)
|
||||
attachments = attach_interfaces.InterfaceAttachmentController()
|
||||
req = webob.Request.blank('/v2/fake/os-interfaces/attach')
|
||||
req.method = 'POST'
|
||||
req.body = jsonutils.dumps({'interfaceAttachment':
|
||||
{'port_id': FAKE_PORT_ID1,
|
||||
'net_id': FAKE_NET_ID2}})
|
||||
req.headers['content-type'] = 'application/json'
|
||||
req.environ['nova.context'] = self.context
|
||||
self.assertRaises(exc.HTTPBadRequest,
|
||||
attachments.create, req, FAKE_UUID1,
|
||||
jsonutils.loads(req.body))
|
@ -62,6 +62,7 @@ from nova.tests.compute import fake_resource_tracker
|
||||
from nova.tests.db import fakes as db_fakes
|
||||
from nova.tests import fake_instance_actions
|
||||
from nova.tests import fake_network
|
||||
from nova.tests import fake_network_cache_model
|
||||
from nova.tests.image import fake as fake_image
|
||||
from nova.tests import matchers
|
||||
from nova import utils
|
||||
@ -5828,6 +5829,41 @@ class ComputeAPITestCase(BaseTestCase):
|
||||
|
||||
db.instance_destroy(self.context, instance['uuid'])
|
||||
|
||||
def test_attach_interface(self):
|
||||
instance = {
|
||||
'image_ref': 'foo',
|
||||
}
|
||||
self.mox.StubOutWithMock(compute_manager, '_get_image_meta')
|
||||
self.mox.StubOutWithMock(self.compute.network_api,
|
||||
'allocate_port_for_instance')
|
||||
nwinfo = network_model.NetworkInfo()
|
||||
nwinfo.append(fake_network_cache_model.new_vif())
|
||||
network_id = nwinfo[0]['network']['id']
|
||||
port_id = nwinfo[0]['id']
|
||||
req_ip = '1.2.3.4'
|
||||
self.compute.network_api.allocate_port_for_instance(
|
||||
self.context, instance, port_id, network_id, req_ip,
|
||||
self.compute.conductor_api).AndReturn(nwinfo)
|
||||
compute_manager._get_image_meta(self.context, instance['image_ref'])
|
||||
self.mox.ReplayAll()
|
||||
network, mapping = self.compute.attach_interface(self.context,
|
||||
instance,
|
||||
network_id,
|
||||
port_id,
|
||||
req_ip)
|
||||
self.assertEqual(network['id'], network_id)
|
||||
return nwinfo, port_id
|
||||
|
||||
def test_detach_interface(self):
|
||||
nwinfo, port_id = self.test_attach_interface()
|
||||
self.stubs.Set(self.compute.network_api, 'get_instance_nw_info',
|
||||
lambda *a, **k: nwinfo)
|
||||
self.stubs.Set(self.compute.network_api,
|
||||
'deallocate_port_for_instance',
|
||||
lambda a, b, c, d: [])
|
||||
self.compute.detach_interface(self.context, {}, port_id)
|
||||
self.assertEqual(self.compute.driver._interfaces, {})
|
||||
|
||||
def test_attach_volume(self):
|
||||
# Ensure instance can be soft rebooted.
|
||||
|
||||
|
@ -105,6 +105,7 @@ policy_data = """
|
||||
"compute_extension:admin_actions:migrate": "",
|
||||
"compute_extension:aggregates": "",
|
||||
"compute_extension:agents": "",
|
||||
"compute_extension:attach_interfaces": "",
|
||||
"compute_extension:baremetal_nodes": "",
|
||||
"compute_extension:cells": "",
|
||||
"compute_extension:certificates": "",
|
||||
|
@ -96,6 +96,14 @@
|
||||
"namespace": "http://docs.openstack.org/compute/ext/agents/api/v2",
|
||||
"updated": "%(timestamp)s"
|
||||
},
|
||||
{
|
||||
"alias": "os-attach-interfaces",
|
||||
"description": "Attach interface support.",
|
||||
"links": [],
|
||||
"name": "AttachInterfaces",
|
||||
"namespace": "http://docs.openstack.org/compute/ext/interfaces/api/v1.1",
|
||||
"updated": "2012-07-22T00:00:00+00:00"
|
||||
},
|
||||
{
|
||||
"alias": "os-availability-zone",
|
||||
"description": "%(text)s",
|
||||
|
@ -33,6 +33,9 @@
|
||||
<extension alias="os-aggregates" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/aggregates/api/v1.1" name="Aggregates">
|
||||
<description>%(text)s</description>
|
||||
</extension>
|
||||
<extension alias="os-attach-interfaces" updated="2012-07-22T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/interfaces/api/v1.1" name="AttachInterfaces">
|
||||
<description>Attach interface support.</description>
|
||||
</extension>
|
||||
<extension alias="os-availability-zone" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/availabilityzone/api/v1.1" name="AvailabilityZone">
|
||||
<description>%(text)s</description>
|
||||
</extension>
|
||||
|
@ -0,0 +1,5 @@
|
||||
{
|
||||
"interfaceAttachment": {
|
||||
"port_id": "ce531f90-199f-48c0-816c-13e38010b442"
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interfaceAttachment>
|
||||
<port_id>%(port_id)s</port_id>
|
||||
</interfaceAttachment>
|
@ -0,0 +1,12 @@
|
||||
{
|
||||
"interfaceAttachment": {
|
||||
"fixed_ips": [{
|
||||
"subnet_id": "%(subnet_id)s",
|
||||
"ip_address": "%(ip_address)s"
|
||||
}],
|
||||
"mac_addr": "fa:16:3e:4c:2c:30",
|
||||
"net_id": "%(net_id)s",
|
||||
"port_id": "%(port_id)s",
|
||||
"port_state": "%(port_state)s"
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
<interfaceAttachment>
|
||||
<net_id>%(net_id)s</net_id>
|
||||
<port_id>%(port_id)s</port_id>
|
||||
<fixed_ips>
|
||||
<fixed_ip>
|
||||
<subnet_id>%(subnet_id)s</subnet_id>
|
||||
<ip_address>%(ip_address)s</ip_address>
|
||||
</fixed_ip>
|
||||
</fixed_ips>
|
||||
<port_state>%(port_state)s</port_state>
|
||||
<mac_addr>%(mac_addr)s</mac_addr>
|
||||
</interfaceAttachment>
|
@ -0,0 +1,16 @@
|
||||
{
|
||||
"interfaceAttachments": [
|
||||
{
|
||||
"port_state": "%(port_state)s",
|
||||
"fixed_ips": [
|
||||
{
|
||||
"subnet_id": "%(subnet_id)s",
|
||||
"ip_address": "%(ip_address)s"
|
||||
}
|
||||
],
|
||||
"net_id": "%(net_id)s",
|
||||
"port_id": "%(port_id)s",
|
||||
"mac_addr": "%(mac_addr)s"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interfaceAttachments>
|
||||
<interfaceAttachment>
|
||||
<port_state>%(port_state)s</port_state>
|
||||
<fixed_ips>
|
||||
<fixed_ip>
|
||||
<subnet_id>%(subnet_id)s</subnet_id>
|
||||
<ip_address>%(ip_address)s</ip_address>
|
||||
</fixed_ip>
|
||||
</fixed_ips>
|
||||
<port_id>%(port_id)s</port_id>
|
||||
<net_id>%(net_id)s</net_id>
|
||||
<mac_addr>%(mac_addr)s</mac_addr>
|
||||
</interfaceAttachment>
|
||||
</interfaceAttachments>
|
@ -0,0 +1,14 @@
|
||||
{
|
||||
"interfaceAttachment": {
|
||||
"port_state": "%(port_state)s",
|
||||
"fixed_ips": [
|
||||
{
|
||||
"subnet_id": "%(subnet_id)s",
|
||||
"ip_address": "%(ip_address)s"
|
||||
}
|
||||
],
|
||||
"net_id": "%(net_id)s",
|
||||
"port_id": "%(port_id)s",
|
||||
"mac_addr": "%(mac_addr)s"
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interfaceAttachment>
|
||||
<port_state>%(port_state)s</port_state>
|
||||
<fixed_ips>
|
||||
<fixed_ip>
|
||||
<subnet_id>%(subnet_id)s</subnet_id>
|
||||
<ip_address>%(ip_address)s</ip_address>
|
||||
</fixed_ip>
|
||||
</fixed_ips>
|
||||
<port_id>%(port_id)s</port_id>
|
||||
<net_id>%(net_id)s</net_id>
|
||||
<mac_addr>%(mac_addr)s</mac_addr>
|
||||
</interfaceAttachment>
|
@ -0,0 +1,16 @@
|
||||
{
|
||||
"server" : {
|
||||
"name" : "new-server-test",
|
||||
"imageRef" : "%(host)s/openstack/images/%(image_id)s",
|
||||
"flavorRef" : "%(host)s/openstack/flavors/1",
|
||||
"metadata" : {
|
||||
"My Server Name" : "Apache1"
|
||||
},
|
||||
"personality" : [
|
||||
{
|
||||
"path" : "/etc/banner.txt",
|
||||
"contents" : "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBpdCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5kIGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVsc2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4gQnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRoZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlvdSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vyc2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6b25zLiINCg0KLVJpY2hhcmQgQmFjaA=="
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<server xmlns="http://docs.openstack.org/compute/api/v1.1" imageRef="%(host)s/openstack/images/%(image_id)s" flavorRef="%(host)s/openstack/flavors/1" name="new-server-test">
|
||||
<metadata>
|
||||
<meta key="My Server Name">Apache1</meta>
|
||||
</metadata>
|
||||
<personality>
|
||||
<file path="/etc/banner.txt">
|
||||
ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBp
|
||||
dCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5k
|
||||
IGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVs
|
||||
c2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4g
|
||||
QnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRo
|
||||
ZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlv
|
||||
dSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vy
|
||||
c2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6
|
||||
b25zLiINCg0KLVJpY2hhcmQgQmFjaA==
|
||||
</file>
|
||||
</personality>
|
||||
</server>
|
@ -0,0 +1,16 @@
|
||||
{
|
||||
"server": {
|
||||
"adminPass": "%(password)s",
|
||||
"id": "%(id)s",
|
||||
"links": [
|
||||
{
|
||||
"href": "%(host)s/v2/openstack/servers/%(uuid)s",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "%(host)s/openstack/servers/%(uuid)s",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<server xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1" id="%(id)s" adminPass="%(password)s">
|
||||
<metadata/>
|
||||
<atom:link href="%(host)s/v2/openstack/servers/%(uuid)s" rel="self"/>
|
||||
<atom:link href="%(host)s/openstack/servers/%(uuid)s" rel="bookmark"/>
|
||||
</server>
|
@ -31,6 +31,7 @@ from nova.api.openstack.compute.contrib import coverage_ext
|
||||
from nova.api.openstack.compute.contrib import fping
|
||||
# Import extensions to pull in osapi_compute_extension CONF option used below.
|
||||
from nova.cloudpipe import pipelib
|
||||
from nova.compute import api as compute_api
|
||||
from nova import context
|
||||
from nova import db
|
||||
from nova.db.sqlalchemy import models
|
||||
@ -266,7 +267,6 @@ class ApiSampleTestBase(integrated_helpers._IntegratedTestBase):
|
||||
sample_data = "{}"
|
||||
else:
|
||||
sample_data = None
|
||||
|
||||
try:
|
||||
response_result = self._verify_something(subs, expected,
|
||||
response_data)
|
||||
@ -3302,3 +3302,174 @@ class FlavorAccessSampleJsonTests(ApiSampleTestBase):
|
||||
|
||||
class FlavorAccessSampleXmlTests(FlavorAccessSampleJsonTests):
|
||||
ctype = "xml"
|
||||
|
||||
|
||||
class AttachInterfacesSampleJsonTest(ServersSampleBase):
|
||||
extension_name = ('nova.api.openstack.compute.contrib.attach_interfaces.'
|
||||
'Attach_interfaces')
|
||||
|
||||
def setUp(self):
|
||||
super(AttachInterfacesSampleJsonTest, self).setUp()
|
||||
|
||||
def fake_list_ports(self, *args, **kwargs):
|
||||
uuid = kwargs.get('device_id', None)
|
||||
if not uuid:
|
||||
raise InstanceNotFound(instance_id=None)
|
||||
port_data = {
|
||||
"id": "ce531f90-199f-48c0-816c-13e38010b442",
|
||||
"network_id": "3cb9bc59-5699-4588-a4b1-b87f96708bc6",
|
||||
"admin_state_up": True,
|
||||
"status": "ACTIVE",
|
||||
"mac_address": "fa:16:3e:4c:2c:30",
|
||||
"fixed_ips": [
|
||||
{
|
||||
"ip_address": "192.168.1.3",
|
||||
"subnet_id": "f8a6e8f8-c2ec-497c-9f23-da9616de54ef"
|
||||
}
|
||||
],
|
||||
"device_id": uuid,
|
||||
}
|
||||
ports = {'ports': [port_data]}
|
||||
return ports
|
||||
|
||||
def fake_show_port(self, context, port_id=None):
|
||||
if not port_id:
|
||||
raise PortNotFound(port_id=None)
|
||||
port_data = {
|
||||
"id": port_id,
|
||||
"network_id": "3cb9bc59-5699-4588-a4b1-b87f96708bc6",
|
||||
"admin_state_up": True,
|
||||
"status": "ACTIVE",
|
||||
"mac_address": "fa:16:3e:4c:2c:30",
|
||||
"fixed_ips": [
|
||||
{
|
||||
"ip_address": "192.168.1.3",
|
||||
"subnet_id": "f8a6e8f8-c2ec-497c-9f23-da9616de54ef"
|
||||
}
|
||||
],
|
||||
"device_id": 'bece68a3-2f8b-4e66-9092-244493d6aba7',
|
||||
}
|
||||
port = {'port': port_data}
|
||||
return port
|
||||
|
||||
def fake_attach_interface(self, context, instance,
|
||||
network_id, port_id,
|
||||
requested_ip='192.168.1.3'):
|
||||
if not network_id:
|
||||
network_id = "fake_net_uuid"
|
||||
if not port_id:
|
||||
port_id = "fake_port_uuid"
|
||||
network_info = [
|
||||
{
|
||||
'bridge': 'br-100',
|
||||
'id': network_id,
|
||||
'cidr': '192.168.1.0/24',
|
||||
'vlan': '101',
|
||||
'injected': 'False',
|
||||
'multi_host': 'False',
|
||||
'bridge_interface': 'bridge_interface'
|
||||
},
|
||||
{
|
||||
"vif_uuid": port_id,
|
||||
"network_id": network_id,
|
||||
"admin_state_up": True,
|
||||
"status": "ACTIVE",
|
||||
"mac_address": "fa:16:3e:4c:2c:30",
|
||||
"fixed_ips": [
|
||||
{
|
||||
"ip_address": requested_ip,
|
||||
"subnet_id": "f8a6e8f8-c2ec-497c-9f23-da9616de54ef"
|
||||
}
|
||||
],
|
||||
"device_id": instance['uuid'],
|
||||
}
|
||||
]
|
||||
return network_info
|
||||
|
||||
def fake_detach_interface(self, context, instance, port_id):
|
||||
pass
|
||||
|
||||
self.stubs.Set(network_api.API, 'list_ports', fake_list_ports)
|
||||
self.stubs.Set(network_api.API, 'show_port', fake_show_port)
|
||||
self.stubs.Set(compute_api.API, 'attach_interface',
|
||||
fake_attach_interface)
|
||||
self.stubs.Set(compute_api.API, 'detach_interface',
|
||||
fake_detach_interface)
|
||||
self.flags(quantum_auth_strategy=None)
|
||||
self.flags(quantum_url='http://anyhost/')
|
||||
self.flags(quantum_url_timeout=30)
|
||||
|
||||
def generalize_subs(self, subs, vanilla_regexes):
|
||||
subs['subnet_id'] = vanilla_regexes['uuid']
|
||||
subs['net_id'] = vanilla_regexes['uuid']
|
||||
subs['port_id'] = vanilla_regexes['uuid']
|
||||
subs['mac_addr'] = '(?:[a-f0-9]{2}:){5}[a-f0-9]{2}'
|
||||
subs['ip_address'] = vanilla_regexes['ip']
|
||||
return subs
|
||||
|
||||
def test_list_interfaces(self):
|
||||
instance_uuid = self._post_server()
|
||||
response = self._do_get('servers/%s/os-interface' % instance_uuid)
|
||||
self.assertEqual(response.status, 200)
|
||||
subs = {
|
||||
'ip_address': '192.168.1.3',
|
||||
'subnet_id': 'f8a6e8f8-c2ec-497c-9f23-da9616de54ef',
|
||||
'mac_addr': 'fa:16:3e:4c:2c:30',
|
||||
'net_id': '3cb9bc59-5699-4588-a4b1-b87f96708bc6',
|
||||
'port_id': 'ce531f90-199f-48c0-816c-13e38010b442',
|
||||
'port_state': 'ACTIVE'
|
||||
}
|
||||
self._verify_response('attach-interfaces-list-resp', subs, response)
|
||||
|
||||
def _stub_show_for_instance(self, instance_uuid, port_id):
|
||||
show_port = network_api.API().show_port(None, port_id)
|
||||
show_port['port']['device_id'] = instance_uuid
|
||||
self.stubs.Set(network_api.API, 'show_port', lambda *a, **k: show_port)
|
||||
|
||||
def test_show_interfaces(self):
|
||||
instance_uuid = self._post_server()
|
||||
port_id = 'ce531f90-199f-48c0-816c-13e38010b442'
|
||||
self._stub_show_for_instance(instance_uuid, port_id)
|
||||
response = self._do_get('servers/%s/os-interface/%s' %
|
||||
(instance_uuid, port_id))
|
||||
self.assertEqual(response.status, 200)
|
||||
subs = {
|
||||
'ip_address': '192.168.1.3',
|
||||
'subnet_id': 'f8a6e8f8-c2ec-497c-9f23-da9616de54ef',
|
||||
'mac_addr': 'fa:16:3e:4c:2c:30',
|
||||
'net_id': '3cb9bc59-5699-4588-a4b1-b87f96708bc6',
|
||||
'port_id': port_id,
|
||||
'port_state': 'ACTIVE'
|
||||
}
|
||||
self._verify_response('attach-interfaces-show-resp', subs, response)
|
||||
|
||||
def test_create_interfaces(self, instance_uuid=None):
|
||||
if instance_uuid is None:
|
||||
instance_uuid = self._post_server()
|
||||
subs = {
|
||||
'net_id': '3cb9bc59-5699-4588-a4b1-b87f96708bc6',
|
||||
'port_id': 'ce531f90-199f-48c0-816c-13e38010b442',
|
||||
'subnet_id': 'f8a6e8f8-c2ec-497c-9f23-da9616de54ef',
|
||||
'ip_address': '192.168.1.3',
|
||||
'port_state': 'ACTIVE',
|
||||
'mac_addr': 'fa:16:3e:4c:2c:30',
|
||||
}
|
||||
self._stub_show_for_instance(instance_uuid, subs['port_id'])
|
||||
response = self._do_post('servers/%s/os-interface' % instance_uuid,
|
||||
'attach-interfaces-create-req', subs)
|
||||
self.assertEqual(response.status, 200)
|
||||
subs.update(self._get_regexes())
|
||||
self._verify_response('attach-interfaces-create-resp',
|
||||
subs, response)
|
||||
|
||||
def test_delete_interfaces(self):
|
||||
instance_uuid = self._post_server()
|
||||
port_id = 'ce531f90-199f-48c0-816c-13e38010b442'
|
||||
response = self._do_delete('servers/%s/os-interface/%s' %
|
||||
(instance_uuid, port_id))
|
||||
self.assertEqual(response.status, 202)
|
||||
self.assertEqual(response.read(), '')
|
||||
|
||||
|
||||
class AttachInterfacesSampleXmlTest(AttachInterfacesSampleJsonTest):
|
||||
ctype = 'xml'
|
||||
|
@ -230,11 +230,10 @@ class TestQuantumv2(test.TestCase):
|
||||
'router_id': 'router_id1'}
|
||||
|
||||
def tearDown(self):
|
||||
try:
|
||||
self.mox.UnsetStubs()
|
||||
self.mox.VerifyAll()
|
||||
finally:
|
||||
CONF.reset()
|
||||
self.addCleanup(CONF.reset)
|
||||
self.addCleanup(self.mox.VerifyAll)
|
||||
self.addCleanup(self.mox.UnsetStubs)
|
||||
self.addCleanup(self.stubs.UnsetAll)
|
||||
super(TestQuantumv2, self).tearDown()
|
||||
|
||||
def _verify_nw_info(self, nw_inf, index=0):
|
||||
@ -614,7 +613,9 @@ class TestQuantumv2(test.TestCase):
|
||||
{'ports': port_data})
|
||||
for port in port_data:
|
||||
self.moxed_client.delete_port(port['id'])
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
api = quantumapi.API()
|
||||
api.deallocate_for_instance(self.context, self.instance)
|
||||
|
||||
@ -626,6 +627,56 @@ class TestQuantumv2(test.TestCase):
|
||||
# Test to deallocate in two ports env.
|
||||
self._deallocate_for_instance(2)
|
||||
|
||||
def _test_deallocate_port_for_instance(self, number):
|
||||
port_data = number == 1 and self.port_data1 or self.port_data2
|
||||
self.moxed_client.delete_port(port_data[0]['id'])
|
||||
|
||||
nets = [port_data[0]['network_id']]
|
||||
quantumv2.get_client(mox.IgnoreArg(), admin=True).AndReturn(
|
||||
self.moxed_client)
|
||||
self.moxed_client.list_ports(
|
||||
tenant_id=self.instance['project_id'],
|
||||
device_id=self.instance['uuid']).AndReturn(
|
||||
{'ports': port_data[1:]})
|
||||
quantumv2.get_client(mox.IgnoreArg()).MultipleTimes().AndReturn(
|
||||
self.moxed_client)
|
||||
self.moxed_client.list_networks(
|
||||
tenant_id=self.instance['project_id'],
|
||||
shared=False).AndReturn(
|
||||
{'networks': [self.nets2[1]]})
|
||||
self.moxed_client.list_networks(shared=True).AndReturn(
|
||||
{'networks': []})
|
||||
for port in port_data[1:]:
|
||||
self.moxed_client.list_subnets(id=['my_subid2']).AndReturn({})
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
api = quantumapi.API()
|
||||
nwinfo = api.deallocate_port_for_instance(self.context, self.instance,
|
||||
port_data[0]['id'])
|
||||
self.assertEqual(len(nwinfo), len(port_data[1:]))
|
||||
if len(port_data) > 1:
|
||||
self.assertEqual(nwinfo[0]['network']['id'], 'my_netid2')
|
||||
|
||||
def test_deallocate_port_for_instance_1(self):
|
||||
# Test to deallocate the first and only port
|
||||
self._test_deallocate_port_for_instance(1)
|
||||
|
||||
def test_deallocate_port_for_instance_2(self):
|
||||
# Test to deallocate the first port of two
|
||||
self._test_deallocate_port_for_instance(2)
|
||||
|
||||
def test_list_ports(self):
|
||||
search_opts = {'parm': 'value'}
|
||||
self.moxed_client.list_ports(**search_opts)
|
||||
self.mox.ReplayAll()
|
||||
quantumapi.API().list_ports(self.context, **search_opts)
|
||||
|
||||
def test_show_port(self):
|
||||
self.moxed_client.show_port('foo')
|
||||
self.mox.ReplayAll()
|
||||
quantumapi.API().show_port(self.context, 'foo')
|
||||
|
||||
def test_validate_networks(self):
|
||||
requested_networks = [('my_netid1', 'test', None),
|
||||
('my_netid2', 'test2', None)]
|
||||
|
@ -302,6 +302,14 @@ class ComputeDriver(object):
|
||||
"""Detach the disk attached to the instance."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def attach_interface(self, instance, image_meta, network_info):
|
||||
"""Attach an interface to the instance."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def detach_interface(self, instance, network_info):
|
||||
"""Detach an interface from the instance."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def migrate_disk_and_power_off(self, context, instance, dest,
|
||||
instance_type, network_info,
|
||||
block_device_info=None):
|
||||
|
@ -102,6 +102,7 @@ class FakeDriver(driver.ComputeDriver):
|
||||
'hypervisor_hostname': 'fake-mini',
|
||||
}
|
||||
self._mounts = {}
|
||||
self._interfaces = {}
|
||||
|
||||
def init_host(self, host):
|
||||
return
|
||||
@ -222,6 +223,19 @@ class FakeDriver(driver.ComputeDriver):
|
||||
pass
|
||||
return True
|
||||
|
||||
def attach_interface(self, instance, image_meta, network_info):
|
||||
for (network, mapping) in network_info:
|
||||
if mapping['vif_uuid'] in self._interfaces:
|
||||
raise exception.InterfaceAttachFailed('duplicate')
|
||||
self._interfaces[mapping['vif_uuid']] = mapping
|
||||
|
||||
def detach_interface(self, instance, network_info):
|
||||
for (network, mapping) in network_info:
|
||||
try:
|
||||
del self._interfaces[mapping['vif_uuid']]
|
||||
except KeyError:
|
||||
raise exception.InterfaceDetachFailed('not attached')
|
||||
|
||||
def get_info(self, instance):
|
||||
if instance['name'] not in self.instances:
|
||||
raise exception.InstanceNotFound(instance_id=instance['name'])
|
||||
|
@ -767,6 +767,50 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
connection_info,
|
||||
disk_dev)
|
||||
|
||||
@exception.wrap_exception()
|
||||
def attach_interface(self, instance, image_meta, network_info):
|
||||
virt_dom = self._lookup_by_name(instance['name'])
|
||||
for (network, mapping) in network_info:
|
||||
self.vif_driver.plug(instance, (network, mapping))
|
||||
self.firewall_driver.setup_basic_filtering(instance,
|
||||
[(network, mapping)])
|
||||
cfg = self.vif_driver.get_config(instance, network, mapping,
|
||||
image_meta)
|
||||
try:
|
||||
flags = libvirt.VIR_DOMAIN_AFFECT_CONFIG
|
||||
state = LIBVIRT_POWER_STATE[virt_dom.info()[0]]
|
||||
if state == power_state.RUNNING:
|
||||
flags |= libvirt.VIR_DOMAIN_AFFECT_LIVE
|
||||
virt_dom.attachDeviceFlags(cfg.to_xml(), flags)
|
||||
except libvirt.libvirtError as ex:
|
||||
LOG.error(_('attaching network adapter failed.'),
|
||||
instance=instance)
|
||||
self.vif_driver.unplug(instance, (network, mapping))
|
||||
raise exception.InterfaceAttachFailed(instance)
|
||||
|
||||
@exception.wrap_exception()
|
||||
def detach_interface(self, instance, network_info):
|
||||
virt_dom = self._lookup_by_name(instance['name'])
|
||||
for (network, mapping) in network_info:
|
||||
cfg = self.vif_driver.get_config(instance, network, mapping, None)
|
||||
try:
|
||||
self.vif_driver.unplug(instance, (network, mapping))
|
||||
flags = libvirt.VIR_DOMAIN_AFFECT_CONFIG
|
||||
state = LIBVIRT_POWER_STATE[virt_dom.info()[0]]
|
||||
if state == power_state.RUNNING:
|
||||
flags |= libvirt.VIR_DOMAIN_AFFECT_LIVE
|
||||
virt_dom.detachDeviceFlags(cfg.to_xml(), flags)
|
||||
except libvirt.libvirtError as ex:
|
||||
error_code = ex.get_error_code()
|
||||
if error_code == libvirt.VIR_ERR_NO_DOMAIN:
|
||||
LOG.warn(_("During detach_interface, "
|
||||
"instance disappeared."),
|
||||
instance=instance)
|
||||
else:
|
||||
LOG.error(_('detaching network adapter failed.'),
|
||||
instance=instance)
|
||||
raise exception.InterfaceDetachFailed(instance)
|
||||
|
||||
def snapshot(self, context, instance, image_href, update_task_state):
|
||||
"""Create snapshot from a running VM instance.
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user