diff --git a/cloudcafe/networking/nets_subnets_ports_api/__init__.py b/cloudcafe/networking/common/__init__.py similarity index 100% rename from cloudcafe/networking/nets_subnets_ports_api/__init__.py rename to cloudcafe/networking/common/__init__.py diff --git a/cloudcafe/networking/nets_subnets_ports_api/client.py b/cloudcafe/networking/nets_subnets_ports_api/client.py deleted file mode 100644 index 56d4561e..00000000 --- a/cloudcafe/networking/nets_subnets_ports_api/client.py +++ /dev/null @@ -1,481 +0,0 @@ -""" -Copyright 2014 Rackspace - -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 cloudcafe.common.client import BaseClient, setup_rest_operation - - -class NetsSubnetsPortsClient(BaseClient): - """Implements the Neutron ReST client for the following API resources: - - networks - subnets - ports - """ - - def __init__(self, url, auth_token, serialize_format=None, - deserialize_format=None): - """ - @param url: Base URL for the Neutron service - @type url: String - @param auth_token: Auth token to be used for all requests - @type auth_token: String - @param serialize_format: Format for serializing requests - @type serialize_format: String - @param deserialize_format: Format for de-serializing responses - @type deserialize_format: String - """ - super(NetsSubnetsPortsClient, self).__init__(url, auth_token, - serialize_format, - deserialize_format) - self._models_classes = {'networks': (None, None), - 'subnets': (None, None), - 'ports': (None, None), } - self._resource_plural_map = {} - - @setup_rest_operation - def list_networks(self, name=None, admin_state_up=None, status=None, - shared=None, tenant_id=None, limit=None, marker=None, - page_reverse=None, requestslib_kwargs=None): - """ - @summary: Lists all networks. Additionally, can filter results by - params. Maps to /networks - @param name: Network name to filter by - @type name: String - @param admin_state_up: Network administrative state up value to filter - by - @type admin_state_up: Boolean - @param status: Network status to filter by - @type status: String - @param shared: Network shared attribute value to filter by - @type shared: Boolean - @param tenant_id: Network owner tenant_id to filter by - @type tenant_id: String - @param marker: Network id to be used as a marker for the next list - @type marker: String - @param limit: The maximum number of results to return - @type limit: Int - @param page_reverse: Page direction setting - @type page_reverse: Boolean - @param requestslib_kwargs: keyword arguments to be passed to the - requests library - @type requestslib_kwargs: Dictionary - @return: response from the API - @rtype: Requests.response - """ - return self._list(name=name, admin_state_up=admin_state_up, - status=status, shared=shared, tenant_id=tenant_id, - marker=marker, limit=limit, - page_reverse=page_reverse, - requestslib_kwargs=requestslib_kwargs) - - @setup_rest_operation - def create_network(self, name, admin_state_up=None, shared=None, - tenant_id=None, requestslib_kwargs=None): - """ - @summary: Creates an instance of a network given the - provided parameters - @param name: human readable name of the network. Might not be unique. - No default value provided by the API - @type name: String - @param admin_state_up: Network administrative state up value. If down, - the network does not forward packets. API default value is true - @type admin_state_up: Boolean - @param shared: Specifies whether the network can be shared by other - tenants or not. API default value is false - @type shared: Boolean - @param tenant_id: owner of network. Only admin users can specify a - tenant id other than its own. No default value provided by the API - @type tenant_id: String - @param requestslib_kwargs: keyword arguments to be passed to the - requests library - @type requestslib_kwargs: Dictionary - @return: response from the API - @rtype: Requests.response - """ - return self._create(name=name, admin_state_up=admin_state_up, - shared=shared, tenant_id=tenant_id, - requestslib_kwargs=requestslib_kwargs) - - @setup_rest_operation - def update_network(self, network_id, name=None, admin_state_up=None, - shared=None, requestslib_kwargs=None): - """ - @summary: updates an instance of a network given the - provided parameters - @param network_id: id of the network to update - @type network_id: String - @param name: human readable name of the network. Might not be unique. - @type name: String - @param admin_state_up: Network administrative state up value. If down, - the network does not forward packets. - @type admin_state_up: Boolean - @param shared: Specifies whether the network can be shared by other - tenants or not. - @type shared: Boolean - @param requestslib_kwargs: keyword arguments to be passed to the - requests library - @type requestslib_kwargs: Dictionary - @return: response from the API - @rtype: Requests.response - """ - return self._update(network_id, name=name, - admin_state_up=admin_state_up, shared=shared, - requestslib_kwargs=requestslib_kwargs) - - @setup_rest_operation - def show_network(self, network_id, requestslib_kwargs=None): - """ - @summary: get the attributes of a network instance - @param network_id: id of the network whose attributes will be gotten - @type network_id: String - @param requestslib_kwargs: keyword arguments to be passed to the - requests library - @type requestslib_kwargs: Dictionary - @return: response from the API - @rtype: Requests.response - """ - return self._show(network_id, requestslib_kwargs=requestslib_kwargs) - - @setup_rest_operation - def delete_network(self, network_id, requestslib_kwargs=None): - """ - @summary: delete a network - @param network_id: id of the network to delete - @type network_id: String - @param requestslib_kwargs: keyword arguments to be passed to the - requests library - @type requestslib_kwargs: Dictionary - @return: response from the API - @rtype: Requests.response - """ - return self._delete(network_id, requestslib_kwargs=requestslib_kwargs) - - @setup_rest_operation - def list_subnets(self, network_id=None, name=None, ip_version=None, - cidr=None, gateway_ip=None, enable_dhcp=None, - tenant_id=None, limit=None, marker=None, - page_reverse=None, requestslib_kwargs=None): - """ - @summary: Lists all subnets. Additionally, can filter results by - params. Maps to /subnets - @param network_id: Network id to filter by - @type network_id: String - @param name: Subnet id to filter by - @type name: String - @param ip_version: IP version to filter by - @type ip_version: Int - @param cidr: cidr to filter by - @type cidr: String - @param gateway_ip: Gateway ip address to filter by - @type gateway_ip: String - @param enable_dhcp: Enable dhcp setting to filter by - @type enable_dhcp: Boolen - @param tenant_id: ID of tenant to filter by - @type tenant_id: String - @param marker: Subnet id to be used as a marker for the next list - @type marker: String - @param limit: The maximum number of results to return - @type limit: Int - @param page_reverse: Page direction setting - @type page_reverse: Boolean - @param requestslib_kwargs: keyword arguments to be passed to the - requests library - @type requestslib_kwargs: Dictionary - @return: response from the API - @rtype: Requests.response - """ - return self._list(network_id=network_id, name=name, - ip_version=ip_version, cidr=cidr, - gateway_ip=gateway_ip, enable_dhcp=enable_dhcp, - tenant_id=tenant_id, marker=marker, limit=limit, - page_reverse=page_reverse, - requestslib_kwargs=requestslib_kwargs) - - @setup_rest_operation - def create_subnet(self, network_id, cidr, name=None, ip_version=None, - gateway_ip=None, dns_nameservers=None, - allocation_pools=None, host_routes=None, - enable_dhcp=None, tenant_id=None, - requestslib_kwargs=None): - """ - @summary: Creates an instance of a subnet given the - provided parameters - @param network_id: the id of the network the subnet will be - associated with - @type network_id: String - @param cidr: the ip range for the subnet - @type cidr: String - @param name: human readable name for the subnet. Might not be unique. - No default value provided by the API - @type name: String - @param ip_version: the IP version for the subnet. API default value is - 4 - @type ip_version: Int - @param gateway_ip: default gateway used by devices in the subnet - API default value is the first address in the cidr - @type gateway_ip: String - @param dns_nameservers: DNS nanme servers used by hosts in the subnet - API default value is an empty list - @type dns_nameservers: List of String - @param allocation_pools: sub-ranges of cidr available for dynamic - allocation to ports. API default value is every address in cidr, - excluding gateway ip if configured - @type allocation_pools: List of Dictionaries - @param host_routes: routes that should be used by devices with IP's - from the subnet (not including local subnet route). API default value - is empty list - @type host_routes: List of Dictionaries - @param enable_dhcp: specifies whether DHCP is enabled for the subnet or - not. API default value is true - @type enable_dhcp: Boolean - @param tenant_id: owner of subnet. Only admin users can specify a - tenant id other than its own. No default value provided by the API - @type tenant_id: String - @param requestslib_kwargs: keyword arguments to be passed to the - requests library - @type requestslib_kwargs: Dictionary - @return: response from the API - @rtype: Requests.response - """ - return self._create(network_id=network_id, cidr=cidr, name=name, - ip_version=ip_version, gateway_ip=gateway_ip, - dns_nameservers=dns_nameservers, - allocation_pools=allocation_pools, - host_routes=host_routes, enable_dhcp=enable_dhcp, - tenant_id=tenant_id, - requestslib_kwargs=requestslib_kwargs) - - @setup_rest_operation - def update_subnet(self, subnet_id, name=None, gateway_ip=None, - dns_nameservers=None, host_routes=None, - enable_dhcp=None, requestslib_kwargs=None): - """ - @summary: updates an instance of a subnet given the - provided parameters - @param subnet_id: the id of the subnet to updtes - @type subnet_id: String - @param name: human readable name for the subnet. Might not be unique. - @type name: String - @param gateway_ip: default gateway used by devices in the subnet - @type gateway_ip: String - @param dns_nameservers: DNS nanme servers used by hosts in the subnet - @type dns_nameservers: List of String - @param host_routes: routes that should be used by devices with IP's - from the subnet (not including local subnet route). - @type host_routes: List of Dictionaries - @param enable_dhcp: specifies whether DHCP is enabled for the subnet or - not. - @type enable_dhcp: Boolean - @param requestslib_kwargs: keyword arguments to be passed to the - requests library - @type requestslib_kwargs: Dictionary - @return: response from the API - @rtype: Requests.response - """ - return self._update(subnet_id, name=name, gateway_ip=gateway_ip, - dns_nameservers=dns_nameservers, - host_routes=host_routes, enable_dhcp=enable_dhcp, - requestslib_kwargs=requestslib_kwargs) - - @setup_rest_operation - def show_subnet(self, subnet_id, requestslib_kwargs=None): - """ - @summary: get the attributes of a subnet instance - @param subnet_id: id of the subnet whose attributes will be gotten - @type subnet_id: String - @param requestslib_kwargs: keyword arguments to be passed to the - requests library - @type requestslib_kwargs: Dictionary - @return: response from the API - @rtype: Requests.response - """ - return self._show(subnet_id, requestslib_kwargs=requestslib_kwargs) - - @setup_rest_operation - def delete_subnet(self, subnet_id, requestslib_kwargs=None): - """ - @summary: delete a subnet - @param subnet_id: id of the subnet to delete - @type subnet_id: String - @param requestslib_kwargs: keyword arguments to be passed to the - requests library - @type requestslib_kwargs: Dictionary - @return: response from the API - @rtype: Requests.response - """ - return self._delete(subnet_id, requestslib_kwargs=requestslib_kwargs) - - @setup_rest_operation - def list_ports(self, network_id=None, name=None, admin_state_up=None, - status=None, mac_address=None, device_id=None, - device_owner=None, tenant_id=None, limit=None, marker=None, - page_reverse=None, requestslib_kwargs=None): - """ - @summary: Lists all ports. Additionally, can filter results by - params. Maps to /ports - @param network_id: Network id to filter by - @type network_id: String - @param name: Port id to filter by - @type name: String - @param admin_state_up: Network administrative state up value to filter - by - @type admin_state_up: Boolean - @param status: Network status to filter by - @type status: String - @param mac_address: mac_address to filter by - @type mac_address: String - @param device_id: Device id to filter by - @type device_id: String - @param device_owner: Device owner to filter by - @type device_owner: String - @param tenant_id: ID of tenant to filter by - @type tenant_id: String - @param marker: Subnet id to be used as a marker for the next list - @type marker: String - @param limit: The maximum number of results to return - @type limit: Int - @param page_reverse: Page direction setting - @type page_reverse: Boolean - @param requestslib_kwargs: keyword arguments to be passed to the - requests library - @type requestslib_kwargs: Dictionary - @return: response from the API - @rtype: Requests.response - """ - return self._list(network_id=network_id, name=name, - admin_state_up=admin_state_up, status=status, - mac_address=mac_address, device_id=device_id, - device_owner=device_owner, tenant_id=tenant_id, - marker=marker, limit=limit, - page_reverse=page_reverse, - requestslib_kwargs=requestslib_kwargs) - - @setup_rest_operation - def create_port(self, network_id, name=None, admin_state_up=None, - mac_address=None, fixed_ips=None, device_id=None, - device_owner=None, tenant_id=None, security_groups=None, - requestslib_kwargs=None): - """ - @summary: Creates an instance of a port given the - provided parameters - @param network_id: network that the port is associated with. No default - value provided by the API - @type neywork_id: String - @param name: hurman readable name for the port. Might not be unique. No - default value provided by the API - @type name: String - @param admin_state_up: administrative state of the port. If false, port - does not forward packets. API default value is true - @type admin_state_up: Boolean - @param mac_address: mac addres to be used on this port. API default - value is generated - @type mac_address: String - @param fixed_ips: specifies ip adddreses for the port, associating it - with the subnets where the ip addresses are picked from. API default - value is an ip address selected from the allocation pools associated - with the network - @type fixed_ips: List of Dictionaries - @param device_id: identifies the device (e.g. virtual server) using the - port. No default value provided by the API - @type device_id: String - @param device_owner: identifies the entity (e.g. dhcp agent) using the - port. No default value provided by the API - @type device_owner: String - @param tenant_id: owner of port. Only admin users can specify tenants - other than their own. No default value provided by the API - @type tenant_id: String - @param security_groups: specifies the ID's of any security groups - associated with the port. No default value provided by the API - @type security_groups: List of Dictionaries - @param requestslib_kwargs: keyword arguments to be passed to the - requests library - @type requestslib_kwargs: Dictionary - @return: response from the API - @rtype: Requests.response - """ - return self._create(network_id=network_id, name=name, - admin_state_up=admin_state_up, - mac_address=mac_address, fixed_ips=fixed_ips, - device_id=device_id, device_owner=device_owner, - tenant_id=tenant_id, - security_groups=security_groups, - requestslib_kwargs=requestslib_kwargs) - - @setup_rest_operation - def update_port(self, port_id, name=None, admin_state_up=None, - fixed_ips=None, device_id=None, device_owner=None, - security_groups=None, requestslib_kwargs=None): - """ - @summary: updates an instance of a port given the - provided parameters - @param port_id: id of the port to be updated - @type neywork_id: String - @param name: hurman readable name for the port. Might not be unique. - @type name: String - @param admin_state_up: administrative state of the port. If false, port - does not forward packets. - @type admin_state_up: Boolean - @param fixed_ips: specifies ip adddreses for the port, associating it - with the subnets where the ip addresses are picked from. - @type fixed_ips: List of Dictionaries - @param device_id: identifies the device (e.g. virtual server) using the - port. - @type device_id: String - @param device_owner: identifies the entity (e.g. dhcp agent) using the - port. - @type device_owner: String - @param security_groups: specifies the ID's of any security groups - associated with the port - @type security_groups: List of Dictionaries - @param requestslib_kwargs: keyword arguments to be passed to the - requests library - @type requestslib_kwargs: Dictionary - @return: response from the API - @rtype: Requests.response - """ - return self._update(port_id, name=name, admin_state_up=admin_state_up, - fixed_ips=fixed_ips, device_id=device_id, - device_owner=device_owner, - security_groups=security_groups, - requestslib_kwargs=requestslib_kwargs) - - @setup_rest_operation - def show_port(self, port_id, requestslib_kwargs=None): - """ - @summary: get the attributes of a port instance - @param port_id: id of the port whose attributes will be gotten - @type port_id: String - @param requestslib_kwargs: keyword arguments to be passed to the - requests library - @type requestslib_kwargs: Dictionary - @return: response from the API - @rtype: Requests.response - """ - return self._show(port_id, requestslib_kwargs=requestslib_kwargs) - - @setup_rest_operation - def delete_port(self, port_id, requestslib_kwargs=None): - """ - @summary: delete a port - @param port_id: id of the port to be deleted - @type port_id: String - @param requestslib_kwargs: keyword arguments to be passed to the - requests library - @type requestslib_kwargs: Dictionary - @return: response from the API - @rtype: Requests.response - """ - return self._delete(port_id, requestslib_kwargs=requestslib_kwargs) diff --git a/cloudcafe/networking/networks/__init__.py b/cloudcafe/networking/networks/__init__.py new file mode 100644 index 00000000..52e3d4c2 --- /dev/null +++ b/cloudcafe/networking/networks/__init__.py @@ -0,0 +1,15 @@ +""" +Copyright 2014 Rackspace + +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. +""" diff --git a/cloudcafe/networking/networks/behaviors.py b/cloudcafe/networking/networks/behaviors.py new file mode 100644 index 00000000..52e3d4c2 --- /dev/null +++ b/cloudcafe/networking/networks/behaviors.py @@ -0,0 +1,15 @@ +""" +Copyright 2014 Rackspace + +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. +""" diff --git a/cloudcafe/networking/networks/common/__init__.py b/cloudcafe/networking/networks/common/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cloudcafe/networking/networks/common/models/__init__.py b/cloudcafe/networking/networks/common/models/__init__.py new file mode 100644 index 00000000..52e3d4c2 --- /dev/null +++ b/cloudcafe/networking/networks/common/models/__init__.py @@ -0,0 +1,15 @@ +""" +Copyright 2014 Rackspace + +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. +""" diff --git a/cloudcafe/networking/networks/common/models/request/__init__.py b/cloudcafe/networking/networks/common/models/request/__init__.py new file mode 100644 index 00000000..52e3d4c2 --- /dev/null +++ b/cloudcafe/networking/networks/common/models/request/__init__.py @@ -0,0 +1,15 @@ +""" +Copyright 2014 Rackspace + +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. +""" diff --git a/cloudcafe/networking/networks/common/models/request/network.py b/cloudcafe/networking/networks/common/models/request/network.py new file mode 100644 index 00000000..c53c0cfb --- /dev/null +++ b/cloudcafe/networking/networks/common/models/request/network.py @@ -0,0 +1,59 @@ +""" +Copyright 2014 Rackspace + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import json +import xml.etree.ElementTree as ET + +from cafe.engine.models.base import AutoMarshallingModel + + +class NetworkRequest(AutoMarshallingModel): + """ + @summary: Network model object for the OpenStack Neutron v2.0 API + requests for creating (POST) and updating (PUT) networks calls + @param string name: human readable name for the network, + may not be unique. (CRUD: CRU) + @param bool admin_state_up: true or false, the admin state + of the network. If down, the network does not forward packets. + Default value is True (CRUD: CRU) + @param bool shared: specifies if the network can be accessed by any + tenant. Default value is False. (CRUD: CRU) + @param string tenant_id: owner of the network. (CRUD: CR) + """ + + def __init__(self, name=None, admin_state_up=None, shared=None, + tenant_id=None, **kwargs): + + # kwargs is to be used for extensions + super(NetworkRequest, self).__init__() + self.name = name + self.admin_state_up = admin_state_up + self.shared = shared + self.tenant_id = tenant_id + + def _obj_to_json(self): + + body = { + 'shared': self.shared, + 'tenant_id': self.tenant_id, + 'name': self.name, + 'admin_state_up': self.admin_state_up + } + + # Removing optional params not given + body = self._remove_empty_values(body) + main_body = {'network': body} + return json.dumps(main_body) diff --git a/cloudcafe/networking/networks/common/models/request/port.py b/cloudcafe/networking/networks/common/models/request/port.py new file mode 100644 index 00000000..d27c9ccf --- /dev/null +++ b/cloudcafe/networking/networks/common/models/request/port.py @@ -0,0 +1,77 @@ +""" +Copyright 2014 Rackspace + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import json +import xml.etree.ElementTree as ET + +from cafe.engine.models.base import AutoMarshallingModel + + +class PortRequest(AutoMarshallingModel): + """ + @summary: Port model object for the OpenStack Neutron v2.0 API + requests for creating (POST) and updating (PUT) ports calls + @param string network_id: network port is associated with (CRUD: CR) + @param string name: human readable name for the port, + may not be unique. (CRUD: CRU) + @param bool admin_state_up: true or false (default true), the admin state + of the port. If down, the port does not forward packets (CRUD: CRU) + @param string mac_address: mac address to use on the port (CRUD: CR) + @param list(dict) fixed_ips: ip addresses for the port associating the + port with the subnets where the IPs come from (CRUD: CRU) + @param string device_id: id of device using this port (CRUD: CRUD) + @param string device_owner: entity using this port (ex. dhcp agent, + CRUD: CRUD) + @param string tenant_id: owner of the port (CRUD: CR) + @param list(dict) security_groups: ids of any security groups associated + with the port (CRUD: CRUD) + """ + + def __init__(self, network_id=None, name=None, admin_state_up=None, + mac_address=None, fixed_ips=None, device_id=None, + device_owner=None, tenant_id=None, security_groups=None, + **kwargs): + + # kwargs is to be used for extensions + super(PortRequest, self).__init__() + self.network_id = network_id + self.name = name + self.admin_state_up = admin_state_up + self.mac_address = mac_address + self.fixed_ips = fixed_ips + self.device_id = device_id + self.device_owner = device_owner + self.tenant_id = tenant_id + self.security_groups = security_groups + + def _obj_to_json(self): + + body = { + 'network_id': self.network_id, + 'name': self.name, + 'admin_state_up': self.admin_state_up, + 'mac_address': self.mac_address, + 'fixed_ips': self.fixed_ips, + 'device_id': self.device_id, + 'device_owner': self.device_owner, + 'tenant_id': self.tenant_id, + 'security_groups': self.security_groups + } + + # The client should instantiate the model with only desired parameters + body = self._remove_empty_values(body) + main_body = {'port': body} + return json.dumps(main_body) diff --git a/cloudcafe/networking/networks/common/models/request/subnet.py b/cloudcafe/networking/networks/common/models/request/subnet.py new file mode 100644 index 00000000..aaa386e6 --- /dev/null +++ b/cloudcafe/networking/networks/common/models/request/subnet.py @@ -0,0 +1,81 @@ +""" +Copyright 2014 Rackspace + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import json +import xml.etree.ElementTree as ET + +from cafe.engine.models.base import AutoMarshallingModel + + +class SubnetRequest(AutoMarshallingModel): + """ + @summary: Subnet model object for the OpenStack Neutron v2.0 API + requests for creating (POST) and updating (PUT) subnets + @param string name: human readable name for the subnet, + may not be unique. (CRUD: CRU) + @param string tenant_id: owner of the network. (CRUD: CR) + @param string network_id: network subnet is associated with (CRUD: CR) + @param int ip_version: IP version 4 or 6 (CRUD: CR) + @param string cidr: represents IP range for the subnet and should be in + the form / (CRUD: CR) + @param string gateway_ip: default gateway used by devices in the subnet + (CRUD: CRUD) + @param list(str) dns_nameservers: DNS name servers used by subnet hosts + (CRUD: CRU) + @param list(dict) allocation_pools: sub range of cidr available for dynamic + allocation to ports (CRUD: CR) + @param list(dict) host_routes: routes that should be used by devices with + IPs from this subnet (does not includes the local route, CRUD: CRU) + @param bool enable_dhcp: whether DHCP is enabled (CRUD:CRU) + """ + + def __init__(self, name=None, tenant_id=None, network_id=None, + ip_version=None, cidr=None, gateway_ip=None, + dns_nameservers=None, allocation_pools=None, + host_routes=None, enable_dhcp=None, **kwargs): + + # kwargs is to be used for extensions + super(SubnetRequest, self).__init__() + self.name = name + self.tenant_id = tenant_id + self.network_id = network_id + self.ip_version = ip_version + self.cidr = cidr + self.gateway_ip = gateway_ip + self.allocation_pools = allocation_pools + self.dns_nameservers = dns_nameservers + self.host_routes = host_routes + self.enable_dhcp = enable_dhcp + + def _obj_to_json(self): + + body = { + 'name': self.name, + 'tenant_id': self.tenant_id, + 'ip_version': self.ip_version, + 'network_id': self.network_id, + 'cidr': self.cidr, + 'dns_nameservers': self.dns_nameservers, + 'gateway_ip': self.gateway_ip, + 'allocation_pools': self.allocation_pools, + 'host_routes': self.host_routes, + 'enable_dhcp': self.enable_dhcp + } + + # The client should instantiate the model with only desired parameters + body = self._remove_empty_values(body) + main_body = {'subnet': body} + return json.dumps(main_body) diff --git a/cloudcafe/networking/networks/common/models/response/__init__.py b/cloudcafe/networking/networks/common/models/response/__init__.py new file mode 100644 index 00000000..52e3d4c2 --- /dev/null +++ b/cloudcafe/networking/networks/common/models/response/__init__.py @@ -0,0 +1,15 @@ +""" +Copyright 2014 Rackspace + +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. +""" diff --git a/cloudcafe/networking/networks/common/models/response/network.py b/cloudcafe/networking/networks/common/models/response/network.py new file mode 100644 index 00000000..aa3e71ce --- /dev/null +++ b/cloudcafe/networking/networks/common/models/response/network.py @@ -0,0 +1,104 @@ +""" +Copyright 2014 Rackspace + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import copy +import json +import xml.etree.ElementTree as ET + +from cafe.engine.models.base import AutoMarshallingListModel, \ + AutoMarshallingModel + + +class Network(AutoMarshallingModel): + """ + @summary: Network model object for the OpenStack Neutron v2.0 API + responses for networks show and list (GET) calls + @param string id: UUID for the network (CRUD: R) + @param string name: human readable name for the network, + may not be unique. (CRUD: CRU) + @param bool admin_state_up: true or false, the admin state + of the network. If down, the network does not forward packets. + Default value is True (CRUD: CRU) + @param string status: Indicates if the network is currently + operational. Possible values: ACTIVE, DOWN, BUILD, ERROR. (CRUD: R) + @param list subnets: associated network subnets UUID list. (CRUD: R) + @param bool shared: specifies if the network can be accessed by any + tenant. Default value is False. (CRUD: CRU) + @param string tenant_id: owner of the network. (CRUD: CR) + """ + NETWORK = 'network' + + def __init__(self, id_=None, name=None, admin_state_up=None, + status=None, subnets=None, shared=None, tenant_id=None, + **kwargs): + + # kwargs is to be used for extensions + super(Network, self).__init__() + self.id = id_ + self.name = name + self.admin_state_up = admin_state_up + self.status = status + self.subnets = subnets + self.shared = shared + self.tenant_id = tenant_id + + @classmethod + def _json_to_obj(cls, serialized_str): + """Return network object from a JSON serialized string""" + + ret = None + json_response = json.loads(serialized_str) + + # Creating a deep copy just in case later we want the original resp + json_dict = copy.deepcopy(json_response) + + # Replacing attribute response names if they are Python reserved words + # with a trailing underscore, for ex. id for id_ or if they have a + # special character within the name replacing it for an underscore too + json_dict = cls._replace_dict_key( + json_dict, 'id', 'id_', recursion=True) + + if cls.NETWORK in json_dict: + network_dict = json_dict.get(cls.NETWORK) + ret = Network(**network_dict) + return ret + + +class Networks(AutoMarshallingListModel): + + NETWORKS = 'networks' + + @classmethod + def _json_to_obj(cls, serialized_str): + """Return a list of network objects from a JSON serialized string""" + + ret = cls() + json_response = json.loads(serialized_str) + + # Creating a deep copy just in case later we want the original resp + json_dict = copy.deepcopy(json_response) + + # Replacing attribute response names if they are Python reserved words + # with a trailing underscore, for ex. id for id_ or if they have a + # special character within the name replacing it for an underscore too + json_dict = cls._replace_dict_key( + json_dict, 'id', 'id_', recursion=True) + + if cls.NETWORKS in json_dict: + networks = json_dict.get(cls.NETWORKS) + for network in networks: + ret.append(Network(**network)) + return ret diff --git a/cloudcafe/networking/networks/common/models/response/port.py b/cloudcafe/networking/networks/common/models/response/port.py new file mode 100644 index 00000000..1b3188f1 --- /dev/null +++ b/cloudcafe/networking/networks/common/models/response/port.py @@ -0,0 +1,115 @@ +""" +Copyright 2014 Rackspace + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import copy +import json +import xml.etree.ElementTree as ET + +from cafe.engine.models.base import AutoMarshallingListModel +from cafe.engine.models.base import AutoMarshallingModel + + +class Port(AutoMarshallingModel): + """ + @summary: Port model object for the OpenStack Neutron v2.0 API + responses for ports show and list (GET) calls + @param string id: UUID for the port (CRUD: R) + @param string network_id: network port is associated with (CRUD: CR) + @param string name: human readable name for the port, + may not be unique. (CRUD: CRU) + @param bool admin_state_up: true or false (default true), the admin state + of the port. If down, the port does not forward packets (CRUD: CRU) + @param string status: Indicates if the port is currently + operational. Possible values: ACTIVE, DOWN, BUILD, ERROR (CRUD: R) + @param string mac_address: mac address to use on the port (CRUD: CR) + @param list(dict) fixed_ips: ip addresses for the port associating the + port with the subnets where the IPs come from (CRUD: CRU) + @param string device_id: id of device using this port (CRUD: CRUD) + @param string device_owner: entity using this port (ex. dhcp agent, + CRUD: CRUD) + @param string tenant_id: owner of the port (CRUD: CR) + @param list(dict) security_groups: ids of any security groups associated + with the port (CRUD: CRUD) + """ + + PORT = 'port' + + def __init__(self, id_=None, network_id=None, name=None, + admin_state_up=None, status=None, mac_address=None, + fixed_ips=None, device_id=None, device_owner=None, + tenant_id=None, security_groups=None, **kwargs): + + # kwargs is to be used for extensions + super(Port, self).__init__() + self.id = id_ + self.network_id = network_id + self.name = name + self.admin_state_up = admin_state_up + self.status = status + self.mac_address = mac_address + self.fixed_ips = fixed_ips + self.device_id = device_id + self.device_owner = device_owner + self.tenant_id = tenant_id + self.security_groups = security_groups + + @classmethod + def _json_to_obj(cls, serialized_str): + """Return port object from a JSON serialized string""" + + ret = None + json_response = json.loads(serialized_str) + + # Creating a deep copy just in case later we want the original resp + json_dict = copy.deepcopy(json_response) + + # Replacing attribute response names if they are Python reserved words + # with a trailing underscore, for ex. id for id_ or if they have a + # special character within the name replacing it for an underscore too + json_dict = cls._replace_dict_key( + json_dict, 'id', 'id_', recursion=True) + + if cls.PORT in json_dict: + subnet_dict = json_dict.get(cls.PORT) + ret = Port(**subnet_dict) + return ret + + +class Ports(AutoMarshallingListModel): + + PORTS = 'ports' + + @classmethod + def _json_to_obj(cls, serialized_str): + """Return a list of port objects from a JSON serialized string""" + + ret = cls() + json_response = json.loads(serialized_str) + + # Creating a deep copy just in case later we want the original resp + json_dict = copy.deepcopy(json_response) + + # Replacing attribute response names if they are Python reserved words + # with a trailing underscore, for ex. id for id_ or if they have a + # special character within the name replacing it for an underscore too + json_dict = cls._replace_dict_key( + json_dict, 'id', 'id_', recursion=True) + + if cls.PORTS in json_dict: + ports = json_dict.get(cls.PORTS) + for port in ports: + ret.append(Port(**port)) + return ret diff --git a/cloudcafe/networking/networks/common/models/response/subnet.py b/cloudcafe/networking/networks/common/models/response/subnet.py new file mode 100644 index 00000000..cfe71612 --- /dev/null +++ b/cloudcafe/networking/networks/common/models/response/subnet.py @@ -0,0 +1,115 @@ +""" +Copyright 2014 Rackspace + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import copy +import json +import xml.etree.ElementTree as ET + +from cafe.engine.models.base import AutoMarshallingListModel +from cafe.engine.models.base import AutoMarshallingModel + + +class Subnet(AutoMarshallingModel): + """ + @summary: Subnet model object for the OpenStack Neutron v2.0 API + responses for subnets show and list (GET) calls + @param string id: UUID for the subnet (CRUD: R) + @param string name: human readable name for the subnet, + may not be unique. (CRUD: CRU) + @param string tenant_id: owner of the network. (CRUD: CR) + @param string network_id: network subnet is associated with (CRUD: CR) + @param int ip_version: IP version 4 or 6 (CRUD: CR) + @param string cidr: represents IP range for the subnet and should be in + the form / (CRUD: CR) + @param string gateway_ip: default gateway used by devices in the subnet + (CRUD: CRUD) + @param list(str) dns_nameservers: DNS name servers used by subnet hosts + (CRUD: CRU) + @param list(dict) allocation_pools: sub range of cidr available for dynamic + allocation to ports (CRUD: CR) + @param list(dict) host_routes: routes that should be used by devices with + IPs from this subnet (does not includes the local route, CRUD: CRU) + @param bool enable_dhcp: whether DHCP is enabled (CRUD:CRU) + """ + + SUBNET = 'subnet' + + def __init__(self, id_=None, name=None, tenant_id=None, network_id=None, + ip_version=None, cidr=None, gateway_ip=None, + dns_nameservers=None, allocation_pools=None, + host_routes=None, enable_dhcp=None, **kwargs): + + # kwargs is to be used for extensions + super(Subnet, self).__init__() + self.id = id_ + self.name = name + self.tenant_id = tenant_id + self.network_id = network_id + self.ip_version = ip_version + self.cidr = cidr + self.gateway_ip = gateway_ip + self.dns_nameservers = dns_nameservers + self.allocation_pools = allocation_pools + self.host_routes = host_routes + self.enable_dhcp = enable_dhcp + + @classmethod + def _json_to_obj(cls, serialized_str): + """Return subnet object from a JSON serialized string""" + + ret = None + json_response = json.loads(serialized_str) + + # Creating a deep copy just in case later we want the original resp + json_dict = copy.deepcopy(json_response) + + # Replacing attribute response names if they are Python reserved words + # with a trailing underscore, for ex. id for id_ or if they have a + # special character within the name replacing it for an underscore too + json_dict = cls._replace_dict_key( + json_dict, 'id', 'id_', recursion=True) + + if cls.SUBNET in json_dict: + subnet_dict = json_dict.get(cls.SUBNET) + ret = Subnet(**subnet_dict) + return ret + + +class Subnets(AutoMarshallingListModel): + + SUBNETS = 'subnets' + + @classmethod + def _json_to_obj(cls, serialized_str): + """Return a list of subnet objects from a JSON serialized string""" + + ret = cls() + json_response = json.loads(serialized_str) + + # Creating a deep copy just in case later we want the original resp + json_dict = copy.deepcopy(json_response) + + # Replacing attribute response names if they are Python reserved words + # with a trailing underscore, for ex. id for id_ or if they have a + # special character within the name replacing it for an underscore too + json_dict = cls._replace_dict_key( + json_dict, 'id', 'id_', recursion=True) + + if cls.SUBNETS in json_dict: + subnets = json_dict.get(cls.SUBNETS) + for subnet in subnets: + ret.append(Subnet(**subnet)) + return ret diff --git a/cloudcafe/networking/networks/composites.py b/cloudcafe/networking/networks/composites.py new file mode 100644 index 00000000..01715665 --- /dev/null +++ b/cloudcafe/networking/networks/composites.py @@ -0,0 +1,146 @@ +""" +Copyright 2014 Rackspace + +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 cloudcafe.auth.provider import MemoizedAuthServiceComposite +from cloudcafe.networking.networks.config import MarshallingConfig,\ + NetworksConfig, NetworksEndpointConfig, NetworksAdminEndpointConfig,\ + NetworksAdminAuthConfig, NetworksSecondUserConfig, \ + NetworksAdminUserConfig, UserAuthConfig, UserConfig +from cloudcafe.networking.networks.networks_api.client import NetworksClient +from cloudcafe.networking.networks.networks_api.config import NetworksAPIConfig +from cloudcafe.networking.networks.networks_api.behaviors \ + import NetworksAPIBehaviors +from cloudcafe.networking.networks.ports_api.client import PortsClient +from cloudcafe.networking.networks.ports_api.config import PortsAPIConfig +from cloudcafe.networking.networks.ports_api.behaviors import PortsAPIBehaviors +from cloudcafe.networking.networks.subnets_api.client import SubnetsClient +from cloudcafe.networking.networks.subnets_api.config import SubnetsAPIConfig +from cloudcafe.networking.networks.subnets_api.behaviors \ + import SubnetsAPIBehaviors + + +class _NetworksAuthComposite(MemoizedAuthServiceComposite): + _networks_config = NetworksConfig + _networks_endpoint_config = NetworksEndpointConfig + _auth_endpoint_config = UserAuthConfig + _auth_user_config = UserConfig + + def __init__(self): + self.networks_endpoint_config = self._networks_endpoint_config() + self.marshalling_config = MarshallingConfig() + self._auth_endpoint_config = self._auth_endpoint_config() + self._auth_user_config = self._auth_user_config() + + super(_NetworksAuthComposite, self).__init__( + service_name=self.networks_endpoint_config.networks_endpoint_name, + region=self.networks_endpoint_config.region, + endpoint_config=self._auth_endpoint_config, + user_config=self._auth_user_config) + + self.networks_url = self.public_url + + # Overriding the publicURL if networks_endpoint_url given + if self.networks_endpoint_config.networks_endpoint_url: + self.networks_url = \ + self.networks_endpoint_config.networks_endpoint_url + + # Ending backslash is not expected, removing if present + if self.networks_url[-1] == '/': + self.networks_url = self.networks_url[:-1] + + self.client_args = { + 'url': self.networks_url, + 'auth_token': self.token_id, + 'serialize_format': self.marshalling_config.serializer, + 'deserialize_format': self.marshalling_config.deserializer} + + if self.networks_endpoint_config.header_tenant_id: + self.client_args.update( + tenant_id=self.networks_endpoint_config.header_tenant_id) + + +class _NetworksAdminAuthComposite(_NetworksAuthComposite): + _networks_endpoint_config = NetworksAdminEndpointConfig + _auth_endpoint_config = NetworksAdminAuthConfig + _auth_user_config = NetworksAdminUserConfig + + +class NetworksComposite(object): + networks_auth_composite = _NetworksAuthComposite + + def __init__(self): + auth_composite = self.networks_auth_composite() + self.url = auth_composite.networks_url + self.user = auth_composite._auth_user_config + self.config = NetworksConfig() + self.networks = NetworksAPIComposite(auth_composite) + self.subnets = SubnetsAPIComposite(auth_composite) + self.ports = PortsAPIComposite(auth_composite) + + self.networks.behaviors = self.networks.behavior_class( + networks_client=self.networks.client, + networks_config=self.networks.config, + subnets_client=self.subnets.client, + subnets_config=self.subnets.config, + ports_client=self.ports.client, + ports_config=self.ports.config) + + self.subnets.behaviors = self.subnets.behavior_class( + subnets_client=self.subnets.client, + subnets_config=self.subnets.config, + networks_client=self.networks.client, + networks_config=self.networks.config, + ports_client=self.ports.client, + ports_config=self.ports.config) + + self.ports.behaviors = self.ports.behavior_class( + ports_client=self.ports.client, + ports_config=self.ports.config, + networks_client=self.networks.client, + networks_config=self.networks.config, + subnets_client=self.subnets.client, + subnets_config=self.subnets.config) + + +class NetworksAdminComposite(NetworksComposite): + _auth_composite = _NetworksAdminAuthComposite + + +class NetworksAPIComposite(NetworksComposite): + behavior_class = NetworksAPIBehaviors + + def __init__(self, auth_composite): + self.config = NetworksAPIConfig() + self.client = NetworksClient(**auth_composite.client_args) + self.behaviors = None + + +class SubnetsAPIComposite(NetworksComposite): + behavior_class = SubnetsAPIBehaviors + + def __init__(self, auth_composite): + self.config = SubnetsAPIConfig() + self.client = SubnetsClient(**auth_composite.client_args) + self.behaviors = None + + +class PortsAPIComposite(NetworksComposite): + behavior_class = PortsAPIBehaviors + + def __init__(self, auth_composite): + self.config = PortsAPIConfig() + self.client = PortsClient(**auth_composite.client_args) + self.behaviors = None diff --git a/cloudcafe/networking/networks/config.py b/cloudcafe/networking/networks/config.py new file mode 100644 index 00000000..170b3cea --- /dev/null +++ b/cloudcafe/networking/networks/config.py @@ -0,0 +1,90 @@ +""" +Copyright 2014 Rackspace + +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 cloudcafe.auth.config import UserAuthConfig, UserConfig +from cloudcafe.common.models.configuration import ConfigSectionInterface + + +class MarshallingConfig(ConfigSectionInterface): + SECTION_NAME = 'marshalling' + + @property + def serializer(self): + return self.get("serialize_format") + + @property + def deserializer(self): + return self.get("deserialize_format") + + +class NetworksConfig(ConfigSectionInterface): + + SECTION_NAME = 'networks' + + @property + def public_network_id(self): + """The uuid of the public network""" + return self.get("public_network_id", + "00000000-0000-0000-0000-000000000000") + + @property + def service_network_id(self): + """The uuid of the service network (aka private)""" + return self.get("service_network_id", + "11111111-1111-1111-1111-111111111111") + + +class NetworksEndpointConfig(ConfigSectionInterface): + + SECTION_NAME = 'networks_endpoint' + + @property + def region(self): + return self.get("region") + + @property + def networks_endpoint_name(self): + return self.get("networks_endpoint_name") + + @property + def networks_endpoint_url(self): + """Optional override of the Networks url""" + return self.get("networks_endpoint_url") + + @property + def header_tenant_id(self): + """Optional tenant ID to set in client request headers""" + return self.get("header_tenant_id") + + +class NetworksAdminEndpointConfig(NetworksEndpointConfig): + """RackerAdmin API endpoint and name""" + SECTION_NAME = 'networks_admin_endpoint' + + +class NetworksAdminAuthConfig(UserAuthConfig): + """Networks Admin endpoint and auth strategy, for ex. keystone""" + SECTION_NAME = 'networks_admin_auth_config' + + +class NetworksSecondUserConfig(UserConfig): + + SECTION_NAME = 'networks_secondary_user' + + +class NetworksAdminUserConfig(UserConfig): + + SECTION_NAME = 'networks_admin_user' diff --git a/cloudcafe/networking/networks/networks_api/__init__.py b/cloudcafe/networking/networks/networks_api/__init__.py new file mode 100644 index 00000000..52e3d4c2 --- /dev/null +++ b/cloudcafe/networking/networks/networks_api/__init__.py @@ -0,0 +1,15 @@ +""" +Copyright 2014 Rackspace + +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. +""" diff --git a/cloudcafe/networking/networks/networks_api/behaviors.py b/cloudcafe/networking/networks/networks_api/behaviors.py new file mode 100644 index 00000000..f6986ab7 --- /dev/null +++ b/cloudcafe/networking/networks/networks_api/behaviors.py @@ -0,0 +1,30 @@ +""" +Copyright 2014 Rackspace + +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 cafe.engine.behaviors import BaseBehavior + + +class NetworksAPIBehaviors(BaseBehavior): + + def __init__(self, networks_client, networks_config, subnets_client, + subnets_config, ports_client, ports_config): + super(NetworksAPIBehaviors, self).__init__() + self.config = networks_config + self.client = networks_client + self.subnets_client = subnets_client + self.subnets_config = subnets_config + self.ports_client = ports_client + self.ports_config = ports_config diff --git a/cloudcafe/networking/networks/networks_api/client.py b/cloudcafe/networking/networks/networks_api/client.py new file mode 100644 index 00000000..264cf6d3 --- /dev/null +++ b/cloudcafe/networking/networks/networks_api/client.py @@ -0,0 +1,137 @@ +""" +Copyright 2014 Rackspace + +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 cafe.engine.http.client import AutoMarshallingHTTPClient +from cloudcafe.networking.networks.common.models.request.network \ + import NetworkRequest +from cloudcafe.networking.networks.common.models.response.network \ + import Network, Networks + + +class NetworksClient(AutoMarshallingHTTPClient): + + def __init__(self, url, auth_token, serialize_format=None, + deserialize_format=None, tenant_id=None): + """ + @param string url: Base URL for the networks service + @param string auth_token: Auth token to be used for all requests + @param string serialize_format: Format for serializing requests + @param string deserialize_format: Format for de-serializing responses + @param string tenant_id: optional tenant id to be included in the + header if given + """ + super(NetworksClient, self).__init__(serialize_format, + deserialize_format) + self.auth_token = auth_token + self.default_headers['X-Auth-Token'] = auth_token + ct = '{content_type}/{content_subtype}'.format( + content_type='application', + content_subtype=self.serialize_format) + accept = '{content_type}/{content_subtype}'.format( + content_type='application', + content_subtype=self.deserialize_format) + self.default_headers['Content-Type'] = ct + self.default_headers['Accept'] = accept + if tenant_id: + self.default_headers['X-Auth-Project-Id'] = tenant_id + self.url = url + + def create_network(self, name=None, admin_state_up=None, shared=None, + tenant_id=None, requestslib_kwargs=None): + """ + @summary: Creates a Network + @param string name: human readable name for the network, + may not be unique. (CRUD: CRU) + @param bool admin_state_up: true or false, the admin state + of the network. If down, the network does not forward packets. + Default value is True (CRUD: CRU) + @param bool shared: specifies if the network can be accessed by any + tenant. Default value is False. (CRUD: CRU) + @param string tenant_id: owner of the network. (CRUD: CR) + """ + url = '{base_url}/networks'.format(base_url=self.url) + + request = NetworkRequest(name=name, admin_state_up=admin_state_up, + shared=shared, tenant_id=tenant_id) + + resp = self.request('POST', url, + response_entity_type=Network, + request_entity=request, + requestslib_kwargs=requestslib_kwargs) + return resp + + def update_network(self, network_id, name=None, admin_state_up=None, + shared=None, tenant_id=None, requestslib_kwargs=None): + """ + @summary: Updates a specified Network + @param string network_id: The UUID for the network + @param string name: human readable name for the network, + may not be unique. (CRUD: CRU) + @param bool admin_state_up: true or false, the admin state + of the network. If down, the network does not forward packets. + Default value is True (CRUD: CRU) + @param bool shared: specifies if the network can be accessed by any + tenant. Default value is False. (CRUD: CRU) + @param string tenant_id: owner of the network. (CRUD: CR) + """ + + url = '{base_url}/networks/{network_id}'.format( + base_url=self.url, network_id=network_id) + + request = NetworkRequest(name=name, admin_state_up=admin_state_up, + shared=shared, tenant_id=tenant_id) + resp = self.request('PUT', url, + response_entity_type=Network, + request_entity=request, + requestslib_kwargs=requestslib_kwargs) + return resp + + def get_network(self, network_id, requestslib_kwargs=None): + """ + @summary: Shows information for a specified network + @param string network_id: The UUID for the network + """ + + url = '{base_url}/networks/{network_id}'.format( + base_url=self.url, network_id=network_id) + resp = self.request('GET', url, + response_entity_type=Network, + requestslib_kwargs=requestslib_kwargs) + return resp + + def list_networks(self, requestslib_kwargs=None): + """ + @summary: Lists networks + """ + + # TODO: add field query params to filter the response + url = '{base_url}/networks'.format(base_url=self.url) + resp = self.request('GET', url, + response_entity_type=Networks, + requestslib_kwargs=requestslib_kwargs) + return resp + + def delete_network(self, network_id, requestslib_kwargs=None): + """ + @summary: Deletes a specified network and its associated resources + @param string network_id: The UUID for the network + """ + + url = '{base_url}/networks/{network_id}'.format( + base_url=self.url, network_id=network_id) + resp = self.request('DELETE', url, + requestslib_kwargs=requestslib_kwargs) + return resp diff --git a/cloudcafe/networking/networks/networks_api/config.py b/cloudcafe/networking/networks/networks_api/config.py new file mode 100644 index 00000000..3fa18700 --- /dev/null +++ b/cloudcafe/networking/networks/networks_api/config.py @@ -0,0 +1,53 @@ +""" +Copyright 2014 Rackspace + +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 cloudcafe.common.models.configuration import ConfigSectionInterface + + +class NetworksAPIConfig(ConfigSectionInterface): + """Network is the resource""" + + SECTION_NAME = 'networks_api' + + @property + def resource_build_attempts(self): + """Number of times to try to create a resource""" + return int(self.get("resource_build_attempts", 1)) + + @property + def keep_resources(self): + """Flag for not deleting resources on tearDown""" + return self.get_boolean("keep_resources", False) + + @property + def keep_resources_on_failure(self): + """Flag for not deleting resources w failures on tearDown""" + return self.get_boolean("keep_resources_on_failure", False) + + @property + def resource_create_timeout(self): + """Seconds to wait for creating a resource""" + return int(self.get("resource_create_timeout", 15)) + + @property + def resource_delete_timeout(self): + """Seconds to wait for deleting a resource""" + return int(self.get("resource_delete_timeout", 15)) + + @property + def resource_change_status_timeout(self): + """Seconds to wait for a status change in the resource""" + return int(self.get("resource_change_status_timeout", 15)) diff --git a/cloudcafe/networking/networks/ports_api/__init__.py b/cloudcafe/networking/networks/ports_api/__init__.py new file mode 100644 index 00000000..52e3d4c2 --- /dev/null +++ b/cloudcafe/networking/networks/ports_api/__init__.py @@ -0,0 +1,15 @@ +""" +Copyright 2014 Rackspace + +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. +""" diff --git a/cloudcafe/networking/networks/ports_api/behaviors.py b/cloudcafe/networking/networks/ports_api/behaviors.py new file mode 100644 index 00000000..dfb6f2ac --- /dev/null +++ b/cloudcafe/networking/networks/ports_api/behaviors.py @@ -0,0 +1,30 @@ +""" +Copyright 2014 Rackspace + +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 cafe.engine.behaviors import BaseBehavior + + +class PortsAPIBehaviors(BaseBehavior): + + def __init__(self, ports_client, ports_config, networks_client, + networks_config, subnets_client, subnets_config): + super(PortsAPIBehaviors, self).__init__() + self.config = ports_config + self.client = ports_client + self.networks_client = networks_client + self.networks_config = networks_config + self.subnets_client = subnets_client + self.subnets_config = subnets_config diff --git a/cloudcafe/networking/networks/ports_api/client.py b/cloudcafe/networking/networks/ports_api/client.py new file mode 100644 index 00000000..e93d48ae --- /dev/null +++ b/cloudcafe/networking/networks/ports_api/client.py @@ -0,0 +1,155 @@ +""" +Copyright 2014 Rackspace + +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 cafe.engine.http.client import AutoMarshallingHTTPClient +from cloudcafe.networking.networks.common.models.request.port \ + import PortRequest +from cloudcafe.networking.networks.common.models.response.port \ + import Port, Ports + + +class PortsClient(AutoMarshallingHTTPClient): + + def __init__(self, url, auth_token, serialize_format=None, + deserialize_format=None, tenant_id=None): + """ + @param string url: Base URL for the ports service + @param string auth_token: Auth token to be used for all requests + @param string serialize_format: Format for serializing requests + @param string deserialize_format: Format for de-serializing responses + @param string tenant_id: optional tenant id to be included in the + header if given + """ + super(PortsClient, self).__init__(serialize_format, + deserialize_format) + self.auth_token = auth_token + self.default_headers['X-Auth-Token'] = auth_token + ct = '{content_type}/{content_subtype}'.format( + content_type='application', + content_subtype=self.serialize_format) + accept = '{content_type}/{content_subtype}'.format( + content_type='application', + content_subtype=self.deserialize_format) + self.default_headers['Content-Type'] = ct + self.default_headers['Accept'] = accept + if tenant_id: + self.default_headers['X-Auth-Project-Id'] = tenant_id + self.url = url + + def create_port(self, network_id, name=None, admin_state_up=None, + mac_address=None, fixed_ips=None, device_id=None, + device_owner=None, tenant_id=None, security_groups=None, + requestslib_kwargs=None): + """ + @summary: Creates a Port + @param string network_id: network port is associated with (CRUD: CR) + @param string name: human readable name for the port, + may not be unique. (CRUD: CRU) + @param bool admin_state_up: true or false (default true), + the admin state of the port. If down, the port does not forward + packets (CRUD: CRU) + @param string mac_address: mac address to use on the port (CRUD: CR) + @param list(dict) fixed_ips: ip addresses for the port associating the + port with the subnets where the IPs come from (CRUD: CRU) + @param string device_id: id of device using this port (CRUD: CRUD) + @param string device_owner: entity using this port (ex. dhcp agent, + CRUD: CRUD) + @param string tenant_id: owner of the port (CRUD: CR) + @param list(dict) security_groups: ids of any security groups + associated with the port (CRUD: CRUD) + """ + + url = '{base_url}/ports'.format(base_url=self.url) + + request = PortRequest( + network_id=network_id, name=name, admin_state_up=admin_state_up, + mac_address=mac_address, fixed_ips=fixed_ips, device_id=device_id, + device_owner=device_owner, tenant_id=tenant_id, + security_groups=security_groups) + + resp = self.request('POST', url, + response_entity_type=Port, + request_entity=request, + requestslib_kwargs=requestslib_kwargs) + return resp + + def update_port(self, port_id, name=None, admin_state_up=None, + fixed_ips=None, device_id=None, device_owner=None, + security_groups=None, requestslib_kwargs=None): + """ + @summary: Updates a specified Port + @param string port_id: The UUID for the port + @param string name: human readable name for the port, + may not be unique. (CRUD: CRU) + @param bool admin_state_up: true or false (default true), + the admin state of the port. If down, the port does not forward + packets (CRUD: CRU) + @param list(dict) fixed_ips: ip addresses for the port associating the + port with the subnets where the IPs come from (CRUD: CRU) + @param string device_id: id of device using this port (CRUD: CRUD) + @param string device_owner: entity using this port (ex. dhcp agent, + CRUD: CRUD) + @param list(dict) security_groups: ids of any security groups + associated with the port (CRUD: CRUD) + """ + + url = '{base_url}/ports/{port_id}'.format( + base_url=self.url, port_id=port_id) + + request = PortRequest(name=name, admin_state_up=admin_state_up, + fixed_ips=fixed_ips, device_id=device_id, + device_owner=device_owner, security_groups=security_groups) + resp = self.request('PUT', url, + response_entity_type=Port, + request_entity=request, + requestslib_kwargs=requestslib_kwargs) + return resp + + def get_port(self, port_id, requestslib_kwargs=None): + """ + @summary: Shows information for a specified port + @param string port_id: The UUID for the port + """ + + url = '{base_url}/ports/{port_id}'.format( + base_url=self.url, port_id=port_id) + resp = self.request('GET', url, + response_entity_type=Port, + requestslib_kwargs=requestslib_kwargs) + return resp + + def list_ports(self, requestslib_kwargs=None): + """ + @summary: Lists ports + """ + + # TODO: add field query params to filter the response + url = '{base_url}/ports'.format(base_url=self.url) + resp = self.request('GET', url, + response_entity_type=Ports, + requestslib_kwargs=requestslib_kwargs) + return resp + + def delete_port(self, port_id, requestslib_kwargs=None): + """ + @summary: Deletes a specified port + @param string port_id: The UUID for the port + """ + + url = '{base_url}/ports/{port_id}'.format( + base_url=self.url, port_id=port_id) + resp = self.request('DELETE', url, + requestslib_kwargs=requestslib_kwargs) + return resp diff --git a/cloudcafe/networking/networks/ports_api/config.py b/cloudcafe/networking/networks/ports_api/config.py new file mode 100644 index 00000000..d553266c --- /dev/null +++ b/cloudcafe/networking/networks/ports_api/config.py @@ -0,0 +1,53 @@ +""" +Copyright 2014 Rackspace + +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 cloudcafe.common.models.configuration import ConfigSectionInterface + + +class PortsAPIConfig(ConfigSectionInterface): + """Port is the resource""" + + SECTION_NAME = 'ports_api' + + @property + def resource_build_attempts(self): + """Number of times to try to create a resource""" + return int(self.get("resource_build_attempts", 1)) + + @property + def keep_resources(self): + """Flag for not deleting resources on tearDown""" + return self.get_boolean("keep_resources", False) + + @property + def keep_resources_on_failure(self): + """Flag for not deleting resources w failures on tearDown""" + return self.get_boolean("keep_resources_on_failure", False) + + @property + def resource_create_timeout(self): + """Seconds to wait for creating a resource""" + return int(self.get("resource_create_timeout", 15)) + + @property + def resource_delete_timeout(self): + """Seconds to wait for deleting a resource""" + return int(self.get("resource_delete_timeout", 15)) + + @property + def resource_change_status_timeout(self): + """Seconds to wait for a status change in the resource""" + return int(self.get("resource_change_status_timeout", 15)) diff --git a/cloudcafe/networking/networks/subnets_api/__init__.py b/cloudcafe/networking/networks/subnets_api/__init__.py new file mode 100644 index 00000000..52e3d4c2 --- /dev/null +++ b/cloudcafe/networking/networks/subnets_api/__init__.py @@ -0,0 +1,15 @@ +""" +Copyright 2014 Rackspace + +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. +""" diff --git a/cloudcafe/networking/networks/subnets_api/behaviors.py b/cloudcafe/networking/networks/subnets_api/behaviors.py new file mode 100644 index 00000000..c00509ec --- /dev/null +++ b/cloudcafe/networking/networks/subnets_api/behaviors.py @@ -0,0 +1,30 @@ +""" +Copyright 2014 Rackspace + +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 cafe.engine.behaviors import BaseBehavior + + +class SubnetsAPIBehaviors(BaseBehavior): + + def __init__(self, subnets_client, subnets_config, networks_client, + networks_config, ports_client, ports_config): + super(SubnetsAPIBehaviors, self).__init__() + self.config = subnets_config + self.client = subnets_client + self.networks_client = networks_client + self.networks_config = networks_config + self.ports_client = ports_client + self.ports_config = ports_config diff --git a/cloudcafe/networking/networks/subnets_api/client.py b/cloudcafe/networking/networks/subnets_api/client.py new file mode 100644 index 00000000..4f4fb0b9 --- /dev/null +++ b/cloudcafe/networking/networks/subnets_api/client.py @@ -0,0 +1,157 @@ +""" +Copyright 2014 Rackspace + +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 cafe.engine.http.client import AutoMarshallingHTTPClient +from cloudcafe.networking.networks.common.models.request.subnet \ + import SubnetRequest +from cloudcafe.networking.networks.common.models.response.subnet \ + import Subnet, Subnets + + +class SubnetsClient(AutoMarshallingHTTPClient): + + def __init__(self, url, auth_token, serialize_format=None, + deserialize_format=None, tenant_id=None): + """ + @param string url: Base URL for the subnets service + @param string auth_token: Auth token to be used for all requests + @param string serialize_format: Format for serializing requests + @param string deserialize_format: Format for de-serializing responses + @param string tenant_id: optional tenant id to be included in the + header if given + """ + super(SubnetsClient, self).__init__(serialize_format, + deserialize_format) + self.auth_token = auth_token + self.default_headers['X-Auth-Token'] = auth_token + ct = '{content_type}/{content_subtype}'.format( + content_type='application', + content_subtype=self.serialize_format) + accept = '{content_type}/{content_subtype}'.format( + content_type='application', + content_subtype=self.deserialize_format) + self.default_headers['Content-Type'] = ct + self.default_headers['Accept'] = accept + if tenant_id: + self.default_headers['X-Auth-Project-Id'] = tenant_id + self.url = url + + def create_subnet(self, network_id, ip_version, cidr, name=None, + tenant_id=None, gateway_ip=None, dns_nameservers=None, + allocation_pools=None, host_routes=None, + enable_dhcp=None, requestslib_kwargs=None): + """ + @summary: Creates a Subnet + @param string name: human readable name for the subnet, + may not be unique. (CRUD: CRU) + @param string tenant_id: owner of the network. (CRUD: CR) + @param string network_id: network subnet is associated with (CRUD: CR) + @param int ip_version: IP version 4 or 6 (CRUD: CR) + @param string cidr: represents IP range for the subnet and should be in + the form / (CRUD: CR) + @param string gateway_ip: default gateway used by devices in the subnet + (CRUD: CRUD) + @param list(str) dns_nameservers: DNS name servers used by subnet hosts + (CRUD: CRU) + @param list(dict) allocation_pools: sub range of cidr available for + dynamic allocation to ports (CRUD: CR) + @param list(dict) host_routes: routes that should be used by devices + with IPs from this subnet (does not includes the local route, + CRUD: CRU) + @param bool enable_dhcp: whether DHCP is enabled (CRUD:CRU) + """ + url = '{base_url}/subnets'.format(base_url=self.url) + + request = SubnetRequest(network_id=network_id, ip_version=ip_version, + cidr=cidr, name=name, tenant_id=tenant_id, + gateway_ip=gateway_ip, + dns_nameservers=dns_nameservers, + allocation_pools=allocation_pools, + host_routes=host_routes, + enable_dhcp=enable_dhcp) + + resp = self.request('POST', url, + response_entity_type=Subnet, + request_entity=request, + requestslib_kwargs=requestslib_kwargs) + return resp + + def update_subnet(self, subnet_id, name=None, gateway_ip=None, + dns_nameservers=None, host_routes=None, + enable_dhcp=None, requestslib_kwargs=None): + """ + @summary: Updates a specified Subnet + @param string subnet_id: The UUID for the subnet + @param string name: human readable name for the subnet, + may not be unique. (CRUD: CRU) + @param string gateway_ip: default gateway used by devices in the subnet + (CRUD: CRUD) + @param list(str) dns_nameservers: DNS name servers used by subnet hosts + (CRUD: CRU) + @param list(dict) host_routes: routes that should be used by devices + with IPs from this subnet (does not includes the local route, + CRUD: CRU) + @param bool enable_dhcp: whether DHCP is enabled (CRUD:CRU) + """ + + url = '{base_url}/subnets/{subnet_id}'.format( + base_url=self.url, subnet_id=subnet_id) + + request = SubnetRequest(name=name, gateway_ip=gateway_ip, + dns_nameservers=dns_nameservers, + host_routes=host_routes, + enable_dhcp=enable_dhcp) + resp = self.request('PUT', url, + response_entity_type=Subnet, + request_entity=request, + requestslib_kwargs=requestslib_kwargs) + return resp + + def get_subnet(self, subnet_id, requestslib_kwargs=None): + """ + @summary: Shows information for a specified subnet + @param string subnet_id: The UUID for the subnet + """ + + url = '{base_url}/subnets/{subnet_id}'.format( + base_url=self.url, subnet_id=subnet_id) + resp = self.request('GET', url, + response_entity_type=Subnet, + requestslib_kwargs=requestslib_kwargs) + return resp + + def list_subnets(self, requestslib_kwargs=None): + """ + @summary: Lists subnets + """ + + # TODO: add field query params to filter the response + url = '{base_url}/subnets'.format(base_url=self.url) + resp = self.request('GET', url, + response_entity_type=Subnets, + requestslib_kwargs=requestslib_kwargs) + return resp + + def delete_subnet(self, subnet_id, requestslib_kwargs=None): + """ + @summary: Deletes a specified subnet + @param string subnet_id: The UUID for the subnet + """ + + url = '{base_url}/subnets/{subnet_id}'.format( + base_url=self.url, subnet_id=subnet_id) + resp = self.request('DELETE', url, + requestslib_kwargs=requestslib_kwargs) + return resp diff --git a/cloudcafe/networking/networks/subnets_api/config.py b/cloudcafe/networking/networks/subnets_api/config.py new file mode 100644 index 00000000..a38c8a2f --- /dev/null +++ b/cloudcafe/networking/networks/subnets_api/config.py @@ -0,0 +1,53 @@ +""" +Copyright 2014 Rackspace + +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 cloudcafe.common.models.configuration import ConfigSectionInterface + + +class SubnetsAPIConfig(ConfigSectionInterface): + """Subnet is the resource""" + + SECTION_NAME = 'subnets_api' + + @property + def resource_build_attempts(self): + """Number of times to try to create a resource""" + return int(self.get("resource_build_attempts", 1)) + + @property + def keep_resources(self): + """Flag for not deleting resources on tearDown""" + return self.get_boolean("keep_resources", False) + + @property + def keep_resources_on_failure(self): + """Flag for not deleting resources w failures on tearDown""" + return self.get_boolean("keep_resources_on_failure", False) + + @property + def resource_create_timeout(self): + """Seconds to wait for creating a resource""" + return int(self.get("resource_create_timeout", 15)) + + @property + def resource_delete_timeout(self): + """Seconds to wait for deleting a resource""" + return int(self.get("resource_delete_timeout", 15)) + + @property + def resource_change_status_timeout(self): + """Seconds to wait for a status change in the resource""" + return int(self.get("resource_change_status_timeout", 15)) diff --git a/metatests/cloudcafe/networking/networks/models/__init__.py b/metatests/cloudcafe/networking/networks/models/__init__.py new file mode 100644 index 00000000..52e3d4c2 --- /dev/null +++ b/metatests/cloudcafe/networking/networks/models/__init__.py @@ -0,0 +1,15 @@ +""" +Copyright 2014 Rackspace + +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. +""" diff --git a/metatests/cloudcafe/networking/networks/models/test_network.py b/metatests/cloudcafe/networking/networks/models/test_network.py new file mode 100755 index 00000000..87dafcd2 --- /dev/null +++ b/metatests/cloudcafe/networking/networks/models/test_network.py @@ -0,0 +1,187 @@ +""" +Copyright 2014 Rackspace + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import unittest + +from cloudcafe.networking.networks.common.models.request.network \ + import NetworkRequest +from cloudcafe.networking.networks.common.models.response.network \ + import Network, Networks + +NETWORK_TAG = Network.NETWORK +NETWORKS_TAG = Networks.NETWORKS + + +class CreateNetworkTest(unittest.TestCase): + """Test for the Network Create (POST) Model object requests""" + @classmethod + def setUpClass(cls): + create_attrs = dict( + name='test_name_value', admin_state_up='test_admin_state_up_value', + shared='test_shared_value', tenant_id='test_tenant_id_value') + cls.network_model = NetworkRequest(**create_attrs) + + def test_json_request(self): + """JSON test with all possible create attrs""" + expected_json_output = ( + '{{"{tag}": {{"shared": "test_shared_value", ' + '"tenant_id": "test_tenant_id_value", "name": "test_name_value", ' + '"admin_state_up": "test_admin_state_up_value"}}}}').format( + tag=NETWORK_TAG) + request_body = self.network_model._obj_to_json() + msg = ('Unexpected JSON Network request serialization. Expected {0} ' + 'instead of {1}'.format(expected_json_output, request_body)) + self.assertEqual(request_body, expected_json_output, msg) + + +class UpdateNetworkTest(unittest.TestCase): + """Test for the Network Update (PUT) Model object requests""" + @classmethod + def setUpClass(cls): + update_attrs = dict( + name='test_name_value', admin_state_up='test_admin_state_up_value', + shared='test_shared_value') + cls.network_model = NetworkRequest(**update_attrs) + + def test_json_request(self): + """JSON test with all possible update attrs""" + expected_json_output = ( + '{{"{tag}": {{"shared": "test_shared_value", ' + '"name": "test_name_value", ' + '"admin_state_up": "test_admin_state_up_value"}}}}').format( + tag=NETWORK_TAG) + request_body = self.network_model._obj_to_json() + msg = ('Unexpected JSON Network request serialization. Expected {0} ' + 'instead of {1}'.format(expected_json_output, request_body)) + self.assertEqual(request_body, expected_json_output, msg) + + +class ShowNetworkTest(unittest.TestCase): + """Test for the Network Show (GET) Model object response""" + @classmethod + def setUpClass(cls): + """Creating network_model with currently supported attributes""" + show_attrs = dict( + status='ACTIVE', subnets=['54d6f61d-db07-451c-9ab3-b9609b6b6f0b', + '79d6f61d-d007-51cd-9a33-b9609b6b6f0c'], + name='net1', admin_state_up=True, + tenant_id='9bacb3c5d39d41a79512987f338cf177', shared=False, + id_='4e8e5957-649f-477b-9e5b-f1f75b21c03c', router_external=True) + cls.expected_response = Network(**show_attrs) + + def test_json_response(self): + # Response data with extension attributes, if supported later on they + # will need to be added to the setUp object model in this test class + api_json_resp = ( + """{{ + "{tag}": {{ + "status": "ACTIVE", + "subnets": [ + "54d6f61d-db07-451c-9ab3-b9609b6b6f0b", + "79d6f61d-d007-51cd-9a33-b9609b6b6f0c" + ], + "name": "net1", + "admin_state_up": true, + "tenant_id": "9bacb3c5d39d41a79512987f338cf177", + "segments": [ + {{ + "provider:segmentation_id": 2, + "provider:physical_network": + "8bab8453-1bc9-45af-8c70-f83aa9b50453", + "provider:network_type": "vlan" + }}, + {{ + "provider:segmentation_id": null, + "provider:physical_network": + "8bab8453-1bc9-45af-8c70-f83aa9b50453", + "provider:network_type": "stt" + }} + ], + "shared": false, + "port_security_enabled": true, + "id": "4e8e5957-649f-477b-9e5b-f1f75b21c03c", + "router:external": true + }} + }}""").format(tag=NETWORK_TAG) + response_obj = Network()._json_to_obj(api_json_resp) + self.assertEqual(response_obj, self.expected_response, + 'JSON to Obj response different than expected') + + +class ShowMultipleNetworksTest(unittest.TestCase): + """Test for the Networks List (GET) Model object response""" + @classmethod + def setUpClass(cls): + """Creating network_model with currently supported attributes""" + show_attrs_1 = dict( + status='ACTIVE', subnets=['54d6f61d-db07-451c-9ab3-b9609b6b6f0b'], + name='private-network', admin_state_up=True, + tenant_id='4fd44f30292945e481c7b8a0c8908869', shared=True, + id_='d32019d3-bc6e-4319-9c1d-6722fc136a22', router_external=True) + show_attrs_2 = dict( + status='ACTIVE', subnets=['08eae331-0402-425a-923c-34f7cfe39c1b'], + name='private', admin_state_up=True, + tenant_id='26a7980765d0414dbc1fc1f88cdb7e6e', shared=True, + id_='db193ab3-96e3-4cb3-8fc5-05f4296d0324', router_external=True) + net1 = Network(**show_attrs_1) + net2 = Network(**show_attrs_2) + cls.expected_response = [net1, net2] + + def test_json_response(self): + # Response data with extension attributes, if supported later on they + # will need to be added to the setUp object model in this test class + api_json_resp = ( + """{{ + "{tag}": [ + {{ + "status": "ACTIVE", + "subnets": [ + "54d6f61d-db07-451c-9ab3-b9609b6b6f0b" + ], + "name": "private-network", + "provider:physical_network": null, + "admin_state_up": true, + "tenant_id": "4fd44f30292945e481c7b8a0c8908869", + "provider:network_type": "local", + "router:external": true, + "shared": true, + "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", + "provider:segmentation_id": null + }}, + {{ + "status": "ACTIVE", + "subnets": [ + "08eae331-0402-425a-923c-34f7cfe39c1b" + ], + "name": "private", + "provider:physical_network": null, + "admin_state_up": true, + "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e", + "provider:network_type": "local", + "router:external": true, + "shared": true, + "id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324", + "provider:segmentation_id": null + }} + ] + }}""").format(tag=NETWORKS_TAG) + response_obj = Networks()._json_to_obj(api_json_resp) + self.assertEqual(response_obj, self.expected_response, + 'JSON to Obj response different than expected') + + +if __name__ == "__main__": + unittest.main() diff --git a/metatests/cloudcafe/networking/networks/models/test_port.py b/metatests/cloudcafe/networking/networks/models/test_port.py new file mode 100755 index 00000000..1f1d6687 --- /dev/null +++ b/metatests/cloudcafe/networking/networks/models/test_port.py @@ -0,0 +1,268 @@ +""" +Copyright 2014 Rackspace + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import unittest + +from cloudcafe.networking.networks.common.models.request.port \ + import PortRequest +from cloudcafe.networking.networks.common.models.response.port \ + import Port, Ports + +PORT_TAG = Port.PORT +PORTS_TAG = Ports.PORTS + + +class CreatePortTest(unittest.TestCase): + """Test for the Port Create (POST) Model object requests""" + @classmethod + def setUpClass(cls): + create_attrs = dict( + network_id='a87cc70a-3e15-4acf-8205-9b711a3531b7', + name='private-port', admin_state_up=True) + cls.subnet_model = PortRequest(**create_attrs) + + # With all possible create attributes + create_attrs_all = dict( + network_id='test_net_id', name='port_name', admin_state_up=False, + mac_address='fa:16:3e:c9:cb:f0', fixed_ips=[{'subnet_id': + 'subnet_id_value', 'ip_address': 'ip_address_value'}, + {'subnet_id2': 'subnet_id_value2', 'ip_address2': + 'ip_address_value2'}], device_id='test_device_id', + device_owner='test_device_owner', tenant_id='test_tenant_id', + security_groups=[[{'key1': 'value1', 'key2': 'value2'}, {'key1b': + 'value1b', 'key2b':'value2b'}]]) + cls.subnet_model_all = PortRequest(**create_attrs_all) + + def test_json_request(self): + """JSON test with create attrs""" + expected_json_output = ( + '{{"{tag}": {{"network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7"' + ', "name": "private-port", "admin_state_up": true}}}}').format( + tag=PORT_TAG) + + request_body = self.subnet_model._obj_to_json() + msg = ('Unexpected JSON Port request serialization. Expected {0} ' + 'instead of {1}'.format(expected_json_output, request_body)) + self.assertEqual(request_body, expected_json_output, msg) + + def test_json_request_all_attrs(self): + """JSON test with all create attrs""" + expected_json_output = ( + '{{"{tag}": {{"name": "port_name", "admin_state_up": false, ' + '"network_id": "test_net_id", "tenant_id": "test_tenant_id", ' + '"device_owner": "test_device_owner", "mac_address": ' + '"fa:16:3e:c9:cb:f0", "fixed_ips": [{{"subnet_id": ' + '"subnet_id_value", "ip_address": "ip_address_value"}}, ' + '{{"subnet_id2": "subnet_id_value2", "ip_address2": ' + '"ip_address_value2"}}], "security_groups": [[{{"key2": "value2", ' + '"key1": "value1"}}, {{"key2b": "value2b", "key1b": "value1b"}}]],' + ' "device_id": "test_device_id"}}}}').format(tag=PORT_TAG) + request_body = self.subnet_model_all._obj_to_json() + msg = ('Unexpected JSON Subnet request serialization. Expected {0} ' + 'instead of {1}'.format(expected_json_output, request_body)) + self.assertEqual(request_body, expected_json_output, msg) + + +class UpdatePortTest(unittest.TestCase): + """Test for the Port Update (PUT) Model object requests""" + @classmethod + def setUpClass(cls): + update_attrs = dict( + name='port_update_name', admin_state_up=True, + fixed_ips=[{'subnet_id': 'subnet_updated_id_value', 'ip_address': + 'ip_address_updated_value'}, {'subnet_id2': + 'subnet_id_updated_value2', 'ip_address2': + 'ip_address_updated_value2'}], + device_id='updated_device_id', device_owner='updated_device_owner', + security_groups=[[{'key1': 'updated_value1', 'key2': + 'updated_value2'}, {'key1b': 'updated_value1b', 'key2b': + 'updated_value2b'}]]) + cls.subnet_model = PortRequest(**update_attrs) + + def test_json_request(self): + """JSON test with all possible update attrs""" + expected_json_output = ( + '{{"{tag}": {{"name": "port_update_name", "admin_state_up": true, ' + '"device_owner": "updated_device_owner", "fixed_ips": ' + '[{{"subnet_id": "subnet_updated_id_value", "ip_address": ' + '"ip_address_updated_value"}}, {{"subnet_id2": ' + '"subnet_id_updated_value2", "ip_address2": ' + '"ip_address_updated_value2"}}], "security_groups": ' + '[[{{"key2": "updated_value2", "key1": "updated_value1"}}, ' + '{{"key2b": "updated_value2b", "key1b": "updated_value1b"}}]], ' + '"device_id": "updated_device_id"}}}}').format(tag=PORT_TAG) + request_body = self.subnet_model._obj_to_json() + msg = ('Unexpected JSON Subnet request serialization. Expected {0} ' + 'instead of {1}'.format(expected_json_output, request_body)) + self.assertEqual(request_body, expected_json_output, msg) + + +class ShowPortTest(unittest.TestCase): + """Test for the Port Show (GET) Model object response""" + @classmethod + def setUpClass(cls): + """Creating port_model with with extension included attributes""" + show_attrs = dict( + status="ACTIVE", binding_host_id="devstack", name="response_name", + allowed_address_pairs=[], admin_state_up=True, + network_id="a87cc70a-3e15-4acf-8205-9b711a3531b7", + tenant_id="7e02058126cc4950b75f9970368ba177", + extra_dhcp_opts=[], binding_vif_details={"port_filter": True, + "ovs_hybrid_plug": True}, binding_vif_type="ovs", + device_owner="network:router_interface", + mac_address="fa:16:3e:23:fd:d7", binding_profile={}, + binding_vnic_type="normal", fixed_ips=[{"subnet_id": + "a0304c3a-4f08-4c43-88af-d796509c97d2", "ip_address": "10.0.0.1"}], + id_="46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2", security_groups=[], + device_id="5e3898d7-11be-483e-9732-b2f5eccd2b2e") + cls.expected_response = Port(**show_attrs) + + def test_json_response(self): + api_json_resp = ( + """{{ + "{tag}": {{ + "status": "ACTIVE", + "binding:host_id": "devstack", + "name": "response_name", + "allowed_address_pairs": [], + "admin_state_up": true, + "network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7", + "tenant_id": "7e02058126cc4950b75f9970368ba177", + "extra_dhcp_opts": [], + "binding:vif_details": {{ + "port_filter": true, + "ovs_hybrid_plug": true + }}, + "binding:vif_type": "ovs", + "device_owner": "network:router_interface", + "mac_address": "fa:16:3e:23:fd:d7", + "binding:profile": {{}}, + "binding:vnic_type": "normal", + "fixed_ips": [ + {{ + "subnet_id": "a0304c3a-4f08-4c43-88af-d796509c97d2", + "ip_address": "10.0.0.1" + }} + ], + "id": "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2", + "security_groups": [], + "device_id": "5e3898d7-11be-483e-9732-b2f5eccd2b2e" + }} + }}""").format(tag=PORT_TAG) + response_obj = Port()._json_to_obj(api_json_resp) + self.assertEqual(response_obj, self.expected_response, + 'JSON to Obj response different than expected') + + +class ShowMultiplePortTest(unittest.TestCase): + """Test for the Port List (GET) Model object response""" + @classmethod + def setUpClass(cls): + """Creating port_model with currently supported attributes""" + show_attrs_1 = dict( + status='ACTIVE', name='', admin_state_up=True, + network_id='70c1db1f-b701-45bd-96e0-a313ee3430b3', tenant_id='', + mac_address='fa:16:3e:58:42:ed', + device_owner='network:router_gateway', + fixed_ips=[{u'subnet_id': u'008ba151-0b8c-4a67-98b5-0d2b87666062', + u'ip_address': u'172.24.4.2'}], + id_='d80b1a3b-4fc1-49f3-952e-1e2ab7081d8b', security_groups=[], + device_id='9ae135f4-b6e0-4dad-9e91-3c223e385824') + show_attrs_2 = dict( + status='ACTIVE', name='', admin_state_up=True, + network_id='f27aa545-cbdd-4907-b0c6-c9e8b039dcc2', + tenant_id='d397de8a63f341818f198abb0966f6f3', + mac_address='fa:16:3e:bb:3c:e4', + device_owner='network:router_interface', fixed_ips=[{u'subnet_id': + u'288bf4a1-51ba-43b6-9d0a-520e9005db17', u'ip_address': + u'10.0.0.1'}], id_='f71a6703-d6de-4be1-a91a-a570ede1d159', + security_groups=[], + device_id='9ae135f4-b6e0-4dad-9e91-3c223e385824',) + sub1 = Port(**show_attrs_1) + sub2 = Port(**show_attrs_2) + cls.expected_response = [sub1, sub2] + + def test_json_response(self): + # Response data with extension attributes, if supported later on they + # will need to be added to the setUp object model in this test class + api_json_resp = ( + """{{ + "{tag}": [ + {{ + "status": "ACTIVE", + "binding:host_id": "devstack", + "name": "", + "allowed_address_pairs": [], + "admin_state_up": true, + "network_id": "70c1db1f-b701-45bd-96e0-a313ee3430b3", + "tenant_id": "", + "extra_dhcp_opts": [], + "binding:vif_details": {{ + "port_filter": true, + "ovs_hybrid_plug": true + }}, + "binding:vif_type": "ovs", + "device_owner": "network:router_gateway", + "mac_address": "fa:16:3e:58:42:ed", + "binding:profile": {{}}, + "binding:vnic_type": "normal", + "fixed_ips": [ + {{ + "subnet_id": "008ba151-0b8c-4a67-98b5-0d2b87666062", + "ip_address": "172.24.4.2" + }} + ], + "id": "d80b1a3b-4fc1-49f3-952e-1e2ab7081d8b", + "security_groups": [], + "device_id": "9ae135f4-b6e0-4dad-9e91-3c223e385824" + }}, + {{ + "status": "ACTIVE", + "binding:host_id": "devstack", + "name": "", + "allowed_address_pairs": [], + "admin_state_up": true, + "network_id": "f27aa545-cbdd-4907-b0c6-c9e8b039dcc2", + "tenant_id": "d397de8a63f341818f198abb0966f6f3", + "extra_dhcp_opts": [], + "binding:vif_details": {{ + "port_filter": true, + "ovs_hybrid_plug": true + }}, + "binding:vif_type": "ovs", + "device_owner": "network:router_interface", + "mac_address": "fa:16:3e:bb:3c:e4", + "binding:profile": {{}}, + "binding:vnic_type": "normal", + "fixed_ips": [ + {{ + "subnet_id": "288bf4a1-51ba-43b6-9d0a-520e9005db17", + "ip_address": "10.0.0.1" + }} + ], + "id": "f71a6703-d6de-4be1-a91a-a570ede1d159", + "security_groups": [], + "device_id": "9ae135f4-b6e0-4dad-9e91-3c223e385824" + }} + ] + }}""").format(tag=PORTS_TAG) + response_obj = Ports()._json_to_obj(api_json_resp) + self.assertEqual(response_obj, self.expected_response, + 'JSON to Obj response different than expected') + + +if __name__ == "__main__": + unittest.main() diff --git a/metatests/cloudcafe/networking/networks/models/test_subnet.py b/metatests/cloudcafe/networking/networks/models/test_subnet.py new file mode 100755 index 00000000..f87ec7d7 --- /dev/null +++ b/metatests/cloudcafe/networking/networks/models/test_subnet.py @@ -0,0 +1,223 @@ +""" +Copyright 2014 Rackspace + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import unittest + +from cloudcafe.networking.networks.common.models.request.subnet \ + import SubnetRequest +from cloudcafe.networking.networks.common.models.response.subnet \ + import Subnet, Subnets + +SUBNET_TAG = Subnet.SUBNET +SUBNETS_TAG = Subnets.SUBNETS + + +class CreateSubnetTest(unittest.TestCase): + """Test for the Subnet Create (POST) Model object requests""" + @classmethod + def setUpClass(cls): + create_attrs = dict( + network_id='d32019d3-bc6e-4319-9c1d-6722fc136a22', + ip_version=4, cidr='192.168.199.0/24') + cls.subnet_model = SubnetRequest(**create_attrs) + + # With all possible create attributes + create_attrs_all = dict( + name='test_subnet_name', tenant_id='test_tenant_id', + network_id='test_network_id', ip_version=6, cidr='test_cidr', + gateway_ip='test_gateway_ip', dns_nameservers=['test_dnsname1', + 'test_dnsname2', 'test_dnsname3'], allocation_pools=[{'start': + 'start_ip', 'end': 'end_ip'}, {'start2': 'start_ip2', 'end2': + 'end_ip2'}, {'start3': 'start_ip3', 'end3': 'end_ip3'}], + host_routes=[{'route1_key': 'route1_value', 'route1_key2': + 'route1_value2'}, {'route2_key': 'route2_value', 'route2_key2': + 'route2_value2'}], enable_dhcp=True) + cls.subnet_model_all = SubnetRequest(**create_attrs_all) + + def test_json_request(self): + """JSON test with create attrs""" + expected_json_output = ( + '{{"{tag}": {{"network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22"' + ', "ip_version": 4, "cidr": "192.168.199.0/24"}}}}').format( + tag=SUBNET_TAG) + request_body = self.subnet_model._obj_to_json() + msg = ('Unexpected JSON Subnet request serialization. Expected {0} ' + 'instead of {1}'.format(expected_json_output, request_body)) + self.assertEqual(request_body, expected_json_output, msg) + + def test_json_request_all_attrs(self): + """JSON test with all create attrs""" + expected_json_output = ( + '{{"{tag}": {{"name": "test_subnet_name", "enable_dhcp": true, ' + '"network_id": "test_network_id", "tenant_id": "test_tenant_id", ' + '"dns_nameservers": ["test_dnsname1", "test_dnsname2", ' + '"test_dnsname3"], "allocation_pools": [{{"start": "start_ip", ' + '"end": "end_ip"}}, {{"start2": "start_ip2", "end2": "end_ip2"}}, ' + '{{"start3": "start_ip3", "end3": "end_ip3"}}], "gateway_ip": ' + '"test_gateway_ip", "ip_version": 6, "host_routes": ' + '[{{"route1_key2": "route1_value2", "route1_key": "route1_value"}}' + ', {{"route2_key2": "route2_value2", "route2_key": "route2_value"' + '}}], "cidr": "test_cidr"}}}}').format(tag=SUBNET_TAG) + request_body = self.subnet_model_all._obj_to_json() + msg = ('Unexpected JSON Subnet request serialization. Expected {0} ' + 'instead of {1}'.format(expected_json_output, request_body)) + self.assertEqual(request_body, expected_json_output, msg) + + +class UpdateSubnetTest(unittest.TestCase): + """Test for the Subnet Update (PUT) Model object requests""" + @classmethod + def setUpClass(cls): + update_attrs = dict( + name='test_subnet_name', gateway_ip='test_gateway_ip', + dns_nameservers=['test_dnsname1', 'test_dnsname2', + 'test_dnsname3'], host_routes=[{'route1_key': 'route1_value', + 'route1_key2': 'route1_value2'}, {'route2_key': 'route2_value', + 'route2_key2': 'route2_value2'}], enable_dhcp=True) + cls.subnet_model = SubnetRequest(**update_attrs) + + def test_json_request(self): + """JSON test with all possible update attrs""" + expected_json_output = ( + '{{"{tag}": {{"gateway_ip": "test_gateway_ip", "host_routes": ' + '[{{"route1_key2": "route1_value2", "route1_key": "route1_value"}}' + ', {{"route2_key2": "route2_value2", "route2_key": "route2_value"' + '}}], "name": "test_subnet_name", "enable_dhcp": true, ' + '"dns_nameservers": ["test_dnsname1", "test_dnsname2", ' + '"test_dnsname3"]}}}}').format(tag=SUBNET_TAG) + request_body = self.subnet_model._obj_to_json() + msg = ('Unexpected JSON Subnet request serialization. Expected {0} ' + 'instead of {1}'.format(expected_json_output, request_body)) + self.assertEqual(request_body, expected_json_output, msg) + + +class ShowSubnetTest(unittest.TestCase): + """Test for the Subnet Show (GET) Model object response""" + @classmethod + def setUpClass(cls): + """Creating subnet_model with currently supported attributes""" + show_attrs = dict( + name='my_subnet', enable_dhcp=True, + network_id='d32019d3-bc6e-4319-9c1d-6722fc136a22', + tenant_id='4fd44f30292945e481c7b8a0c8908869', dns_nameservers=[], + allocation_pools=[{u'start': u'192.0.0.2', u'end': + u'192.255.255.254'}], gateway_ip='192.0.0.1', ip_version=4, + host_routes=[], cidr='192.0.0.0/8', + id_='54d6f61d-db07-451c-9ab3-b9609b6b6f0b') + cls.expected_response = Subnet(**show_attrs) + + def test_json_response(self): + api_json_resp = ( + """{{ + "{tag}": {{ + "name": "my_subnet", + "enable_dhcp": true, + "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", + "tenant_id": "4fd44f30292945e481c7b8a0c8908869", + "dns_nameservers": [], + "allocation_pools": [ + {{ + "start": "192.0.0.2", + "end": "192.255.255.254" + }} + ], + "host_routes": [], + "ip_version": 4, + "gateway_ip": "192.0.0.1", + "cidr": "192.0.0.0/8", + "id": "54d6f61d-db07-451c-9ab3-b9609b6b6f0b" + }} + }}""").format(tag=SUBNET_TAG) + response_obj = Subnet()._json_to_obj(api_json_resp) + self.assertEqual(response_obj, self.expected_response, + 'JSON to Obj response different than expected') + + +class ShowMultipleSubnetTest(unittest.TestCase): + """Test for the Subnet List (GET) Model object response""" + @classmethod + def setUpClass(cls): + """Creating subnet_model with currently supported attributes""" + show_attrs_1 = dict( + name='private-subnet', enable_dhcp=True, + network_id='db193ab3-96e3-4cb3-8fc5-05f4296d0324', + tenant_id='26a7980765d0414dbc1fc1f88cdb7e6e', dns_nameservers=[], + allocation_pools=[{u'start': u'10.0.0.2', u'end': u'10.0.0.254'}], + gateway_ip='10.0.0.1', ip_version=4, host_routes=[], + cidr='10.0.0.0/24', id_='08eae331-0402-425a-923c-34f7cfe39c1b') + show_attrs_2 = dict( + name='my_subnet', enable_dhcp=True, + network_id='d32019d3-bc6e-4319-9c1d-6722fc136a22', + tenant_id='4fd44f30292945e481c7b8a0c8908869', dns_nameservers=[], + allocation_pools=[{u'start': u'192.0.0.2', u'end': + u'192.255.255.254'}], gateway_ip='192.0.0.1', ip_version=4, + host_routes=[], cidr='192.0.0.0/8', + id_='54d6f61d-db07-451c-9ab3-b9609b6b6f0b') + sub1 = Subnet(**show_attrs_1) + sub2 = Subnet(**show_attrs_2) + cls.expected_response = [sub1, sub2] + + def test_json_response(self): + # Response data with extension attributes, if supported later on they + # will need to be added to the setUp object model in this test class + api_json_resp = ( + """{{ + "{tag}": [ + {{ + "name": "private-subnet", + "enable_dhcp": true, + "network_id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324", + "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e", + "dns_nameservers": [], + "allocation_pools": [ + {{ + "start": "10.0.0.2", + "end": "10.0.0.254" + }} + ], + "host_routes": [], + "ip_version": 4, + "gateway_ip": "10.0.0.1", + "cidr": "10.0.0.0/24", + "id": "08eae331-0402-425a-923c-34f7cfe39c1b" + }}, + {{ + "name": "my_subnet", + "enable_dhcp": true, + "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", + "tenant_id": "4fd44f30292945e481c7b8a0c8908869", + "dns_nameservers": [], + "allocation_pools": [ + {{ + "start": "192.0.0.2", + "end": "192.255.255.254" + }} + ], + "host_routes": [], + "ip_version": 4, + "gateway_ip": "192.0.0.1", + "cidr": "192.0.0.0/8", + "id": "54d6f61d-db07-451c-9ab3-b9609b6b6f0b" + }} + ] + }}""").format(tag=SUBNETS_TAG) + response_obj = Subnets()._json_to_obj(api_json_resp) + self.assertEqual(response_obj, self.expected_response, + 'JSON to Obj response different than expected') + + +if __name__ == "__main__": + unittest.main()