1
|
|
|
"""Module with main classes related to Connections.""" |
2
|
1 |
|
import logging |
3
|
1 |
|
from enum import Enum |
4
|
1 |
|
from errno import EBADF, ENOTCONN |
5
|
1 |
|
from socket import SHUT_RDWR |
6
|
1 |
|
from socket import error as SocketError |
7
|
|
|
|
8
|
1 |
|
__all__ = ('Connection', 'ConnectionProtocol', 'ConnectionState') |
9
|
|
|
|
10
|
1 |
|
LOG = logging.getLogger(__name__) |
11
|
|
|
|
12
|
|
|
|
13
|
1 |
|
class ConnectionState(Enum): |
14
|
|
|
"""Enum of possible general connections states.""" |
15
|
|
|
|
16
|
1 |
|
NEW = 0 |
17
|
1 |
|
SETUP = 1 |
18
|
1 |
|
ESTABLISHED = 2 |
19
|
1 |
|
FAILED = 3 |
20
|
1 |
|
FINISHED = 4 |
21
|
|
|
|
22
|
|
|
|
23
|
1 |
|
class ConnectionProtocol: |
24
|
|
|
"""Class to hold simple protocol information for the connection.""" |
25
|
|
|
|
26
|
1 |
|
def __init__(self, name=None, version=None, state=None): |
27
|
|
|
"""Assign parameters to instance variables.""" |
28
|
1 |
|
self.name = name |
29
|
1 |
|
self.version = version |
30
|
1 |
|
self.state = state |
31
|
|
|
|
32
|
|
|
|
33
|
1 |
|
class Connection: |
34
|
|
|
"""Connection class to abstract a network connections.""" |
35
|
|
|
|
36
|
1 |
|
def __init__(self, address, port, socket, switch=None): |
37
|
|
|
"""Assign parameters to instance variables. |
38
|
|
|
|
39
|
|
|
Args: |
40
|
|
|
address (|hw_address|): Source address. |
41
|
|
|
port (int): Port number. |
42
|
|
|
socket (socket): socket. |
43
|
|
|
switch (:class:`~.Switch`): switch with this connection. |
44
|
|
|
""" |
45
|
1 |
|
self.address = address |
46
|
1 |
|
self.port = port |
47
|
1 |
|
self.socket = socket |
48
|
1 |
|
self.switch = switch |
49
|
1 |
|
self.state = ConnectionState.NEW |
50
|
1 |
|
self.protocol = ConnectionProtocol() |
51
|
1 |
|
self.remaining_data = b'' |
52
|
|
|
|
53
|
1 |
|
def __str__(self): |
54
|
1 |
|
return f"Connection({self.address!r}, {self.port!r})" |
55
|
|
|
|
56
|
1 |
|
def __repr__(self): |
57
|
1 |
|
return f"Connection({self.address!r}, {self.port!r}," + \ |
58
|
|
|
f" {self.socket!r}, {self.switch!r}, {self.state!r})" |
59
|
|
|
|
60
|
1 |
|
@property |
61
|
|
|
def state(self): |
62
|
|
|
"""Return the state of the connection.""" |
63
|
1 |
|
return self._state |
64
|
|
|
|
65
|
1 |
|
@state.setter |
66
|
|
|
def state(self, new_state): |
67
|
1 |
|
if new_state not in ConnectionState: |
68
|
1 |
|
raise Exception('Unknown State', new_state) |
69
|
|
|
# pylint: disable=attribute-defined-outside-init |
70
|
1 |
|
self._state = new_state |
71
|
|
|
# pylint: enable=attribute-defined-outside-init |
72
|
1 |
|
LOG.debug('Connection %s changed state: %s', |
73
|
|
|
self.id, self.state) |
74
|
|
|
|
75
|
1 |
|
@property |
76
|
|
|
def id(self): # pylint: disable=invalid-name |
77
|
|
|
"""Return id from Connection instance. |
78
|
|
|
|
79
|
|
|
Returns: |
80
|
|
|
string: Connection id. |
81
|
|
|
|
82
|
|
|
""" |
83
|
1 |
|
return (self.address, self.port) |
84
|
|
|
|
85
|
1 |
|
def send(self, buffer): |
86
|
|
|
"""Send a buffer message using the socket from the connection instance. |
87
|
|
|
|
88
|
|
|
Args: |
89
|
|
|
buffer (bytes): Message buffer that will be sent. |
90
|
|
|
""" |
91
|
1 |
|
try: |
92
|
1 |
|
if self.is_alive(): |
93
|
1 |
|
self.socket.sendall(buffer) |
94
|
1 |
|
except (OSError, SocketError) as exception: |
95
|
1 |
|
LOG.debug('Could not send packet. Exception: %s', exception) |
96
|
1 |
|
self.close() |
97
|
|
|
|
98
|
1 |
|
def close(self): |
99
|
|
|
"""Close the socket from connection instance.""" |
100
|
1 |
|
self.state = ConnectionState.FINISHED |
101
|
1 |
|
if self.switch and self.switch.connection is self: |
102
|
1 |
|
self.switch.connection = None |
103
|
|
|
|
104
|
1 |
|
LOG.debug('Shutting down Connection %s', self.id) |
105
|
|
|
|
106
|
1 |
|
try: |
107
|
1 |
|
self.socket.shutdown(SHUT_RDWR) |
108
|
1 |
|
self.socket.close() |
109
|
1 |
|
self.socket = None |
110
|
1 |
|
LOG.debug('Connection Closed: %s', self.id) |
111
|
1 |
|
except OSError as exception: |
112
|
1 |
|
if exception.errno not in (ENOTCONN, EBADF): |
113
|
1 |
|
raise exception |
114
|
1 |
|
except AttributeError as exception: |
115
|
1 |
|
LOG.debug('Socket Already Closed: %s', self.id) |
116
|
|
|
|
117
|
1 |
|
def is_alive(self): |
118
|
|
|
"""Return True if the connection socket is alive. False otherwise.""" |
119
|
1 |
|
return self.socket is not None and self.state not in ( |
120
|
|
|
ConnectionState.FINISHED, ConnectionState.FAILED) |
121
|
|
|
|
122
|
1 |
|
def is_new(self): |
123
|
|
|
"""Return True if the connection is new. False otherwise.""" |
124
|
1 |
|
return self.state == ConnectionState.NEW |
125
|
|
|
|
126
|
1 |
|
def is_established(self): |
127
|
|
|
"""Return True if the connection is established. False otherwise.""" |
128
|
1 |
|
return self.state == ConnectionState.ESTABLISHED |
129
|
|
|
|
130
|
1 |
|
def is_during_setup(self): |
131
|
|
|
"""Return True if the connection is in setup state. False otherwise.""" |
132
|
1 |
|
return self.state == ConnectionState.SETUP |
133
|
|
|
|
134
|
1 |
|
def set_established_state(self): |
135
|
|
|
"""Set the connection state to Established.""" |
136
|
1 |
|
self.state = ConnectionState.ESTABLISHED |
137
|
|
|
|
138
|
1 |
|
def set_setup_state(self): |
139
|
|
|
"""Set the connection state to Setup.""" |
140
|
1 |
|
self.state = ConnectionState.SETUP |
141
|
|
|
|
142
|
1 |
|
def update_switch(self, switch): |
143
|
|
|
"""Update switch with this instance of Connection. |
144
|
|
|
|
145
|
|
|
Args: |
146
|
|
|
switch (:class:`~.Switch`): switch instance. |
147
|
|
|
""" |
148
|
1 |
|
self.switch = switch |
149
|
|
|
self.switch.connection = self |
150
|
|
|
|