-
Notifications
You must be signed in to change notification settings - Fork 26
Expand file tree
/
Copy pathsqlite_ns.py
More file actions
132 lines (104 loc) · 4.74 KB
/
Copy pathsqlite_ns.py
File metadata and controls
132 lines (104 loc) · 4.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import sqlite3
import time
import tempfile
import os
import collections
import socket
NSInfo = collections.namedtuple('NSInfo', ('name', 'address', 'port', 'creation_time', 'URI'))
def make_info(info):
"""Translate our results into something that looks like ``zeroconf.ServiceInfo`` - specifically convert the ip address
into the expected format"""
name, address, port, creation_time, URI = info
return NSInfo(name, socket.inet_aton(address), port, creation_time, URI)
def is_port_open(ip, port):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(10)
try:
s.connect((ip, int(port)))
s.shutdown(socket.SHUT_RDWR)
return True
except:
return False
finally:
s.close()
class SQLiteNS(object):
"""This spoofs (but does not fully re-implement) a Pyro.naming.Nameserver using a locally held sqlite database
In this case we are simply using sqlite as a key-value store which handles concurrent access across processes.
"""
def __init__(self, protocol='_pyme-sql'):
self._protocol = protocol
self._dbname = os.path.join(tempfile.gettempdir(), '%s.sqlite' %self._protocol)
with sqlite3.connect(self._dbname) as conn:
# sqlite wants single quotes around literal strings and the sqlite package just became pickier about this
# see also https://www.reddit.com/r/freebsd/comments/1chb82b/sqlite3_pkg_just_became_stricter_with_quoting/
tableNames = [a[0] for a in conn.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall()]
if not 'dns' in tableNames:
try:
conn.execute("CREATE TABLE dns (name TEXT, address TEXT, port INTEGER, creation_time FLOAT, URI TEXT)")
except sqlite3.OperationalError as e:
# catch race condition where table is created in another process
if 'table dns already exists' in str(e):
pass
else:
raise
self.remove_inactive_services()
def register(self, name, URI):
""" This only exists for principally for pyro compatibility - use register_service for non pyro uses
Takes a Pyro URI object
"""
with sqlite3.connect(self._dbname) as conn:
conn.execute("INSERT INTO dns VALUES(?, ?, ?, ?, ?)", (name, URI.address, URI.port, time.time(), str(URI)))
conn.commit()
# @property
# def advertised_services(self):
# return self.listener.advertised_services
def get_advertised_services(self):
with sqlite3.connect(self._dbname) as conn:
names = [r[0] for r in conn.execute("SELECT DISTINCT name FROM dns").fetchall()]
services = [(n, make_info(conn.execute("SELECT * FROM dns WHERE name=? ORDER BY creation_time DESC ", (n,)).fetchone())) for n in names]
return services
def register_service(self, name, address, port, desc={}, URI=''):
"""
Parameters
----------
name : str
"""
with sqlite3.connect(self._dbname) as conn:
conn.execute("INSERT INTO dns VALUES(?, ?, ?, ?, ?)", (name, address, port, time.time(), URI))
conn.commit()
def unregister(self, name):
"""
Parameters
----------
name : str
must be the same service name used to register
"""
with sqlite3.connect(self._dbname) as conn:
conn.execute("DELETE FROM dns WHERE name=? ", (name,))
conn.commit()
def resolve(self, name):
""" mainly for PYRO compatibility - returns a string version of the URI"""
with sqlite3.connect(self._dbname) as conn:
uri = conn.execute("SELECT URI FROM dns WHERE name=? ORDER BY creation_time DESC ", (name,)).fetchone()
return uri[0]
def list(self, filterby=''):
with sqlite3.connect(self._dbname) as conn:
return [r[0] for r in conn.execute("SELECT DISTINCT name FROM dns").fetchall()]
def remove_inactive_services(self):
#test to see if we can open the port, if not, remove
for name, info in self.get_advertised_services():
if not is_port_open(socket.inet_ntoa(info.address), info.port):
self.unregister(name)
nsd = {}
import threading
sqlite_ns_lock = threading.Lock()
def getNS(protocol='_pyme-pyro'):
#TODO - cache connections per thread?
with sqlite_ns_lock:
try:
ns = nsd[protocol]
except KeyError:
ns = SQLiteNS(protocol)
nsd[protocol] = ns
#time.sleep(1) #wait for the services to come up
return ns