# limitations under the License.
import logging
-from neutronclient.common.exceptions import NotFound
+import enum
+from neutronclient.common.exceptions import NetworkNotFoundClient
+
+from snaps.openstack.openstack_creator import OpenStackNetworkObject
from snaps.openstack.utils import keystone_utils, neutron_utils
__author__ = 'spisarski'
logger = logging.getLogger('OpenStackNetwork')
-class OpenStackNetwork:
+class OpenStackNetwork(OpenStackNetworkObject):
"""
- Class responsible for creating a network in OpenStack
+ Class responsible for managing a network in OpenStack
"""
def __init__(self, os_creds, network_settings):
:param os_creds: The credentials to connect with OpenStack
:param network_settings: The settings used to create a network
"""
- self.__os_creds = os_creds
+ super(self.__class__, self).__init__(os_creds)
+
self.network_settings = network_settings
- self.__neutron = None
# Attributes instantiated on create()
self.__network = None
- self.__subnets = list()
- def create(self, cleanup=False):
+ def initialize(self):
+ """
+ Loads the existing OpenStack network/subnet
+ :return: The Network domain object or None
+ """
+ super(self.__class__, self).initialize()
+
+ self.__network = neutron_utils.get_network(
+ self._neutron, network_settings=self.network_settings,
+ project_id=self.network_settings.get_project_id(self._os_creds))
+
+ return self.__network
+
+ def create(self):
"""
Responsible for creating not only the network but then a private
subnet, router, and an interface to the router.
- :param cleanup: When true, only perform lookups for OpenStack objects.
- :return: the created network object or None
+ :return: the Network domain object
"""
- self.__neutron = neutron_utils.neutron_client(self.__os_creds)
-
- logger.info(
- 'Creating neutron network %s...' % self.network_settings.name)
- net_inst = neutron_utils.get_network(
- self.__neutron, self.network_settings.name,
- self.network_settings.get_project_id(self.__os_creds))
- if net_inst:
- self.__network = net_inst
- else:
- if not cleanup:
- self.__network = neutron_utils.create_network(
- self.__neutron, self.__os_creds, self.network_settings)
- else:
- logger.info(
- 'Network does not exist and will not create as in cleanup'
- ' mode')
- return
- logger.debug(
- "Network '%s' created successfully" % self.__network.id)
-
- logger.debug('Creating Subnets....')
- for subnet_setting in self.network_settings.subnet_settings:
- sub_inst = neutron_utils.get_subnet_by_name(
- self.__neutron, subnet_setting.name)
- if sub_inst:
- self.__subnets.append(sub_inst)
- logger.debug(
- "Subnet '%s' created successfully" % sub_inst.id)
- else:
- if not cleanup:
- self.__subnets.append(
- neutron_utils.create_subnet(
- self.__neutron, subnet_setting, self.__os_creds,
- self.__network))
+ self.initialize()
+
+ if not self.__network:
+ self.__network = neutron_utils.create_network(
+ self._neutron, self._os_creds, self.network_settings)
+ logger.debug(
+ 'Network [%s] created successfully' % self.__network.id)
return self.__network
"""
Removes and deletes all items created in reverse order.
"""
- for subnet in self.__subnets:
- try:
- logger.info(
- 'Deleting subnet with name ' + subnet.name)
- neutron_utils.delete_subnet(self.__neutron, subnet)
- except NotFound as e:
- logger.warning(
- 'Error deleting subnet with message - ' + str(e))
- pass
- self.__subnets = list()
-
if self.__network:
try:
- neutron_utils.delete_network(self.__neutron, self.__network)
- except NotFound:
+ neutron_utils.delete_network(self._neutron, self.__network)
+ except NetworkNotFoundClient:
pass
-
self.__network = None
def get_network(self):
"""
return self.__network
- def get_subnets(self):
- """
- Returns the OpenStack subnet objects
- :return:
- """
- return self.__subnets
-
class NetworkSettings:
"""
(default False).
:param network_type: the type of network (i.e. vlan|flat).
:param physical_network: the name of the physical network
- (this is required when network_type is 'flat')
+ (required when network_type is 'flat')
+ :param segmentation_id: the id of the segmentation
+ (this is required when network_type is 'vlan')
:param subnets or subnet_settings: List of SubnetSettings objects.
:return:
"""
self.network_type = kwargs.get('network_type')
self.physical_network = kwargs.get('physical_network')
+ self.segmentation_id = kwargs.get('segmentation_id')
self.subnet_settings = list()
subnet_settings = kwargs.get('subnets')
if not subnet_settings:
- subnet_settings = kwargs.get('subnet_settings')
+ subnet_settings = kwargs.get('subnet_settings', list())
if subnet_settings:
for subnet_config in subnet_settings:
if isinstance(subnet_config, SubnetSettings):
if not self.name or len(self.name) < 1:
raise NetworkSettingsError('Name required for networks')
- raise NetworkSettingsError('Name required for networks')
def get_project_id(self, os_creds):
"""
else:
if self.project_name:
keystone = keystone_utils.keystone_client(os_creds)
- project = keystone_utils.get_project(keystone,
- self.project_name)
+ project = keystone_utils.get_project(
+ keystone=keystone, project_name=self.project_name)
if project:
return project.id
if self.project_name:
project_id = self.get_project_id(os_creds)
if project_id:
- out['project_id'] = project_id
+ out['tenant_id'] = project_id
else:
raise NetworkSettingsError(
'Could not find project ID for project named - ' +
out['provider:network_type'] = self.network_type
if self.physical_network:
out['provider:physical_network'] = self.physical_network
+ if self.segmentation_id:
+ out['provider:segmentation_id'] = self.segmentation_id
if self.external:
out['router:external'] = self.external
return {'network': out}
"""
+class IPv6Mode(enum.Enum):
+ """
+ A rule's direction
+ """
+ slaac = 'slaac'
+ stateful = 'dhcpv6-stateful'
+ stateless = 'dhcpv6-stateless'
+
+
class SubnetSettings:
"""
Class representing a subnet configuration
def __init__(self, **kwargs):
"""
Constructor - all parameters are optional except cidr (subnet mask)
- :param cidr: The CIDR. REQUIRED if config parameter is None
- :param ip_version: The IP version, which is 4 or 6.
- :param name: The subnet name.
+ :param name: The subnet name (required)
+ :param cidr: The CIDR (required)
+ :param ip_version: The IP version, which is 4 or 6 (required)
:param project_name: The name of the project who owns the network.
Only administrative users can specify a project ID
other than their own. You cannot change this value
- through authorization policies.
- :param start: The start address for the allocation pools.
- :param end: The end address for the allocation pools.
- :param gateway_ip: The gateway IP address.
+ through authorization policies (optional)
+ :param start: The start address for the allocation pools (optional)
+ :param end: The end address for the allocation pools (optional)
+ :param gateway_ip: The gateway IP address (optional)
:param enable_dhcp: Set to true if DHCP is enabled and false if DHCP is
- disabled.
+ disabled (optional)
:param dns_nameservers: A list of DNS name servers for the subnet.
Specify each name server as an IP address
and separate multiple entries with a space.
- For example [8.8.8.7 8.8.8.8].
+ For example [8.8.8.7 8.8.8.8]
+ (default '8.8.8.8')
:param host_routes: A list of host route dictionaries for the subnet.
For example:
"host_routes":[
"nexthop":"192.168.0.1"
}
]
- :param destination: The destination for static route
- :param nexthop: The next hop for the destination.
- :param ipv6_ra_mode: A valid value is dhcpv6-stateful,
- dhcpv6-stateless, or slaac.
- :param ipv6_address_mode: A valid value is dhcpv6-stateful,
- dhcpv6-stateless, or slaac.
+ :param destination: The destination for static route (optional)
+ :param nexthop: The next hop for the destination (optional)
+ :param ipv6_ra_mode: an instance of the IPv6Mode enum
+ (optional when enable_dhcp is True)
+ :param ipv6_address_mode: an instance of the IPv6Mode enum
+ (optional when enable_dhcp is True)
:raise: SubnetSettingsError when config does not have or cidr values
are None
"""
self.gateway_ip = kwargs.get('gateway_ip')
self.enable_dhcp = kwargs.get('enable_dhcp')
- if kwargs.get('dns_nameservers'):
+ if 'dns_nameservers' in kwargs:
self.dns_nameservers = kwargs.get('dns_nameservers')
else:
- self.dns_nameservers = ['8.8.8.8']
+ if self.ip_version == 4:
+ self.dns_nameservers = ['8.8.8.8']
+ else:
+ self.dns_nameservers = list()
self.host_routes = kwargs.get('host_routes')
self.destination = kwargs.get('destination')
self.nexthop = kwargs.get('nexthop')
- self.ipv6_ra_mode = kwargs.get('ipv6_ra_mode')
- self.ipv6_address_mode = kwargs.get('ipv6_address_mode')
+ self.ipv6_ra_mode = map_mode(kwargs.get('ipv6_ra_mode'))
+ self.ipv6_address_mode = map_mode(kwargs.get('ipv6_address_mode'))
if not self.name or not self.cidr:
raise SubnetSettingsError('Name and cidr required for subnets')
out['name'] = self.name
if self.project_name:
keystone = keystone_utils.keystone_client(os_creds)
- project = keystone_utils.get_project(keystone, self.project_name)
+ project = keystone_utils.get_project(
+ keystone=keystone, project_name=self.project_name)
project_id = None
if project:
project_id = project.id
if project_id:
- out['project_id'] = project_id
+ out['tenant_id'] = project_id
else:
raise SubnetSettingsError(
'Could not find project ID for project named - ' +
if self.nexthop:
out['nexthop'] = self.nexthop
if self.ipv6_ra_mode:
- out['ipv6_ra_mode'] = self.ipv6_ra_mode
+ out['ipv6_ra_mode'] = self.ipv6_ra_mode.value
if self.ipv6_address_mode:
- out['ipv6_address_mode'] = self.ipv6_address_mode
+ out['ipv6_address_mode'] = self.ipv6_address_mode.value
return out
+def map_mode(mode):
+ """
+ Takes a the direction value maps it to the Direction enum. When None return
+ None
+ :param mode: the mode value
+ :return: the IPv6Mode enum object
+ :raise: SubnetSettingsError if value is invalid
+ """
+ if not mode:
+ return None
+ if isinstance(mode, IPv6Mode):
+ return mode
+ else:
+ mode_str = str(mode)
+ if mode_str == 'slaac':
+ return IPv6Mode.slaac
+ elif mode_str == 'dhcpv6-stateful':
+ return IPv6Mode.stateful
+ elif mode_str == 'stateful':
+ return IPv6Mode.stateful
+ elif mode_str == 'dhcpv6-stateless':
+ return IPv6Mode.stateless
+ elif mode_str == 'stateless':
+ return IPv6Mode.stateless
+ else:
+ raise SubnetSettingsError('Invalid mode - ' + mode_str)
+
+
class SubnetSettingsError(Exception):
"""
Exception to be thrown when subnet settings attributes are incorrect
def __init__(self, **kwargs):
"""
- Constructor - all parameters are optional
- :param name: A symbolic name for the port.
+ Constructor
+ :param name: A symbolic name for the port (optional).
:param network_name: The name of the network on which to create the
- port.
+ port (required).
:param admin_state_up: A boolean value denoting the administrative
- status of the port. True = up / False = down
+ status of the port (default = True)
:param project_name: The name of the project who owns the network.
Only administrative users can specify a project ID
other than their own. You cannot change this value
- through authorization policies.
+ through authorization policies (optional)
:param mac_address: The MAC address. If you specify an address that is
not valid, a Bad Request (400) status code is
returned. If you do not specify a MAC address,
OpenStack Networking tries to allocate one. If a
failure occurs, a Service Unavailable (503) status
- code is returned.
+ code is returned (optional)
:param ip_addrs: A list of dict objects where each contains two keys
'subnet_name' and 'ip' values which will get mapped to
self.fixed_ips. These values will be directly
- translated into the fixed_ips dict
- :param fixed_ips: A dict where the key is the subnet IDs and value is
- the IP address to assign to the port
+ translated into the fixed_ips dict (optional)
:param security_groups: One or more security group IDs.
:param allowed_address_pairs: A dictionary containing a set of zero or
more allowed address pairs. An address
pair contains an IP address and MAC
- address.
- :param opt_value: The extra DHCP option value.
- :param opt_name: The extra DHCP option name.
+ address (optional)
+ :param opt_value: The extra DHCP option value (optional)
+ :param opt_name: The extra DHCP option name (optional)
:param device_owner: The ID of the entity that uses this port.
- For example, a DHCP agent.
+ For example, a DHCP agent (optional)
:param device_id: The ID of the device that uses this port.
- For example, a virtual server.
+ For example, a virtual server (optional)
:return:
"""
if 'port' in kwargs:
kwargs = kwargs['port']
- self.network = None
-
self.name = kwargs.get('name')
self.network_name = kwargs.get('network_name')
self.project_name = kwargs.get('project_name')
self.mac_address = kwargs.get('mac_address')
self.ip_addrs = kwargs.get('ip_addrs')
- self.fixed_ips = kwargs.get('fixed_ips')
self.security_groups = kwargs.get('security_groups')
self.allowed_address_pairs = kwargs.get('allowed_address_pairs')
self.opt_value = kwargs.get('opt_value')
self.device_owner = kwargs.get('device_owner')
self.device_id = kwargs.get('device_id')
- if not self.name or not self.network_name:
+ if not self.network_name:
raise PortSettingsError(
- 'The attributes neutron, name, and network_name are required '
- 'for PortSettings')
+ 'The attribute network_name is required')
- def __set_fixed_ips(self, neutron):
+ def __get_fixed_ips(self, neutron):
"""
Sets the self.fixed_ips value
:param neutron: the Neutron client
:return: None
"""
- if not self.fixed_ips and self.ip_addrs:
- self.fixed_ips = list()
+
+ fixed_ips = list()
+ if self.ip_addrs:
for ip_addr_dict in self.ip_addrs:
- subnet = neutron_utils.get_subnet_by_name(neutron,
- ip_addr_dict[
- 'subnet_name'])
- if subnet:
- self.fixed_ips.append({'ip_address': ip_addr_dict['ip'],
- 'subnet_id': subnet.id})
+ subnet = neutron_utils.get_subnet(
+ neutron, subnet_name=ip_addr_dict['subnet_name'])
+ if subnet and 'ip' in ip_addr_dict:
+ fixed_ips.append({'ip_address': ip_addr_dict['ip'],
+ 'subnet_id': subnet.id})
else:
raise PortSettingsError(
'Invalid port configuration, subnet does not exist '
'with name - ' + ip_addr_dict['subnet_name'])
+ return fixed_ips
+
def dict_for_neutron(self, neutron, os_creds):
"""
Returns a dictionary object representing this object.
:param os_creds: the OpenStack credentials
:return: the dictionary object
"""
- self.__set_fixed_ips(neutron)
out = dict()
project_id = None
if self.project_name:
keystone = keystone_utils.keystone_client(os_creds)
- project = keystone_utils.get_project(keystone, self.project_name)
+ project = keystone_utils.get_project(
+ keystone=keystone, project_name=self.project_name)
if project:
project_id = project.id
- if not self.network:
- self.network = neutron_utils.get_network(neutron,
- self.network_name,
- project_id)
- if not self.network:
+ network = neutron_utils.get_network(
+ neutron, network_name=self.network_name, project_id=project_id)
+ if not network:
raise PortSettingsError(
'Cannot locate network with name - ' + self.network_name)
- out['network_id'] = self.network.id
+ out['network_id'] = network.id
if self.admin_state_up is not None:
out['admin_state_up'] = self.admin_state_up
out['name'] = self.name
if self.project_name:
if project_id:
- out['project_id'] = project_id
+ out['tenant_id'] = project_id
else:
raise PortSettingsError(
'Could not find project ID for project named - ' +
self.project_name)
if self.mac_address:
out['mac_address'] = self.mac_address
- if self.fixed_ips and len(self.fixed_ips) > 0:
- out['fixed_ips'] = self.fixed_ips
+
+ fixed_ips = self.__get_fixed_ips(neutron)
+ if fixed_ips and len(fixed_ips) > 0:
+ out['fixed_ips'] = fixed_ips
+
if self.security_groups:
out['security_groups'] = self.security_groups
if self.allowed_address_pairs and len(self.allowed_address_pairs) > 0:
self.project_name == other.project_name and
self.mac_address == other.mac_address and
self.ip_addrs == other.ip_addrs and
- self.fixed_ips == other.fixed_ips and
+ # self.fixed_ips == other.fixed_ips and
self.security_groups == other.security_groups and
self.allowed_address_pairs == other.allowed_address_pairs and
self.opt_value == other.opt_value and