kloudbuster/kloudbuster/base_compute.py

318 lines
11 KiB
Python

# Copyright 2015 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 os
import time
import kloudbuster.log as logging
from novaclient.exceptions import BadRequest
LOG = logging.getLogger(__name__)
class KBVolAttachException(Exception):
pass
class BaseCompute():
"""
The Base class for nova compute resources
1. Creates virtual machines with specific configs
"""
def __init__(self, vm_name, network):
self.novaclient = network.router.user.nova_client
self.network = network
self.res_logger = network.res_logger
self.vm_name = vm_name
self.instance = None
self.host = None
self.fip = None
self.fip_ip = None
self.subnet_ip = None
self.fixed_ip = None
self.ssh_ip = None
# Shared interface ip for tested and testing cloud
self.shared_interface_ip = None
self.vol = None
def create_server(self, image_name, flavor_type, keyname,
nic, sec_group, avail_zone=None, user_data=None,
config_drive=True, retry_count=100):
"""
Create a server instance with associated security group, keypair with a provided public key.
Create a VM instance given following parameters
1. VM Name
2. Image Name
3. Flavor name
4. key pair name
5. Security group instance
6. Optional parameters: availability zone, user data, config drive
"""
kloud = self.network.router.user.tenant.kloud
image = kloud.vm_img
flavor = kloud.flavors[flavor_type]
# Also attach the created security group for the test
instance = self.novaclient.servers.create(name=self.vm_name,
image=image,
flavor=flavor,
key_name=keyname,
nics=nic,
availability_zone=avail_zone,
userdata=user_data,
config_drive=config_drive,
security_groups=[sec_group['id']])
self.res_logger.log('instances', self.vm_name, instance.id)
if not instance:
return None
# Verify that the instance gets into the ACTIVE state
for _ in range(retry_count):
instance = self.novaclient.servers.get(instance.id)
if instance.status == 'ACTIVE':
self.instance = instance
if 'OS-EXT-SRV-ATTR:hypervisor_hostname' in vars(instance):
self.host = vars(instance)['OS-EXT-SRV-ATTR:hypervisor_hostname']
else:
self.host = "Unknown"
return instance
if instance.status == 'ERROR':
LOG.error('Instance creation error:' + instance.fault['message'])
return None
time.sleep(2)
return None
def attach_vol(self):
if self.vol.status != 'available':
raise KBVolAttachException('Volume must be in available status before attaching.')
for _ in range(10):
try:
self.novaclient.volumes.create_server_volume(self.instance.id, self.vol.id)
break
except Exception:
time.sleep(1)
def get_server_list(self):
servers_list = self.novaclient.servers.list()
return servers_list
def delete_server(self):
# First delete the instance
if self.instance:
self.novaclient.servers.delete(self.instance)
self.instance = None
def detach_vol(self):
if self.instance and self.vol:
attached_vols = self.novaclient.volumes.get_server_volumes(self.instance.id)
if attached_vols:
try:
self.novaclient.volumes.delete_server_volume(self.instance.id, self.vol.id)
except BadRequest:
# WARNING Some resources in client cloud are not cleaned up properly.:
# BadRequest: Invalid volume: Volume must be attached in order to detach
pass
def find_flavor(self, flavor_type):
"""
Given a named flavor return the flavor
"""
flavor = self.novaclient.flavors.find(name=flavor_type)
return flavor
class SecGroup():
def __init__(self, novaclient, neutronclient):
self.secgroup = None
self.secgroup_name = None
self.novaclient = novaclient
self.neutronclient = neutronclient
def create_secgroup_with_rules(self, group_name, is_ipv6=False):
body = {
'security_group': {
'name': group_name,
'description': 'KloudBuster security group'
}
}
group = self.neutronclient.create_security_group(body)['security_group']
body = {
'security_group_rule': {
'direction': 'ingress',
'security_group_id': group['id'],
'remote_group_id': None
}
}
if is_ipv6:
body['security_group_rule']['ethertype'] = 'IPv6'
# Allow ping traffic
body['security_group_rule']['protocol'] = 'icmp'
body['security_group_rule']['port_range_min'] = None
body['security_group_rule']['port_range_max'] = None
self.neutronclient.create_security_group_rule(body)
# Allow SSH traffic
body['security_group_rule']['protocol'] = 'tcp'
body['security_group_rule']['port_range_min'] = 22
body['security_group_rule']['port_range_max'] = 22
self.neutronclient.create_security_group_rule(body)
# Allow HTTP traffic
body['security_group_rule']['protocol'] = 'tcp'
body['security_group_rule']['port_range_min'] = 80
body['security_group_rule']['port_range_max'] = 80
self.neutronclient.create_security_group_rule(body)
# Allow Redis traffic
body['security_group_rule']['protocol'] = 'tcp'
body['security_group_rule']['port_range_min'] = 6379
body['security_group_rule']['port_range_max'] = 6379
self.neutronclient.create_security_group_rule(body)
# Allow Nuttcp traffic
body['security_group_rule']['protocol'] = 'tcp'
body['security_group_rule']['port_range_min'] = 5000
body['security_group_rule']['port_range_max'] = 6000
self.neutronclient.create_security_group_rule(body)
body['security_group_rule']['protocol'] = 'tcp'
body['security_group_rule']['port_range_min'] = 12000
body['security_group_rule']['port_range_max'] = 13000
self.neutronclient.create_security_group_rule(body)
body['security_group_rule']['protocol'] = 'udp'
body['security_group_rule']['port_range_min'] = 123
body['security_group_rule']['port_range_max'] = 123
self.neutronclient.create_security_group_rule(body)
body['security_group_rule']['protocol'] = 'udp'
body['security_group_rule']['port_range_min'] = 5000
body['security_group_rule']['port_range_max'] = 6000
self.neutronclient.create_security_group_rule(body)
body['security_group_rule']['protocol'] = 'udp'
body['security_group_rule']['port_range_min'] = 12000
body['security_group_rule']['port_range_max'] = 14000
self.neutronclient.create_security_group_rule(body)
body['security_group_rule']['protocol'] = 'udp'
body['security_group_rule']['port_range_min'] = 319
body['security_group_rule']['port_range_max'] = 320
self.neutronclient.create_security_group_rule(body)
self.secgroup = group
self.secgroup_name = group_name
def delete_secgroup(self):
"""
Delete the security group
Sometimes this maybe in use if instance is just deleted
Add a retry mechanism
"""
if not self.secgroup:
return True
for _ in range(10):
try:
self.neutronclient.delete_security_group(self.secgroup['id'])
return True
except Exception:
time.sleep(2)
LOG.error('Failed while deleting security group %s.' % self.secgroup['id'])
return False
class KeyPair():
def __init__(self, novaclient):
self.keypair = None
self.keypair_name = None
self.novaclient = novaclient
def add_public_key(self, name, public_key_file=None):
"""
Add the KloudBuster public key to openstack
"""
public_key = None
try:
with open(os.path.expanduser(public_key_file)) as pkf:
public_key = pkf.read()
except IOError as exc:
LOG.error("Cannot open public key file %s: %s" % (public_key_file,
exc))
LOG.info("Adding public key %s" % name)
keypair = self.novaclient.keypairs.create(name, public_key)
self.keypair = keypair
self.keypair_name = name
def remove_public_key(self):
"""
Remove the keypair created by KloudBuster
"""
if self.keypair:
self.novaclient.keypairs.delete(self.keypair)
class Flavor():
def __init__(self, novaclient):
self.novaclient = novaclient
def get(self, name):
flavor = None
try:
flavor = vars(self.novaclient.flavors.find(name=name))
except Exception:
pass
return flavor
def list(self):
return self.novaclient.flavors.list()
def create_flavor(self, flavor_dict):
'''Delete the old flavor with same name if any and create a new one
flavor_dict: dict with following keys: name, ram, vcpus, disk, ephemeral
'''
name = flavor_dict['name']
flavor = self.get(name)
if flavor:
LOG.info('Deleting old flavor %s', name)
self.delete_flavor(flavor)
LOG.info('Creating flavor %s', name)
return self.novaclient.flavors.create(**flavor_dict)
def delete_flavor(self, flavor):
try:
flavor.delete()
except Exception:
pass
class NovaQuota():
def __init__(self, novaclient, tenant_id):
self.novaclient = novaclient
self.tenant_id = tenant_id
def get(self):
return vars(self.novaclient.quotas.get(self.tenant_id))
def update_quota(self, **kwargs):
self.novaclient.quotas.update(self.tenant_id, **kwargs)