diff --git a/hnv_client/client.py b/hnv_client/client.py index 2df32a7..b774846 100644 --- a/hnv_client/client.py +++ b/hnv_client/client.py @@ -1096,3 +1096,93 @@ class VirtualSwitchManager(_BaseHNVModel): """Delete the required resource.""" raise exception.NotSupported(feature="DELETE", context="VirtualSwitchManager") + + +class Routes(_BaseHNVModel): + + """Routes Model. + + A routes resource is used to create routes under a tenant's Route Table. + The tenant can specify the addressPrefix of the route, the type of next + hop, and the next hop customer IP address. + """ + + _endpoint = "/networking/v1/routeTables/{parent_id}/routes/{resource_id}" + + parent_id = model.Field(name="parent_id", + key="parentResourceID", + is_property=False, is_required=False, + is_read_only=True) + """The parent resource ID field contains the resource ID that is + associated with network objects that are ancestors of the necessary + resource. + """ + + address_prefix = model.Field(name="address_prefix", key="addressPrefix", + is_required=True) + """The destination CIDR to which the route applies, such as 10.1.0.0/16""" + + next_hop_type = model.Field(name="next_hop_type", key="nextHopType", + is_required=True) + """The type of hop to which the packet is sent. + + Valid values are: + * `constant.VIRTUAL_APPLIANCE` represents a virtual appliance VM + within the tenant virtual network. + * `constant.VNET_LOCAL` represents the local virtual network. + * `constant.VIRTUAL_NETWORK_GATEWAY` represents a virtual network + gateway. + * `constant.INTERNET` represents the default internet gateway. + * `None` represents a black hole. + """ + + next_hop_ip_address = model.Field(name="next_hop_ip_address", + key="nextHopIpAddress", + is_required=False) + """Indicates the next hop to which IP address packets are forwarded, + such as 11.0.0.23.""" + + +class RouteTables(_BaseHNVModel): + + """Route Table Model. + + The RouteTable resource contains a list of routes. RouteTable resources + can be applied to subnets of a tenant virtual network to control routing + within virtual network. Once routeTables has been associated to a virtual + subnet, all tenant VMs created within that subnet will inherit the + RouteTable and will have their traffic routed per the routes contained + in the table. + """ + + _endpoint = "/networking/v1/routeTables/{resource_id}" + + routes = model.Field(name="routes", key="routes", is_required=False, + default=[]) + """Indicates the routes in a route table, see routes resource for full + details on this element.""" + + subnetworks = model.Field(name="subnetworks", key="subnets", + is_read_only=True) + """Indicates an array of references to subnets resources this route + table is associated with.""" + + @classmethod + def from_raw_data(cls, raw_data): + """Create a new model using raw API response.""" + properties = raw_data["properties"] + + routes = [] + raw_routes = properties.get("routes", []) + for raw_route in raw_routes: + raw_route["parentResourceID"] = raw_data["resourceId"] + routes.append(Routes.from_raw_data(raw_route)) + properties["routes"] = routes + + subnets = [] + raw_subnets = properties.get("subnets", []) + for raw_subnet in raw_subnets: + subnets.append(Resource.from_raw_data(raw_subnet)) + properties["subnets"] = subnets + + return super(RouteTables, cls).from_raw_data(raw_data) diff --git a/hnv_client/tests/fake/fake_response.py b/hnv_client/tests/fake/fake_response.py index 9aa0161..caf9421 100644 --- a/hnv_client/tests/fake/fake_response.py +++ b/hnv_client/tests/fake/fake_response.py @@ -73,3 +73,11 @@ class FakeResponse(object): def virtual_switch_manager(self): """Fake GET response for virtual switch manager.""" return self._load_resource("virtual_switch_manager.json") + + def routes(self): + """Fake GET(all) response for routes.""" + return self._load_resource("routes.json") + + def route_tables(self): + """Fake GET(all) response for route tables.""" + return self._load_resource("route_tables.json") diff --git a/hnv_client/tests/fake/response/route_tables.json b/hnv_client/tests/fake/response/route_tables.json new file mode 100644 index 0000000..41011e3 --- /dev/null +++ b/hnv_client/tests/fake/response/route_tables.json @@ -0,0 +1,71 @@ +{ + "value": [ + { + "resourceRef": "/routeTables/rt", + "resourceId": "rt", + "resourceMetadata": {}, + "etag": "W/\"153bce9f-1830-4f13-b90d-a7017119ac24\"", + "instanceId": "0cbeadb5-6bc8-41b6-9bba-6b96ca010eba", + "properties": { + "provisioningState": "Succeeded", + "routes": [ + { + "resourceRef": "/routeTables/rt/routes/4f7b9b29-6744-436d-af0e-779fa7093f29", + "resourceId": "4f7b9b29-6744-436d-af0e-779fa7093f29", + "resourceMetadata": {}, + "etag": "W/\"153bce9f-1830-4f13-b90d-a7017119ac24\"", + "instanceId": "cdbf5edf-d288-4d8e-89b9-f45a2a1d59ec", + "properties": { + "provisioningState": "Succeeded", + "addressPrefix": "11.0.0.0/24", + "nextHopType": "VirtualAppliance", + "nextHopIpAddress": "12.0.0.21" + } + } + ], + "subnets": [] + } + }, + { + "resourceRef": "/routeTables/d81c27bd-4be4-438a-8b88-31ca717cfe75", + "resourceId": "d81c27bd-4be4-438a-8b88-31ca717cfe75", + "etag": "W/\"7a107f52-a9b3-486e-b8a0-cb85426c1400\"", + "instanceId": "a6070cef-9db4-439a-a095-1cc5e5b9ed8c", + "properties": { + "provisioningState": "Succeeded", + "routes": [ + { + "resourceRef": "/routeTables/d81c27bd-4be4-438a-8b88-31ca717cfe75/routes/4f7b9b29-6744-436d-af0e-779fa7093f29", + "resourceId": "4f7b9b29-6744-436d-af0e-779fa7093f29", + "etag": "W/\"7a107f52-a9b3-486e-b8a0-cb85426c1400\"", + "instanceId": "94428b30-47fa-4ba3-b5c5-0fa949eb0ccc", + "properties": { + "provisioningState": "Succeeded", + "addressPrefix": "11.0.0.0/24", + "nextHopType": "VirtualAppliance", + "nextHopIpAddress": "12.0.0.21" + } + }, + { + "resourceRef": "/routeTables/d81c27bd-4be4-438a-8b88-31ca717cfe75/routes/4e65fd4c-51bd-4ac5-bbec-c9fad8d66a24", + "resourceId": "4e65fd4c-51bd-4ac5-bbec-c9fad8d66a24", + "etag": "W/\"7a107f52-a9b3-486e-b8a0-cb85426c1400\"", + "instanceId": "1dcd588f-56b9-4807-b818-b1325831684b", + "properties": { + "provisioningState": "Succeeded", + "addressPrefix": "11.0.0.22/32", + "nextHopType": "VnetLocal", + "nextHopIpAddress": "" + } + } + ], + "subnets": [ + { + "resourceRef": "/virtualNetworks/13b0d711-6db5-4309-b454-595625165034/subnets/4e577d52-e7be-4c45-a369-f0f941f3555a" + } + ] + } + } + ], + "nextLink": "" +} diff --git a/hnv_client/tests/fake/response/routes.json b/hnv_client/tests/fake/response/routes.json new file mode 100644 index 0000000..4b98f21 --- /dev/null +++ b/hnv_client/tests/fake/response/routes.json @@ -0,0 +1,44 @@ +{ + "value": [ + { + "resourceId": "{uniqueString}", + "instanceId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", + "tags": { + "key": "value" + }, + "resourceMetadata": { + "client": "WAP Network Resource Provider", + "tenantId": "{subscriptionid}", + "groupId": "{groupname}", + "name": "{name}", + "originalHref": "https://..." + }, + "properties": { + "provisioningState": "Updating|Deleting|Failed|Succeeded", + "addressPrefix": "10.0.0.0/24", + "nextHopType": "VirtualAppliance", + "nextHopIpAddress": "11.0.0.5" + } + }, + { + "resourceId": "{uniqueString}", + "instanceId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", + "tags": { + "key": "value" + }, + "resourceMetadata": { + "client": "WAP Network Resource Provider", + "tenantId": "{subscriptionid}", + "groupId": "{groupname}", + "name": "{name}", + "originalHref": "https://..." + }, + "properties": { + "provisioningState": "Updating|Deleting|Failed|Succeeded", + "addressPrefix": "11.11.0.0/16", + "nextHopType": "VirtualAppliance", + "nextHopIpAddress": "11.12.5.5" + } + } + ] +} diff --git a/hnv_client/tests/test_client.py b/hnv_client/tests/test_client.py index 0e16955..65291bd 100644 --- a/hnv_client/tests/test_client.py +++ b/hnv_client/tests/test_client.py @@ -275,3 +275,15 @@ class TestClient(unittest.TestCase): raw_data = self._response.virtual_switch_manager() self._test_get_resource(model=client.VirtualSwitchManager, raw_data=raw_data) + + def test_routes(self): + resources = self._response.routes() + for raw_data in resources.get("value", []): + self._test_get_resource(model=client.Routes, + raw_data=raw_data) + + def test_route_tables(self): + resources = self._response.route_tables() + for raw_data in resources.get("value", []): + self._test_get_resource(model=client.RouteTables, + raw_data=raw_data)