diff --git a/makepythonrpm b/makepythonrpm new file mode 100755 index 000000000..72527f512 --- /dev/null +++ b/makepythonrpm @@ -0,0 +1,38 @@ +#!/bin/bash + +# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html + +function makepythonrpm { + RPMNAME="$1" + SPEC_FILE="$RPMNAME.spec" + tar --exclude .svn -czvf $RPMROOT/SOURCES/$RPMNAME-${VER}.tar.gz $RPMNAME + rm -f $RPMROOT/SRPMS/$RPMNAME-$VER*rpm $RPMROOT/RPMS/noarch/$RPMNAME-$VER*rpm + echo "Building $RPMROOT/RPMS/noarch/$RPMNAME-$VER-snap*.noarch.rpm $EMBEDTXT..." + rpmbuild --quiet -ta $RPMROOT/SOURCES/$RPMNAME-${VER}.tar.gz --define "version $VER" $REL "$EASE" + if [ $? -eq 0 ]; then + exit 0 + else + exit 1 + fi +} + +# Main code.... +OSNAME=$(uname) +VER=`cat Version` +REL="--define" +EASE='usedate 1' +RPMROOT=/root/rpmbuild + +rpmbuild --version > /dev/null +if [ $? -gt 0 ]; then + echo "Error: rpmbuild does not appear to be installed or working." + exit 2 +fi + +if [ -n "$1" -a "$1" = "xCAT-openbmc-py" ]; then + makepythonrpm $1 +else + echo 'Usage: makepythonrpm xCAT-openbmc-py' + exit 1 +fi + diff --git a/xCAT-openbmc-py/lib/python/agent/agent.py b/xCAT-openbmc-py/lib/python/agent/agent.py new file mode 100755 index 000000000..b7a1827bc --- /dev/null +++ b/xCAT-openbmc-py/lib/python/agent/agent.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- +from __future__ import print_function +import argparse +import sys +from xcatagent import server + + +class AgentShell(object): + def get_base_parser(self): + parser = argparse.ArgumentParser( + prog='xcatagent', + add_help=False, + formatter_class=HelpFormatter, + ) + parser.add_argument('-h', '--help', + action='store_true', + help=argparse.SUPPRESS, + ) + parser.add_argument('--standalone', + help="Start xcat agent as a standalone service, " + "mostly work for test purpose. ", + action='store_true') + parser.add_argument('--sock', + help="The unix domain sock file to communicate " + "with the client", + default='/var/run/xcat/agent.sock', + type=str) + return parser + + def do_help(self, args): + self.parser.print_help() + + def main(self, argv): + self.parser = self.get_base_parser() + (options, args) = self.parser.parse_known_args(argv) + + if options.help: + self.do_help(options) + return 0 + s = server.Server(options.sock, options.standalone) + s.start() + +class HelpFormatter(argparse.HelpFormatter): + def start_section(self, heading): + # Title-case the headings + heading = '%s%s' % (heading[0].upper(), heading[1:]) + super(HelpFormatter, self).start_section(heading) + + +if __name__ == '__main__': + AgentShell().main(sys.argv[1:]) diff --git a/xCAT-openbmc-py/lib/python/agent/client.py b/xCAT-openbmc-py/lib/python/agent/client.py new file mode 100755 index 000000000..ed5bd3e84 --- /dev/null +++ b/xCAT-openbmc-py/lib/python/agent/client.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- +# just for test +from __future__ import print_function + +import argparse +import json +import os +import socket +import sys +from xcatagent import utils + +class ClientShell(object): + def get_base_parser(self): + parser = argparse.ArgumentParser( + prog='agentclient', + add_help=False, + formatter_class=HelpFormatter, + ) + parser.add_argument('-h', '--help', + action='store_true', + help=argparse.SUPPRESS, + ) + parser.add_argument('--sock', + help="The unix domain sock file to communicate " + "with the server", + default='/var/run/xcat/agent.sock', + type=str) + return parser + + def do_help(self, args): + self.parser.print_help() + + def main(self, argv): + self.parser = self.get_base_parser() + (options, args) = self.parser.parse_known_args(argv) + + if options.help: + self.do_help(options) + return 0 + + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + nodes = ['node1', 'node2'] + nodeinfo = {'node1':{'bmc':'10.0.0.1', 'bmcip':'10.0.0.1', 'username':'root', 'password': 'xxxxxx'}, + 'node2':{'bmc':'10.0.0.2', 'bmcip':'10.0.0.2', 'username':'root', 'password': 'xxxxxx'}} + + s.connect(options.sock) + req = {'module': 'openbmc', + 'command': 'rpower', + 'args': ['state'], + 'cwd': os.getcwd(), + 'nodes': nodes, + 'nodeinfo': nodeinfo, + 'envs': {'debugmode': 1}} + + buf = json.dumps(req) + s.send(utils.int2bytes(len(buf))) + s.send(buf) + while True: + sz = s.recv(4) + if len(sz) == 0: + break + sz = utils.bytes2int(sz) + data = s.recv(sz) + print(data) + + +class HelpFormatter(argparse.HelpFormatter): + def start_section(self, heading): + # Title-case the headings + heading = '%s%s' % (heading[0].upper(), heading[1:]) + super(HelpFormatter, self).start_section(heading) + + +if __name__ == '__main__': + ClientShell().main(sys.argv[1:]) diff --git a/xCAT-openbmc-py/lib/python/agent/xcatagent/__init__.py b/xCAT-openbmc-py/lib/python/agent/xcatagent/__init__.py new file mode 100644 index 000000000..a9ad45f68 --- /dev/null +++ b/xCAT-openbmc-py/lib/python/agent/xcatagent/__init__.py @@ -0,0 +1,2 @@ +from gevent import monkey +monkey.patch_all(thread=False) diff --git a/xCAT-openbmc-py/lib/python/agent/xcatagent/base.py b/xCAT-openbmc-py/lib/python/agent/xcatagent/base.py new file mode 100644 index 000000000..b1ebb6aac --- /dev/null +++ b/xCAT-openbmc-py/lib/python/agent/xcatagent/base.py @@ -0,0 +1,47 @@ +from xcatagent import utils +import gevent +from gevent.pool import Pool + +MODULE_MAP = {"openbmc": "OpenBMCManager"} + + +class BaseManager(object): + def __init__(self, messager, cwd): + self.messager = messager + self.cwd = cwd + + @classmethod + def get_manager_func(self, name): + module_name = 'xcatagent.%s' % name + try: + __import__(module_name) + except ImportError: + return None + + class_name = MODULE_MAP[name] + return utils.class_func(module_name, class_name) + + def process_nodes_worker(self, name, classname, nodes, nodeinfo, command, args): + + module_name = 'xcatagent.%s' % name + obj_func = utils.class_func(module_name, classname) + + gevent_pool = Pool(1000) + + for node in nodes: + obj = obj_func(self.messager, node, nodeinfo[node]) + if not hasattr(obj, command): + self.messager.error('%s: command %s is not supported for %s' % (node, command, classname)) + func = getattr(obj, command) + try: + gevent_pool.add( gevent.spawn(func, args) ) + except Exception: + error = '%s: Internel Error occured in gevent' % node + self.messager.error(error) + + gevent_pool.join() + + +class BaseDriver(object): + def __init__(self, messager): + self.messager = messager diff --git a/xCAT-openbmc-py/lib/python/agent/xcatagent/openbmc.py b/xCAT-openbmc-py/lib/python/agent/xcatagent/openbmc.py new file mode 100644 index 000000000..1276bc85b --- /dev/null +++ b/xCAT-openbmc-py/lib/python/agent/xcatagent/openbmc.py @@ -0,0 +1,242 @@ +from xcatagent import base +import time +import sys +import gevent + +import xcat_exception +import rest + +HTTP_PROTOCOL = "https://" +PROJECT_URL = "/xyz/openbmc_project" + +RESULT_OK = 'ok' + +DEBUGMODE = False + +POWER_SET_OPTIONS = ('on', 'off', 'bmcreboot', 'softoff') +POWER_GET_OPTIONS = ('bmcstate', 'state', 'stat', 'status') + +RPOWER_URLS = { + "on" : { + "url" : PROJECT_URL + "/state/host0/attr/RequestedHostTransition", + "field" : "xyz.openbmc_project.State.Host.Transition.On", + }, + "off" : { + "url" : PROJECT_URL + "/state/chassis0/attr/RequestedPowerTransition", + "field" : "xyz.openbmc_project.State.Chassis.Transition.Off", + }, + "softoff" : { + "url" : PROJECT_URL + "/state/host0/attr/RequestedHostTransition", + "field" : "xyz.openbmc_project.State.Host.Transition.Off", + }, + "bmcreboot" : { + "url" : PROJECT_URL + "/state/bmc0/attr/RequestedBMCTransition", + "field" : "xyz.openbmc_project.State.BMC.Transition.Reboot", + }, + "state" : { + "url" : PROJECT_URL + "/state/enumerate", + }, +} + +RPOWER_STATE = { + "on" : "on", + "off" : "off", + "Off" : "off", + "softoff" : "softoff", + "boot" : "reset", + "reset" : "reset", + "bmcreboot" : "BMC reboot", + "Ready" : "BMC Ready", + "NotReady" : "BMC NotReady", + "chassison" : "on (Chassis)", + "Running" : "on", + "Quiesced" : "quiesced", +} + +POWER_STATE_DB = { + "on" : "powering-on", + "off" : "powering-off", + "softoff" : "powering-off", + "boot" : "powering-on", + "reset" : "powering-on", +} + +class OpenBMC(base.BaseDriver): + + headers = {'Content-Type': 'application/json'} + + def __init__(self, messager, name, node_info): + super(OpenBMC, self).__init__(messager) + self.node = name + for key, value in node_info.items() : + setattr(self, key, value) + global DEBUGMODE + self.client = rest.RestSession(messager, DEBUGMODE) + + def _login(self) : + """ Login + :raise: error message if failed + """ + url = HTTP_PROTOCOL + self.bmcip + '/login' + data = { "data": [ self.username, self.password ] } + self.client.request('POST', url, OpenBMC.headers, data, self.node, 'login') + return RESULT_OK + + def _set_power_onoff(self, subcommand) : + """ Set power on/off/softoff/bmcreboot + :param subcommand: subcommand for rpower + :returns: ok if success + :raise: error message if failed + """ + url = HTTP_PROTOCOL + self.bmcip + RPOWER_URLS[subcommand]['url'] + data = { "data": RPOWER_URLS[subcommand]['field'] } + try : + response = self.client.request('PUT', url, OpenBMC.headers, data, self.node, 'rpower_' + subcommand) + except (xcat_exception.SelfServerException, + xcat_exception.SelfClientException) as e : + if subcommand != 'bmcreboot': + result = e.message + return result + + return RESULT_OK + + def _get_power_state(self, subcommand) : + """ Get power current state + :param subcommand: state/stat/status/bmcstate + :returns: current state if success + :raise: error message if failed + """ + result = '' + bmc_not_ready = 'NotReady' + url = HTTP_PROTOCOL + self.bmcip + RPOWER_URLS['state']['url'] + try : + response = self.client.request('GET', url, OpenBMC.headers, '', self.node, 'rpower_' + subcommand) + except xcat_exception.SelfServerException, e : + if subcommand == 'bmcstate': + result = bmc_not_ready + else : + result = e.message + except xcat_exception.SelfClientException, e : + result = e.message + + if result : + return result + + for key in response['data'] : + key_type = key.split('/')[-1] + if key_type == 'bmc0' : + bmc_current_state = response['data'][key]['CurrentBMCState'].split('.')[-1] + if key_type == 'chassis0' : + chassis_current_state = response['data'][key]['CurrentPowerState'].split('.')[-1] + if key_type == 'host0' : + host_current_state = response['data'][key]['CurrentHostState'].split('.')[-1] + + if subcommand == 'bmcstate' : + if bmc_current_state == 'Ready' : + return bmc_current_state + else : + return bmc_not_ready + + if chassis_current_state == 'Off' : + return chassis_current_state + elif chassis_current_state == 'On' : + if host_current_state == 'Off' : + return 'chassison' + elif host_current_state == 'Quiesced' : + return host_current_state + elif host_current_state == 'Running' : + return host_current_state + else : + return 'Unexpected chassis state=' + host_current_state + else : + return 'Unexpected chassis state=' + chassis_current_state + + + def _rpower_boot(self) : + """Power boot + :returns: 'reset' if success + :raise: error message if failed + """ + result = self._set_power_onoff('off') + if result != RESULT_OK : + return result + self.messager.update_node_attributes('status', self.node, POWER_STATE_DB['off']) + + start_timeStamp = int(time.time()) + for i in range (0,30) : + status = self._get_power_state('state') + if status in RPOWER_STATE and RPOWER_STATE[status] == 'off': + break + gevent.sleep( 2 ) + + end_timeStamp = int(time.time()) + + if status not in RPOWER_STATE or RPOWER_STATE[status] != 'off': + wait_time = str(end_timeStamp - start_timeStamp) + result = 'Error: Sent power-off command but state did not change to off after waiting ' + wait_time + ' seconds. (State=' + status + ').' + return result + + result = self._set_power_onoff('on') + return result + + + def rpower(self, args) : + """handle rpower command + :param args: subcommands for rpower + """ + subcommand = args[0] + try : + result = self._login() + except xcat_exception.SelfServerException as e : + if subcommand == 'bmcstate' : + result = '%s: %s' % (self.node, RPOWER_STATE['NotReady']) + else : + result = '%s: %s' % (self.node, e.message) + except xcat_exception.SelfClientException as e : + result = '%s: %s' % (self.node, e.message) + + if result != RESULT_OK : + self.messager.info(result) + self._update2Ddict(node_rst, self.node, 'rst', result) + return + new_status = '' + if subcommand in POWER_SET_OPTIONS : + result = self._set_power_onoff(subcommand) + if result == RESULT_OK : + result = RPOWER_STATE[subcommand] + new_status = POWER_STATE_DB.get(subcommand, '') + + if subcommand in POWER_GET_OPTIONS : + tmp_result = self._get_power_state(subcommand) + result = RPOWER_STATE.get(tmp_result, tmp_result) + + if subcommand == 'boot' : + result = self._rpower_boot() + if result == RESULT_OK : + result = RPOWER_STATE[subcommand] + new_status = POWER_STATE_DB.get(subcommand, '') + + if subcommand == 'reset' : + status = self._get_power_state('state') + if status == 'Off' or status == 'chassison': + result = RPOWER_STATE['Off'] + else : + result = self._rpower_boot() + if result == RESULT_OK : + result = RPOWER_STATE[subcommand] + new_status = POWER_STATE_DB.get(subcommand, '') + + message = '%s: %s' % (self.node, result) + self.messager.info(message) + if new_status : + self.messager.update_node_attributes('status', self.node, new_status) + +class OpenBMCManager(base.BaseManager): + def __init__(self, messager, cwd, nodes, envs): + super(OpenBMCManager, self).__init__(messager, cwd) + self.nodes = nodes + global DEBUGMODE + DEBUGMODE = envs['debugmode'] + + def rpower(self, nodeinfo, args): + super(OpenBMCManager, self).process_nodes_worker('openbmc', 'OpenBMC', self.nodes, nodeinfo, 'rpower', args) diff --git a/xCAT-openbmc-py/lib/python/agent/xcatagent/rest.py b/xCAT-openbmc-py/lib/python/agent/xcatagent/rest.py new file mode 100644 index 000000000..c3057ddb3 --- /dev/null +++ b/xCAT-openbmc-py/lib/python/agent/xcatagent/rest.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python +import requests +import json +import time +import urllib3 +urllib3.disable_warnings() + +import xcat_exception + +class RestSession : + + def __init__(self, messager, debugmode) : + self.session = requests.Session() + self.messager = messager + self.debugmode = debugmode + + def _print_record_log (self, node, log_string, status) : + if self.debugmode : + localtime = time.asctime( time.localtime(time.time()) ) + log = node + ': [openbmc_debug] ' + status + ' ' + log_string + self.messager.info(localtime + ' ' + log) + self.messager.syslog(log) + + def _request_log (self, method, url, headers, data): + log_string = 'curl -k -c cjar' + log_string += ' -X %s' % method + for key,value in headers.items() : + header_data = key + ": " + value + log_string += ' -H "' + header_data + '"' + log_string += ' %s' % url + + if data : + log_string += ' -d \'%s\'' % data + + return log_string + + def request (self, method, url, headers, in_data, node, status) : + if in_data : + data = json.dumps(in_data) + else : + data = '' + + if status == 'login' : + in_data['data'][1] = 'xxxxxx' + log_data = json.dumps(in_data) + else : + log_data = data + + log_string = self._request_log(method, url, headers, log_data) + self._print_record_log(node, log_string, status) + + response = '' + error = '' + try : + response = self.session.request(method, url, + data=data, + headers=headers, + verify=False, + timeout=30) + except requests.exceptions.ConnectionError : + error = 'Error: BMC did not respond. Validate BMC configuration and retry the command.' + except requests.exceptions.Timeout : + error = 'Error: Timeout to connect to server' + + if error : + self._print_record_log(node, error, status) + raise xcat_exception.SelfServerException(error) + + try : + response_dict = response.json() + except ValueError : + error = 'Error: Received wrong format response:' + response_dict + self._print_record_log(node, error, status) + raise xcat_exception.SelfServerException(error) + + if response.status_code != requests.codes.ok : + description = ''.join(response_dict['data']['description']) + error = 'Error: [%d] %s' % (response.status_code, description) + self._print_record_log(node, error, status) + raise xcat_exception.SelfClientException(error) + else : + self._print_record_log(node, response_dict['message'], status) + + return response_dict diff --git a/xCAT-openbmc-py/lib/python/agent/xcatagent/server.py b/xCAT-openbmc-py/lib/python/agent/xcatagent/server.py new file mode 100644 index 000000000..a0b242356 --- /dev/null +++ b/xCAT-openbmc-py/lib/python/agent/xcatagent/server.py @@ -0,0 +1,123 @@ +# -*- encoding: utf-8 -*- +from __future__ import print_function +import json +import sys +import os +import threading +import fcntl +import traceback +from gevent import socket +from gevent.server import StreamServer +from gevent.lock import BoundedSemaphore +from xcatagent import utils +from xcatagent import base as xcat_manager + +MSG_TYPE = 'message' +DB_TYPE = 'db' +LOCK_FILE = '/var/lock/xcat/agent.lock' + + +class Messager(object): + def __init__(self, sock): + self.sock = sock + self.sem = BoundedSemaphore(1) + + def _send(self, d): + buf = json.dumps(d) + self.sem.acquire() + self.sock.sendall(utils.int2bytes(len(buf)) + buf) + self.sem.release() + + def info(self, msg): + d = {'type': MSG_TYPE, 'msg': {'type': 'info', 'data': msg}} + self._send(d) + + def warn(self, msg): + d = {'type': MSG_TYPE, 'msg': {'type': 'warning', 'data': msg}} + self._send(d) + + def error(self, msg): + d = {'type': MSG_TYPE, 'msg': {'type': 'error', 'data': msg}} + self._send(d) + + def syslog(self, msg): + d = {'type': MSG_TYPE, 'msg': {'type': 'syslog', 'data': msg}} + self._send(d) + + def update_node_attributes(self, attribute, node, data): + d = {'type': DB_TYPE, 'attribute': {'name': attribute, 'method': 'set', 'type': 'node', 'node': node, 'value': data}} + self._send(d) + + +class Server(object): + def __init__(self, address, standalone): + try: + os.unlink(address) + except OSError: + if os.path.exists(address): + raise + self.address = address + self.standalone = standalone + self.server = StreamServer(self._serve(), self._handle) + + def _serve(self): + listener = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + listener.bind(self.address) + listener.listen(1) + return listener + + def _handle(self, sock, address): + try: + messager = Messager(sock) + buf = sock.recv(4) + sz = utils.bytes2int(buf) + buf = utils.recv_all(sock, sz) + req = json.loads(buf) + if not 'command' in req: + messager.error("Could not find command") + return + if not 'module' in req: + messager.error("Please specify the request module") + return + if not 'cwd' in req: + messager.error("Please specify the cwd parameter") + return + manager_func = xcat_manager.BaseManager.get_manager_func( + req['module']) + if manager_func is None: + messager.error("Could not find manager for %s" % req['module']) + return + nodes = req.get("nodes", None) + manager = manager_func(messager, req['cwd'], nodes, req['envs']) + if not hasattr(manager, req['command']): + messager.error("command %s is not supported" % req['command']) + func = getattr(manager, req['command']) + # call the function in the specified manager + func(req['nodeinfo'], req['args']) + # after the method returns, the request should be handled + # completely, close the socket for client + if not self.standalone: + sock.close() + self.server.stop() + os._exit(0) + except Exception: + print(traceback.format_exc(), file=sys.stderr) + self.server.stop() + os._exit(1) + + def keep_peer_alive(self): + def acquire(): + fd = open(LOCK_FILE, "r+") + fcntl.flock(fd.fileno(), fcntl.LOCK_EX) + # if reach here, parent process may exit + print("xcat process exit unexpectedly.", file=sys.stderr) + self.server.stop() + os._exit(1) + + t = threading.Thread(target=acquire) + t.start() + + def start(self): + if not self.standalone: + self.keep_peer_alive() + self.server.serve_forever() diff --git a/xCAT-openbmc-py/lib/python/agent/xcatagent/utils.py b/xCAT-openbmc-py/lib/python/agent/xcatagent/utils.py new file mode 100644 index 000000000..8fff2403a --- /dev/null +++ b/xCAT-openbmc-py/lib/python/agent/xcatagent/utils.py @@ -0,0 +1,41 @@ +import struct +import sys +import inspect + + +def int2bytes(num): + return struct.pack('i', num) + + +def bytes2int(buf): + return struct.unpack('i', buf)[0] + + +def get_classes(module_name): + ret = [] + for name, obj in inspect.getmembers(sys.modules[module_name]): + if inspect.isclass(obj): + ret.append(obj) + + return ret + + +def class_func(module_name, class_name): + func = getattr(sys.modules[module_name], class_name) + return func + + +def recv_all(sock, size): + recv_size = 4096 + buf_size = 0 + buf_parts = [] + while buf_size < size: + tmp_size = recv_size + left_size = size - buf_size + if left_size < recv_size: + tmp_size = left_size + buf_part = sock.recv(tmp_size) + buf_parts.append(buf_part) + buf_size += len(buf_part) + buf = ''.join(buf_parts) + return buf diff --git a/xCAT-openbmc-py/lib/python/agent/xcatagent/xcat_exception.py b/xCAT-openbmc-py/lib/python/agent/xcatagent/xcat_exception.py new file mode 100644 index 000000000..f544cf2c8 --- /dev/null +++ b/xCAT-openbmc-py/lib/python/agent/xcatagent/xcat_exception.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python + +class SelfServerException(Exception) : + pass + +class SelfClientException(Exception) : + pass diff --git a/xCAT-openbmc-py/xCAT-openbmc-py.spec b/xCAT-openbmc-py/xCAT-openbmc-py.spec new file mode 100644 index 000000000..70776df50 --- /dev/null +++ b/xCAT-openbmc-py/xCAT-openbmc-py.spec @@ -0,0 +1,54 @@ +Summary: xCAT openbmc python +Name: xCAT-openbmc-py +Version: %{?version:%{version}}%{!?version:%(cat Version)} +Release: %{?release:%{release}}%{!?release:snap%(date +"%Y%m%d%H%M")} +Epoch: 4 +License: EPL +Group: Applications/System +Source: xCAT-openbmc-py-%{version}.tar.gz +Packager: IBM Corp. +Vendor: IBM Corp. +Distribution: %{?_distribution:%{_distribution}}%{!?_distribution:%{_vendor}} +Prefix: /opt/xcat +BuildRoot: /var/tmp/%{name}-%{version}-%{release}-root + +%ifnos linux +AutoReqProv: no +%endif + +BuildArch: noarch +Requires: xCAT-server, python-requests + +%description +xCAT-openbmc-py provides openbmc related functions. + +%prep +%setup -q -n xCAT-openbmc-py +%build + +%install +rm -rf $RPM_BUILD_ROOT +install -d $RPM_BUILD_ROOT/%{prefix}/lib/python/agent +install -d $RPM_BUILD_ROOT/%{prefix}/lib/python/agent/xcatagent +install -m644 lib/python/agent/*.py $RPM_BUILD_ROOT/%{prefix}/lib/python/agent +install -m644 lib/python/agent/xcatagent/*.py $RPM_BUILD_ROOT/%{prefix}/lib/python/agent/xcatagent + +%ifnos linux +rm -rf $RPM_BUILD_ROOT/%{prefix}/lib/python +%endif + +%clean +rm -rf $RPM_BUILD_ROOT + +%files +%defattr(-,root,root) +%{prefix} + +%changelog + +%pre + +%post + +%preun +