kloudbuster/kloudbuster/base_network.py

515 lines
18 KiB
Python

# Copyright 2016 Cisco Systems, Inc. All rights reserved.
#
# 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 time
from kloudbuster.perf_instance import PerfInstance
import kloudbuster.base_compute as base_compute
import kloudbuster.base_storage as base_storage
import kloudbuster.log as logging
import netaddr
from neutronclient.common.exceptions import NetworkInUseClient
LOG = logging.getLogger(__name__)
# Global CIDR shared by all objects of this class
# Enables each network to get a unique CIDR
START_CIDR = "10.0.0.0/16"
cidr = START_CIDR
class KBGetExtNetException(Exception):
pass
class KBGetProvNetException(Exception):
pass
def create_floating_ip(neutron_client, ext_net, port_id):
"""
Function that creates a floating ip and returns it
Accepts the neutron client, ext_net, and port_id
Module level function since this is not associated with a
specific network instance
"""
body = {
"floatingip": {
"floating_network_id": ext_net['id'],
"port_id": port_id
}
}
fip = neutron_client.create_floatingip(body)
return fip
def disable_port_security(neutron_client, ip):
ports = neutron_client.list_ports()
for p in ports['ports']:
if p['fixed_ips'][0]['ip_address'] == ip:
body = {"port": {"security_groups": [], "port_security_enabled": False}}
neutron_client.update_port(p['id'], body)
def delete_floating_ip(neutron_client, fip):
"""
Deletes the floating ip
Module level function since this operation
is not associated with a network
Sometimes this will fail if instance is just deleted
Add a retry mechanism
"""
for _ in range(10):
try:
neutron_client.delete_floatingip(fip)
return True
except Exception:
time.sleep(1)
LOG.error('Failed while deleting floating IP %s.' % fip)
return False
def find_external_network(neutron_client):
"""
Find the external network
and return it
If no external network is found return None
"""
networks = neutron_client.list_networks()['networks']
for network in networks:
if network['router:external']:
return network
LOG.error("No external network is found.")
raise KBGetExtNetException()
def find_provider_network(neutron_client, name):
"""
Find the external network
and return it
If no external network is found return None
"""
networks = neutron_client.list_networks()['networks']
for network in networks:
if network['provider:physical_network']:
if name in ("", network['name']):
return network
if name != "":
LOG.error("The provider network: " + name + " was not found.")
else:
LOG.error("No provider network is found.")
raise KBGetProvNetException()
def find_first_network(neutron_client):
"""
Find the external network
and return it
If no external network is found return None
"""
networks = neutron_client.list_networks()['networks']
if networks:
return networks[0]
return None
class BaseNetwork():
"""
The Base class for neutron network operations
1. Creates networks with 1 subnet inside each network
2. Increments a global CIDR for all network instances
3. Deletes all networks on completion
4. Also interacts with the compute class for instances
"""
def __init__(self, router):
"""
Store the neutron client
User name for this network
and network object
"""
self.neutron_client = router.user.neutron_client
self.nova_client = router.user.nova_client
self.cinder_client = router.user.cinder_client
self.router = router
self.res_logger = router.res_logger
self.network = None
self.instance_list = []
self.secgroup_list = []
def create_compute_resources(self, network_prefix, config_scale):
"""
Creates the compute resources includes the following resources
1. VM instances
2. Security groups
3. Keypairs
"""
# Create the security groups first
for secgroup_count in range(config_scale['secgroups_per_network']):
secgroup_instance = base_compute.SecGroup(self.nova_client, self.neutron_client)
self.secgroup_list.append(secgroup_instance)
secgroup_name = network_prefix + "-SG" + str(secgroup_count)
secgroup_instance.create_secgroup_with_rules(
secgroup_name, is_ipv6=self.network['is_ipv6'])
self.res_logger.log('sec_groups', secgroup_instance.secgroup['name'],
secgroup_instance.secgroup['id'])
LOG.info("Scheduled to create VMs for network %s..." % network_prefix)
vm_total = config_scale['vms_per_network']
if config_scale['use_floatingip']:
external_network = find_external_network(self.neutron_client)
volume_type = None
storage_mode = self.router.user.tenant.kloud.storage_mode
if storage_mode and config_scale['storage_stage_configs']['target'] == 'volume':
bs_obj = base_storage.BaseStorage(self.cinder_client)
vol_size = config_scale['storage_stage_configs']['disk_size']
volume_type = config_scale['storage_stage_configs'].get('volume_type', None)
else:
vol_size = 0
# Schedule to create the required number of VMs
for instance_count in range(vm_total):
vm_name = network_prefix + "-I" + str(instance_count)
perf_instance = PerfInstance(vm_name, self, config_scale)
self.instance_list.append(perf_instance)
# Create volume if needed
# Don't create volumn for KB-Proxy
if vol_size and instance_count < vm_total - 1:
vol_name = network_prefix + "-V" + str(instance_count)
if volume_type:
perf_instance.vol = bs_obj.create_vol(vol_size, name=vol_name,
type=volume_type)
else:
perf_instance.vol = bs_obj.create_vol(vol_size, name=vol_name)
self.res_logger.log('volumes', vol_name, perf_instance.vol.id)
perf_instance.subnet_ip = self.network['subnet_ip']
if config_scale['use_floatingip']:
# Create the floating ip for the instance
# store it and the ip address in perf_instance object
port_id = perf_instance.instance.interface_list()[0].id
perf_instance.fip = create_floating_ip(self.neutron_client, external_network, port_id)
perf_instance.fip_ip = perf_instance.fip['floatingip']['floating_ip_address']
self.res_logger.log('floating_ips',
perf_instance.fip['floatingip']['floating_ip_address'],
perf_instance.fip['floatingip']['id'])
# Create the VMs on specified network, first keypair, first secgroup
perf_instance.boot_info['image_name'] = config_scale['image_name']
perf_instance.boot_info['keyname'] = self.router.user.key_name
perf_instance.boot_info['nic'] = [{'net-id': self.network['id']}]
perf_instance.boot_info['sec_group'] = self.secgroup_list[0].secgroup
perf_instance.boot_info['avail_zone'] = self.router.user.tenant.kloud.get_az()
def delete_compute_resources(self):
"""
Deletes the compute resources
Security groups, keypairs and instances
"""
flag = True
if self.instance_list and self.instance_list[0].vol:
bs_obj = base_storage.BaseStorage(self.cinder_client)
# Delete the instances first
for instance in self.instance_list:
if instance.vol:
instance.detach_vol()
flag = flag & bs_obj.delete_vol(instance.vol)
instance.delete_server()
if instance.fip:
# Delete the Floating IP
flag = flag &\
delete_floating_ip(self.neutron_client, instance.fip['floatingip']['id'])
# Delete all security groups
for secgroup_instance in self.secgroup_list:
flag = flag & secgroup_instance.delete_secgroup()
return flag
def create_network_and_subnet(self, network_name):
"""
Create a network with 1 subnet inside it
"""
subnet_name = "KB_subnet_" + network_name
body = {
'network': {
'name': network_name,
'admin_state_up': True
}
}
self.network = self.neutron_client.create_network(body)['network']
# Now create the subnet inside this network support ipv6 in future
body = {
'subnet': {
'name': subnet_name,
'cidr': self.generate_cidr(),
'network_id': self.network['id'],
'enable_dhcp': True,
'ip_version': 4
}
}
subnet = self.neutron_client.create_subnet(body)['subnet']
# add subnet id to the network dict since it has just been added
self.network['subnets'] = [subnet['id']]
self.network['subnet_ip'] = cidr
self.network['is_ipv6'] = False
def add_provider_network(self, name):
self.network = find_provider_network(self.neutron_client, name)
if len(self.network['subnets']) > 0:
subnet = self.neutron_client.show_subnet(self.network['subnets'][0])['subnet']
self.network['subnet_ip'] = subnet['cidr']
self.network['is_ipv6'] = bool(subnet['ipv6_address_mode'])
def get_cidr_from_subnet_id(self, subnetID):
sub = self.neutron_client.show_subnet(subnetID)
return sub['subnet']['cidr']
def generate_cidr(self):
"""Generate next CIDR for network or subnet, without IP overlapping.
"""
global cidr
# pylint: disable=not-callable
cidr = str(netaddr.IPNetwork(cidr).next())
return cidr
def delete_network(self):
"""
Deletes the network and associated subnet
retry the deletion since network may be in use
"""
if not self.network:
return True
for _ in range(10):
try:
self.neutron_client.delete_network(self.network['id'])
return True
except NetworkInUseClient:
time.sleep(1)
LOG.error('Failed while deleting network %s.' % self.network['id'])
return False
def get_all_instances(self):
return self.instance_list
class Router():
"""
Router class to create a new routers
Supports addition and deletion
of network interfaces to router
"""
def __init__(self, user, provider_network=None):
self.neutron_client = user.neutron_client
self.nova_client = user.nova_client
self.router = None
self.user = user
self.res_logger = user.res_logger
# Stores the list of networks
self.network_list = []
# Store the shared network
self.shared_network = None
self.shared_port_id = None
# Store the interface ip of shared network attached to router
self.shared_interface_ip = None
self.provider_network = provider_network
def create_network_resources(self, config_scale):
"""
Creates the required number of networks per router
Also triggers the creation of compute resources inside each
network
"""
if self.provider_network:
# This is dummy router, use provider network directly
network_instance = BaseNetwork(self)
self.network_list.append(network_instance)
network_instance.add_provider_network(self.provider_network)
network_instance.create_compute_resources(network_instance.network['name'],
config_scale)
return
for network_count in range(config_scale['networks_per_router']):
network_instance = BaseNetwork(self)
self.network_list.append(network_instance)
# Create the network and subnet
network_name = self.router['router']['name'] + "-N" + str(network_count)
network_instance.create_network_and_subnet(network_name)
self.res_logger.log('networks', network_instance.network['name'],
network_instance.network['id'])
# Attach the created network to router interface
self.attach_router_interface(network_instance)
# Create the compute resources in the network
network_instance.create_compute_resources(network_name, config_scale)
def get_first_network(self):
if self.network_list:
return self.network_list[0]
return None
def get_all_instances(self):
all_instances = []
for network in self.network_list:
all_instances.extend(network.get_all_instances())
return all_instances
def delete_network_resources(self):
"""
Delete all network and compute resources
associated with a router
"""
flag = True
for network in self.network_list:
# Now delete the compute resources and the network resources
flag = flag & network.delete_compute_resources()
if network.network:
if self.provider_network:
continue
flag = flag & self.remove_router_interface(network)
flag = flag & network.delete_network()
# Also delete the shared port and remove it from router interface
if self.shared_network and not self.provider_network:
flag = flag & self.remove_router_interface(self.shared_network, use_port=True)
self.shared_network = None
return flag
def create_router(self, router_name, ext_net):
"""
Create the router and attach it to
external network
"""
# Attach an external network if available
if ext_net:
body = {
"router": {
"name": router_name,
"admin_state_up": True,
"external_gateway_info": {
"network_id": ext_net['id']
}
}
}
else:
body = {
"router": {
"name": router_name,
"admin_state_up": True
}
}
self.router = self.neutron_client.create_router(body)
return self.router['router']
def delete_router(self):
"""
Delete the router
Also delete the networks attached to this router
"""
# Delete the network resources first and than delete the router itself
if not self.router and not self.provider_network:
return True
network_flag = self.delete_network_resources()
if self.provider_network:
return network_flag
router_flag = False
for _ in range(10):
try:
self.neutron_client.remove_gateway_router(self.router['router']['id'])
self.neutron_client.delete_router(self.router['router']['id'])
router_flag = True
break
except Exception:
time.sleep(1)
if not router_flag:
LOG.error('Failed while deleting router %s.' % self.router['router']['id'])
return network_flag & router_flag
def _port_create_neutron(self, network_instance):
"""
Creates a port on a specific network
"""
body = {
"port": {
"admin_state_up": True,
"network_id": network_instance.network['id']
}
}
post_output = self.neutron_client.create_port(body)
self.shared_interface_ip = post_output['port']['fixed_ips'][0]['ip_address']
return post_output['port']['id']
def _port_delete_neutron(self, port):
self.neutron_client.delete_port(port)
def attach_router_interface(self, network_instance, use_port=False):
"""
Attach a network interface to the router
"""
# If shared port is specified use that
if use_port:
self.shared_port_id = self._port_create_neutron(network_instance)
body = {
'port_id': self.shared_port_id
}
else:
body = {
'subnet_id': network_instance.network['subnets'][0]
}
self.neutron_client.add_interface_router(self.router['router']['id'], body)
def remove_router_interface(self, network_instance, use_port=False):
"""
Remove the network interface from router
"""
if use_port:
body = {
'port_id': self.shared_port_id
}
else:
body = {
'subnet_id': network_instance.network['subnets'][0]
}
for _ in range(10):
try:
self.neutron_client.remove_interface_router(self.router['router']['id'], body)
return True
except Exception:
time.sleep(1)
return False
class NeutronQuota():
def __init__(self, neutronclient, tenant_id):
self.neutronclient = neutronclient
self.tenant_id = tenant_id
def get(self):
return self.neutronclient.show_quota(self.tenant_id)['quota']
def update_quota(self, quotas):
body = {
'quota': quotas
}
self.neutronclient.update_quota(self.tenant_id, body)