Skip to content

Commit 4cae553

Browse files
committedOct 28, 2014
pc_rpc: server identification support
1 parent 8d305e3 commit 4cae553

File tree

8 files changed

+97
-20
lines changed

8 files changed

+97
-20
lines changed
 

‎artiq/devices/pdq2/pdq2-client

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def _get_args():
5454

5555
def _main():
5656
args = _get_args()
57-
dev = Client(args.server, args.port)
57+
dev = Client(args.server, args.port, "pdq2")
5858
dev.init()
5959

6060
if args.reset:

‎artiq/devices/pdq2/pdq2-controller

+2-1
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,8 @@ def main():
355355

356356
dev = Pdq2(serial=args.serial)
357357
try:
358-
simple_server_loop(dev, args.bind, args.port)
358+
simple_server_loop(dev, "pdq2", args.bind, args.port,
359+
id_parameters="serial="+str(args.serial))
359360
finally:
360361
dev.close()
361362

‎artiq/management/pc_rpc.py

+52-12
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,16 @@
1414

1515

1616
class RemoteError(Exception):
17-
"""Exception raised when a RPC failed or raised an exception on the
18-
remote (server) side.
17+
"""Raised when a RPC failed or raised an exception on the remote (server)
18+
side.
19+
20+
"""
21+
pass
22+
23+
24+
class IncompatibleServer(Exception):
25+
"""Raised by the client when attempting to connect to a server that does
26+
not have the expected type.
1927
2028
"""
2129
pass
@@ -44,10 +52,21 @@ class Client:
4452
hostname or a IPv4 or IPv6 address (see
4553
``socket.create_connection`` in the Python standard library).
4654
:param port: TCP port to use.
55+
:param expected_id_type: Server type to expect. ``IncompatibleServer`` is
56+
raised when the types do not match. Use ``None`` to accept any server
57+
type.
4758
4859
"""
49-
def __init__(self, host, port):
60+
def __init__(self, host, port, expected_id_type):
5061
self.socket = socket.create_connection((host, port))
62+
self._identify(expected_id_type)
63+
64+
def get_rpc_id(self):
65+
"""Returns a dictionary containing the identification information of
66+
the server.
67+
68+
"""
69+
return self._server_identification
5170

5271
def close_rpc(self):
5372
"""Closes the connection to the RPC server.
@@ -57,8 +76,7 @@ def close_rpc(self):
5776
"""
5877
self.socket.close()
5978

60-
def _do_rpc(self, name, args, kwargs):
61-
obj = {"action": "call", "name": name, "args": args, "kwargs": kwargs}
79+
def _send_recv(self, obj):
6280
line = pyon.encode(obj) + "\n"
6381
self.socket.sendall(line.encode())
6482

