Repository: libcloud Updated Branches: refs/heads/trunk 1ac9467a9 -> b2b7684a7
implement OpenStackv2 port attaching/detaching Adds Port Interface calls for the OpenStack v2 driver. Similar in functionality to a floating IPs (can be attached / detached from a compute instance to move IPs between instances) but a bit different. > A port is a connection point for attaching a single device, such as the > NIC of a server, to a network. The port also describes the associated > network configuration, such as the MAC and IP addresses to be used on > that port. https://docs.openstack.org/python-openstackclient/pike/cli/command-objects/port.html Also see: https://developer.openstack.org/api-ref/compute/#port-interfaces-servers-os-interface This commit adds: - a connection to the neutron network API for functionality that is not exposed through the nova compute api - an OpenStack_2_PortInterface object - an OpenStack_2_PortInterfaceState object for port interface states - an ex_list_ports method (via the neutron api) - an ex_delete_port method (via the neutron api) - an ex_detach_port_interface method (via the nova api) - an ex_attach_port_interface method (via the nova api) Signed-off-by: Quentin Pradet <[email protected]> Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/c24c71fd Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/c24c71fd Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/c24c71fd Branch: refs/heads/trunk Commit: c24c71fd3265270cc2df0ab08f53239d7a06063c Parents: 1ac9467 Author: Rick van de Loo <[email protected]> Authored: Thu Jul 5 17:18:27 2018 +0200 Committer: Quentin Pradet <[email protected]> Committed: Fri Jul 6 10:02:23 2018 +0400 ---------------------------------------------------------------------- libcloud/compute/drivers/openstack.py | 209 ++++++++++++++++++- libcloud/test/common/test_openstack_identity.py | 7 +- .../compute/fixtures/openstack/_v2_0__auth.json | 22 ++ .../fixtures/openstack_v1.1/_ports_v2.json | 185 ++++++++++++++++ libcloud/test/compute/test_openstack.py | 83 +++++++- 5 files changed, 499 insertions(+), 7 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/libcloud/blob/c24c71fd/libcloud/compute/drivers/openstack.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/drivers/openstack.py b/libcloud/compute/drivers/openstack.py index 5307376..e0a5ff0 100644 --- a/libcloud/compute/drivers/openstack.py +++ b/libcloud/compute/drivers/openstack.py @@ -38,12 +38,13 @@ from libcloud.common.openstack import OpenStackDriverMixin from libcloud.common.openstack import OpenStackException from libcloud.common.openstack import OpenStackResponse from libcloud.utils.networking import is_public_subnet -from libcloud.compute.base import NodeSize, NodeImage, NodeImageMember +from libcloud.compute.base import NodeSize, NodeImage, NodeImageMember, \ + UuidMixin from libcloud.compute.base import (NodeDriver, Node, NodeLocation, StorageVolume, VolumeSnapshot) from libcloud.compute.base import KeyPair from libcloud.compute.types import NodeState, StorageVolumeState, Provider, \ - VolumeSnapshotState + VolumeSnapshotState, Type from libcloud.pricing import get_size_price from libcloud.utils.xml import findall from libcloud.utils.py3 import ET @@ -59,6 +60,8 @@ __all__ = [ 'OpenStack_1_1_NodeDriver', 'OpenStack_1_1_FloatingIpPool', 'OpenStack_1_1_FloatingIpAddress', + 'OpenStack_2_PortInterfaceState', + 'OpenStack_2_PortInterface', 'OpenStackNodeDriver' ] @@ -80,6 +83,12 @@ class OpenStackImageConnection(OpenStackBaseConnection): service_region = 'RegionOne' +class OpenStackNetworkConnection(OpenStackBaseConnection): + service_type = 'network' + service_name = 'neutron' + service_region = 'RegionOne' + + class OpenStackNodeDriver(NodeDriver, OpenStackDriverMixin): """ Base OpenStack node driver. Should not be used directly. @@ -2491,6 +2500,25 @@ class OpenStack_2_ImageConnection(OpenStackImageConnection): return json.dumps(data) +class OpenStack_2_NetworkConnection(OpenStackNetworkConnection): + responseCls = OpenStack_1_1_Response + accept_format = 'application/json' + default_content_type = 'application/json; charset=UTF-8' + + def encode_data(self, data): + return json.dumps(data) + + +class OpenStack_2_PortInterfaceState(Type): + """ + Standard states of OpenStack_2_PortInterfaceState + """ + BUILD = 'build' + ACTIVE = 'active' + DOWN = 'down' + UNKNOWN = 'unknown' + + class OpenStack_2_NodeDriver(OpenStack_1_1_NodeDriver): """ OpenStack node driver. @@ -2514,11 +2542,33 @@ class OpenStack_2_NodeDriver(OpenStack_1_1_NodeDriver): # image/v2/index.html#list-image-members image_connectionCls = OpenStack_2_ImageConnection image_connection = None + + # Similarly not all node-related operations are exposed through the + # compute API + # See https://developer.openstack.org/api-ref/compute/ + # For example, creating a new node in an OpenStack that is configured to + # create a new port for every new instance will make it so that if that + # port is detached it disappears. But if the port is manually created + # beforehand using the neutron network API and node is booted with that + # port pre-specified, then detaching that port later will result in that + # becoming a re-attachable resource much like a floating ip. So because + # even though this is the compute driver, we do connect to the networking + # API here because some operations relevant for compute can only be + # accessed from there. + network_connectionCls = OpenStack_2_NetworkConnection + network_connection = None type = Provider.OPENSTACK features = {"create_node": ["generates_password"]} _networks_url_prefix = '/os-networks' + PORT_INTERFACE_MAP = { + 'BUILD': OpenStack_2_PortInterfaceState.BUILD, + 'ACTIVE': OpenStack_2_PortInterfaceState.ACTIVE, + 'DOWN': OpenStack_2_PortInterfaceState.DOWN, + 'UNKNOWN': OpenStack_2_PortInterfaceState.UNKNOWN + } + def __init__(self, *args, **kwargs): original_connectionCls = self.connectionCls self._ex_force_api_version = str(kwargs.pop('ex_force_api_version', @@ -2532,11 +2582,47 @@ class OpenStack_2_NodeDriver(OpenStack_1_1_NodeDriver): super(OpenStack_2_NodeDriver, self).__init__(*args, **kwargs) self.image_connection = self.connection - # We run the init again to get the compute API connection + # We run the init again to get the Neutron V2 API connection + # and put that on the object under self.network_connection. + self.connectionCls = self.network_connectionCls + super(OpenStack_2_NodeDriver, self).__init__(*args, **kwargs) + self.network_connection = self.connection + + # We run the init once again to get the compute API connection # and that's put under self.connection as normal. self.connectionCls = original_connectionCls super(OpenStack_2_NodeDriver, self).__init__(*args, **kwargs) + def _to_port(self, element): + created = element['created_at'] + updated = element.get('updated_at') + return OpenStack_2_PortInterface( + id=element['id'], + state=self.PORT_INTERFACE_MAP.get( + element.get('status'), OpenStack_2_PortInterfaceState.UNKNOWN + ), + created=created, + driver=self, + extra=dict( + allowed_address_pairs=element['allowed_address_pairs'], + binding_vnic_type=element['binding:vnic_type'], + device_id=element['device_id'], + description=element['description'], + device_owner=element['device_owner'], + fixed_ips=element['fixed_ips'], + mac_address=element['mac_address'], + name=element['name'], + network_id=element['network_id'], + project_id=element['project_id'], + port_security_enabled=element['port_security_enabled'], + revision_number=element['revision_number'], + security_groups=element['security_groups'], + tags=element['tags'], + tenant_id=element['tenant_id'], + updated=updated, + ) + ) + def get_image(self, image_id): """ Get a NodeImage using the V2 Glance API @@ -2696,6 +2782,73 @@ class OpenStack_2_NodeDriver(OpenStack_1_1_NodeDriver): ) return self._to_image_member(response.object) + def ex_list_ports(self): + """ + List all OpenStack_2_PortInterfaces + + https://developer.openstack.org/api-ref/network/v2/#list-ports + + :rtype: ``list`` of :class:`OpenStack_2_PortInterface` + """ + response = self.network_connection.request( + '/v2.0/ports' + ) + return [self._to_port(port) for port in response.object['ports']] + + def ex_delete_port(self, port): + """ + Delete an OpenStack_2_PortInterface + + https://developer.openstack.org/api-ref/network/v2/#delete-port + + :param port: port interface to remove + :type port: :class:`OpenStack_2_PortInterface` + + :rtype: ``bool`` + """ + response = self.network_connection.request( + '/v2.0/ports/%s' % port.id, method='DELETE' + ) + return response.success() + + def ex_detach_port_interface(self, node, port): + """ + Detaches an OpenStack_2_PortInterface interface from a Node. + :param node: node + :type node: :class:`Node` + + :param port: port interface to remove + :type port: :class:`OpenStack_2_PortInterface` + + :rtype: ``bool`` + """ + return self.connection.request( + '/servers/%s/os-interface/%s' % (node.id, port.id), + method='DELETE' + ).success() + + def ex_attach_port_interface(self, node, port): + """ + Attaches an OpenStack_2_PortInterface to a Node. + + :param node: node + :type node: :class:`Node` + + :param port: port interface to remove + :type port: :class:`OpenStack_2_PortInterface` + + :rtype: ``bool`` + """ + data = { + 'interfaceAttachment': { + 'port_id': port.id + } + } + return self.connection.request( + '/servers/{}/os-interface'.format(node.id), + method='POST', data=data + ).success() + class OpenStack_1_1_FloatingIpPool(object): """ @@ -2800,3 +2953,53 @@ class OpenStack_1_1_FloatingIpAddress(object): return ('<OpenStack_1_1_FloatingIpAddress: id=%s, ip_addr=%s,' ' pool=%s, driver=%s>' % (self.id, self.ip_address, self.pool, self.driver)) + + +class OpenStack_2_PortInterface(UuidMixin): + """ + Port Interface info. Similar in functionality to a floating IP (can be + attached / detached from a compute instance) but implementation-wise a + bit different. + + > A port is a connection point for attaching a single device, such as the + > NIC of a server, to a network. The port also describes the associated + > network configuration, such as the MAC and IP addresses to be used on + > that port. + https://docs.openstack.org/python-openstackclient/pike/cli/command-objects/port.html + + Also see: + https://developer.openstack.org/api-ref/compute/#port-interfaces-servers-os-interface + """ + + def __init__(self, id, state, driver, created=None, extra=None): + """ + :param id: Port Interface ID. + :type id: ``str`` + :param state: State of the OpenStack_2_PortInterface. + :type state: :class:`.OpenStack_2_PortInterfaceState` + :param created: A datetime object that represents when the + port interface was created + :type created: ``datetime.datetime`` + :param extra: Optional provided specific attributes associated with + this image. + :type extra: ``dict`` + """ + self.id = str(id) + self.state = state + self.driver = driver + self.created = created + self.extra = extra or {} + UuidMixin.__init__(self) + + def delete(self): + """ + Delete this Port Interface + + :rtype: ``bool`` + """ + return self.driver.ex_delete_port(self) + + def __repr__(self): + return (('<OpenStack_2_PortInterface: id=%s, state=%s, ' + 'driver=%s ...>') + % (self.id, self.state, self.driver.name)) http://git-wip-us.apache.org/repos/asf/libcloud/blob/c24c71fd/libcloud/test/common/test_openstack_identity.py ---------------------------------------------------------------------- diff --git a/libcloud/test/common/test_openstack_identity.py b/libcloud/test/common/test_openstack_identity.py index 9ac042c..c36f13a 100644 --- a/libcloud/test/common/test_openstack_identity.py +++ b/libcloud/test/common/test_openstack_identity.py @@ -525,7 +525,7 @@ class OpenStackServiceCatalogTestCase(unittest.TestCase): catalog = OpenStackServiceCatalog(service_catalog=service_catalog, auth_version='2.0') entries = catalog.get_entries() - self.assertEqual(len(entries), 7) + self.assertEqual(len(entries), 8) entry = [e for e in entries if e.service_name == 'cloudServers'][0] self.assertEqual(entry.service_type, 'compute') @@ -591,8 +591,8 @@ class OpenStackServiceCatalogTestCase(unittest.TestCase): catalog = OpenStackServiceCatalog(service_catalog=service_catalog, auth_version='2.0') service_types = catalog.get_service_types() - self.assertEqual(service_types, ['compute', 'image', 'object-store', - 'rax:object-cdn']) + self.assertEqual(service_types, ['compute', 'image', 'network', + 'object-store', 'rax:object-cdn']) service_types = catalog.get_service_types(region='ORD') self.assertEqual(service_types, ['rax:object-cdn']) @@ -611,6 +611,7 @@ class OpenStackServiceCatalogTestCase(unittest.TestCase): 'cloudServersOpenStack', 'cloudServersPreprod', 'glance', + 'neutron', 'nova']) service_names = catalog.get_service_names(service_type='compute') http://git-wip-us.apache.org/repos/asf/libcloud/blob/c24c71fd/libcloud/test/compute/fixtures/openstack/_v2_0__auth.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/openstack/_v2_0__auth.json b/libcloud/test/compute/fixtures/openstack/_v2_0__auth.json index 91fe35b..79c6776 100644 --- a/libcloud/test/compute/fixtures/openstack/_v2_0__auth.json +++ b/libcloud/test/compute/fixtures/openstack/_v2_0__auth.json @@ -101,6 +101,28 @@ { "endpoints": [ { + "region": "RegionOne", + "tenantId": "1337", + "publicURL": "https://test_endpoint.com/v2/1337", + "versionInfo": "https://test_endpoint.com/v2/", + "versionList": "https://test_endpoint.com/", + "versionId": "2" + }, + { + "region": "fr1", + "tenantId": "1337", + "publicURL": "https://test_endpoint.com/v2/1337", + "versionInfo": "https://test_endpoint.com/v2/", + "versionList": "https://test_endpoint.com/", + "versionId": "2" + } + ], + "name": "neutron", + "type": "network" + }, + { + "endpoints": [ + { "region": "DFW", "tenantId": "613469", "publicURL": "https://dfw.servers.api.rackspacecloud.com/v2/1337", http://git-wip-us.apache.org/repos/asf/libcloud/blob/c24c71fd/libcloud/test/compute/fixtures/openstack_v1.1/_ports_v2.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/openstack_v1.1/_ports_v2.json b/libcloud/test/compute/fixtures/openstack_v1.1/_ports_v2.json new file mode 100644 index 0000000..e31f8f3 --- /dev/null +++ b/libcloud/test/compute/fixtures/openstack_v1.1/_ports_v2.json @@ -0,0 +1,185 @@ +{ + "ports": [ + { + "status": "BUILD", + "extra_dhcp_opts": [], + "description": "", + "allowed_address_pairs": [], + "tags": [], + "network_id": "123c8a8c-6427-4e8f-a805-2035365f4d43", + "tenant_id": "abcdec85bee34bb0a44ab8255eb36abc", + "created_at": "2018-07-04T14:38:18Z", + "admin_state_up": true, + "updated_at": "2018-07-05T14:40:43Z", + "binding:vnic_type": "normal", + "device_owner": "compute:nova", + "name": "", + "revision_number": 2036, + "mac_address": "ba:12:12:8a:b2:73", + "port_security_enabled": true, + "project_id": "abcdec85bee34bb0a44ab8255eb36abc", + "fixed_ips": [ + { + "subnet_id": "1231a12a-125b-4329-a3c5-312ea86a7577", + "ip_address": "12.123.12.32" + } + ], + "id": "126da55e-cfcb-41c8-ae39-a26cb8a7e723", + "security_groups": [ + "abcfb112-5b5c-4c6b-8b3f-dbaee57df440" + ], + "device_id": "95e75643-2008-123f-ad13-e20ea64e3c87" + }, + { + "status": "BUILD", + "extra_dhcp_opts": [], + "description": "porttest", + "allowed_address_pairs": [], + "tags": [], + "network_id": "123c8a8c-6427-4e8f-a805-2035365f4d43", + "tenant_id": "abcdec85bee34bb0a44ab8255eb36abc", + "created_at": "2018-07-05T12:38:50Z", + "admin_state_up": true, + "updated_at": "2018-07-05T14:40:43Z", + "binding:vnic_type": "normal", + "device_owner": "compute:nova", + "name": "porttest", + "revision_number": 865, + "mac_address": "ba:12:12:48:42:9b", + "port_security_enabled": true, + "project_id": "abcdec85bee34bb0a44ab8255eb36abc", + "fixed_ips": [ + { + "subnet_id": "1231a12a-125b-4329-a3c5-312ea86a7577", + "ip_address": "12.123.12.31" + } + ], + "id": "a8f3ddbe-9b29-41ac-9c0a-9ea7cc012dfb", + "security_groups": [ + "abcfb112-5b5c-4c6b-8b3f-dbaee57df440" + ], + "device_id": "7b4743a6-f7f7-4764-9854-cf43312e6d49" + }, + { + "status": "DOWN", + "extra_dhcp_opts": [], + "description": "", + "allowed_address_pairs": [], + "tags": [], + "network_id": "123c8a8c-6427-4e8f-a805-2035365f4d43", + "tenant_id": "abcdec85bee34bb0a44ab8255eb36abc", + "created_at": "2018-07-05T13:09:27Z", + "admin_state_up": true, + "updated_at": "2018-07-05T13:29:38Z", + "binding:vnic_type": "normal", + "device_owner": "compute:nova", + "name": "", + "revision_number": 10, + "mac_address": "ba:12:12:95:13:cc", + "port_security_enabled": true, + "project_id": "abcdec85bee34bb0a44ab8255eb36abc", + "fixed_ips": [ + { + "subnet_id": "1231a12a-125b-4329-a3c5-312ea86a7577", + "ip_address": "12.123.12.44" + } + ], + "id": "bad9af6a-121d-4772-9ae0-7d127b712f5d", + "security_groups": [ + "abcfb112-5b5c-4c6b-8b3f-dbaee57df440" + ], + "device_id": "500d78d1-f84f-4949-12da-5205c1237121" + }, + { + "status": "DOWN", + "extra_dhcp_opts": [], + "description": "testport", + "allowed_address_pairs": [], + "tags": [], + "network_id": "123c8a8c-6427-4e8f-a805-2035365f4d43", + "tenant_id": "abcdec85bee34bb0a44ab8255eb36abc", + "created_at": "2018-07-05T11:59:13Z", + "admin_state_up": true, + "updated_at": "2018-07-05T12:39:48Z", + "binding:vnic_type": "normal", + "device_owner": "", + "name": "testport", + "revision_number": 32, + "mac_address": "ba:12:12:6f:ea:12", + "port_security_enabled": true, + "project_id": "abcdec85bee34bb0a44ab8255eb36abc", + "fixed_ips": [ + { + "subnet_id": "1231a12a-125b-4329-a3c5-312ea86a7577", + "ip_address": "12.123.12.12" + } + ], + "id": "c21297ca-4e68-4384-badf-10903cd2cbb0", + "security_groups": [ + "abcfb112-5b5c-4c6b-8b3f-dbaee57df440" + ], + "device_id": "" + }, + { + "status": "DOWN", + "extra_dhcp_opts": [], + "description": "testport", + "allowed_address_pairs": [], + "tags": [], + "network_id": "123c8a8c-6427-4e8f-a805-2035365f4d43", + "tenant_id": "abcdec85bee34bb0a44ab8255eb36abc", + "created_at": "2018-07-05T11:56:59Z", + "admin_state_up": true, + "updated_at": "2018-07-05T11:57:00Z", + "binding:vnic_type": "normal", + "device_owner": "", + "name": "testport", + "revision_number": 3, + "mac_address": "ba:12:12:e6:03:ba", + "port_security_enabled": true, + "project_id": "abcdec85bee34bb0a44ab8255eb36abc", + "fixed_ips": [ + { + "subnet_id": "1231a12a-125b-4329-a3c5-312ea86a7577", + "ip_address": "12.123.12.28" + } + ], + "id": "ca335147-273c-4c72-9bab-11a122a95ce1", + "security_groups": [ + "abcfb112-5b5c-4c6b-8b3f-dbaee57df440" + ], + "device_id": "" + }, + { + "status": "DOWN", + "extra_dhcp_opts": [], + "description": "testport", + "allowed_address_pairs": [], + "tags": [], + "network_id": "123c8a8c-6427-4e8f-a805-2035365f4d43", + "tenant_id": "abcdec85bee34bb0a44ab8255eb36abc", + "created_at": "2018-07-05T11:12:17Z", + "admin_state_up": true, + "updated_at": "2018-07-05T11:12:17Z", + "binding:vnic_type": "normal", + "device_owner": "", + "name": "testport", + "revision_number": 3, + "mac_address": "ba:12:12:12:12:12", + "port_security_enabled": true, + "project_id": "abcdec85bee34bb0a44ab8255eb36abc", + "fixed_ips": [ + { + "subnet_id": "1231a12a-125b-4329-a3c5-312ea86a7577", + "ip_address": "12.123.12.12" + } + ], + "id": "a128dec6-4f3a-45c4-a89c-678f69a72044", + "security_groups": [ + "abcfb112-5b5c-4c6b-8b3f-dbaee57df440" + ], + "device_id": "" + } + ] +} + http://git-wip-us.apache.org/repos/asf/libcloud/blob/c24c71fd/libcloud/test/compute/test_openstack.py ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/test_openstack.py b/libcloud/test/compute/test_openstack.py index 54606ef..19b75e7 100644 --- a/libcloud/test/compute/test_openstack.py +++ b/libcloud/test/compute/test_openstack.py @@ -47,7 +47,7 @@ from libcloud.compute.drivers.openstack import ( OpenStack_1_1_FloatingIpAddress, OpenStackKeyPair, OpenStack_1_0_Connection, OpenStackNodeDriver, - OpenStack_2_NodeDriver) + OpenStack_2_NodeDriver, OpenStack_2_PortInterfaceState) from libcloud.compute.base import Node, NodeImage, NodeSize from libcloud.pricing import set_pricing, clear_pricing_data @@ -1578,6 +1578,11 @@ class OpenStack_2_Tests(OpenStack_1_1_Tests): # normally authentication happens lazily, but we force it here self.driver.image_connection._populate_hosts_and_request_paths() + self.driver_klass.network_connectionCls.conn_class = OpenStack_2_0_MockHttp + self.driver_klass.network_connectionCls.auth_url = "https://auth.api.example.com" + # normally authentication happens lazily, but we force it here + self.driver.network_connection._populate_hosts_and_request_paths() + def test_ex_force_auth_token_passed_to_connection(self): base_url = 'https://servers.api.rackspacecloud.com/v1.1/slug' kwargs = { @@ -1692,6 +1697,57 @@ class OpenStack_2_Tests(OpenStack_1_1_Tests): self.assertEqual(image_member.extra['updated'], '2018-03-02T14:20:37Z') self.assertEqual(image_member.extra['schema'], '/v2/schemas/member') + def test_ex_list_ports(self): + ports = self.driver.ex_list_ports() + + port = ports[0] + self.assertEqual(port.id, '126da55e-cfcb-41c8-ae39-a26cb8a7e723') + self.assertEqual(port.state, OpenStack_2_PortInterfaceState.BUILD) + self.assertEqual(port.created, '2018-07-04T14:38:18Z') + self.assertEqual( + port.extra['network_id'], + '123c8a8c-6427-4e8f-a805-2035365f4d43' + ) + self.assertEqual( + port.extra['project_id'], + 'abcdec85bee34bb0a44ab8255eb36abc' + ) + self.assertEqual( + port.extra['tenant_id'], + 'abcdec85bee34bb0a44ab8255eb36abc' + ) + self.assertEqual(port.extra['name'], '') + + def test_ex_delete_port(self): + ports = self.driver.ex_list_ports() + port = ports[0] + + ret = self.driver.ex_delete_port(port) + + self.assertTrue(ret) + + def test_detach_port_interface(self): + node = Node(id='1c01300f-ef97-4937-8f03-ac676d6234be', name=None, + state=None, public_ips=None, private_ips=None, + driver=self.driver) + ports = self.driver.ex_list_ports() + port = ports[0] + + ret = self.driver.ex_detach_port_interface(node, port) + + self.assertTrue(ret) + + def test_attach_port_interface(self): + node = Node(id='1c01300f-ef97-4937-8f03-ac676d6234be', name=None, + state=None, public_ips=None, private_ips=None, + driver=self.driver) + ports = self.driver.ex_list_ports() + port = ports[0] + + ret = self.driver.ex_attach_port_interface(node, port) + + self.assertTrue(ret) + class OpenStack_1_1_FactoryMethodTests(OpenStack_1_1_Tests): should_list_locations = False @@ -1905,6 +1961,31 @@ class OpenStack_1_1_MockHttp(MockHttp, unittest.TestCase): else: raise NotImplementedError() + def _v2_1337_v2_0_ports(self, method, url, body, headers): + if method == "GET": + body = self.fixtures.load('_ports_v2.json') + return (httplib.OK, body, self.json_content_headers, httplib.responses[httplib.OK]) + else: + raise NotImplementedError() + + def _v2_1337_v2_0_ports_126da55e_cfcb_41c8_ae39_a26cb8a7e723(self, method, url, body, headers): + if method == "DELETE": + return (httplib.NO_CONTENT, "", {}, httplib.responses[httplib.NO_CONTENT]) + else: + raise NotImplementedError() + + def _v2_1337_servers_1c01300f_ef97_4937_8f03_ac676d6234be_os_interface_126da55e_cfcb_41c8_ae39_a26cb8a7e723(self, method, url, body, headers): + if method == "DELETE": + return (httplib.NO_CONTENT, "", {}, httplib.responses[httplib.NO_CONTENT]) + else: + raise NotImplementedError() + + def _v2_1337_servers_1c01300f_ef97_4937_8f03_ac676d6234be_os_interface(self, method, url, body, headers): + if method == "POST": + return (httplib.NO_CONTENT, "", {}, httplib.responses[httplib.NO_CONTENT]) + else: + raise NotImplementedError() + def _v1_1_slug_servers_1c01300f_ef97_4937_8f03_ac676d6234be_os_security_groups(self, method, url, body, headers): if method == "GET": body = self.fixtures.load(
