2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2025-05-22 03:32:04 +00:00

packaging xCAT-openbmc-py method

This commit is contained in:
bybai 2018-01-16 03:41:20 -05:00
parent a41e138fc0
commit 3f0a152acc
11 changed files with 766 additions and 0 deletions

38
makepythonrpm Executable file
View File

@ -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

View File

@ -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:])

View File

@ -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:])

View File

@ -0,0 +1,2 @@
from gevent import monkey
monkey.patch_all(thread=False)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -0,0 +1,7 @@
#!/usr/bin/env python
class SelfServerException(Exception) :
pass
class SelfClientException(Exception) :
pass

View File

@ -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