diff --git a/openstack/network/v2/_proxy.py b/openstack/network/v2/_proxy.py index c53b01c6..fc01f15b 100644 --- a/openstack/network/v2/_proxy.py +++ b/openstack/network/v2/_proxy.py @@ -2099,6 +2099,40 @@ class Proxy(proxy.BaseProxy): return self._list(_security_group_rule.SecurityGroupRule, paginated=False, **query) + def create_segment(self, **attrs): + """Create a new segment from attributes + + .. caution:: + BETA: This API is a work in progress and is subject to change. + + :param dict attrs: Keyword arguments which will be used to create + a :class:`~openstack.network.v2.segment.Segment`, + comprised of the properties on the Segment class. + + :returns: The results of segment creation + :rtype: :class:`~openstack.network.v2.segment.Segment` + """ + return self._create(_segment.Segment, **attrs) + + def delete_segment(self, segment, ignore_missing=True): + """Delete a segment + + .. caution:: + BETA: This API is a work in progress and is subject to change. + + :param segment: The value can be either the ID of a segment or a + :class:`~openstack.network.v2.segment.Segment` + instance. + :param bool ignore_missing: When set to ``False`` + :class:`~openstack.exceptions.ResourceNotFound` will be + raised when the segment does not exist. + When set to ``True``, no exception will be set when + attempting to delete a nonexistent segment. + + :returns: ``None`` + """ + self._delete(_segment.Segment, segment, ignore_missing=ignore_missing) + def find_segment(self, name_or_id, ignore_missing=True): """Find a single segment @@ -2141,6 +2175,7 @@ class Proxy(proxy.BaseProxy): :param kwargs \*\*query: Optional query parameters to be sent to limit the resources being returned. Available parameters include: + * name: Name of the segments * network_id: ID of the network that owns the segments * network_type: Network type for the segments * physical_network: Physical network name for the segments @@ -2151,6 +2186,23 @@ class Proxy(proxy.BaseProxy): """ return self._list(_segment.Segment, paginated=False, **query) + def update_segment(self, segment, **attrs): + """Update a segment + + .. caution:: + BETA: This API is a work in progress and is subject to change. + + :param segment: Either the id of a segment or a + :class:`~openstack.network.v2.segment.Segment` + instance. + :attrs kwargs: The attributes to update on the segment represented + by ``value``. + + :returns: The update segment + :rtype: :class:`~openstack.network.v2.segment.Segment` + """ + return self._update(_segment.Segment, segment, **attrs) + def create_subnet(self, **attrs): """Create a new subnet from attributes diff --git a/openstack/network/v2/segment.py b/openstack/network/v2/segment.py index d8681f90..682c8d87 100644 --- a/openstack/network/v2/segment.py +++ b/openstack/network/v2/segment.py @@ -22,24 +22,26 @@ class Segment(resource.Resource): service = network_service.NetworkService() # capabilities - allow_create = False + allow_create = True allow_retrieve = True - allow_update = False - allow_delete = False + allow_update = True + allow_delete = True allow_list = True - # TODO(rtheis): Add description and name properties when support - # is available. - # Properties + #: The segment description. + description = resource.prop('description') + #: The segment name. + name = resource.prop('name') #: The ID of the network associated with this segment. network_id = resource.prop('network_id') #: The type of network associated with this segment, such as - #: ``flat``, ``gre``, ``vlan`` or ``vxlan``. + #: ``flat``, ``geneve``, ``gre``, ``local``, ``vlan`` or ``vxlan``. network_type = resource.prop('network_type') #: The name of the physical network associated with this segment. physical_network = resource.prop('physical_network') #: The segmentation ID for this segment. The network type #: defines the segmentation model, VLAN ID for ``vlan`` network type - #: and tunnel ID for ``gre`` and ``vxlan`` network types. *Type: int* + #: and tunnel ID for ``geneve``, ``gre`` and ``vxlan`` network types. + #: *Type: int* segmentation_id = resource.prop('segmentation_id', type=int) diff --git a/openstack/tests/functional/network/v2/test_segment.py b/openstack/tests/functional/network/v2/test_segment.py index b73d8585..54200ded 100644 --- a/openstack/tests/functional/network/v2/test_segment.py +++ b/openstack/tests/functional/network/v2/test_segment.py @@ -10,7 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -import unittest import uuid from openstack.network.v2 import network @@ -18,8 +17,6 @@ from openstack.network.v2 import segment from openstack.tests.functional import base -# NOTE(rtheis): Routed networks is still a WIP and not enabled by default. -@unittest.skip("bp/routed-networks") class TestSegment(base.BaseFunctionalTest): NETWORK_NAME = uuid.uuid4().hex @@ -28,26 +25,32 @@ class TestSegment(base.BaseFunctionalTest): SEGMENTATION_ID = None NETWORK_ID = None SEGMENT_ID = None + SEGMENT_EXTENSION = None @classmethod def setUpClass(cls): super(TestSegment, cls).setUpClass() + # NOTE(rtheis): The segment extension is not yet enabled by default. + # Skip the tests if not enabled. + cls.SEGMENT_EXTENSION = cls.conn.network.find_extension('segment') + # Create a network to hold the segment. net = cls.conn.network.create_network(name=cls.NETWORK_NAME) assert isinstance(net, network.Network) cls.assertIs(cls.NETWORK_NAME, net.name) cls.NETWORK_ID = net.id - # Get the segment for the network. - for seg in cls.conn.network.segments(): - assert isinstance(seg, segment.Segment) - if cls.NETWORK_ID == seg.network_id: - cls.NETWORK_TYPE = seg.network_type - cls.PHYSICAL_NETWORK = seg.physical_network - cls.SEGMENTATION_ID = seg.segmentation_id - cls.SEGMENT_ID = seg.id - break + if cls.SEGMENT_EXTENSION: + # Get the segment for the network. + for seg in cls.conn.network.segments(): + assert isinstance(seg, segment.Segment) + if cls.NETWORK_ID == seg.network_id: + cls.NETWORK_TYPE = seg.network_type + cls.PHYSICAL_NETWORK = seg.physical_network + cls.SEGMENTATION_ID = seg.segmentation_id + cls.SEGMENT_ID = seg.id + break @classmethod def tearDownClass(cls): @@ -55,18 +58,57 @@ class TestSegment(base.BaseFunctionalTest): ignore_missing=False) cls.assertIs(None, sot) + def test_create_delete(self): + if self.SEGMENT_EXTENSION: + sot = self.conn.network.create_segment( + description='test description', + name='test name', + network_id=self.NETWORK_ID, + network_type='geneve', + segmentation_id=2055, + ) + self.assertIsInstance(sot, segment.Segment) + del_sot = self.conn.network.delete_segment(sot.id) + self.assertEqual('test description', sot.description) + self.assertEqual('test name', sot.name) + self.assertEqual(self.NETWORK_ID, sot.network_id) + self.assertEqual('geneve', sot.network_type) + self.assertIsNone(sot.physical_network) + self.assertEqual(2055, sot.segmentation_id) + self.assertIsNone(del_sot) + else: + self.skipTest('Segment extension disabled') + def test_find(self): - sot = self.conn.network.find_segment(self.SEGMENT_ID) - self.assertEqual(self.SEGMENT_ID, sot.id) + if self.SEGMENT_EXTENSION: + sot = self.conn.network.find_segment(self.SEGMENT_ID) + self.assertEqual(self.SEGMENT_ID, sot.id) + else: + self.skipTest('Segment extension disabled') def test_get(self): - sot = self.conn.network.get_segment(self.SEGMENT_ID) - self.assertEqual(self.SEGMENT_ID, sot.id) - self.assertEqual(self.NETWORK_ID, sot.network_id) - self.assertEqual(self.NETWORK_TYPE, sot.network_type) - self.assertEqual(self.PHYSICAL_NETWORK, sot.physical_network) - self.assertEqual(self.SEGMENTATION_ID, sot.segmentation_id) + if self.SEGMENT_EXTENSION: + sot = self.conn.network.get_segment(self.SEGMENT_ID) + self.assertEqual(self.SEGMENT_ID, sot.id) + self.assertIsNone(sot.name) + self.assertEqual(self.NETWORK_ID, sot.network_id) + self.assertEqual(self.NETWORK_TYPE, sot.network_type) + self.assertEqual(self.PHYSICAL_NETWORK, sot.physical_network) + self.assertEqual(self.SEGMENTATION_ID, sot.segmentation_id) + else: + self.skipTest('Segment extension disabled') def test_list(self): - ids = [o.id for o in self.conn.network.segments()] - self.assertIn(self.SEGMENT_ID, ids) + if self.SEGMENT_EXTENSION: + ids = [o.id for o in self.conn.network.segments(name=None)] + self.assertIn(self.SEGMENT_ID, ids) + else: + self.skipTest('Segment extension disabled') + + def test_update(self): + if self.SEGMENT_EXTENSION: + sot = self.conn.network.update_segment(self.SEGMENT_ID, + description='update') + self.assertEqual('update', sot.description) + else: + self.skipTest('Segment extension disabled') diff --git a/openstack/tests/unit/network/v2/test_proxy.py b/openstack/tests/unit/network/v2/test_proxy.py index 0f8c8e79..de30256f 100644 --- a/openstack/tests/unit/network/v2/test_proxy.py +++ b/openstack/tests/unit/network/v2/test_proxy.py @@ -725,6 +725,15 @@ class TestNetworkProxy(test_proxy_base.TestProxyBase): security_group_rule.SecurityGroupRule, paginated=False) + def test_segment_create_attrs(self): + self.verify_create(self.proxy.create_segment, segment.Segment) + + def test_segment_delete(self): + self.verify_delete(self.proxy.delete_segment, segment.Segment, False) + + def test_segment_delete_ignore(self): + self.verify_delete(self.proxy.delete_segment, segment.Segment, True) + def test_segment_find(self): self.verify_find(self.proxy.find_segment, segment.Segment) @@ -734,6 +743,9 @@ class TestNetworkProxy(test_proxy_base.TestProxyBase): def test_segments(self): self.verify_list(self.proxy.segments, segment.Segment, paginated=False) + def test_segment_update(self): + self.verify_update(self.proxy.update_segment, segment.Segment) + def test_subnet_create_attrs(self): self.verify_create(self.proxy.create_subnet, subnet.Subnet) diff --git a/openstack/tests/unit/network/v2/test_segment.py b/openstack/tests/unit/network/v2/test_segment.py index fba14ed2..dcfe9eb7 100644 --- a/openstack/tests/unit/network/v2/test_segment.py +++ b/openstack/tests/unit/network/v2/test_segment.py @@ -16,11 +16,13 @@ from openstack.network.v2 import segment IDENTIFIER = 'IDENTIFIER' EXAMPLE = { + 'description': '1', 'id': IDENTIFIER, - 'network_id': '1', - 'network_type': 'vxlan', + 'name': '2', + 'network_id': '3', + 'network_type': 'geneve', 'physical_network': None, - 'segmentation_id': 2, + 'segmentation_id': 4, } @@ -32,15 +34,17 @@ class TestSegment(testtools.TestCase): self.assertEqual('segments', sot.resources_key) self.assertEqual('/segments', sot.base_path) self.assertEqual('network', sot.service.service_type) - self.assertFalse(sot.allow_create) + self.assertTrue(sot.allow_create) self.assertTrue(sot.allow_retrieve) - self.assertFalse(sot.allow_update) - self.assertFalse(sot.allow_delete) + self.assertTrue(sot.allow_update) + self.assertTrue(sot.allow_delete) self.assertTrue(sot.allow_list) def test_make_it(self): sot = segment.Segment(EXAMPLE) + self.assertEqual(EXAMPLE['description'], sot.description) self.assertEqual(EXAMPLE['id'], sot.id) + self.assertEqual(EXAMPLE['name'], sot.name) self.assertEqual(EXAMPLE['network_id'], sot.network_id) self.assertEqual(EXAMPLE['network_type'], sot.network_type) self.assertEqual(EXAMPLE['physical_network'], sot.physical_network)