196 lines
7.8 KiB
Python
196 lines
7.8 KiB
Python
# Copyright 2014
|
|
# The Cloudscaling Group, Inc.
|
|
#
|
|
# 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 netaddr
|
|
from neutronclient.common import exceptions as neutron_exception
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
|
|
from ec2api.api import clients
|
|
from ec2api.api import common
|
|
from ec2api.api import ec2utils
|
|
from ec2api.api import network_interface as network_interface_api
|
|
from ec2api.api import route_table as route_table_api
|
|
from ec2api.db import api as db_api
|
|
from ec2api import exception
|
|
from ec2api.i18n import _
|
|
|
|
|
|
CONF = cfg.CONF
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
"""Subnet related API implementation
|
|
"""
|
|
|
|
|
|
Validator = common.Validator
|
|
|
|
|
|
def create_subnet(context, vpc_id, cidr_block,
|
|
availability_zone=None):
|
|
vpc = ec2utils.get_db_item(context, vpc_id)
|
|
vpc_ipnet = netaddr.IPNetwork(vpc['cidr_block'])
|
|
subnet_ipnet = netaddr.IPNetwork(cidr_block)
|
|
if subnet_ipnet not in vpc_ipnet:
|
|
raise exception.InvalidSubnetRange(cidr_block=cidr_block)
|
|
|
|
gateway_ip = str(netaddr.IPAddress(subnet_ipnet.first + 1))
|
|
main_route_table = db_api.get_item_by_id(context, vpc['route_table_id'])
|
|
host_routes = route_table_api._get_subnet_host_routes(
|
|
context, main_route_table, gateway_ip)
|
|
neutron = clients.neutron(context)
|
|
with common.OnCrashCleaner() as cleaner:
|
|
os_network_body = {'network': {}}
|
|
try:
|
|
os_network = neutron.create_network(os_network_body)['network']
|
|
cleaner.addCleanup(neutron.delete_network, os_network['id'])
|
|
# NOTE(Alex): AWS takes 4 first addresses (.1 - .4) but for
|
|
# OpenStack we decided not to support this as compatibility.
|
|
os_subnet_body = {'subnet': {'network_id': os_network['id'],
|
|
'ip_version': '4',
|
|
'cidr': cidr_block,
|
|
'host_routes': host_routes}}
|
|
os_subnet = neutron.create_subnet(os_subnet_body)['subnet']
|
|
cleaner.addCleanup(neutron.delete_subnet, os_subnet['id'])
|
|
except neutron_exception.OverQuotaClient:
|
|
raise exception.SubnetLimitExceeded()
|
|
try:
|
|
neutron.add_interface_router(vpc['os_id'],
|
|
{'subnet_id': os_subnet['id']})
|
|
except neutron_exception.BadRequest:
|
|
raise exception.InvalidSubnetConflict(cidr_block=cidr_block)
|
|
cleaner.addCleanup(neutron.remove_interface_router,
|
|
vpc['os_id'], {'subnet_id': os_subnet['id']})
|
|
subnet = db_api.add_item(context, 'subnet',
|
|
{'os_id': os_subnet['id'],
|
|
'vpc_id': vpc['id']})
|
|
cleaner.addCleanup(db_api.delete_item, context, subnet['id'])
|
|
neutron.update_network(os_network['id'],
|
|
{'network': {'name': subnet['id']}})
|
|
neutron.update_subnet(os_subnet['id'],
|
|
{'subnet': {'name': subnet['id']}})
|
|
os_ports = neutron.list_ports()['ports']
|
|
return {'subnet': _format_subnet(context, subnet, os_subnet,
|
|
os_network, os_ports)}
|
|
|
|
|
|
def delete_subnet(context, subnet_id):
|
|
subnet = ec2utils.get_db_item(context, subnet_id)
|
|
vpc = db_api.get_item_by_id(context, subnet['vpc_id'])
|
|
network_interfaces = network_interface_api.describe_network_interfaces(
|
|
context,
|
|
filter=[{'name': 'subnet-id',
|
|
'value': [subnet_id]}])['networkInterfaceSet']
|
|
if network_interfaces:
|
|
msg = _("The subnet '%(subnet_id)s' has dependencies and "
|
|
"cannot be deleted.") % {'subnet_id': subnet_id}
|
|
raise exception.DependencyViolation(msg)
|
|
neutron = clients.neutron(context)
|
|
with common.OnCrashCleaner() as cleaner:
|
|
db_api.delete_item(context, subnet['id'])
|
|
cleaner.addCleanup(db_api.restore_item, context, 'subnet', subnet)
|
|
try:
|
|
neutron.remove_interface_router(vpc['os_id'],
|
|
{'subnet_id': subnet['os_id']})
|
|
except neutron_exception.NotFound:
|
|
pass
|
|
cleaner.addCleanup(neutron.add_interface_router,
|
|
vpc['os_id'],
|
|
{'subnet_id': subnet['os_id']})
|
|
try:
|
|
os_subnet = neutron.show_subnet(subnet['os_id'])['subnet']
|
|
except neutron_exception.NotFound:
|
|
pass
|
|
else:
|
|
try:
|
|
neutron.delete_network(os_subnet['network_id'])
|
|
except neutron_exception.NetworkInUseClient as ex:
|
|
LOG.warning(_('Failed to delete network %(os_id)s during '
|
|
'deleting Subnet %(id)s. Reason: %(reason)s'),
|
|
{'id': subnet['id'],
|
|
'os_id': os_subnet['network_id'],
|
|
'reason': ex.message})
|
|
|
|
return True
|
|
|
|
|
|
class SubnetDescriber(common.TaggableItemsDescriber):
|
|
|
|
KIND = 'subnet'
|
|
FILTER_MAP = {'available-ip-address-count': 'availableIpAddressCount',
|
|
'cidr': 'cidrBlock',
|
|
'cidrBlock': 'cidrBlock',
|
|
'cidr-block': 'cidrBlock',
|
|
'subnet-id': 'subnetId',
|
|
'state': 'state',
|
|
'vpc-id': 'vpcId'}
|
|
|
|
def format(self, subnet, os_subnet):
|
|
if not subnet:
|
|
return None
|
|
os_network = next((n for n in self.os_networks
|
|
if n['id'] == os_subnet['network_id']),
|
|
None)
|
|
if not os_network:
|
|
self.delete_obsolete_item(subnet)
|
|
return None
|
|
return _format_subnet(self.context, subnet, os_subnet, os_network,
|
|
self.os_ports)
|
|
|
|
def get_name(self, os_item):
|
|
return ''
|
|
|
|
def get_os_items(self):
|
|
neutron = clients.neutron(self.context)
|
|
self.os_networks = neutron.list_networks()['networks']
|
|
self.os_ports = neutron.list_ports()['ports']
|
|
return neutron.list_subnets()['subnets']
|
|
|
|
|
|
def describe_subnets(context, subnet_id=None, filter=None):
|
|
formatted_subnets = SubnetDescriber().describe(context, ids=subnet_id,
|
|
filter=filter)
|
|
return {'subnetSet': formatted_subnets}
|
|
|
|
|
|
def _format_subnet(context, subnet, os_subnet, os_network, os_ports):
|
|
status_map = {'ACTIVE': 'available',
|
|
'BUILD': 'pending',
|
|
'DOWN': 'available',
|
|
'ERROR': 'available'}
|
|
cidr_range = int(os_subnet['cidr'].split('/')[1])
|
|
# NOTE(Alex) First and last IP addresses are system ones.
|
|
ip_count = pow(2, 32 - cidr_range) - 2
|
|
# TODO(Alex): Probably performance-killer. Will have to optimize.
|
|
dhcp_port_accounted = False
|
|
for port in os_ports:
|
|
for fixed_ip in port.get('fixed_ips', []):
|
|
if fixed_ip['subnet_id'] == os_subnet['id']:
|
|
ip_count -= 1
|
|
if port['device_owner'] == 'network:dhcp':
|
|
dhcp_port_accounted = True
|
|
if not dhcp_port_accounted:
|
|
ip_count -= 1
|
|
return {
|
|
'subnetId': subnet['id'],
|
|
'state': status_map.get(os_network['status'], 'available'),
|
|
'vpcId': subnet['vpc_id'],
|
|
'cidrBlock': os_subnet['cidr'],
|
|
'defaultForAz': 'false',
|
|
'mapPublicIpOnLaunch': 'false',
|
|
'availableIpAddressCount': ip_count
|
|
}
|