PT LAG and ECMP resolution require JSON-RPC ID number as input for later cancellation
Change-Id: I3d0139f88df1407ea1f5c1678055be2bb852038b
This commit is contained in:
parent
363c4003e3
commit
a72b75082e
@ -15,9 +15,12 @@
|
|||||||
import json
|
import json
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from agentconnection import AgentConnection
|
from agentconnection import AgentConnection
|
||||||
|
from broadviewconfig import BroadViewLibConfig
|
||||||
|
import fcntl
|
||||||
|
|
||||||
class AgentAPI(object):
|
class AgentAPI(object):
|
||||||
__serial = 1
|
__serial = None
|
||||||
|
__cfg = BroadViewLibConfig()
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.__httpMethod = "POST"
|
self.__httpMethod = "POST"
|
||||||
self.__feature = None
|
self.__feature = None
|
||||||
@ -55,6 +58,88 @@ class AgentAPI(object):
|
|||||||
if self.__payload:
|
if self.__payload:
|
||||||
ret = self.__payload["method"]
|
ret = self.__payload["method"]
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
def getLastUsedSerial(self):
|
||||||
|
'''
|
||||||
|
Some requests to the agent, like PT's get lag resolution with a
|
||||||
|
non-zero collection interval, require knowlege of the JSON RPC
|
||||||
|
serial number because it is used as a key/index for later
|
||||||
|
cancelling the collection. While this violates layering, this is
|
||||||
|
no big deal if only one client (or client instance) ever connects
|
||||||
|
to an agent in the data center, and the client never generates
|
||||||
|
(within reason) a request with a duplicate ID that maps to an ID
|
||||||
|
used by the agent as an index.
|
||||||
|
|
||||||
|
When multiple clients, or even clients that are restarted enter
|
||||||
|
the picture, requiring clients to generate IDs that are
|
||||||
|
unique across applications and hosts is obviously more difficult.
|
||||||
|
Such difficulties are communication among clients within
|
||||||
|
the datacenter to ensure that no client uses the same IDs as any
|
||||||
|
other, and requiring each client to persist the last used ID in
|
||||||
|
case of crash or restart.
|
||||||
|
|
||||||
|
The strategy we take here to deal with this is to use a file to
|
||||||
|
store the last used ID, and protect that file with a SYSV file
|
||||||
|
lock. If a client wants to make a request, the lock is acquired,
|
||||||
|
the last used ID is read from the file, the ID is incremented by
|
||||||
|
1 and the file rewritten, followed by release of the lock. Sharing
|
||||||
|
across nodes in the datacenter can be acheived by all hosts using
|
||||||
|
a lock file that is located in shared filesystem location, such
|
||||||
|
as NFS.
|
||||||
|
|
||||||
|
Obviously all apps on a host or within the datacenter that access
|
||||||
|
a given agent must use the same ID file. If there are multiple agents
|
||||||
|
in the datacenter (which is going to be the case, as no interesting
|
||||||
|
datacenter is going to have one switch), there must be one (and only
|
||||||
|
one) id file per switch/agent.
|
||||||
|
|
||||||
|
We can eliminate this need for request ID management once the
|
||||||
|
agent adopts a method where it allocates and returns handles for
|
||||||
|
objects and tasks that it is managing to be used by clients to
|
||||||
|
refer to those same objects and tasks in subsequent requests.
|
||||||
|
|
||||||
|
This function exposes the last used ID so that the client can use
|
||||||
|
it in a subsequent (cancellation) request.
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
return AgentAPI.__serial
|
||||||
|
|
||||||
|
def getIDFileLocation(self):
|
||||||
|
return AgentAPI.__cfg.getRequestIDFile()
|
||||||
|
|
||||||
|
def getNextSerial(self):
|
||||||
|
|
||||||
|
'''
|
||||||
|
Get the next request ID, updating the shared ID file. The name
|
||||||
|
of the ID file comes from a configuration file named
|
||||||
|
/etc/broadviewlib.conf via a setting named json_rpc_id_path. Here
|
||||||
|
is an example conf file that sets the ID file path to /tmp/foo.txt:
|
||||||
|
|
||||||
|
[misc]
|
||||||
|
|
||||||
|
json_rpc_id_path = /tmp/foo.txt
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
filename = self.getIDFileLocation()
|
||||||
|
|
||||||
|
f = open(filename, "a").close() # create if needed
|
||||||
|
f = open(filename, "r+")
|
||||||
|
fd = f.fileno()
|
||||||
|
fcntl.lockf(fd, fcntl.LOCK_EX)
|
||||||
|
serial = f.read()
|
||||||
|
if len(serial) == 0:
|
||||||
|
serial = 1 # new file, read zero bytes, so initialize
|
||||||
|
else:
|
||||||
|
serial = int(serial)
|
||||||
|
f.seek(0)
|
||||||
|
f.write("{}".format(serial + 1))
|
||||||
|
fcntl.lockf(fd, fcntl.LOCK_UN)
|
||||||
|
f.close()
|
||||||
|
AgentAPI.__serial = serial # XXX not thread safe
|
||||||
|
return serial
|
||||||
|
|
||||||
def _send(self, o, timeout):
|
def _send(self, o, timeout):
|
||||||
self.__payload = {}
|
self.__payload = {}
|
||||||
@ -62,8 +147,7 @@ class AgentAPI(object):
|
|||||||
self.__payload["asic-id"] = o["asic-id"]
|
self.__payload["asic-id"] = o["asic-id"]
|
||||||
self.__payload["method"] = o["method"]
|
self.__payload["method"] = o["method"]
|
||||||
self.__payload["params"] = o["params"]
|
self.__payload["params"] = o["params"]
|
||||||
self.__payload["id"] = AgentAPI.__serial
|
self.__payload["id"] = self.getNextSerial()
|
||||||
AgentAPI.__serial = AgentAPI.__serial + 1
|
|
||||||
conn = AgentConnection(self.__host, self.__port, self.__feature, timeout)
|
conn = AgentConnection(self.__host, self.__port, self.__feature, timeout)
|
||||||
r = conn.makeRequest(self)
|
r = conn.makeRequest(self)
|
||||||
conn.close()
|
conn.close()
|
||||||
|
@ -16,6 +16,27 @@ import ConfigParser
|
|||||||
import json
|
import json
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
class BroadViewLibConfig():
|
||||||
|
cfg = None
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
if not BroadViewLibConfig.cfg:
|
||||||
|
try:
|
||||||
|
BroadViewLibConfig.cfg = ConfigParser.ConfigParser()
|
||||||
|
BroadViewLibConfig.cfg.read("/etc/broadviewlib.conf")
|
||||||
|
except:
|
||||||
|
BroadViewLibConfig.cfg = None
|
||||||
|
pass
|
||||||
|
|
||||||
|
def getRequestIDFile(self):
|
||||||
|
ret = "/tmp/bvserial.txt"
|
||||||
|
try:
|
||||||
|
ret = BroadViewLibConfig.cfg.get("misc", "json_rpc_id_path")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
class BroadViewBSTSwitches():
|
class BroadViewBSTSwitches():
|
||||||
cfg = None
|
cfg = None
|
||||||
bst_switches = None
|
bst_switches = None
|
||||||
|
@ -343,6 +343,8 @@ class GetPacketTraceLAGResolution(AgentAPI):
|
|||||||
rep = None
|
rep = None
|
||||||
if status == 200:
|
if status == 200:
|
||||||
self.__json = json["report"]
|
self.__json = json["report"]
|
||||||
|
print self.__json
|
||||||
|
self.__json[0]["cancellation-id"] = self.getLastUsedSerial()
|
||||||
rep = PTParser()
|
rep = PTParser()
|
||||||
rep.process(json)
|
rep.process(json)
|
||||||
else:
|
else:
|
||||||
@ -402,6 +404,7 @@ class GetPacketTraceECMPResolution(AgentAPI):
|
|||||||
rep = None
|
rep = None
|
||||||
if status == 200:
|
if status == 200:
|
||||||
self.__json = json["report"]
|
self.__json = json["report"]
|
||||||
|
self.__json[0]["cancellation-id"] = self.getLastUsedSerial()
|
||||||
rep = PTParser()
|
rep = PTParser()
|
||||||
rep.process(json)
|
rep.process(json)
|
||||||
else:
|
else:
|
||||||
@ -461,6 +464,7 @@ class GetPacketTraceProfile(AgentAPI):
|
|||||||
rep = None
|
rep = None
|
||||||
if status == 200:
|
if status == 200:
|
||||||
self.__json = json["report"]
|
self.__json = json["report"]
|
||||||
|
self.__json[0]["cancellation-id"] = self.getLastUsedSerial()
|
||||||
rep = PTParser()
|
rep = PTParser()
|
||||||
rep.process(json)
|
rep.process(json)
|
||||||
else:
|
else:
|
||||||
|
@ -236,10 +236,24 @@ class PTCommand():
|
|||||||
|
|
||||||
def handleCancelProfile(self, args):
|
def handleCancelProfile(self, args):
|
||||||
usage = False
|
usage = False
|
||||||
|
reqid = None
|
||||||
usage, asic, host, port = self.getASICHostPort(args)
|
usage, asic, host, port = self.getASICHostPort(args)
|
||||||
|
if not usage:
|
||||||
|
for arg in args:
|
||||||
|
if "id:" in arg:
|
||||||
|
v = arg.split(":")
|
||||||
|
if len(v) == 2:
|
||||||
|
reqid = int(v[1])
|
||||||
|
else:
|
||||||
|
print "invalid id: bad argument count"
|
||||||
|
usage = True
|
||||||
|
if reqid == None:
|
||||||
|
print "missing id argument"
|
||||||
|
usage = True
|
||||||
if not usage:
|
if not usage:
|
||||||
x = CancelPacketTraceProfile(host, port)
|
x = CancelPacketTraceProfile(host, port)
|
||||||
x.setASIC(asic)
|
x.setASIC(asic)
|
||||||
|
x.setId(reqid)
|
||||||
status = x.send(self._timeout)
|
status = x.send(self._timeout)
|
||||||
if status != 200:
|
if status != 200:
|
||||||
print "failure: {}".format(status)
|
print "failure: {}".format(status)
|
||||||
@ -249,13 +263,31 @@ class PTCommand():
|
|||||||
|
|
||||||
def helpCancelProfile(self, name):
|
def helpCancelProfile(self, name):
|
||||||
print name
|
print name
|
||||||
|
print
|
||||||
|
print "args:"
|
||||||
|
print
|
||||||
|
print " id:id (see cancellation-id output from get-profile command)"
|
||||||
|
print " id:id"
|
||||||
|
|
||||||
def handleCancelLAGResolution(self, args):
|
def handleCancelLAGResolution(self, args):
|
||||||
usage = False
|
usage = False
|
||||||
|
reqid = None
|
||||||
usage, asic, host, port = self.getASICHostPort(args)
|
usage, asic, host, port = self.getASICHostPort(args)
|
||||||
|
if not usage:
|
||||||
|
for arg in args:
|
||||||
|
if "id:" in arg:
|
||||||
|
v = arg.split(":")
|
||||||
|
if len(v) == 2:
|
||||||
|
reqid = int(v[1])
|
||||||
|
else:
|
||||||
|
print "invalid id: bad argument count"
|
||||||
|
usage = True
|
||||||
|
if reqid == None:
|
||||||
|
print "missing id argument"
|
||||||
if not usage:
|
if not usage:
|
||||||
x = CancelPacketTraceLAGResolution(host, port)
|
x = CancelPacketTraceLAGResolution(host, port)
|
||||||
x.setASIC(asic)
|
x.setASIC(asic)
|
||||||
|
x.setId(reqid)
|
||||||
status = x.send(self._timeout)
|
status = x.send(self._timeout)
|
||||||
if status != 200:
|
if status != 200:
|
||||||
print "failure: {}".format(status)
|
print "failure: {}".format(status)
|
||||||
@ -265,13 +297,30 @@ class PTCommand():
|
|||||||
|
|
||||||
def helpCancelLAGResolution(self, name):
|
def helpCancelLAGResolution(self, name):
|
||||||
print name
|
print name
|
||||||
|
print
|
||||||
|
print "args:"
|
||||||
|
print
|
||||||
|
print " id:id (see cancellation-id output from get-lag-resolution command"
|
||||||
|
|
||||||
def handleCancelECMPResolution(self, args):
|
def handleCancelECMPResolution(self, args):
|
||||||
usage = False
|
usage = False
|
||||||
|
reqid = None
|
||||||
usage, asic, host, port = self.getASICHostPort(args)
|
usage, asic, host, port = self.getASICHostPort(args)
|
||||||
|
if not usage:
|
||||||
|
for arg in args:
|
||||||
|
if "id:" in arg:
|
||||||
|
v = arg.split(":")
|
||||||
|
if len(v) == 2:
|
||||||
|
reqid = int(v[1])
|
||||||
|
else:
|
||||||
|
print "invalid id: bad argument count"
|
||||||
|
usage = True
|
||||||
|
if reqid == None:
|
||||||
|
print "missing id argument"
|
||||||
if not usage:
|
if not usage:
|
||||||
x = CancelPacketTraceECMPResolution(host, port)
|
x = CancelPacketTraceECMPResolution(host, port)
|
||||||
x.setASIC(asic)
|
x.setASIC(asic)
|
||||||
|
x.setId(reqid)
|
||||||
status = x.send(self._timeout)
|
status = x.send(self._timeout)
|
||||||
if status != 200:
|
if status != 200:
|
||||||
print "failure: {}".format(status)
|
print "failure: {}".format(status)
|
||||||
@ -281,6 +330,10 @@ class PTCommand():
|
|||||||
|
|
||||||
def helpCancelECMPResolution(self, name):
|
def helpCancelECMPResolution(self, name):
|
||||||
print name
|
print name
|
||||||
|
print
|
||||||
|
print "args:"
|
||||||
|
print
|
||||||
|
print " id:id (see cancellation-id output from get-ecmp-resolution command"
|
||||||
|
|
||||||
def handleCancelSendDropPacket(self, args):
|
def handleCancelSendDropPacket(self, args):
|
||||||
usage = False
|
usage = False
|
||||||
@ -450,6 +503,10 @@ class PTCommand():
|
|||||||
print " port-list:port[,port][,port]...[,port]"
|
print " port-list:port[,port][,port]...[,port]"
|
||||||
print " collection-interval: interval"
|
print " collection-interval: interval"
|
||||||
print " drop-packet: [0|1]"
|
print " drop-packet: [0|1]"
|
||||||
|
print
|
||||||
|
print "notes:"
|
||||||
|
print
|
||||||
|
print " use the cancellation-id output as id arg for cancel-lag-resolution command to cancel when collection-interval non-zero"
|
||||||
|
|
||||||
def handleGetECMPResolution(self, args):
|
def handleGetECMPResolution(self, args):
|
||||||
usage = False
|
usage = False
|
||||||
@ -510,6 +567,10 @@ class PTCommand():
|
|||||||
print " port-list:port[,port][,port]...[,port]"
|
print " port-list:port[,port][,port]...[,port]"
|
||||||
print " collection-interval: interval"
|
print " collection-interval: interval"
|
||||||
print " drop-packet: [0|1]"
|
print " drop-packet: [0|1]"
|
||||||
|
print
|
||||||
|
print "notes:"
|
||||||
|
print
|
||||||
|
print " use the cancellation-id output as id arg for cancel-ecmp-resolution command to cancel when collection-interval non-zero"
|
||||||
|
|
||||||
def handleGetProfile(self, args):
|
def handleGetProfile(self, args):
|
||||||
usage = False
|
usage = False
|
||||||
@ -575,6 +636,10 @@ class PTCommand():
|
|||||||
print " port-list:port[,port][,port]...[,port]"
|
print " port-list:port[,port][,port]...[,port]"
|
||||||
print " collection-interval: interval"
|
print " collection-interval: interval"
|
||||||
print " drop-packet: [0|1]"
|
print " drop-packet: [0|1]"
|
||||||
|
print
|
||||||
|
print "notes:"
|
||||||
|
print
|
||||||
|
print " use the cancellation-id output as id arg for cancel-profile command to cancel when collection-interval non-zero"
|
||||||
|
|
||||||
def handleGetDropReason(self, args):
|
def handleGetDropReason(self, args):
|
||||||
usage = False
|
usage = False
|
||||||
|
Loading…
Reference in New Issue
Block a user