-
Notifications
You must be signed in to change notification settings - Fork 169
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
rpower support utilizing the python framework for OpenBMC #4587
Conversation
80fa3b6
to
043e27e
Compare
|
@@ -1,26 +1,223 @@ | |||
from xcatagent import base | |||
import gevent | |||
import gevent.monkey | |||
gevent.monkey.patch_all() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'd better put this in the __init__.py
, not sure if this would affect the keep_peer_alive
method.
'cwd': os.getcwd(), | ||
'nodes': nodes, | ||
'nodeinfo': nodeinfo} | ||
'nodeinfo': nodeinfo, | ||
'debugmode': '1'} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If multiple levels , integer should be used, otherwise boolean value would be better.
import openbmc_rest | ||
|
||
http_protocol="https://"; | ||
openbmc_project_url = "/xyz/openbmc_project"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In python, if it is a constant value, upper case should be used.
} | ||
|
||
subcommand_tup_dict = { | ||
"rpower_set_onoff" : ['on', 'off', 'bmcreboot', 'softoff'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- upper case
- tuple is better than list here.
No need to use subcommand_tup_dict, you are writing openbmc module, a class to provide service, not command handler, I prefer the following:
POWER_STATE = ('on', 'off', 'bmcreboot', 'softoff')
http_protocol="https://"; | ||
openbmc_project_url = "/xyz/openbmc_project"; | ||
|
||
command_dict = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only method, no command here.
I think it's better to put the url and the data field in the method.
def _power_on():
url = PROJECT_URL + "/state/host0/attr/RequestedHostTransition"
field = "xyz.openbmc_project.State.Host.Transition.On"
You could also rename this dict like RPOWER_URLS = { on: {...}, off:...}, init_url
is not a proper name here.
@@ -0,0 +1,85 @@ | |||
#!/usr/bin/env python |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the file name should be rest.py, make it as a common module. Add get, post, put, delete, patch method may be more friendly.
gevent.joinall(lst) | ||
|
||
def rpower(self, nodeinfo, args, debugmode): | ||
glist = [ gevent.spawn(self.handler, node, nodeinfo[node], 'rpower', args, debugmode) for node in self.nodes ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually your OpenBMC class is worked like the original OpenBMC driver.
You should create a list of driver(OpenBMC) objects, then create gevent objects to call the driver method(rpower). Like below:
objs = [OpenBMC(node, nodeinfo[node]) for node in self.nodes]
glist = [ gevent.spawn(obj.rpower, args, debugmode) for obj in objs]
super(OpenBMCDriver, self).__init__(messager) | ||
def handler(self, node, node_info, command, args, debugmode): | ||
openbmc = OpenBMC(node, node_info, self.messager, debugmode) | ||
getattr(openbmc,command)(args) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Call the rpower method directly, handler is not a meaningful method name compare with the behavior like rpower
|
||
if subcommand == 'bmcstate' : | ||
if bmc_current_state == 'Ready' : | ||
return 'BMC Ready' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Put the message in a constant dict, the return the message based on that dict.
''' | ||
def rpower(self, args) : | ||
subcommand = args[0] | ||
self._login() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think _login() method should return something or raise some exception, it could not be success all the time.
login_data = { "data": [ self.username, self.password ] } | ||
self.client.request('POST', login_url, OpenBMC.headers, login_data, self.node, 'login') | ||
|
||
''' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Take the docstring from pyghmi and ironic as a example
def _got_session(self, response):
"""Private function to navigate SOL payload activation
"""
if 'error' in response:
self._print_error(response['error'])
return
#Send activate sol payload directive
#netfn= 6 (application)
#command = 0x48 (activate payload)
#data = (1, sol payload type
# 1, first instance
# 0b11000000, -encrypt, authenticate,
# disable serial/modem alerts, CTS fine
# 0, 0, 0 reserved
response = self.ipmi_session.raw_command(netfn=0x6, command=0x48,
data=(1, 1, 192, 0, 0, 0))
#given that these are specific to the command,
#it's probably best if one can grep the error
#here instead of in constants
sol_activate_codes = {
0x81: 'SOL is disabled',
0x82: 'Maximum SOL session count reached',
0x83: 'Cannot activate payload with encryption',
0x84: 'Cannot activate payload without encryption',
}
From ironic which list the parameters
def update_volume_target(self, context, target):
"""Update a volume target.
:param context: request context
:param target: a changed (but not saved) volume target object
:returns: an updated volume target object
:raises: InvalidParameterValue if the volume target's UUID is being
changed
:raises: NodeLocked if the node is already locked
:raises: NodeNotFound if the node associated with the volume target
does not exist
:raises: VolumeTargetNotFound if the volume target cannot be found
:raises: VolumeTargetBootIndexAlreadyExists if a volume target already
exists with the same node ID and boot index values
"""
LOG.debug("RPC update_volume_target called for target %(target)s.",
{'target': target.uuid})
4dba9d4
to
65ffc75
Compare
@@ -0,0 +1,7 @@ | |||
#!/usr/bin/env python | |||
|
|||
class SelfServerException(Exception) : |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Define the exception like this is meaningless, you could refer the design of openstack nova https://github.com/openstack/nova/blob/master/nova/exception.py to make the error message more standard.
When the exception is captured, we could call messager.error(exception.msg)
to send the error message back to the user directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But I do not want to show the message to the user directly. Whether show message should method decide.
:returns: on/off/softoff/BMC reboot if success | ||
:raise: error message if failed | ||
""" | ||
request_url = HTTP_PROTOCOL + self.bmcip + RPOWER_URLS[subcommand]['url'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer url, data directly, the prefix of request_
is meaningless here.
gevent.sleep(3) | ||
self.messager.info( | ||
"%s: rpower called info=%s args=%s" % (node, info, args)) | ||
super(OpenBMCManager, self).process_nodes_worker('openbmc', 'OpenBMC', self.nodes, nodeinfo, 'rpower', args, self.debugmode) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You misunderstand again.
OpenBMCManager is to manage the collection of nodes. It call the method of OpenBMC inside the coroutine.
OpenBMC class is for one node, you should make it work without the OpenBMCManager.
In short, manager is a scheduler, OpenBMCDriver is a class to handle the business logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Append my concern inline. When you think it's done, you could merge it.
gevent.sleep(3) | ||
self.messager.info( | ||
"%s: rpower called info=%s args=%s" % (node, info, args)) | ||
super(OpenBMCManager, self).process_nodes_worker('openbmc', 'OpenBMC', self.nodes, nodeinfo, 'rpower', args) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My concern is that you use reflect again which is unnecessary. Why not to just create the openbmc objects, and trasfer the rpower func directly?
nodes} | ||
nodes = ['node1', 'node2'] | ||
nodeinfo = {'node1':{'bmc':'9.3.185.161', 'bmcip':'9.3.185.161', 'username':'root', 'password': '0penBmc1'}, | ||
'node2':{'bmc':'10.6.17.100', 'bmcip': '10.6.17.100', 'username':'root', 'password': '0penBmc'}} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't put the the real IP into public repository, even it is for UT.
'cwd': os.getcwd(), | ||
'nodes': nodes, | ||
'nodeinfo': nodeinfo} | ||
'nodeinfo': nodeinfo, | ||
'debugmode': 1} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
better to use envs
to cover debugmode, as we may have more variables which could passed into python client.
@@ -86,9 +86,9 @@ def _handle(self, sock, address): | |||
messager.error("Could not find manager for %s" % req['module']) | |||
return | |||
nodes = req.get("nodes", None) | |||
manager = manager_func(messager, req['cwd'], nodes) | |||
manager = manager_func(messager, req['cwd'], nodes, req['envs']) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@chenglch Maybe we can cache the whole req
dict in manager, I wonder we may have more variables in req from perl.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@robin2008 , dict is too flexible, maybe we need a context class to help store the global attributes like this in the future.
in | ||
nodes} | ||
nodes = ['node1', 'node2'] | ||
nodeinfo = {'node1':{'bmc':'10.0.0.1', 'bmcip':'10.0.0.1', 'username':'root', 'password': 'xxxxxx'}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What are this used for? nodes
and nodeinfo
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The client.py is used to debug python part code, "nodes" and "nodeinfo" are got from perl script and used in python script. We could modify here instead of running perl code to generate data for test.
Before we allow this to merge this have a few questions:
|
Hi @whowutwut , For 2, For "rpower" command, before release, we will check mgt if "mgt=openbmc2", run python script. If release, will not run perl version again. For 3, we can create "RedFish" class if we needed. In init.py will check the "module" to mapping class. For 4, we just could run python script for debug. |
@robin2008 whomever merges, please set the current milestone at the time of the merge so that it accurately reflects the correct release that the code went into and leverage GitHub queries |
Now it is still in the dev branch, and we will see the test result to decide if it is okay to in master branch. And for how to be delivered in master (just in xcat-server or separate package), it is still under discussion. |
#4554
UT:
UT with "xcatdebugmode" is 1:
UT when password is wrong:
500 all timeout 47.763s (Timeout is 30s)
500 all ok 22.039s
500 all 503 21.692s