def service_stop(self, service): """ stop service """ logFull('CeServices:service_stop') rc = self.service_status(service) if not rc: logDebug('SM: Service name `{}` is not running.'.format(service['name'])) return False tprocess = service.get('pid', 0) if isinstance(tprocess, int): logError('SM: Cannot stop service `{}`!'.format(service['name'])) try: tprocess.terminate() except Exception as exp_err: logError('SM: Cannot stop service: `{}`, exception `{}`!'.format(service['name'], exp_err)) return False try: time.sleep(0.1) os.killpg(tprocess.pid, signal.SIGTERM) time.sleep(0.1) tprocess.kill() except: pass logWarning('SM: Stopped service: `{}`.'.format(service['name'])) return True
def reset_user(self, user): """ clean user related information """ logFull('CeWebUi:reset_user user `{}`.'.format(user)) self.project.reset_project(user) self.project.reset_logs(user) raise cherrypy.HTTPRedirect('http://{host}/web/users/{user}'.\ format(host=cherrypy.request.headers['Host'], user=user))
def list_services(self): """ return list of services """ logFull('CeServices:list_services') srv = [] for service in self.twister_services: srv.append(service['name']) return ','.join(srv)
def get_binding(self, fpath): """ Read a binding between a CFG and a SUT. The result is XML. """ logFull('xmlparser:get_binding') cfg_file = '{}/twister/config/bindings.xml'.format(userHome(self.user)) if not os.path.isfile(cfg_file): err = '*ERROR* Bindings Config file `{}`, for user `{}` does not exist!'.format(cfg_file, self.user) logError(err) return err bind_xml = parseXML(self.user, cfg_file) if bind_xml is None: err = '*ERROR* Config file `{}`, for user `{}` cannot be parsed!'.format(cfg_file, self.user) return err # Find the old binding found = bind_xml.xpath('/root/binding/name[text()="{}"]/..'.format(fpath)) if found: xml_string = etree.tostring(found[0]) return xml_string.replace('binding>', 'root>') else: logWarning('*ERROR* Cannot find binding name `{}` for user {}!'.format(fpath, self.user)) return False
def get_resource(self, query, resource=None): """ Get the resource from resource by path or id. We can search for query in self.resources or in any other resource given as parameter. """ logFull("CeCommonAllocator: Getting the resource {}".format(query)) if not resource: resource = self.resources if not resource or not query: msg = 'Cannot get a null resource {} or a new query {}!'.format( resource, query) logError(msg) return False if ':' in query: query = query.split(':')[0] # If the query is an ID if '/' not in query: result = self.get_id(query, resource) else: result = self.get_path(query, resource) if result: return dict(result) return result
def getBindingsConfig(self): """ Parse the bindings file that connects Roots from a config file, with SUTs. """ logFull('xmlparser:getBindingsConfig') cfg_file = '{}/twister/config/bindings.xml'.format(userHome(self.user)) bindings = {} if not os.path.isfile(cfg_file): err = '*ERROR* Bindings Config file `{}`, for user `{}` does not exist!'.format(cfg_file, self.user) logError(err) return err bind_xml = parseXML(self.user, cfg_file) if bind_xml is None: err = '*ERROR* Config file `{}`, for user `{}` cannot be parsed!'.format(cfg_file, self.user) return err for binding in bind_xml.xpath('/root/binding'): name = binding.find('name') # Valid names ? if name is None: continue name = name.text.strip() if not name: continue bindings[name] = {} # All binds cfg -> sut for bind in binding.findall('bind'): cfg = bind.get('config') sut = bind.get('sut') bindings[name][cfg] = sut logDebug('Found `{}` bindings for user `{}`.'.format(len(bindings), self.user)) return bindings
def json_save_project(self, user, epname): """ save project """ logFull('CeWebUi:json_save_project user `{}`.'.format(user)) if self.user_agent() == 'x': return 0 c_l = cherrypy.request.headers['Content-Length'] raw_data = cherrypy.request.body.read(int(c_l)) json_data = json.loads(raw_data) del c_l, raw_data # Delete everything from XML Root self.project.del_settings_key(user, 'project', '//TestSuite') changes = 'Reset project file...\n' for suite_data in json_data: self.project.set_persistent_suite(user, suite_data['data'], {'ep': decode(epname)}) changes += 'Created suite: {0}.\n'.format(suite_data['data']) for file_data in suite_data.get('children', []): changes += 'Created file: {0}.\n'.format(file_data['data']) self.project.set_persistent_file(user, suite_data['data'], file_data['data'], {}) changes += '>.<\n' logDebug(changes) return 'true'
def save_release_reserved_tb(self, res_query, props={}): """ Save the changes. Sync self.resources with self.reserved_resources and save to the disk """ logDebug('CeTestBeds:save_release_reserved_tb {} {}'.format(res_query, props)) # save changes result = self.save_reserved_tb(res_query, props) if result and not result.startswith("*ERROR*"): user_info = self.user_info(props) if ':' in res_query: res_query = res_query.split(':')[0] # get only the component resource_node = self.get_resource(res_query) if not resource_node or isinstance(resource_node, str): logFull("Can not find the resoruce {}".format(res_query)) return None # get the entire TB if len(resource_node['path']) > 1: resource_node = self.get_path(resource_node['path'][0], self.resources) # delete this entry from reservedResources reserved_node = self.reservedResources[user_info[0]][resource_node['id']] self.reservedResources[user_info[0]].pop(reserved_node['id']) if not self.reservedResources[user_info[0]]: self.reservedResources.pop(user_info[0]) else: return result return True
def setFileOwner(user, path): """ Update file ownership for 1 file or folder.\n `Chown` function works ONLY in Linux. """ logFull('helpers:setFileOwner user `{}`.'.format(user)) try: from pwd import getpwnam uid = getpwnam(user)[2] gid = getpwnam(user)[3] except: return False if os.path.isdir(path): try: proc = subprocess.Popen( ['chown', str(uid) + ':' + str(gid), path, '-R', '--silent'], ) proc.wait() except: logWarning( 'Cannot change owner! Cannot chown folder `{}:{}` on `{} -R`!'. format(uid, gid, path)) return False else: try: os.chown(path, uid, gid) except: logWarning( 'Cannot set owner! Cannot chown file `{}:{}` on `{}`!'.format( uid, gid, path)) return False return True
def users(self, user=''): """ list all users """ logFull('CeWebUi:users') if self.user_agent() == 'x': return 0 if not user: raise cherrypy.HTTPRedirect('/web/#tab_users') host = cherrypy.request.headers['Host'] rev_dict = dict((v, k) for k, v in EXEC_STATUS.iteritems()) int_status = self.project.get_user_info(user, 'status') or STATUS_INVALID status = rev_dict[int_status] try: eps_file = self.project.parsers[user].project_globals['ep_names'] except: eps_file = '' eps = self.project.get_user_info(user, 'eps') ep_statuses = [rev_dict[eps[ep].get('status', STATUS_INVALID)] \ for ep in eps] logs = self.project.get_user_info(user, 'log_types') output = Template(filename=TWISTER_PATH + '/server/template/rest_user.htm') return output.render(host=host, user=user, status=status,\ exec_status=rev_dict, eps_file=eps_file, eps=eps,\ ep_statuses=ep_statuses, logs=logs)
def del_binding(self, fpath): """ Delete a binding between a CFG and a SUT. Return True/ False. """ logFull('xmlparser:del_binding') cfg_file = '{}/twister/config/bindings.xml'.format(userHome(self.user)) if not os.path.isfile(cfg_file): err = '*ERROR* Bindings Config file `{}`, for user `{}` does not exist!'.format(cfg_file, self.user) logError(err) return err bind_xml = parseXML(self.user, cfg_file) if bind_xml is None: err = '*ERROR* Config file `{}`, for user `{}` cannot be parsed!'.format(cfg_file, self.user) return err # Find the binding found = bind_xml.xpath('/root/binding/name[text()="{}"]/..'.format(fpath)) # If found, delete it if found: bind_xml.remove(found[0]) logDebug('Removed binding `{}`, for user `{}`.'.format(fpath, self.user)) else: err = '*WARN* Invalid binding `{}`, user `{}`! Cannot unbind!'.format(fpath, self.user) # logDebug(err) return False return dumpXML(self.user, cfg_file, bind_xml)
def getFileInfo(self, file_soup): """ Returns a dict with information about 1 File from Test-Suites XML. The "file" must be a XML class. """ logFull('xmlparser:getFileInfo') res = OrderedDict() res['type'] = 'file' # Get File ID from testsuites.xml res['id'] = file_soup.xpath('ID')[0].text # The second parameter is the Suite name res['suite'] = file_soup.getparent().xpath('id')[0].text # Parse all known File Tags for tag_dict in TESTS_TAGS: # Create default entry res[tag_dict['name']] = tag_dict['default'] # Exception for config files if tag_dict['name'] == '_cfg_files': cfg_files = [] for cfg_soup in file_soup.xpath(tag_dict['tag']): if cfg_soup.get('enabled').lower() == 'true': cfg = { 'name': cfg_soup.get('name'), 'iter_default': cfg_soup.get('iterator_default'), 'iter_sof': cfg_soup.get('iterator_sof') } cfg_files.append(cfg) if cfg_files: res[tag_dict['name']] = cfg_files # Update value from XML elif file_soup.xpath(tag_dict['tag'] + '/text()'): value = file_soup.xpath(tag_dict['tag'])[0].text if not value.strip(): continue res[tag_dict['name']] = value # Inject this empty variable res['twister_tc_revision'] = '-1' # Add property/ value tags prop_keys = file_soup.xpath('Property/propName') prop_vals = file_soup.xpath('Property/propValue') params = '' # The order of the properties is important! for i in range(len(prop_keys)): p_key = prop_keys[i].text p_val = prop_vals[i].text # Param tags are special if p_key == 'param': params += p_val + ',' p_val = params res[p_key] = p_val return res
def systemInfo(): """ Returns some system information. """ logFull('helpers:systemInfo') system = platform.machine() + ' ' + platform.system() + ', ' + ' '.join( platform.linux_distribution()) python = '.'.join([str(v) for v in sys.version_info]) return '{}\nPython {}'.format(system.strip(), python)
def get_query(self, field_id): """ Used by the applet. """ logFull('dbparser:get_query') res = self.user_xml.xpath('insert_section/field[@ID="%s"]' % field_id) if not res: logWarning('User {}: Cannot find field ID `{}`!'.format(self.user, field_id)) return False query = res[0].get('SQLQuery') return query
def json_get_project(self): """ get the project """ logFull('CeWebUi:json_get_project') if self.user_agent() == 'x': return 0 cherrypy.response.headers[ 'Content-Type'] = 'application/json; charset=utf-8' cherrypy.response.headers[ 'Cache-Control'] = 'no-cache, no-store, must-revalidate' cherrypy.response.headers['Pragma'] = 'no-cache' cherrypy.response.headers['Expires'] = 0 return open(TWISTER_PATH + '/config/project_users.json', 'r').read()
def save_config(self, service, data): """ Save configuration """ logFull('CeServices:save_config') config_path = '{0}/services/{1}/{2}'.format(TWISTER_PATH, service['name'], service['config']) if not os.path.isfile(config_path): logError('SM: No such config file `{0}`!'.format(config_path)) return False with open(config_path, 'wb') as out: out.write(data) return True
def json_stats(self): """ get stats """ logFull('CeWebUi:json_stats') if self.user_agent() == 'x': return 0 cherrypy.response.headers[ 'Content-Type'] = 'application/json; charset=utf-8' cherrypy.response.headers[ 'Cache-Control'] = 'no-cache, no-store, must-revalidate' cherrypy.response.headers['Pragma'] = 'no-cache' cherrypy.response.headers['Expires'] = 0 data = {'mem': calcMemory(), 'cpu': calcCpu()} return json.dumps(data)
def read_config(self, service): """ Read configuration """ logFull('CeServices:read_config') config_path = '{0}/services/{1}/{2}'.format(TWISTER_PATH, service['name'], service['config']) if not os.path.isfile(config_path): logError('SM: No such config file `{0}`!'.format(config_path)) return False with open(config_path, 'rb') as out: data = out.read() return data or ''
def send_command(self, command, name='', *args, **kwargs): """ send command to service """ logFull('CeServices:send_command') if command == SM_LIST or command == SM_COMMAND_MAP[SM_LIST]: return self.list_services() found = False service = None for service_item in self.twister_services: if name == service_item['name']: found = True service = service_item break if not found: logDebug('SM: Invalid service name: `{}`!'.format(name)) return False elif command == SM_STATUS or command == SM_COMMAND_MAP[SM_STATUS]: return self.service_status(service) elif command == SM_DESCRIP or command == SM_COMMAND_MAP[SM_DESCRIP]: return service.get('descrip') if command == SM_START or command == SM_COMMAND_MAP[SM_START]: return self.service_start(service) elif command == SM_STOP or command == SM_COMMAND_MAP[SM_STOP]: return self.service_stop(service) elif command == SM_GET_CONFIG or command == SM_COMMAND_MAP[SM_GET_CONFIG]: return self.read_config(service) elif command == SM_SET_CONFIG or command == SM_COMMAND_MAP[SM_SET_CONFIG]: try: return self.save_config(service, args[0][0]) except: return 'SM: Invalid number of parameters for save config!' elif command == SM_GET_LOG or command == SM_COMMAND_MAP[SM_GET_LOG]: try: return self.get_console_log(service, read=args[0][0], fstart=args[0][1]) except: return 'SM: Invalid number of parameters for read log!' else: return 'SM: Unknown command number: `{0}`!'.format(command)
def list_settings(self, xmlFile, xFilter=''): """ High level function for listing all settings from a Twister XML config file. """ logFull('xmlparser:list_settings') if not os.path.isfile(xmlFile): logError('User {}: Parse settings error! File path `{}` does not exist!'.format(self.user, xmlFile)) return False xmlSoup = parseXML(self.user, xmlFile) if xmlSoup is None: return [] if xFilter: return [x.tag for x in xmlSoup.xpath('//*') if xFilter in x.tag] else: return [x.tag for x in xmlSoup.xpath('//*')]
def encrypt(bdata, encr_key): """ Encrypt some data. """ logFull('helpers:encrypt') # Enhance user password with PBKDF2 pwd = PBKDF2(password=encr_key, salt='^0Twister-Salt9$', dkLen=32, count=100) crypt = AES.new(pwd) pad_len = 16 - (len(bdata) % 16) padding = (chr(pad_len) * pad_len) # Encrypt user data + correct padding data = crypt.encrypt(bdata + padding) return binascii.hexlify(data)
def _fixLogType(self, logType): """ Helper function to fix log names. """ logFull('xmlparser:_fixLogType') if logType.lower() == 'logrunning': logType = 'logRunning' elif logType.lower() == 'logdebug': logType = 'logDebug' elif logType.lower() == 'logsummary': logType = 'logSummary' elif logType.lower() == 'logtest': logType = 'logTest' elif logType.lower() == 'logcli': logType = 'logCli' return logType
def del_settings_key(self, xmlFile, key, index=0): """ High level function for deleting a value from a Twister XML config file. If the `index` is specified and the `key` returns more values, only the index-th value is deleted; unless the `index` is -1, in this case, all values are deleted. """ logFull('xmlparser:del_settings_key') if not os.path.isfile(xmlFile): logError('User {}: Parse settings error! File path `{}` does not exist!'.format(self.user, xmlFile)) return False # The key must be string if not (isinstance(key, str) or isinstance(key, unicode)): return False # The index must be integer if not isinstance(index, int): return False # The key must not be Null if not key: return False else: key = str(key) xmlSoup = parseXML(self.user, xmlFile) if xmlSoup is None: return False xml_key = xmlSoup.xpath(key) if xml_key is None: return False # For index -1, delete all matches if index == -1: for xml_v in xml_key: xml_parent = xml_v.getparent() xml_parent.remove(xml_v) else: # Use the index-th occurence, or, if the index is wrong, exit try: xml_key = xml_key[index] except Exception: return False xml_parent = xml_key.getparent() xml_parent.remove(xml_key) return dumpXML(self.user, xmlFile, xmlSoup)
def service_status(self, service): """ return status of service """ logFull('CeServices:service_status') # Values are: -1, 0, or any error code # -1 means the app is still running tprocess = service.get('pid', 0) retc = 0 if tprocess: tprocess.poll() retc = tprocess.returncode if retc is None: retc = -1 return retc
def is_resource_reserved(self, res_query, props={}): """ Verify if a resource is already reserved. Returns the user or false. """ logFull( 'CeCommonAllocator:is_resource_reserved: res_query = {}'.format( res_query)) resources = self.resources if '/' not in res_query: res_for_user = [ u for u in self.reservedResources if res_query in self.reservedResources[u] ] if '/' in res_query or not res_for_user: #if res_query contains components unsaved yet, search only for the TB if '/' in res_query: parts = [q for q in res_query.split('/') if q] node_path = self.get_resource('/' + parts[0]) else: node_path = self.get_resource(res_query) if not node_path or isinstance(node_path, str): msg = "No such resource {}".format(res_query) logError(msg) return False if isinstance(node_path['path'], list) and len(node_path['path']) > 1: node_path = self.get_path(node_path['path'][0], resources) if not node_path and isinstance(node_path, str): msg = "No such resource {}".format(res_query) logError(msg) return False res_for_user = [ u for u in self.reservedResources if node_path['id'] in self.reservedResources[u] ] if not res_for_user: return False return res_for_user[0]
def getEmailConfig(self, eml_file=''): """ Returns the e-mail configuration. After Central Engine stops, an e-mail must be sent to the people interested. """ logFull('xmlparser:getEmailConfig') if not eml_file: eml_file = self.project_globals['eml_config'] if not os.path.isfile(eml_file): logError('User {}: Parser: E-mail Config file `{}` does not exist!'.format(self.user, eml_file)) return {} econfig = parseXML(self.user, eml_file) if econfig is None: return {} res = {} res['Enabled'] = '' res['SMTPPath'] = '' res['SMTPUser'] = '' res['SMTPPwd'] = '' res['From'] = '' res['To'] = '' res['Subject'] = '' res['Message'] = '' if econfig.xpath('Enabled/text()'): res['Enabled'] = econfig.xpath('Enabled')[0].text if econfig.xpath('SMTPPath/text()'): res['SMTPPath'] = econfig.xpath('SMTPPath')[0].text if econfig.xpath('SMTPUser/text()'): res['SMTPUser'] = econfig.xpath('SMTPUser')[0].text if econfig.xpath('SMTPPwd/text()'): res['SMTPPwd'] = econfig.xpath('SMTPPwd')[0].text if econfig.xpath('From/text()'): res['From'] = econfig.xpath('From')[0].text if econfig.xpath('To/text()'): res['To'] = econfig.xpath('To')[0].text if econfig.xpath('Subject/text()'): res['Subject'] = econfig.xpath('Subject')[0].text if econfig.xpath('Message/text()'): res['Message'] = econfig.xpath('Message')[0].text return res
def set_settings_value(self, xmlFile, key, value): """ High level function for setting a value in a Twister XML config file. """ logFull('xmlparser:set_settings_value') if not os.path.isfile(xmlFile): logError('User {}: Parse settings error! File path `{}` does not exist!'.format(self.user, xmlFile)) return False if not key: return False else: key = str(key) if not value: value = '' else: value = str(value) xmlSoup = parseXML(self.user, xmlFile) if xmlSoup is None: return False xml_key = xmlSoup.xpath(key) # If the key is found, update it if xml_key: xml_key[0].text = value # Else, create it else: # Try and split the key into parent and node if '/' in key: parent_path, node_name = '/'.join(key.split('/')[:-1]), key.split('/')[-1] else: parent_path, node_name = '/', key parent = xmlSoup.xpath(parent_path) # Invalid parent path ? if not parent: return False # Create the new node node = etree.Element(node_name) node.text = value node.tail = '\n' parent[0].insert(-1, node) return dumpXML(self.user, xmlFile, xmlSoup)
def create_new_tb(self, name, parent=None, props={}): """ Create new test bed. Return the id of the new created tb. """ user_info = self.user_info(props) resources = self.resources if parent != '/' and parent != '1': msg = "The parent value is not root. Maybe you want to add a component\ to an existing SUT. Parent: {}".format(parent) logError(msg) return "*ERROR* " + msg props = self.valid_props(props) with self.acc_lock: # root can not be reserved so we just take it parent_p = self.get_resource('/', resources) if not parent_p or isinstance(parent_p, str): logFull("User: {} no result for query `{}`" .format(user_info[0], parent)) return None if '/' in name: logDebug('Stripping slash characters from `{}`...'.format(name)) name = name.replace('/', '') if name in self.resources['children']: msg = "User {}: A TB with name `{}` already exists!".format(user_info[0], name) logDebug(msg) return "*ERROR* " + msg # the resource doesn't exist - create it res_id = self.generate_index() parent_p['children'][name] = {'id': res_id, 'meta': props, 'children': {}, 'path': [name]} issaved = self.save_tb(props) if not issaved: msg = "User {}: Could not save TB `{}`".format(user_info[0], name) logDebug(msg) return "*ERROR* " + msg return res_id
def change_path(self, result, path): ''' update the path of all kids if the result is renamed for example ''' logFull("CeCommonAllocator: change_path {} ".format(path)) if not result: return False if not result.get('children'): return True for node in result.get('children'): result['children'][node]['path'] = path + [node] self.change_path(result['children'][node], result['children'][node]['path']) if not result: return True
def updateProjectGlobals(self): """ Returns the values of many global tags, from FWM and Test-Suites XML. """ logFull('xmlparser:updateProjectGlobals') if self.configTS is None: logError('User {}: Parser: Cannot get project globals, because'\ ' Test-Suites XML is invalid!'.format(self.user)) return False # Reset globals self.project_globals = OrderedDict() # Parse all known FWMCONFIG tags for tag_dict in FWMCONFIG_TAGS: # Create default entry self.project_globals[tag_dict['name']] = tag_dict['default'] # Update value from XML if self.xmlDict.xpath(tag_dict['tag'] + '/text()'): path = self.xmlDict.xpath(tag_dict['tag'])[0].text if path[0] == '~': path = self.user_home + path[1:] self.project_globals[tag_dict['name']] = path # Parse all known PROJECT tags for tag_dict in PROJECTCONFIG_TAGS: # Create default entry self.project_globals[tag_dict['name']] = tag_dict['default'] # Update value from XML if self.configTS.xpath(tag_dict['tag'] + '/text()'): # If the variable should be a Boolean if tag_dict.get('type') == 'bool': if self.configTS.xpath(tag_dict['tag'] + '/text()')[0].lower() == 'true': value = True else: value = False # If the variable should be a Number elif tag_dict.get('type') == 'number': value = self.configTS.xpath('round({})'.format(tag_dict['tag'])) else: value = self.configTS.xpath(tag_dict['tag'])[0].text self.project_globals[tag_dict['name']] = value return True