Open Kilda Java Documentation
model.py
Go to the documentation of this file.
1 # Copyright 2017 Telstra Open Source
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 #
15 
16 import collections
17 import enum
18 import ipaddress
19 import itertools
20 import json
21 import socket
22 import uuid
23 import weakref
24 
25 import pyroute2
26 
27 from kilda.traffexam import exc
28 
29 
30 class TargetIface(object):
31  @classmethod
32  def new_from_cli(cls, name):
33  try:
34  value = cls(name)
35  except ValueError as e:
36  raise exc.InvalidTargetIfaceError(name, e.args[0])
37  return value
38 
39  def __init__(self, name):
40  self.name = name
41 
42  ip = pyroute2.IPRoute()
43  idx = ip.link_lookup(ifname=self.name)
44  if not idx:
45  raise ValueError('network interface not found.'.format(name), name)
46  self.index = idx[0]
47 
48 
49 class BindAddress(object):
50  @classmethod
51  def new_from_cli(cls, raw):
52  try:
53  address, port = raw.rsplit(':', 1)
54  except ValueError:
56  raw, 'expect address as HOST:PORT pair')
57 
58  try:
59  value = cls(address, port)
60  except ValueError as e:
61  raise exc.InvalidBindAddressError(raw, str(e))
62  return value
63 
64  def __init__(self, address, port):
65  self.address = address
66  self.port = self.convert_port(port)
67 
68  @staticmethod
69  def convert_address(address):
70  try:
71  address = socket.gethostbyname(address)
72  except socket.gaierror as e:
73  raise ValueError(
74  'Unresolvable host name {!r}: {}'.format(address, e))
75  return address
76 
77  @staticmethod
78  def convert_port(port):
79  try:
80  port = int(port)
81  except ValueError:
82  pass
83  return port
84 
85 
86 class Default(object):
87  def __init__(self, value, produce=False, override_none=True):
88  self._resolve_cache = weakref.WeakKeyDictionary()
89  self.value = value
90  self.produce = produce
91  self.override_none = override_none
92 
93  def __get__(self, instance, owner):
94  if instance is None:
95  return self
96 
97  value = self.value
98  if self.produce:
99  value = value()
100 
101  setattr(instance, self._resolve_name(owner), value)
102  return value
103 
104  def is_filled(self, instance):
105  name = self._resolve_name(type(instance))
106  data = vars(instance)
107  return name in data
108 
109  def _resolve_name(self, owner):
110  try:
111  return self._resolve_cache[owner]
112  except KeyError:
113  pass
114 
115  for name in dir(owner):
116  try:
117  attr = getattr(owner, name)
118  except AttributeError:
119  continue
120  if attr is not self:
121  continue
122  break
123  else:
124  raise RuntimeError(
125  '{!r} Unable to resolve bounded name (UNREACHABLE)'.format(
126  self))
127 
128  self._resolve_cache[owner] = name
129  return name
130 
131 
132 class Abstract(object):
133  pack_exclude = frozenset()
134 
135  def __init__(self, **fields):
136  cls = type(self)
137  extra = set()
138  for name in fields:
139  extra.add(name)
140  try:
141  attr = getattr(cls, name)
142  except AttributeError:
143  continue
144  if not isinstance(attr, Default):
145  continue
146 
147  extra.remove(name)
148 
149  if attr.override_none and fields[name] is None:
150  continue
151  setattr(self, name, fields[name])
152 
153  if extra:
154  raise TypeError('{!r} got unknown arguments: "{}"'.format(
155  self, '", "'.join(sorted(extra))))
156 
157  def __str__(self):
158  return '<{}:{}>'.format(
159  type(self).__name__,
160  json.dumps(self.pack(), sort_keys=True, cls=JSONEncoder))
161 
162  def pack(self):
163  payload = vars(self).copy()
164 
165  cls = type(self)
166  for name in dir(cls):
167  if name.startswith('_'):
168  continue
169  if name in payload:
170  continue
171 
172  attr = getattr(cls, name)
173  if not isinstance(attr, Default):
174  continue
175  payload[name] = getattr(self, name)
176 
177  for name in tuple(payload):
178  if not name.startswith('_') and name not in self.pack_exclude:
179  continue
180  del payload[name]
181 
182  return payload
183 
184 
186  idnr = Default(uuid.uuid1, produce=True)
187 
188 
190  index = Default(None)
191  vlan_tag = Default(None)
192  pack_exclude = frozenset(('index', ))
193 
194  def __init__(self, name, **fields):
195  super().__init__(**fields)
196  self.name = name
197 
198  def get_ipdb_key(self):
199  key = self.index
200  if key is None:
201  key = self.name
202  return key
203 
204 
205 class VLAN(Abstract):
206  iface = Default(None)
207 
208  def __init__(self, tag, **fields):
209  super().__init__(**fields)
210  self.tag = tag
211 
212  def set_iface(self, iface):
213  self.iface = iface
214  return self
215 
216 
218  iface = Default(None)
219  prefix = Default(None)
220 
221  pack_exclude = frozenset(('iface', ))
222 
223  def __init__(self, address, **fields):
224  super().__init__(**fields)
225  if not self.prefix:
226  self.address, self.prefix = self.unpack_cidr(address)
227  else:
228  self.address = address
229  self.network = ipaddress.IPv4Network(
230  '{}/{}'.format(self.address, self.prefix), strict=False)
231  self._ports = PortQueue(6000, 7000)
232 
233  def pack(self):
234  payload = super().pack()
235  payload.pop('network')
236  payload['vlan'] = self.iface.vlan_tag
237  return payload
238 
239  def alloc_port(self):
240  return next(self._ports)
241 
242  def free_port(self, port):
243  self._ports.free(port)
244 
245  @staticmethod
246  def unpack_cidr(cidr):
247  try:
248  addr, prefix = cidr.rsplit('/', 1)
249  prefix = int(prefix, 10)
250  except ValueError:
251  raise ValueError(
252  'Invalid address {!r}, expect ipv4/prefix value'.format(cidr))
253  return addr, prefix
254 
255 
257  bind_address = Default(None)
258  proc = None
259 
260  pack_exclude = frozenset(('proc', ))
261 
262  def set_proc(self, proc):
263  self.proc = proc
264 
265  def pack(self):
266  payload = super().pack()
267 
268  kind = endpoint_type_map[type(self)]
269  payload['type'] = kind.value
270  if self.bind_address:
271  payload['bind_address'] = self.bind_address.idnr
272 
273  return payload
274 
275 
277  bind_port = Default(None)
278 
279  def __init__(self, bind_address, **fields):
280  super().__init__(**fields)
281  self.bind_address = bind_address
282 
283 
285  bandwidth = Default(1024)
286  burst_pkt = Default(0)
287  time = Default(10)
288  use_udp = Default(False)
289 
290  def __init__(self, remote_address, **fields):
291  super().__init__(**fields)
292  self.remote_address = remote_address
293 
294  if not self.time:
295  raise ValueError('Invalid time: {!r}'.format(self.time))
296 
297 
299  def __init__(self, address, port, **fields):
300  super().__init__(**fields)
301  self.address = address
302  self.port = port
303 
304 
305 class EndpointKind(enum.Enum):
306  producer = 'producer'
307  consumer = 'consumer'
308 
309 
310 endpoint_klass_map = {
311  EndpointKind.producer: ProducerEndpoint,
312  EndpointKind.consumer: ConsumerEndpoint}
313 endpoint_type_map = {v: k for k, v in endpoint_klass_map.items()}
314 
315 
316 class PortQueue(collections.Iterator):
317  def __init__(self, lower=1024, upper=65535):
318  self.lower = lower
319  self.upper = upper
320  self.counter = itertools.count(lower)
321  self.released = set()
322 
323  def __next__(self):
324  try:
325  item = self.released.pop()
326  except KeyError:
327  item = next(self.counter)
328  if self.upper <= item:
329  raise StopIteration
330  return item
331 
332  def free(self, item):
333  if not self.lower <= item < self.upper:
334  raise ValueError
335  self.released.add(item)
336 
337 
338 class JSONEncoder(json.JSONEncoder):
339  def default(self, o):
340  if isinstance(o, Abstract):
341  value = o.pack()
342  elif isinstance(o, uuid.UUID):
343  value = str(o)
344  else:
345  value = super().default(o)
346  return value
def __init__(self, fields)
Definition: model.py:135
def __init__(self, address, fields)
Definition: model.py:223
def __init__(self, address, port, fields)
Definition: model.py:299
def __init__(self, name)
Definition: model.py:39
def free(self, item)
Definition: model.py:332
def set_proc(self, proc)
Definition: model.py:262
def new_from_cli(cls, name)
Definition: model.py:32
def __init__(self, name, fields)
Definition: model.py:194
def __init__(self, remote_address, fields)
Definition: model.py:290
def __init__(self, bind_address, fields)
Definition: model.py:279
def __get__(self, instance, owner)
Definition: model.py:93
def __init__(self, address, port)
Definition: model.py:64
def __init__(self, tag, fields)
Definition: model.py:208
def free_port(self, port)
Definition: model.py:242
def set_iface(self, iface)
Definition: model.py:212
def is_filled(self, instance)
Definition: model.py:104
def _resolve_name(self, owner)
Definition: model.py:109
def __init__(self, value, produce=False, override_none=True)
Definition: model.py:87
def convert_address(address)
Definition: model.py:69
def new_from_cli(cls, raw)
Definition: model.py:51
def __init__(self, lower=1024, upper=65535)
Definition: model.py:317