diff --git a/README.md b/README.md index e5e251c..f904e50 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,8 @@ Hole Detection feature. ### bv-ctl.py A command line application that can be used to issue general BroadView -commands for querying the supported features of BroadView, etc. +commands for querying the supported features of BroadView, cancelling +requests, etc. # Classes @@ -120,7 +121,31 @@ This command is used to retrieve the switch properties. ### GetSystemFeature This command is used to retrieve the current configuration of the System -module on the Agent. +module on the Agent. + +### ConfigureSystemFeature + +This command can be used to enable or disable heartbeat messages from the +agent, as well as specify a heartbeat interval. + +### CancelRequest + +All JSON RPC requests to the agent must be identified by a unique integer ID. +The reference implementation of the agent does not provide any support for +managing the ID space, ad it is left to client to ensure that IDs are unique. +broadview-lib supports this by maintaining an ID file that is shared across all +broadview-lib instances on the host and possibly the datacenter should the +ID file be placed in a shared file system. See commit +a72b75082ee961abcdc7da542da6767ee484560e for details on the implementation. + +The CancelRequest command takes the ID of a previous request as an argument +and cancels that command on the agent. To obtain this ID, refer to the JSON +output of the corresponding command for the cancellation-id field. Alternately, +Python code using configuration objects can get at this ID for a request by +calling getLastUsedSerial() member function of the object before making another +request with that object (the configuration objects record the last used ID +number within the object, so as long as the object itself is being used in a +thread safe manner, this ID will be correct). ## BST Configuration and Data Gathering diff --git a/broadview_lib/config/broadview.py b/broadview_lib/config/broadview.py index ff40ba5..5b0e7f7 100644 --- a/broadview_lib/config/broadview.py +++ b/broadview_lib/config/broadview.py @@ -80,6 +80,85 @@ class GetSystemFeature(AgentAPI): self.__json = res return status +class ConfigureSystemFeature(AgentAPI): + def __init__(self, host, port): + super(ConfigureSystemFeature, self).__init__() + self.setFeature("") + self.setHttpMethod("POST") + self.setHost(host) + self.setPort(port) + self.__asic_id = "1" + self.__json = None + self.__heartbeatEnable = False + self.__msgInterval = 30 + + def setASIC(self, val): + self.__asic_id = val + + def setHeartbeatEnable(self, val): + self.__heartbeatEnable = val + + def setMsgInterval(self, val): + self.__msgInterval = val + + def toDict(self): + ret = {} + params = {} + params["heartbeat-enable"] = 1 if self.__heartbeatEnable else 0 + params["msg-interval"] = self.__msgInterval + ret["asic-id"] = self.__asic_id + ret["params"] = params + ret["method"] = "configure-system-feature" + return ret + + def getJSON(self): + return self.__json + + def send(self, timeout=30): + status, json = self._send(self.toDict(), timeout) + if status == 200: + self.__version = json["version"] + res = json["result"] + self.__json = res + return status + +class CancelRequest(AgentAPI): + def __init__(self, host, port): + super(CancelRequest, self).__init__() + self.setFeature("") + self.setHttpMethod("POST") + self.setHost(host) + self.setPort(port) + self.__asic_id = "1" + self.__json = None + self.__requestId = None + + def setASIC(self, val): + self.__asic_id = val + + def setRequestId(self, val): + self.__requestId = val + + def toDict(self): + ret = {} + params = {} + params["request-id"] = self.__requestId + ret["asic-id"] = self.__asic_id + ret["params"] = params + ret["method"] = "cancel-request" + return ret + + def getJSON(self): + return self.__json + + def send(self, timeout=30): + status, json = self._send(self.toDict(), timeout) + if status == 200: + self.__version = json["version"] + res = json["result"] + self.__json = res + return status + class TestTAPIParams(unittest.TestCase): def setUp(self): @@ -133,5 +212,59 @@ class TestTAPIParams(unittest.TestCase): self.assertTrue(d["asic-id"] == "1") self.assertTrue(d["method"] == "get-system-feature") + def test_ConfigureSystemFeature(self): + + sw = BroadViewBSTSwitches() + if len(sw): + for x in sw: + host = x["ip"] + port = x["port"] + break + else: + host = "192.168.3.1" + port = 8080 + + x = ConfigureSystemFeature(host, port) + x.setHeartbeatEnable(False) + x.setMsgInterval(10) + d = x.toDict() + self.assertTrue("asic-id" in d) + self.assertTrue("params" in d) + self.assertTrue(d["params"]["msg-interval"] == 10) + self.assertTrue(d["params"]["heartbeat-enable"] == 0) + self.assertTrue("method" in d) + self.assertTrue(x.getFeature() == "") + self.assertTrue(x.getHttpMethod() == "POST") + self.assertTrue(x.getHost() == host) + self.assertTrue(x.getPort() == port) + self.assertTrue(d["asic-id"] == "1") + self.assertTrue(d["method"] == "configure-system-feature") + + def test_CancelRequest(self): + + sw = BroadViewBSTSwitches() + if len(sw): + for x in sw: + host = x["ip"] + port = x["port"] + break + else: + host = "192.168.3.1" + port = 8080 + + x = CancelRequest(host, port) + x.setRequestId(2) + d = x.toDict() + self.assertTrue("asic-id" in d) + self.assertTrue("params" in d) + self.assertTrue(d["params"]["request-id"] == 2) + self.assertTrue("method" in d) + self.assertTrue(x.getFeature() == "") + self.assertTrue(x.getHttpMethod() == "POST") + self.assertTrue(x.getHost() == host) + self.assertTrue(x.getPort() == port) + self.assertTrue(d["asic-id"] == "1") + self.assertTrue(d["method"] == "cancel-request") + if __name__ == "__main__": unittest.main() diff --git a/broadview_lib/tools/bv-ctl.py b/broadview_lib/tools/bv-ctl.py index 5fd93bf..cf70f02 100644 --- a/broadview_lib/tools/bv-ctl.py +++ b/broadview_lib/tools/bv-ctl.py @@ -25,12 +25,16 @@ class BroadViewCommand(): self.__cmds = { "get-switch-properties" : self.handleGetSwitchProperties, "get-system-feature" : self.handleGetSystemFeature, + "configure-system-feature" : self.handleConfigureSystemFeature, + "cancel-request" : self.handleCancelRequest, "help": self.handleHelp, } self.__help = { "get-switch-properties" : self.helpGetSwitchProperties, "get-system-feature" : self.helpGetSystemFeature, + "configure-system-feature" : self.helpConfigureSystemFeature, + "cancel-request" : self.helpCancelRequest, } def getTimeout(self, args): @@ -140,6 +144,72 @@ class BroadViewCommand(): def helpGetSystemFeature(self, name): print name + def handleConfigureSystemFeature(self, args): + usage = False + usage, asic, host, port = self.getASICHostPort(args) + usage, self._timeout = self.getTimeout(args) + if not usage: + x = ConfigureSystemFeature(host, port) + x.setASIC(asic) + x.setHeartbeatEnable("heartbeat_enable" in args) + for arg in args: + if "msg_interval:" in arg: + v = arg.split(":") + if len(v) == 2: + x.setMsgInterval(int(v[1])) + else: + print "invalid msg-interval argument" + usage = True + + status = x.send(timeout=self._timeout) + if status != 200: + print "failure: {}".format(status) + + ret = None + return usage, ret + + def helpConfigureSystemFeature(self, name): + print name, "[args]" + print + print "args:" + print + print " heartbeat_enable" + print " msg_interval:interval_in_seconds" + print + print "Note: if heartbeat_enable not specified, heartbeats will be disabled" + + def handleCancelRequest(self, args): + usage = False + usage, asic, host, port = self.getASICHostPort(args) + usage, self._timeout = self.getTimeout(args) + if not usage: + x = CancelRequest(host, port) + x.setASIC(asic) + for arg in args: + if "request_id:" in arg: + v = arg.split(":") + if len(v) == 2: + x.setRequestId(int(v[1])) + else: + print "invalid request id argument" + usage = True + + status = x.send(timeout=self._timeout) + if status != 200: + print "failure: {}".format(status) + + ret = None + return usage, ret + + def helpCancelRequest(self, name): + print name, "[args]" + print + print "args:" + print + print " request_id:id" + print "" + print "Note: see the cancellation-id member of the JSON output for the corresponding command for the ID." + def isCmd(self, cmd): return cmd in self.__cmds