Add discovery methods.

Add methods to discover tenants and identity region name.

Change-Id: I7e711663c80a5d181031d4c2e0c5338f12128263
Partially-implements: blueprint discover-cloud-artifacts
This commit is contained in:
ted chang 2014-03-19 15:28:45 -07:00
parent 5c05c7609a
commit cfec0cfa25
2 changed files with 184 additions and 77 deletions

View File

@ -8,13 +8,6 @@
"tempest_config":
{
"identity":
{
"region": "RegionOne",
"tenant_name": "demo",
"alt_tenant_name": "alt_demo",
"admin_tenant_name": "admin"
},
"compute":
{
"image_ref": "a8d70acb-f1c4-4171-b0ce-d73e5de21a9d",

View File

@ -32,11 +32,11 @@ class Test:
def __init__(self, args):
'''Prepare a tempest test against a cloud.'''
_format = "%(asctime)s %(name)s %(levelname)s %(message)s"
log_format = "%(asctime)s %(name)s %(levelname)s %(message)s"
if args.verbose:
logging.basicConfig(level=logging.INFO, format=_format)
logging.basicConfig(level=logging.INFO, format=log_format)
else:
logging.basicConfig(level=logging.CRITICAL, format=_format)
logging.basicConfig(level=logging.CRITICAL, format=log_format)
self.logger = logging.getLogger("execute_test")
self.app_server_address = None
@ -44,61 +44,68 @@ class Test:
if args.callback:
self.app_server_address, self.test_id = args.callback
self.extraConfDict = dict()
self.mini_conf_dict = json.loads(self.get_mini_config())
self.extra_conf_dict = dict()
if args.conf_json:
self.extraConfDict = args.conf_json
self.extra_conf_dict = args.conf_json
self.testcases = {"testcases": ["tempest"]}
if args.testcases:
self.testcases = {"testcases": args.testcases}
self.tempestHome = os.path.join(os.path.dirname(
os.path.abspath(__file__)),
'tempest')
self.TEMPEST_HOME =\
os.path.join(os.path.dirname(os.path.abspath(__file__)), 'tempest')
if args.tempest_home:
self.tempestHome = args.tempest_home
self.TEMPEST_HOME = args.tempest_home
self.sampleConfFile = os.path.join(self.tempestHome, 'etc',
'tempest.conf.sample')
self.tempestConfFile = os.path.join(self.tempestHome, 'tempest.config')
self.resultDir = os.path.join(self.tempestHome, '.testrepository')
self.result = os.path.join(self.resultDir, 'result')
self.tempestScript = os.path.join(self.tempestHome, 'run_tests.sh')
self.sampleConfParser = ConfigParser.SafeConfigParser()
self.sampleConfParser.read(self.sampleConfFile)
self.SAMPLE_CONF_FILE = os.path.join(self.TEMPEST_HOME, 'etc',
'tempest.conf.sample')
self.TEMPEST_CONF_FILE = os.path.join(self.TEMPEST_HOME,
'tempest.config')
self.RESULT_DIR = os.path.join(self.TEMPEST_HOME, '.testrepository')
self.RESULT = os.path.join(self.RESULT_DIR, 'result')
self.TEMPEST_SCRIPT = os.path.join(self.TEMPEST_HOME, 'run_tests.sh')
self.sample_conf_parser = ConfigParser.SafeConfigParser()
self.sample_conf_parser.read(self.SAMPLE_CONF_FILE)
def genConfig(self):
def gen_config(self):
'''Merge mini config, extra config, tempest.conf.sample
and write to tempest.config.
'''
self.logger.info('Generating tempest.config')
miniConfDict = json.loads(self.getMiniConfig())
self.mergeToSampleConf(miniConfDict)
self.mergeToSampleConf(self.extraConfDict)
self.sampleConfParser.write(open(self.tempestConfFile, 'w'))
self.merge_to_sample_conf(self.mini_conf_dict)
self.merge_to_sample_conf(self.extra_conf_dict)
#discovered config will not overwrite the value in the
#mini_conf_dict and extra_conf_dict
discovered_conf_dict = self._buildDiscoveredDictConf()
self._subtract_dictionaries(discovered_conf_dict, self.mini_conf_dict)
self._subtract_dictionaries(discovered_conf_dict, self.extra_conf_dict)
self.merge_to_sample_conf(discovered_conf_dict)
self.sample_conf_parser.write(open(self.TEMPEST_CONF_FILE, 'w'))
def mergeToSampleConf(self, dic):
def merge_to_sample_conf(self, dic):
'''Merge values in a dictionary to tempest.conf.sample.'''
for section, data in dic.items():
for key, value in data.items():
if self.sampleConfParser.has_option(section, key):
self.sampleConfParser.set(section, key, value)
if self.sample_conf_parser.has_option(section, key):
self.sample_conf_parser.set(section, key, value)
def getMiniConfig(self):
def get_mini_config(self):
'''Return a mini config in JSON string.'''
if self.app_server_address and self.test_id:
url = "http://%s/get-miniconf?test_id=%s" % \
(self.app_server_address, self.test_id)
try:
j = urllib2.urlopen(url=url, timeout=10)
return j.readlines()[0]
req = urllib2.urlopen(url=url, timeout=10)
return req.readlines()[0]
except:
self.logger.critical('Failed to get mini config from %s' % url)
raise
else:
return json.dumps(dict())
def getTestCases(self):
def get_test_cases(self):
'''Return list of tempest testcases in JSON string.
For certification, the list will contain only one test case.
@ -109,33 +116,34 @@ class Test:
url = "http://%s/get-testcases?test_id=%s" % \
(self.app_server_address, self.test_id)
try:
j = urllib2.urlopen(url=url, timeout=10)
return j.readlines()[0]
req = urllib2.urlopen(url=url, timeout=10)
return req.readlines()[0]
except:
self.logger.crtical('Failed to get test cases from %s' % url)
raise
else:
return json.dumps(self.testcases)
def runTestCases(self):
def run_test_cases(self):
'''Executes each test case in the testcase list.'''
#Make a backup in case previous data exists in the the directory
if os.path.exists(self.resultDir):
if os.path.exists(self.RESULT_DIR):
date = time.strftime("%m%d%H%M%S")
backupPath = os.path.join(os.path.dirname(self.resultDir),
"%s_backup_%s" %
(os.path.basename(self.resultDir), date))
backup_path = os.path.join(os.path.dirname(self.RESULT_DIR),
"%s_backup_%s" %
(os.path.basename(self.RESULT_DIR),
date))
self.logger.info("Rename existing %s to %s" %
(self.resultDir, backupPath))
os.rename(self.resultDir, backupPath)
(self.RESULT_DIR, backup_path))
os.rename(self.RESULT_DIR, backup_path)
#Execute each testcase.
testcases = json.loads(self.getTestCases())['testcases']
testcases = json.loads(self.get_test_cases())['testcases']
self.logger.info('Running test cases')
for case in testcases:
cmd = ('%s -C %s -N -- %s' %
(self.tempestScript, self.tempestConfFile, case))
(self.TEMPEST_SCRIPT, self.TEMPEST_CONF_FILE, case))
#When a testcase fails
#continue execute all remaining cases so any partial result can be
#reserved and posted later.
@ -145,57 +153,163 @@ class Test:
self.logger.error('%s %s testcases failed to complete' %
(e, case))
def postTestResult(self):
def post_test_result(self):
'''Post the combined results back to the server.'''
if self.app_server_address and self.test_id:
self.logger.info('Send back the result')
url = "http://%s/post-result?test_id=%s" % \
(self.app_server_address, self.test_id)
files = {'file': open(self.result, 'rb')}
files = {'file': open(self.RESULT, 'rb')}
try:
requests.post(url, files=files)
except:
self.logger.critical('failed to post result to %s' % url)
raise
else:
self.logger.info('Testr result can be found at %s' % (self.result))
self.logger.info('Testr result can be found at %s' % (self.RESULT))
def combineTestrResult(self):
def combine_test_result(self):
'''Generate a combined testr result.'''
r_list = [l for l in os.listdir(self.resultDir)
if fnmatch.fnmatch(l, '[0-9]*')]
r_list.sort(key=int)
with open(self.result, 'w') as outfile:
for r in r_list:
with open(os.path.join(self.resultDir, r), 'r') as infile:
testr_results = [item for item in os.listdir(self.RESULT_DIR)
if fnmatch.fnmatch(item, '[0-9]*')]
testr_results.sort(key=int)
with open(self.RESULT, 'w') as outfile:
for fp in testr_results:
with open(os.path.join(self.RESULT_DIR, fp), 'r') as infile:
outfile.write(infile.read())
self.logger.info('Combined testr result')
def run(self):
'''Execute tempest test against the cloud.'''
self.genConfig()
self.gen_config()
self.runTestCases()
self.run_test_cases()
self.combineTestrResult()
self.combine_test_result()
self.postTestResult()
self.post_test_result()
# Discover tenants and region
def _subtract_dictionaries(self, discovered_conf_dict, conf_dict):
'''Remove the tempest configs in conf_dict from discovered_conf_dict
'''
for section, data in discovered_conf_dict.items():
for key in data.keys():
if section in conf_dict and key in conf_dict[section]:
self.logger.info("user choose to overwrite the discovered "
"%s %s" % (section, key))
del discovered_conf_dict[section][key]
def _buildDiscoveredDictConf(self):
'''Return a discoverable tempest config in json string
'''
keystone_url = self.sample_conf_parser.get("identity", "uri")
#Find admin tenant name and identity region name
admin_user = self.sample_conf_parser.get("identity", "admin_username")
admin_pw = self.sample_conf_parser.get("identity", "admin_password")
token_id = json.loads(self.get_keystone_token(url=keystone_url +
"/tokens",
user=admin_user,
password=admin_pw)
)["access"]["token"]["id"]
'''TODO: Authenticate as "admin" (public URL) against each tenant found
in tanantList until a tenant is found on which "admin" has
"admin"role. For now, assuming admin user ONLY belones to admin tenant
and the admin has admin role as defined in
tempest.sample.conf.identiy.admin_role
'''
admin_tenant = self.get_tenants(keystone_url + "/tenants",
token_id)[0]["name"]
'''
TODO:The admin_token will be used to authenticate glance
image discovery
'''
admin_token = json.loads(self.get_keystone_token
(url=keystone_url + "/tokens",
user=admin_user,
password=admin_pw,
tenant=admin_tenant))
identity_region =\
[service["endpoints"][0]["region"]
for service in admin_token["access"]["serviceCatalog"]
if service["type"] == "identity"][0]
#Find user tenant name
user = self.sample_conf_parser.get("identity", "username")
pw = self.sample_conf_parser.get("identity", "password")
token_id = json.loads(self.get_keystone_token(url=keystone_url +
"/tokens",
user=user,
password=pw)
)["access"]["token"]["id"]
tenant = self.get_tenants(keystone_url + "/tenants",
token_id)[0]["name"]
#Find alter tenant name
alt_user = self.sample_conf_parser.get("identity", "alt_username")
alt_pw = self.sample_conf_parser.get("identity", "alt_password")
token_id = json.loads(self.get_keystone_token(url=keystone_url +
"/tokens",
user=alt_user,
password=alt_pw)
)["access"]["token"]["id"]
alt_tenant = self.get_tenants(keystone_url + "/tenants",
token_id)[0]["name"]
discovered_dict_conf = {"identity": {"region": identity_region,
"admin_tenant_name": admin_tenant,
"tenant_name": tenant,
"alt_tenant_name": alt_tenant}
}
return discovered_dict_conf
def get_keystone_token(self, url, user, password, tenant=None):
''' Returns a json response from keystone tokens API call '''
parameter = {"auth": {"tenantName": tenant,
"passwordCredentials":
{"username": user,
"password": password}
}
}
header = {"Content-type": "application/json"}
try:
req = requests.post(url, data=json.dumps(parameter),
headers=header)
except:
self.logger.critical("failed to get a Keystone token %s %s %s"
(url, user, tenant))
raise
return req.content
def get_tenants(self, url, token):
'''Return a list of tenant(s) a token has access to'''
headers = {"Content-type": "application/json",
"X-Auth-Token": token}
try:
req = requests.get(url, headers=headers)
except:
self.logger.critical("failed to get tenant for %s %s" %
(url, token))
return json.loads(req.content)["tenants"]
''' TODO: The remaining methods are for image discovery. '''
def createImage(self):
def create_image(self):
'''Download and create cirros image.
Return the image reference id
'''
pass
def findSmallestFlavor(self):
def find_smallest_flavor(self):
'''Find the smallest flavor by sorting by memory size.
'''
pass
def deleteImage(self):
def delete_image(self):
'''Delete a image.
'''
pass
@ -206,24 +320,24 @@ if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Starts a tempest test',
formatter_class=argparse.
ArgumentDefaultsHelpFormatter)
conflictGroup = parser.add_mutually_exclusive_group()
conflict_group = parser.add_mutually_exclusive_group()
conflictGroup.add_argument("--callback",
nargs=2,
metavar=("APP_SERVER_ADDRESS", "TEST_ID"),
type=str,
help="refstack API IP address and test ID to\
retrieve configurations. i.e.:\
--callback 127.0.0.1:8000 1234")
conflict_group.add_argument("--callback",
nargs=2,
metavar=("APP_SERVER_ADDRESS", "TEST_ID"),
type=str,
help="refstack API IP address and test ID to\
retrieve configurations. i.e.:\
--callback 127.0.0.1:8000 1234")
parser.add_argument("--tempest-home",
help="tempest directory path")
#with nargs, arguments are returned as a list
conflictGroup.add_argument("--testcases",
nargs='+',
help="tempest test cases. Use space to separate\
each testcase")
conflict_group.add_argument("--testcases",
nargs='+',
help="tempest test cases. Use space to\
separate each testcase")
'''
TODO: May need to decrypt/encrypt password in args.JSON_CONF
'''