@@ -69,6 +87,19 @@ def _do_rpc(self, name, args, kwargs):
6987
break
7088
buf += more.decode()
7189
obj = pyon.decode(buf)
90+
91+
return obj
92+
93+
def _identify(self, expected_id_type):
94+
obj = {"action": "identify"}
95+
self._server_identification = self._send_recv(obj)
96+
if (expected_id_type is not None
97+
and self._server_identification["type"] != expected_id_type):
98+
raise IncompatibleServer
99+
100+
def _do_rpc(self, name, args, kwargs):
101+
obj = {"action": "call", "name": name, "args": args, "kwargs": kwargs}
102+
obj = self._send_recv(obj)
72103
if obj["result"] == "ok":
73104
return obj["ret"]
74105
elif obj["result"] == "error":
@@ -94,10 +125,16 @@ class Server:
94125
95126
:param target: Object providing the RPC methods to be exposed to the
96127
client.
128+
:param id_type: A string identifying the server type. Clients use it to
129+
verify that they are connected to the proper server.
130+
:param id_parameters: An optional human-readable string giving more
131+
information about the parameters of the server.
97132
98133
"""
99-
def __init__(self, target):
134+
def __init__(self, target, id_type, id_parameters=None):
100135
self.target = target
136+
self.id_type = id_type
137+
self.id_parameters = id_parameters
101138
self._client_tasks = set()
102139

103140
@asyncio.coroutine
@@ -156,22 +193,25 @@ def _handle_connection_task(self, reader, writer):
156193
"traceback": traceback.format_exc()}
157194
line = pyon.encode(obj) + "\n"
158195
writer.write(line.encode())
196+
elif action == "identify":
197+
obj = {"type": self.id_type}
198+
if self.id_parameters is not None:
199+
obj["parameters"] = self.id_parameters
200+
line = pyon.encode(obj) + "\n"
201+
writer.write(line.encode())
159202
finally:
160203
writer.close()
161204

162205

163-
def simple_server_loop(target, host, port):
206+
def simple_server_loop(target, id_type, host, port, id_parameters=None):
164207
"""Runs a server until an exception is raised (e.g. the user hits Ctrl-C).
165208
166-
:param target: Object providing the RPC methods to be exposed to the
167-
client.
168-
:param host: Bind address of the server.
169-
:param port: TCP port to bind to.
209+
See ``Server`` for a description of the parameters.
170210
171211
"""
172212
loop = asyncio.get_event_loop()
173213
try:
174-
server = Server(target)
214+
server = Server(target, id_type, id_parameters)
175215
loop.run_until_complete(server.start(host, port))
176216
try:
177217
loop.run_forever()

‎doc/manual/writing_a_driver.rst

+7-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ To turn it into a server, we use :class:`artiq.management.pc_rpc`. Import the fu
2323
and add a ``main`` function that is run when the program is executed: ::
2424

2525
def main():
26-
simple_server_loop(Hello(), "::1", 7777)
26+
simple_server_loop(Hello(), "hello", "::1", 7777)
2727

2828
if __name__ == "__main__":
2929
main()
@@ -49,6 +49,11 @@ and verify that you can connect to the TCP port: ::
4949

5050
:tip: Use the key combination Ctrl-AltGr-9 to get the ``telnet>`` prompt, and enter ``close`` to quit Telnet. Quit the controller with Ctrl-C.
5151

52+
Also verify that you can get the type of the server (the "hello" string passed to ``simple_server_loop``) using the ``identify-controller`` program from the ARTIQ front-end tools: ::
53+
54+
./identify-controller ::1 7777
55+
Type: hello
56+
5257
The client
5358
----------
5459

@@ -62,7 +67,7 @@ Create a ``hello-client`` file with the following contents: ::
6267

6368

6469
def main():
65-
remote = Client("::1", 7777)
70+
remote = Client("::1", 7777, "hello")
6671
try:
6772
remote.message("Hello World!")
6873
finally:

‎frontend/artiq

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def _get_args():
2222

2323
def main():
2424
args = _get_args()
25-
remote = Client(args.server, args.port)
25+
remote = Client(args.server, args.port, "master")
2626
try:
2727
for path, name, timeout in args.run_once:
2828
remote.run_once(

‎frontend/artiqd

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def main():
2525
scheduler = Scheduler()
2626
loop.run_until_complete(scheduler.start())
2727
try:
28-
server = Server(scheduler)
28+
server = Server(scheduler, "master")
2929
loop.run_until_complete(server.start(args.bind, args.port))
3030
try:
3131
loop.run_forever()

‎frontend/identify-controller

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
5+
from artiq.management.pc_rpc import Client
6+
7+
8+
def _get_args():
9+
parser = argparse.ArgumentParser(
10+
description="ARTIQ controller identification tool")
11+
parser.add_argument("server",
12+
help="hostname or IP of the controller to connect to")
13+
parser.add_argument("port", type=int,
14+
help="TCP port to use to connect to the controller")
15+
return parser.parse_args()
16+
17+
18+
def main():
19+
args = _get_args()
20+
remote = Client(args.server, args.port, None)
21+
try:
22+
ident = remote.get_rpc_id()
23+
finally:
24+
remote.close_rpc()
25+
print("Type: " + ident["type"])
26+
if "parameters" in ident:
27+
print("Parameters: " + ident["parameters"])
28+
29+
if __name__ == "__main__":
30+
main()

‎test/pc_rpc.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ def test_echo(self):
2525
for attempt in range(100):
2626
time.sleep(.2)
2727
try:
28-
remote = pc_rpc.Client(test_address, test_port)
28+
remote = pc_rpc.Client(test_address, test_port,
29+
"test")
2930
except ConnectionRefusedError:
3031
pass
3132
else:
@@ -65,7 +66,7 @@ def run_server():
6566
loop = asyncio.get_event_loop()
6667
try:
6768
echo = Echo()
68-
server = pc_rpc.Server(echo)
69+
server = pc_rpc.Server(echo, "test")
6970
loop.run_until_complete(server.start(test_address, test_port))
7071
try:
7172
loop.run_until_complete(echo.wait_quit())

0 commit comments

Comments
 (0)
Please sign in to comment.