fuel-main/test/cookbooks/__init__.py
2012-08-14 13:02:56 +04:00

286 lines
8.6 KiB
Python

import time, os
import devops
import unittest
from devops.model import Environment, Network, Node, Disk, Cdrom, Interface
from devops.controller import Controller
from devops.driver.libvirt import Libvirt
from devops.helpers import tcp_ping, wait, TimeoutError, ssh, http_server
import traceback
import simplejson as json
import logging
logger = logging.getLogger('cookbooks')
class ChefRunError(Exception):
pass
class Ci:
hostname = 'nailgun'
domain = 'mirantis.com'
def __init__(self, cache_file=None, base_image=None):
self.environment_cache_file = cache_file
self.base_image = base_image
self.environment = None
if self.environment_cache_file and os.path.exists(self.environment_cache_file):
logger.info("Loading existing cookbooks environment...")
with file(self.environment_cache_file) as f:
environment_id = f.read()
try:
self.environment = devops.load(environment_id)
self.node = self.environment.nodes[0]
self.network = self.environment.networks[0]
logger.info("Successfully loaded existing environment")
except Exception, e:
logger.error("Failed to load existing cookbooks environment: " + str(e) + "\n" + traceback.format_exc())
pass
def setup_environment(self):
if self.environment:
return True
if not self.base_image:
logger.critical("Base image path is missing while trying to build cookbooks environment")
return False
logger.info("Building cookbooks environment")
try:
environment = Environment('cookbooks')
network = Network(name='default', dhcp_server=True)
environment.networks.append(network)
node = Node('cookbooks')
node.memory = 1024
node.vnc = True
node.interfaces.append(Interface(network))
node.disks.append(Disk(base_image=self.base_image, format='qcow2'))
node.boot = ['disk']
environment.nodes.append(node)
devops.build(environment)
except Exception, e:
logger.error("Failed to build environment: %s\n%s" % (str(e), traceback.format_exc()))
return False
self.environment = environment
self.node = node
self.network = network
try:
logger.info("Starting test node")
node.start()
logger.info("Waiting ssh to respond...")
wait(lambda: tcp_ping(node.ip_address, 22), timeout=1800)
logger.info("Setting up repository configuration")
remote = ssh(node.ip_address, username='root', password='r00tme')
repo = remote.open('/etc/yum.repos.d/mirantis.repo', 'w')
repo.write("""
[mirantis]
name=Mirantis repository
baseurl=http://%s:8000
enabled=1
gpgcheck=0
""" % network.ip_addresses[1])
repo.close()
remote.execute('yum makecache')
logger.info("Disabling firewall")
remote.execute('service iptables save')
remote.execute('service iptables stop')
remote.execute('chkconfig iptables off')
logger.info("Creating snapshot 'blank'")
node.save_snapshot('empty')
logger.info("Test node is ready at %s" % node.ip_address)
devops.save(self.environment)
if not os.path.exists(os.path.dirname(self.environment_cache_file)):
os.makedirs(os.path.dirname(self.environment_cache_file))
with file(self.environment_cache_file, 'w') as f:
f.write(self.environment.id)
logger.info("Environment has been saved")
except Exception, e:
logger.error("Exception during environment setup: %s" % str(e))
devops.save(self.environment)
cache_file = self.environment_cache_file + '.candidate'
if not os.path.exists(os.path.dirname(cache_file)):
os.makedirs(os.path.dirname(cache_file))
with file(cache_file, 'w') as f:
f.write(self.environment.id)
logger.error("Failed to build environment. Candidate environment cache file is %s" % cache_file)
return False
return True
def destroy_environment(self):
if self.environment:
devops.destroy(self.environment)
if self.environment_cache_file and os.path.exists(self.environment_cache_file):
os.remove(self.environment_cache_file)
return True
def setup_repository(self):
remote = ssh(self.node.ip_address, username='root', password='r00tme')
repo = remote.open('/etc/yum.repos.d/mirantis.repo', 'w')
repo.write("""
[mirantis]
name=Mirantis repository
baseurl=http://%s:%d
enabled=1
gpgcheck=0
""" % (self.network.ip_addresses[1], self.repository_server.port))
repo.close()
remote.execute('yum makecache')
def start_environment(self):
self.repository_server = http_server(
os.path.abspath(
os.path.join(
os.path.dirname(__file__), "..", "..",
"build", "packages", "centos", "Packages"
)
)
)
self.setup_repository()
self.node.save_snapshot('blank', force=True)
return True
def shutdown_environment(self):
if hasattr(self, 'repository_server'):
self.repository_server.stop()
return True
ci = None
def setUp():
if not ci.setup_environment():
raise Exception, "Failed to setup cookbooks environment"
if not ci.start_environment():
raise Exception, "Failed to run cookboks environment"
def tearDown():
ci.shutdown_environment()
if not ci.environment_cache_file:
ci.destroy_environment()
class CookbookTestCase(unittest.TestCase):
cookbooks = []
cookbooks_dir = os.path.abspath(
os.path.join(
os.path.dirname(__file__),
"..", "..", "cooks", "cookbooks"
)
)
@classmethod
def setUpState(klass):
pass
@classmethod
def setUpClass(klass):
klass.node = ci.environment.node['cookbooks']
klass.ip = klass.node.ip_address
klass.remote = ssh(klass.ip, username='root', password='r00tme')
snapshot = klass.__name__
if not os.environ.get('DEVELOPMENT') or not snapshot in klass.node.snapshots:
logger.info('Setting up state for %s' % klass.__name__)
if snapshot in klass.node.snapshots:
klass.node.delete_snapshot(snapshot)
klass.node.restore_snapshot('blank')
klass.remote.reconnect()
klass.upload_cookbooks(klass.cookbooks)
klass.setUpState()
klass.node.save_snapshot(snapshot)
logger.info('Finished state for %s' % klass.__name__)
@classmethod
def upload_cookbooks(klass, cookbooks):
klass.remote.mkdir("/opt/os-cookbooks/")
solo_rb = klass.remote.open('/tmp/solo.rb', 'w')
solo_rb.write("""
file_cache_path "/tmp/chef"
cookbook_path "/opt/os-cookbooks"
""")
solo_rb.close()
for cookbook in cookbooks:
klass.remote.upload(os.path.join(klass.cookbooks_dir, cookbook), "/opt/os-cookbooks/")
@classmethod
def chef_solo(klass, attributes={}):
ci.setup_repository()
recipes = attributes.get('recipes') or attributes.get('run_list')
logger.info('Running Chef with recipes: %s' % (', '.join(recipes)))
solo_json = klass.remote.open('/tmp/solo.json', 'w')
solo_json.write(json.dumps(attributes))
solo_json.close()
result = klass.remote.execute("chef-solo -l debug -c /tmp/solo.rb -j /tmp/solo.json")
if result['exit_code'] != 0:
stdout = result['stdout']
stderr = result['stderr']
while stdout[-1] == '':
stdout.pop()
logger.error(''.join(stdout))
raise ChefRunError(result['exit_code'], stdout[-1], ''.join(stderr))
logger.info('Chef run successfully finished')
return result
@classmethod
def chef_run_recipe(klass, recipe):
klass.remote.mkdir('/opt/os-cookbooks/test')
klass.remote.mkdir('/opt/os-cookbooks/test/recipes')
default_rb = klass.remote.open('/opt/os-cookbooks/test/recipes/default.rb', 'w')
default_rb.write(recipe)
default_rb.close()
klass.chef_solo({'recipes': ['test']})
def setUp(self):
self.node.restore_snapshot(self.__class__.__name__)
self.remote.reconnect()