From 3f5f1632d6d78878e4a30cfc6ea012c9bad0f674 Mon Sep 17 00:00:00 2001 From: Tony Mack Date: Mon, 7 Jan 2008 15:24:25 +0000 Subject: [PATCH] initial checkin of qa api --- qaapi/System.py | 147 +++++++ qaapi/TestAPI.py | 678 ++++++++++++++++++++++++++++++ qaapi/qa/Config.py | 20 + qaapi/qa/Makefile | 8 + qaapi/qa/QAAPI.py | 90 ++++ qaapi/qa/Test.py | 68 +++ qaapi/qa/__init__.py | 3 + qaapi/qa/logger.py | 63 +++ qaapi/qa/modules/__init__.py | 0 qaapi/qa/modules/api/__init__.py | 0 qaapi/qa/modules/plc/__init__.py | 0 qaapi/qa/modules/plc/configure.py | 37 ++ qaapi/qa/modules/plc/install.py | 34 ++ qaapi/qa/modules/plc/start.py | 15 + qaapi/qa/modules/plc/stop.py | 15 + qaapi/qa/modules/plc/uninstall.py | 23 + qaapi/qa/qa_config | 36 ++ qaapi/qa/tests/__init__.py | 0 qaapi/qa/utils.py | 7 + qaapi/qash | 108 +++++ qaapi/setup.py | 28 ++ 21 files changed, 1380 insertions(+) create mode 100644 qaapi/System.py create mode 100755 qaapi/TestAPI.py create mode 100644 qaapi/qa/Config.py create mode 100644 qaapi/qa/Makefile create mode 100644 qaapi/qa/QAAPI.py create mode 100644 qaapi/qa/Test.py create mode 100644 qaapi/qa/__init__.py create mode 100644 qaapi/qa/logger.py create mode 100644 qaapi/qa/modules/__init__.py create mode 100644 qaapi/qa/modules/api/__init__.py create mode 100644 qaapi/qa/modules/plc/__init__.py create mode 100644 qaapi/qa/modules/plc/configure.py create mode 100644 qaapi/qa/modules/plc/install.py create mode 100644 qaapi/qa/modules/plc/start.py create mode 100644 qaapi/qa/modules/plc/stop.py create mode 100644 qaapi/qa/modules/plc/uninstall.py create mode 100644 qaapi/qa/qa_config create mode 100644 qaapi/qa/tests/__init__.py create mode 100644 qaapi/qa/utils.py create mode 100755 qaapi/qash create mode 100755 qaapi/setup.py diff --git a/qaapi/System.py b/qaapi/System.py new file mode 100644 index 0000000..400a7b9 --- /dev/null +++ b/qaapi/System.py @@ -0,0 +1,147 @@ +from optparse import OptionParser +from logger import log +import utils +import os, sys +import traceback +from Config import Config + + +class System: + """ + Installs a myplc and starts the service. This is required + before any other tests can take place. + """ + + def __init__(self): + self.path=os.path.dirname(sys.argv[0]) + self.config = Config() + self.errors = [] + + def check(method, fatal = True): + """ + Check if there were errors + """ + def wrapper(*args, **kwds): + result = method(*args, **kwds) + if self.errors: + print "\n".join(self.errors) + if fatal: sys.exit(1) + return result + + @check + @log + def get_url(self): + """ + Determine current url and save it + """ + try: + if len(self.args) == 1: self.url = self.args[0] + else: + url_file=open("%s/URL"%self.path) + self.url=url_file.read().strip() + url_file.close() + + if self.options.verbose: + utils.header('Saving current myplc url into %s/URL' % self.path) + fsave=open('%s/URL'%self.path,"w") + fsave.write(url+'\n') + fsave.close() + except: + self.errors = ["Cannot determine myplc url"] + self.parser.print_help() + raise + + @check + @log + def install_plc(self): + """ + Install the myplc + """ + if self.options.verbose: + utils.header("Installing myplc from url %s" % self.url) + (stdin, stdout, stderr) = os.popen3('set -x; rpm -Uvh ' + self.url) + self.errors = stderr.readlines() + if self.errors: raise "\n".join(self.errors) + + + @check + @log + def config_plc(self): + """ + Configure the plc + """ + tmpname='/tmp/plc-config-tty-%d'%os.getpid() + fileconf=open(tmpname,'w') + for var in [ 'PLC_NAME', + 'PLC_ROOT_PASSWORD', + 'PLC_ROOT_USER', + 'PLC_MAIL_ENABLED', + 'PLC_MAIL_SUPPORT_ADDRESS', + 'PLC_DB_HOST', + 'PLC_API_HOST', + 'PLC_WWW_HOST', + 'PLC_BOOT_HOST', + 'PLC_NET_DNS1', + 'PLC_NET_DNS2']: + fileconf.write ('e %s\n%s\n'%(var,config[var])) + fileconf.write('w\nq\n') + fileconf.close() + + if self.options.verbose: os.system('set -x ; cat %s'%tmpname) + (stdin, stdout, stderr) = \ + os.popen3('set -x ; chroot /plc/root plc-config-tty < %s'%tmpname) + self.errors = stderr.readlines() + if self.errors: raise "\n".join(self.errors) + os.system('set -x; rm %s'%tmpname) + + + @check + @log + def start_plc(self): + """ + Start the plc service + """ + (stdin, stdout, stderr) = os.popen3('set -x ; service plc start') + self.errors = stderr.readlines() + if self.errors: raise "\n".join(self.errors) + + @check + @log + def remove_plc(self): + """ + Completely remove all traces of myplc install + """ + if self.options.verbose: + utils.header("Removing myplc") + (stdin, stdout, stderr) = os.popen3('set -x; service plc safestop') + self.errors = stderr.readlines() + (stdin, stdout, stderr) = os.popen3('set -x; rpm -e myplc') + self.errors.extend(stderr.readlines()) + if self.errors: raise "\n".join(self.errors) + (stdin, stdout, stderr) = os.popen3('set -x; rm -rf /plc/data') + self.errors.extend(stderr.readlines()) + if self.errors: raise "\n".join(self.errors) + + def run(self): + try: + usage = """usage: %prog [options] [myplc-url] + myplc-url defaults to the last value used, + as stored in URL""" + self.parser=OptionParser(usage=usage) + + #parser.add_option("-d","--display", action="store", dest="Xdisplay", default='bellami:0.0', + # help="sets DISPLAY for vmplayer") + self.parser.add_option("-v","--verbose", action="store_true", dest="verbose", default=False, + help="Run in verbose mode") + (self.options, self.args) = self.parser.parse_args() + + self.get_url() + self.install_plc() + + except: + pass + + + +if __name__ == 'main': + System().run() diff --git a/qaapi/TestAPI.py b/qaapi/TestAPI.py new file mode 100755 index 0000000..bf1b80e --- /dev/null +++ b/qaapi/TestAPI.py @@ -0,0 +1,678 @@ +#!/usr/bin/python +# +# Test script example +# +# Mark Huang +# Copyright (C) 2006 The Trustees of Princeton University +# +# $Id: Test.py,v 1.18 2007/01/09 16:22:49 mlhuang Exp $ +# + +from pprint import pprint +from string import letters, digits, punctuation +from traceback import print_exc +from optparse import OptionParser +import base64 +import os +import socket +import xmlrpclib + +from Config import Config +from logger import log +from random import Random +random = Random() + +config = Config() +api = config.api +auth = api.auth + +boot_states = api.GetBootStates(auth) +roles = [role['name'] for role in api.GetRoles(auth)] +methods = api.GetNetworkMethods(auth) +types = api.GetNetworkTypes(auth) + +#ifrom PLC.Shell import Shell +#shell = Shell(globals()) + +def randfloat(min = 0.0, max = 1.0): + return float(min) + (random.random() * (float(max) - float(min))) + +def randint(min = 0, max = 1): + return int(randfloat(min, max + 1)) + +# See "2.2 Characters" in the XML specification: +# +# #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] +# avoiding +# [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF] + +ascii_xml_chars = map(unichr, [0x9, 0xA, 0xD]) +ascii_xml_chars += map(unichr, xrange(0x20, 0x7F - 1)) +low_xml_chars = list(ascii_xml_chars) +low_xml_chars += map(unichr, xrange(0x84 + 1, 0x86 - 1)) +low_xml_chars += map(unichr, xrange(0x9F + 1, 0xFF)) +valid_xml_chars = list(low_xml_chars) +valid_xml_chars += map(unichr, xrange(0xFF + 1, 0xD7FF)) +valid_xml_chars += map(unichr, xrange(0xE000, 0xFDD0 - 1)) +valid_xml_chars += map(unichr, xrange(0xFDDF + 1, 0xFFFD)) + + +def randstr(length, pool = valid_xml_chars, encoding = "utf-8"): + sample = random.sample(pool, min(length, len(pool))) + while True: + s = u''.join(sample) + bytes = len(s.encode(encoding)) + if bytes > length: + sample.pop() + elif bytes < length: + sample += random.sample(pool, min(length - bytes, len(pool))) + random.shuffle(sample) + else: + break + return s + +def randhostname(): + # 1. Each part begins and ends with a letter or number. + # 2. Each part except the last can contain letters, numbers, or hyphens. + # 3. Each part is between 1 and 64 characters, including the trailing dot. + # 4. At least two parts. + # 5. Last part can only contain between 2 and 6 letters. + hostname = 'a' + randstr(61, letters + digits + '-') + '1.' + \ + 'b' + randstr(61, letters + digits + '-') + '2.' + \ + 'c' + randstr(5, letters) + return hostname + +def randpath(length): + parts = [] + for i in range(randint(1, 10)): + parts.append(randstr(randint(1, 30), ascii_xml_chars)) + return os.sep.join(parts)[0:length] + +def randemail(): + return (randstr(100, letters + digits) + "@" + randhostname()).lower() + +def randkey(bits = 2048): + key_types = ["ssh-dss", "ssh-rsa"] + key_type = random.sample(key_types, 1)[0] + return ' '.join([key_type, + base64.b64encode(''.join(randstr(bits / 8).encode("utf-8"))), + randemail()]) + +def random_site(): + return { + 'name': randstr(254), + 'abbreviated_name': randstr(50), + 'login_base': randstr(20, letters).lower(), + 'latitude': int(randfloat(-90.0, 90.0) * 1000) / 1000.0, + 'longitude': int(randfloat(-180.0, 180.0) * 1000) / 1000.0, + } + +def random_address_type(): + return { + 'name': randstr(20), + 'description': randstr(254), + } + +def random_address(): + return { + 'line1': randstr(254), + 'line2': randstr(254), + 'line3': randstr(254), + 'city': randstr(254), + 'state': randstr(254), + 'postalcode': randstr(64), + 'country': randstr(128), + } + +def random_person(): + return { + 'first_name': randstr(128), + 'last_name': randstr(128), + 'email': randemail(), + 'bio': randstr(254), + # Accounts are disabled by default + 'enabled': False, + 'password': randstr(254), + } + +def random_key(): + return { + 'key_type': random.sample(key_types, 1)[0], + 'key': randkey() + } + +def random_slice(): + return { + 'name': site['login_base'] + "_" + randstr(11, letters).lower(), + 'url': "http://" + randhostname() + "/", + 'description': randstr(2048), + } + +def random_nodegroup(): + return { + 'name': randstr(50), + 'description': randstr(200), + } + +def random_node(): + return { + 'hostname': randhostname(), + 'boot_state': random.sample(boot_states, 1)[0], + 'model': randstr(255), + 'version': randstr(64), + } + +def random_nodenetwork(): + nodenetwork_fields = { + 'method': random.sample(methods, 1)[0], + 'type': random.sample(types, 1)[0], + 'bwlimit': randint(500000, 10000000), + } + + if method != 'dhcp': + ip = randint(0, 0xffffffff) + netmask = (0xffffffff << randint(2, 31)) & 0xffffffff + network = ip & netmask + broadcast = ((ip & netmask) | ~netmask) & 0xffffffff + gateway = randint(network + 1, broadcast - 1) + dns1 = randint(0, 0xffffffff) + + for field in 'ip', 'netmask', 'network', 'broadcast', 'gateway', 'dns1': + nodenetwork_fields[field] = socket.inet_ntoa(struct.pack('>L', locals()[field])) + + return nodenetwork_fields + +def random_pcu(): + return { + 'hostname': randhostname(), + 'ip': socket.inet_ntoa(struct.pack('>L', randint(0, 0xffffffff))), + 'protocol': randstr(16), + 'username': randstr(254), + 'password': randstr(254), + 'notes': randstr(254), + 'model': randstr(32), + } + +def random_conf_file(): + return { + 'enabled': bool(randint()), + 'source': randpath(255), + 'dest': randpath(255), + 'file_permissions': "%#o" % randint(0, 512), + 'file_owner': randstr(32, letters + '_' + digits), + 'file_group': randstr(32, letters + '_' + digits), + 'preinstall_cmd': randpath(100), + 'postinstall_cmd': randpath(100), + 'error_cmd': randpath(100), + 'ignore_cmd_errors': bool(randint()), + 'always_update': bool(randint()), + } + +def random_attribute_type(): + return { + 'name': randstr(100), + 'description': randstr(254), + 'min_role_id': random.sample(roles.values(), 1)[0], + } + +def isequal(object_fields, expected_fields): + for field in expected_fields: + assert field in object_fields + assert object_fields[field] == expected_fields[field] + +def islistequal(list1, list2): + assert set(list1) == set(list2) + +def isunique(id, id_list): + assert id not in id_list + +def get_methods(self, prefix): + method_list = filter(lambda name: name.startswith(prefix), self.methods) + + + +tests = [ + # test_type: (Method, arguments, method_result_holder, number_to_add) + {'add': (api.AddSite, random_site(), site_ids, 3), + 'add_check': (isunique, + + ] + +class Entity: + """ + Template class for testing Add methods + """ + def __init__(self, auth, add_method, update_method, get_method, delete_method, primary_key): + + self.methods_tested = [] + for method in [add_method, update_method, get_method, delete_method]: + self.methods_tested.append(method._Method_name) + + self.auth = auth + self.Add = log(add_method) + self.Update = log(update_method) + self.Get = log(get_method) + self.Delete = log(delete_method) + self.primary_key = primary_key + self.object_ids = [] + + def test(self, args_method, num): + + self.object_ids = [] + + for i in range(num): + # Add object + object_fields = args_method() + object_id = self.Add(self.auth, object_fields) + + # Should return a unique id + AddCheck = log(isunique, 'Unique Check') + AddCheck(object_id, object_ids) + self.object_ids.extend([object_id]) + + # Check object + object = slef.Get(self.auth, [object_id])[0] + CheckObject = log(isequal, 'Add Check') + CheckObject(object, object_fields) + + # Update object + object_fields = args_method() + self.Update(self.auth, object_id, object_fields) + + # Check again + object = self.Get(self.auth, [object_id])[0] + CheckObject = log(isqeual, 'Update Check') + CheckSite(object, object_fields) + + # Check Get all sites + objects = self.Get(self.auth, object_ids) + CheckObjects = log(islistqual, 'Get Check') + CheckObjects(object_ids, [object[self.primary_key] for object in objects]) + + return self.object_ids + + def cleanup(self): + # Delete objects + for object_id in sefl.object_ids: + self.Delete(self.auth, object_id) + + # Check if objects are deleted + CheckObjects = log(islistequal, 'Delete Check') + ChecObjects(api.Get(auth, self.object_ids), []) + + + +class Test: + def __init__(self, config, verbose = True): + self.check = True + self.verbose = verbose + self.methods = set(api.system.listMethods()) + self.methods_tested = set() + + self.site_ids = [] + self.address_type_ids = [] + self.address_ids = [] + self.person_ids = [] + + + def run(self, + sites = 1, + address_types = 3, + addresses = 2, + persons = 1000, + keys = 3): + try: + try: + site_test = Entity(auth, api.AddSite, api.UpdateSite, api.GetSite, api.DeleteSite, 'site_id') site_test.test(random_site, sites) + + self.AddressTypes(address_types) + self.AddAddresses(addresses) + self.AddPersons(persons) + except: + print_exc() + finally: + for method in set(self.methods).difference(self.methoods_tested): + print >> test_log, "%(method)s [Not Tested]" % locals() + + def cleanup(self): + self.DeletePersons() + self.DeleteAddresses() + self.DeleteAddressTypes() + self.DeleteSites() + + + + def Sites(self, n = 1): + """ + Add and Modify a random site. + """ + + for i in range(n): + # Add site + site_fields = random_site() + AddSite = log(api.AddSite) + site_id = AddSite(auth, site_fields) + self.methods_tested.update(['AddSite']) + + # Should return a unique site_id + CheckSite = log(isunique, 'Unique Check') + CheckSite(site_id, self.site_ids) + self.site_ids.append(site_id) + + # Check site + GetSites = log(api.GetSites) + site = GetSites(auth, [site_id])[0] + CheckSite = log(isequal, 'AddSite Check') + CheckSite(site, site_fields) + self.methods_tested.update(['GetSites']) + + # Update site + site_fields = random_site() + # XXX Currently cannot change login_base + del site_fields['login_base'] + site_fields['max_slices'] = randint(1, 10) + UpdateSite = log(api.UpdateSite) + UpdateSite(auth, site_id, site_fields) + self.methods_tested.update(['UpdateSite']) + + # Check site again + site = GetSites(auth, [site_id])[0] + CheckSite = log(isequal, 'UpdateSite Check') + CheckSite(site, site_fields) + + + # Check Get all sites + sites = GetSites(auth, self.site_ids) + CheckSite = log(islistequal, 'GetSites Check') + CheckSite(self.site_ids, [site['site_id'] for site in sites]) + + if self.verbose: + print "Added sites", self.site_ids + + def DeleteSites(self): + """ + Delete any random sites we may have added. + """ + # Delete all sites + DeleteSite = log(api.DeleteSite) + for site_id in self.site_ids: + DeleteSite(auth, site_id) + self.methods_tested.update(['DeleteSite']) + + # Check if sites are deleted + CheckSite = log(islistequal, 'DeleteSite Check') + CheckSite(api.GetSites(auth, self.site_ids), []) + + if self.verbose: + print "Deleted sites", self.site_ids + + self.site_ids = [] + + def AddAddressTypes(self, n = 3): + """ + Add a number of random address types. + """ + + for i in range(n): + address_type_fields = random_address_type() + AddAddressType = log(api.AddAddressType) + address_type_id = AddAddressType(auth, address_type_fields) + self.methods_tested.update(['AddAddressType']) + + # Should return a unique address_type_id + CheckAddressType = log(isunique, 'Unique Check') + CheckAddressType(address_type_id, self.address_type_ids) + self.address_type_ids.append(address_type_id) + + # Check address type + GetAddressTypes = log(api.GetAddressTypes) + address_type = GetAddressTypes(auth, [address_type_id])[0] + CheckAddressType = log(isequal, 'AddAddressType Check') + CheckAddressType(address_type, address_type_fields) + + # Update address type + address_type_fields = random_address_type() + UpdateAddressType = log(api.UpdateAddressType) + UpdateAddressType(auth, address_type_id, address_type_fields) + + # Check address type again + address_type = GetAddressTypes([address_type_id])[0] + CheckAddressType = log(isequal, 'UpdateAddressType Check') + CheckAddressType(address_type, address_type_fields) + self.methods_tested.update(['UpdateAddressType']) + + # Check get all address types + address_types = GetAddressTypes(auth, self.address_type_ids) + CheckAddressType = log(islistequal, 'GetAddressType Check') + CheckAddressType(self.address_type_ids, + [address_type['address_type_id' for address_type in address_types]) + self.methods_tested.update(['GetAddressTypes']) + + if self.verbose: + print "Added address types", self.address_type_ids + + def DeleteAddressTypes(self): + """ + Delete any random address types we may have added. + """ + + DeleteAddressType = log(api.DeleteAddressType) + for address_type_id in self.address_type_ids: + DeleteAddressType(auth, address_type_id) + self.methods_tested.update(['DeleteAddressType']) + + CheckAddressType = log(islistequal, 'DeleteAddressType Check') + CheckAddressType(api.GetAddressTypes(auth, self.address_type_ids), []) + + if self.verbose: + print "Deleted address types", self.address_type_ids + + self.address_type_ids = [] + + def AddAddresses(self, n = 3): + """ + Add a number of random addresses to each site. + """ + + for site_id in self.site_ids: + for i in range(n): + address_fields = random_address() + address_id = AddSiteAddress(site_id, address_fields) + + # Should return a unique address_id + assert address_id not in self.address_ids + self.address_ids.append(address_id) + + if self.check: + # Check address + address = GetAddresses([address_id])[0] + for field in address_fields: + assert address[field] == address_fields[field] + + # Update address + address_fields = random_address() + UpdateAddress(address_id, address_fields) + + # Check address again + address = GetAddresses([address_id])[0] + for field in address_fields: + assert address[field] == address_fields[field] + + # Add address types + for address_type_id in self.address_type_ids: + AddAddressTypeToAddress(address_type_id, address_id) + + if self.check: + addresses = GetAddresses(self.address_ids) + assert set(self.address_ids) == set([address['address_id'] for address in addresses]) + for address in addresses: + assert set(self.address_type_ids) == set(address['address_type_ids']) + + if self.verbose: + print "Added addresses", self.address_ids + + def DeleteAddresses(self): + """ + Delete any random addresses we may have added. + """ + + # Delete site addresses + for address_id in self.address_ids: + # Remove address types + for address_type_id in self.address_type_ids: + DeleteAddressTypeFromAddress(address_type_id, address_id) + + if self.check: + address = GetAddresses([address_id])[0] + assert not address['address_type_ids'] + + DeleteAddress(address_id) + if self.check: + assert not GetAddresses([address_id]) + + if self.check: + assert not GetAddresses(self.address_ids) + + if self.verbose: + print "Deleted addresses", self.address_ids + + self.address_ids = [] + + def AddPersons(self, n = 3): + """ + Add a number of random users to each site. + """ + + roles = GetRoles() + role_ids = [role['role_id'] for role in roles] + roles = [role['name'] for role in roles] + roles = dict(zip(roles, role_ids)) + + for i in range(n): + + # Add account + person_fields = random_person() + person_id = AddPerson(person_fields) + + # Should return a unique person_id + assert person_id not in self.person_ids + self.person_ids.append(person_id) + + if self.check: + # Check account + person = GetPersons([person_id])[0] + for field in person_fields: + if field != 'password': + assert person[field] == person_fields[field] + + # Update account + person_fields = random_person() + UpdatePerson(person_id, person_fields) + + # Check account again + person = GetPersons([person_id])[0] + for field in person_fields: + if field != 'password': + assert person[field] == person_fields[field] + + auth = {'AuthMethod': "password", + 'Username': person_fields['email'], + 'AuthString': person_fields['password']} + + if self.check: + # Check that account is disabled + try: + assert not AuthCheck(auth) + except: + pass + + # Add random set of roles + person_roles = random.sample(['user', 'pi', 'tech'], randint(1, 3)) + for person_role in person_roles: + role_id = roles[person_role] + AddRoleToPerson(role_id, person_id) + + if self.check: + person = GetPersons([person_id])[0] + assert set(person_roles) == set(person['roles']) + + # Enable account + UpdatePerson(person_id, {'enabled': True}) + + if self.check: + # Check that account is enabled + assert AuthCheck(auth) + + # Associate account with random set of sites + person_site_ids = [] + for site_id in random.sample(self.site_ids, randint(1, len(self.site_ids))): + AddPersonToSite(person_id, site_id) + person_site_ids.append(site_id) + + if self.check: + # Make sure it really did it + person = GetPersons([person_id])[0] + assert set(person_site_ids) == set(person['site_ids']) + + # Set a primary site + primary_site_id = random.sample(person_site_ids, randint(1, len(person_site_ids)))[0] + SetPersonPrimarySite(person_id, primary_site_id) + + if self.check: + person = GetPersons([person_id])[0] + assert person['site_ids'][0] == primary_site_id + + if self.verbose: + print "Added users", self.person_ids + + def DeletePersons(self): + # Delete users + for person_id in self.person_ids: + # Remove from each site + for site_id in self.site_ids: + DeletePersonFromSite(person_id, site_id) + + if self.check: + person = GetPersons([person_id])[0] + assert not person['site_ids'] + + # Revoke roles + person = GetPersons([person_id])[0] + for role_id in person['role_ids']: + DeleteRoleFromPerson(role_id, person_id) + + if self.check: + person = GetPersons([person_id])[0] + assert not person['role_ids'] + + # Disable account + UpdatePerson(person_id, {'enabled': False}) + + if self.check: + person = GetPersons([person_id])[0] + assert not person['enabled'] + + # Delete account + DeletePerson(person_id) + + if self.check: + assert not GetPersons([person_id]) + + if self.check: + assert not GetPersons(self.person_ids) + + if self.verbose: + print "Deleted users", self.person_ids + + self.person_ids = [] + +if __name__ == "__main__": + parser = OptionParser() + parser.add_option("-c", "--check", action = "store_true", default = False, help = "Verify actions (default: %default)") + parser.add_option("-q", "--quiet", action = "store_true", default = False, help = "Be quiet (default: %default)") + parser.add_option("-p", "--populate", action = "store_true", default = False, help = "Do not cleanup (default: %default)") + (options, args) = parser.parse_args() + test = Test(check = options.check, verbose = not options.quiet) + test.run() + if not options.populate: + test.cleanup() diff --git a/qaapi/qa/Config.py b/qaapi/qa/Config.py new file mode 100644 index 0000000..d230d51 --- /dev/null +++ b/qaapi/qa/Config.py @@ -0,0 +1,20 @@ +import xmlrpclib +import os + +class Config: + + path = os.path.dirname(os.path.abspath(__file__)) + def __init__(self, config_file = path+os.sep+'qa_config'): + try: + execfile(config_file, self.__dict__) + except: + print __file__ + raise "Could not find system config in %s" % config_file + + self.auth = {} + self.auth['Username'] = self.PLC_ROOT_USER + self.auth['AuthString'] = self.PLC_ROOT_PASSWORD + self.auth['AuthMethod'] = 'password' + self.api = xmlrpclib.Server('https://%s/PLCAPI/' % self.PLC_API_HOST) + self.verbose = True + diff --git a/qaapi/qa/Makefile b/qaapi/qa/Makefile new file mode 100644 index 0000000..4510709 --- /dev/null +++ b/qaapi/qa/Makefile @@ -0,0 +1,8 @@ + +MODULES_DIR := Tests +QA_MODULES := plc api + +inits := $(foreach module, $(QA_MODULES), $(module)-init) + + + diff --git a/qaapi/qa/QAAPI.py b/qaapi/qa/QAAPI.py new file mode 100644 index 0000000..5310895 --- /dev/null +++ b/qaapi/qa/QAAPI.py @@ -0,0 +1,90 @@ +import sys, os +import traceback +import qa.modules +from qa.Config import Config +from qa.logger import log + +class QAAPI: + + modules_path = os.path.realpath(qa.modules.__path__[0]) + methods = [] + + + def __init__(self, globals = globals(), config = None, logging=None): + if config is None: self.config = Config() + else: self.config = Config(config) + + # Load methods + real_files = lambda name: not name.startswith('__init__') \ + and name.endswith('.py') + remove_ext = lambda name: name.split(".py")[0] + iterator = os.walk(self.modules_path) + (root, basenames, files) = iterator.next() + method_base = "" + self.methods.extend([method_base+file for file in map(remove_ext, filter(real_files, files))]) + for (root, dirs, files) in iterator: + parts = root.split(os.sep) + for basename in basenames: + if basename in parts: + method_base = ".".join(parts[parts.index(basename):])+"." + files = filter(real_files, files) + files = map(remove_ext, files) + self.methods.extend([method_base+file for file in files]) + + # Add methods to self and global environment + for method in self.methods: + callable = self.callable(method)(self.config) + if logging: callable = log(callable, method) + elif hasattr(self.config, 'log') and self.config.log: + callable = log(callable, method) + + class Dummy: pass + paths = method.split(".") + if len(paths) > 1: + first = paths.pop(0) + + if not hasattr(self, first): + obj = Dummy() + setattr(self, first, obj) + # Also add to global environment if specified + if globals is not None: + globals[first] = obj + + obj = getattr(self, first) + + for path in paths: + if not hasattr(obj, path): + if path == paths[-1]: + setattr(obj, path, callable) + globals[method]=obj + else: + setattr(obj, path, Dummy()) + obj = getattr(obj, path) + else: + if not hasattr(self, method): + setattr(self, method, callable) + if globals is not None: + globals[method] = callable + + + def callable(self, method): + """ + Return a new instance of the specified method. + """ + + # Look up test + if method not in self.methods: + raise Exception, "Invalid method: %s" % method + + # Get new instance of method + try: + #classname = method.split(".")[-1] + module_name = "qa.modules."+method + module = __import__(module_name, globals(), locals(), module_name) + components = module_name.split('.') + module = getattr(module, components[-1:][0]) + return module + except ImportError, AttributeError: + raise + + diff --git a/qaapi/qa/Test.py b/qaapi/qa/Test.py new file mode 100644 index 0000000..9a80345 --- /dev/null +++ b/qaapi/qa/Test.py @@ -0,0 +1,68 @@ +import utils +import sys, os +from types import * +from logger import log + + +class Test: + """ + Base class for all QA test functions. At a minimum all tests + must define: + + call(arg1, arg2, ...): method body + """ + + accepts = [] + returns = bool + + + def call(self, *args): + """ + Method body for test functions. Must override. + """ + return True + + def __init__(self, config): + self.name = self.__class__.__name__ + self.path=os.path.dirname(sys.argv[0]) + self.config = config + self.errors = [] + + + def __call__(self, *args, **kwds): + """ + Main entry point for test functions. logs methods + """ + + #(min_args, max_args, defaults) = self.args() + + # Check that the right number of arguments were passed in + #if len(args) < len(min_args) or len(args) > len(max_args): + # raise Exception#, (len(args), len(min_args), len(max_args)) + + result = self.call(*args, **kwds) + return result + + + def args(self): + """ + Returns a tuple: + + ((arg1_name, arg2_name, ...), + (arg1_name, arg2_name, ..., optional1_name, optional2_name, ...), + (None, None, ..., optional1_default, optional2_default, ...)) + + That represents the minimum and maximum sets of arguments that + this function accepts and the defaults for the optional arguments. + """ + + # Inspect call. Remove self from the argument list. + max_args = self.call.func_code.co_varnames[1:self.call.func_code.co_argcount] + defaults = self.call.func_defaults + if defaults is None: + defaults = () + + min_args = max_args[0:len(max_args) - len(defaults)] + defaults = tuple([None for arg in min_args]) + defaults + + return (min_args, max_args, defaults) diff --git a/qaapi/qa/__init__.py b/qaapi/qa/__init__.py new file mode 100644 index 0000000..5408724 --- /dev/null +++ b/qaapi/qa/__init__.py @@ -0,0 +1,3 @@ +all=""" +Test +""" diff --git a/qaapi/qa/logger.py b/qaapi/qa/logger.py new file mode 100644 index 0000000..1f88eca --- /dev/null +++ b/qaapi/qa/logger.py @@ -0,0 +1,63 @@ +import os +import traceback +import time + +class Logfile: + """ + Represents a logfile. Used to write data to a file. e.g. + + logfile = Logfile('filename') + print >> logfile, data + """ + def __init__(self, filename): + self.filename = filename + + def write(self, data): + try: + fd = os.open(self.filename, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0644) + os.write(fd, '%s' % data) + os.close(fd) + except OSError: + sys.stderr.write(data) + sys.stderr.flush() + + + +def log(method, method_name = None, \ + log_filename = 'system.log', errorlog_filename = 'system_error.log'): + """ + Logs whether the specified method completed successfully or not and + returns the method result. Use as a decorator, e.g., + + @log + def foo(...): + ... + + Or: + def foo(...): + ... + foo = log(foo) + + Or: + result = log(foo)(...) + + """ + logfile = Logfile(log_filename) + error_logfile = Logfile(errorlog_filename) + + if method_name is None: + method_name = method.__name__ + + def wrapper(*args, **kwds): + + print >> logfile, method_name + ": ", + try: + #print >> logfile, args, + result = method(*args, **kwds) + print >> logfile, " [OK]" + except: + print >>logfile, " [FAILED]" + print >> error_logfile, "%s: %s\n" % (method_name, traceback.format_exc()) + raise + + return wrapper diff --git a/qaapi/qa/modules/__init__.py b/qaapi/qa/modules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/qaapi/qa/modules/api/__init__.py b/qaapi/qa/modules/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/qaapi/qa/modules/plc/__init__.py b/qaapi/qa/modules/plc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/qaapi/qa/modules/plc/configure.py b/qaapi/qa/modules/plc/configure.py new file mode 100644 index 0000000..6d50303 --- /dev/null +++ b/qaapi/qa/modules/plc/configure.py @@ -0,0 +1,37 @@ + +import os, sys +import traceback +from qa.Test import Test + +class configure(Test): + """ + Configure the myplc from config options in config file + """ + + def call(self): + tmpname = '/tmp/plc-cinfig-tty-%d' % os.getpid() + fileconf = open(tmpname, 'w') + for var in [ 'PLC_NAME', + 'PLC_ROOT_PASSWORD', + 'PLC_ROOT_USER', + 'PLC_MAIL_ENABLED', + 'PLC_MAIL_SUPPORT_ADDRESS', + 'PLC_DB_HOST', + 'PLC_API_HOST', + 'PLC_WWW_HOST', + 'PLC_BOOT_HOST', + 'PLC_NET_DNS1', + 'PLC_NET_DNS2']: + fileconf.write('e %s\n%s\n' % (var, self.config[var])) + fileconf.write('w\nq\n') + fileconf.close() + + # set configuration options + if self.config.verbose: os.system('set -x ; cat %s'%tmpname) + (stdin, stdout, stderr) = \ + os.popen3('set -x ; chroot /plc/root plc-config-tty < %s'%tmpname) + self.errors = stderr.readlines() + if self.errors: raise "\n".join(self.errors) + os.system('set -x; rm %s'%tmpname) + + return 1 diff --git a/qaapi/qa/modules/plc/install.py b/qaapi/qa/modules/plc/install.py new file mode 100644 index 0000000..679ca28 --- /dev/null +++ b/qaapi/qa/modules/plc/install.py @@ -0,0 +1,34 @@ + +import os, sys +import traceback +from qa.Test import Test + + +class install(Test): + """ + Installs a myplc + """ + + def call(self, url=None): + + # Determine url + if not url: + url_file = open("%s/URL" % self.path) + url = url_file.read().strip() + url_file.close() + + # Save url + if self.config.verbose: + utils.header('Saving current myplc url into %s/URL' % self.path) + fsave=open('%s/URL' % self.path, "w") + fsave.write(url+'\n') + fsave.close() + + # Instal myplc from url + if self.config.verbose: + utils.header('Installing myplc from url %s' % url) + (stdin, stdout, stderr) = os.popen3('set -x; rpm -Uvh ' + self.url) + self.errors = stderr.readlines() + if self.errors: raise "\n".join(self.errors) + + return 1 diff --git a/qaapi/qa/modules/plc/start.py b/qaapi/qa/modules/plc/start.py new file mode 100644 index 0000000..97c3c75 --- /dev/null +++ b/qaapi/qa/modules/plc/start.py @@ -0,0 +1,15 @@ +import os, sys +import traceback +from qa.Test import Test + +class start(Test): + """ + Installs a myplc + """ + + def call(self): + (stdin, stdout, stderr) = os.popen3('set -x ; service plc start') + self.errors = stderr.readlines() + if self.errors: raise "\n".join(self.errors) + + return 1 diff --git a/qaapi/qa/modules/plc/stop.py b/qaapi/qa/modules/plc/stop.py new file mode 100644 index 0000000..9e250ca --- /dev/null +++ b/qaapi/qa/modules/plc/stop.py @@ -0,0 +1,15 @@ +import os, sys +import traceback +from qa.Test import Test + +class stop(Test): + """ + Installs a myplc + """ + + def call(self): + (stdin, stdout, stderr) = os.popen3('set -x ; service plc stop') + self.errors = stderr.readlines() + if self.errors: raise "\n".join(self.errors) + + return 1 diff --git a/qaapi/qa/modules/plc/uninstall.py b/qaapi/qa/modules/plc/uninstall.py new file mode 100644 index 0000000..fdca683 --- /dev/null +++ b/qaapi/qa/modules/plc/uninstall.py @@ -0,0 +1,23 @@ +import os, sys +import traceback +from qa.Test import Test +from qa import utils + +class uninstall(Test): + """ + Completely removes the installed myplc + """ + + def call(self): + if self.config.verbose: + utils.header("Removing myplc") + (stdin, stdout, stderr) = os.popen3('set -x; service plc safestop') + self.errors = stderr.readlines() + (stdin, stdout, stderr) = os.popen3('set -x; rpm -e myplc') + self.errors.extend(stderr.readlines()) + if self.errors: raise "\n".join(self.errors) + (stdin, stdout, stderr) = os.popen3('set -x; rm -rf /plc/data') + self.errors.extend(stderr.readlines()) + if self.errors: raise "\n".join(self.errors) + + return 1 diff --git a/qaapi/qa/qa_config b/qaapi/qa/qa_config new file mode 100644 index 0000000..61d1c2f --- /dev/null +++ b/qaapi/qa/qa_config @@ -0,0 +1,36 @@ +# system test config file + +# DISPLAY VMPlayer will use +DISPLAY=":0.0" + +HOSTNAME="localhost" + +DOMAIN_NAME="localhost.localdomain" + +#PLC_ROOT_USER="root@localhost.localdomain" +PLC_ROOT_USER="root@localhost.localdomain" + +#PLC_ROOT_PASSWORD="password" +PLC_ROOT_PASSWORD="root" + +PLC_NAME="TestLab" + +PLC_API_HOST="localhost.localdomain" + +PLC_DB_HOST="localhost.localdomain" + +PLC_WWW_HOST="localhost.localdomain" + +PLC_BOOT_HOST="localhost.localdomain" + +PLC_MAIL_ENABLED=1 + +PLC_MAIL_SUPPORT_ADDRESS="root@localhost.localdomain" + +PLC_NET_DNS1="138.96.0.10" + +PLC_NET_DNS2="138.96.0.11" + +NODE_1_HOSTNAME="hostname" + +NODE_2_HOSTNAME="hostname" diff --git a/qaapi/qa/tests/__init__.py b/qaapi/qa/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/qaapi/qa/utils.py b/qaapi/qa/utils.py new file mode 100644 index 0000000..971ff52 --- /dev/null +++ b/qaapi/qa/utils.py @@ -0,0 +1,7 @@ +# $Id$ +import time + +# how could this accept a list again ? +def header(message): + now=time.strftime("%H:%M:%S", time.localtime()) + print "*",now,'--',message diff --git a/qaapi/qash b/qaapi/qash new file mode 100755 index 0000000..3eae64f --- /dev/null +++ b/qaapi/qash @@ -0,0 +1,108 @@ +#!/usr/bin/python +# +# Interactive shell for using QA test suite + + + +import os, sys +from optparse import OptionParser +from traceback import print_exc + +import utils +import System +from qa.QAAPI import QAAPI + +sys.path.append(os.path.dirname(os.path.realpath(sys.argv[0]))) + +parser = OptionParser(add_help_option = False) +parser.add_option("-f", "--config", help = "configuration file") +parser.add_option("-l", "--log", help="enable logging") +parser.add_option("--help", action = "help", help = "show this help message and exit") +(options, args) = parser.parse_args() + +# XX Load callable tests +try: + qaapi import QAAPI(globals(), + options.config, + options.log) + +except Exception, err: + print "Error: ", err + parser.print_help() + sys.exit(1) + +# If called by a script +if len(sys.argv) > 1 and os.path.exists(sys.argv[1]): + # Pop us off the argument stack + sys.argv.pop(0) + execfile(sys.argv[0]) + +else: + prompt = "[QA]" + + # Readline and tab completion support + import atexit + import readline + import rlcompleter + + print 'Type "help" for more information.' + # Load command history + history_path = os.path.join(os.environ["HOME"], ".qa_history") + try: + file(history_path, 'a').close() + readline.read_history_file(history_path) + atexit.register(readline.write_history_file, history_path) + except IOError: + pass + + # Enable tab completion + readline.parse_and_bind("tab: complete") + + try: + while True: + command = "" + while True: + # Get line + try: + if command == "": + sep = ">>> " + else: + sep = "... " + line = raw_input(prompt + sep) + # Ctrl-C + except KeyboardInterrupt: + command = "" + print + break + + # Build up multi-line command + command += line + + # Blank line or first line does not end in : + if line == "" or (command == line and line[-1] != ':'): + break + + command += os.linesep + + # Blank line + if command == "": + continue + # Quit + elif command in ["q", "quit", "exit"]: + break + + try: + try: + # Try evaluating as an expression and printing the result + result = eval(command) + if result is not None: + print result + except SyntaxError: + # Fall back to executing as a statement + exec command + except Exception, err: + print_exc() + + except EOFError: + print + pass diff --git a/qaapi/setup.py b/qaapi/setup.py new file mode 100755 index 0000000..91da055 --- /dev/null +++ b/qaapi/setup.py @@ -0,0 +1,28 @@ +#!/usr/bin/python +# +# Setup script for PLCAPI +# +# Mark Huang +# Copyright (C) 2006 The Trustees of Princeton University +# +# $Id: setup.py 5574 2007-10-25 20:33:17Z thierry $ +# + +import os +from distutils.core import setup +from glob import glob + +path = os.getcwd() +name = 'qa' +path = path+os.sep+name +pkgs = [path] + +iterator = os.walk(path) +for (root, dirs, files) in iterator: + pkgs.extend([root+os.sep+d for d in dirs]) + +print pkgs +setup(name = name, + packages = pkgs, + scripts = ['qash'] + ) -- 2.47.0