1 # -*- coding: utf-8 -*-
4 # Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
5 # This file is part of openvim
8 # Licensed under the Apache License, Version 2.0 (the "License"); you may
9 # not use this file except in compliance with the License. You may obtain
10 # a copy of the License at
12 # http://www.apache.org/licenses/LICENSE-2.0
14 # Unless required by applicable law or agreed to in writing, software
15 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17 # License for the specific language governing permissions and limitations
20 # For those usages not covered by the Apache License, Version 2.0 please
21 # contact with: nfvlabs@tid.es
25 This is thread that interact with the dhcp server to get the IP addresses
27 __author__
="Pablo Montes, Alfonso Tierno"
28 __date__
="$4-Jan-2016 12:07:15$"
39 #TODO: insert a logging system
41 class dhcp_thread(threading
.Thread
):
42 def __init__(self
, dhcp_params
, db
, db_lock
, test
, dhcp_nets
, debug
=None):
44 Arguments: thread_info must be a dictionary with:
45 'dhcp_params' dhcp server parameters with the following keys:
46 mandatory : user, host, port, key, ifaces(interface name list of the one managed by the dhcp)
47 optional: password, key, port(22)
48 'db' 'db_lock': database class and lock for accessing it
49 'test': in test mode no acces to a server is done, and ip is invented
51 threading
.Thread
.__init
__(self
)
52 self
.name
= "dhcp_thread"
53 self
.dhcp_params
= dhcp_params
56 self
.db_lock
= db_lock
58 self
.dhcp_nets
= dhcp_nets
61 self
.mac_status
={} #dictionary of mac_address to retrieve information
64 #next_reading: time for the next trying to check ACTIVE status or IP
65 #created: time when it was added
66 #active: time when the VM becomes into ACTIVE status
69 self
.queueLock
= threading
.Lock()
70 self
.taskQueue
= Queue
.Queue(2000)
72 def ssh_connect(self
):
75 self
.ssh_conn
= paramiko
.SSHClient()
76 self
.ssh_conn
.set_missing_host_key_policy(paramiko
.AutoAddPolicy())
77 self
.ssh_conn
.load_system_host_keys()
78 self
.ssh_conn
.connect(self
.dhcp_params
["host"], port
=self
.dhcp_params
.get("port",22),
79 username
=self
.dhcp_params
["user"], password
=self
.dhcp_params
.get("password"), key_filename
=self
.dhcp_params
.get("key"),
81 except paramiko
.ssh_exception
.SSHException
as e
:
83 print self
.name
, ": ssh_connect ssh Exception:", text
85 def load_mac_from_db(self
):
86 #TODO get macs to follow from the database
87 print self
.name
, " load macs from db"
88 self
.db_lock
.acquire()
89 r
,c
= self
.db
.get_table(SELECT
=('mac','ip_address','nets.uuid as net_id', ),
90 FROM
='ports join nets on ports.net_id=nets.uuid',
91 WHERE_NOT
={'ports.instance_id': None, 'nets.provider': None})
92 self
.db_lock
.release()
96 print self
.name
, ": Error getting data from database:", c
99 if port
["net_id"] in self
.dhcp_nets
:
100 self
.mac_status
[ port
["mac"] ] = {"ip": port
["ip_address"], "next_reading": now
, "created": now
, "retries":0}
102 def insert_task(self
, task
, *aditional
):
104 self
.queueLock
.acquire()
105 task
= self
.taskQueue
.put( (task
,) + aditional
, timeout
=5)
106 self
.queueLock
.release()
109 return -1, "timeout inserting a task over host " + self
.name
112 print self
.name
, " starting, nets", self
.dhcp_nets
113 next_iteration
= time
.time() + 10
115 self
.load_mac_from_db()
117 self
.queueLock
.acquire()
118 if not self
.taskQueue
.empty():
119 task
= self
.taskQueue
.get()
122 self
.queueLock
.release()
126 if now
>= next_iteration
:
127 next_iteration
= self
.get_ip_from_dhcp()
133 print self
.name
, ": processing task add mac", task
[1]
135 self
.mac_status
[task
[1] ] = {"ip": None, "next_reading": now
, "created": now
, "retries":0}
137 elif task
[0] == 'del':
138 print self
.name
, ": processing task del mac", task
[1]
139 if task
[1] in self
.mac_status
:
140 del self
.mac_status
[task
[1] ]
141 elif task
[0] == 'exit':
142 print self
.name
, ": processing task exit"
146 print self
.name
, ": unknown task", task
151 self
.ssh_conn
.close()
152 except Exception as e
:
154 print self
.name
, ": terminate Exception:", text
155 print self
.name
, ": exit from host_thread"
157 def get_ip_from_dhcp(self
):
160 next_iteration
= now
+ 40000 # >10 hores
162 #print self.name, "Iteration"
163 for mac_address
in self
.mac_status
:
164 if now
< self
.mac_status
[mac_address
]["next_reading"]:
165 if self
.mac_status
[mac_address
]["next_reading"] < next_iteration
:
166 next_iteration
= self
.mac_status
[mac_address
]["next_reading"]
169 if self
.mac_status
[mac_address
].get("active") == None:
170 #check from db if already active
171 self
.db_lock
.acquire()
172 r
,c
= self
.db
.get_table(FROM
="ports as p join instances as i on p.instance_id=i.uuid",
173 WHERE
={"p.mac": mac_address
, "i.status": "ACTIVE"})
174 self
.db_lock
.release()
176 self
.mac_status
[mac_address
]["active"] = now
177 self
.mac_status
[mac_address
]["next_reading"] = (int(now
)/2 +1)* 2
178 print self
.name
, "mac %s VM ACTIVE" % (mac_address
)
179 self
.mac_status
[mac_address
]["retries"] = 0
181 #print self.name, "mac %s VM INACTIVE" % (mac_address)
182 if now
- self
.mac_status
[mac_address
]["created"] > 300:
183 #modify Database to tell openmano that we can not get dhcp from the machine
184 if not self
.mac_status
[mac_address
].get("ip"):
185 self
.db_lock
.acquire()
186 r
,c
= self
.db
.update_rows("ports", {"ip_address": "0.0.0.0"}, {"mac": mac_address
})
187 self
.db_lock
.release()
188 self
.mac_status
[mac_address
]["ip"] = "0.0.0.0"
189 print self
.name
, "mac %s >> set to 0.0.0.0 because of timeout" % (mac_address
)
190 self
.mac_status
[mac_address
]["next_reading"] = (int(now
)/60 +1)* 60
192 self
.mac_status
[mac_address
]["next_reading"] = (int(now
)/6 +1)* 6
193 if self
.mac_status
[mac_address
]["next_reading"] < next_iteration
:
194 next_iteration
= self
.mac_status
[mac_address
]["next_reading"]
199 if self
.mac_status
[mac_address
]["retries"]>random
.randint(10,100): #wait between 10 and 100 seconds to produce a fake IP
200 content
= self
.get_fake_ip()
203 elif self
.dhcp_params
["host"]=="localhost":
205 command
= ['get_dhcp_lease.sh', mac_address
]
206 content
= subprocess
.check_output(command
)
207 except Exception as e
:
209 print self
.name
, ": get_ip_from_dhcp subprocess Exception", text
213 if not self
.ssh_conn
:
215 command
= 'get_dhcp_lease.sh ' + mac_address
216 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
217 content
= stdout
.read()
218 except paramiko
.ssh_exception
.SSHException
as e
:
220 print self
.name
, ": get_ip_from_dhcp: ssh_Exception:", text
223 except Exception as e
:
225 print self
.name
, ": get_ip_from_dhcp: Exception:", text
230 self
.mac_status
[mac_address
]["ip"] = content
232 self
.db_lock
.acquire()
233 r
,c
= self
.db
.update_rows("ports", {"ip_address": content
}, {"mac": mac_address
})
234 self
.db_lock
.release()
236 print self
.name
, ": Database update error:", c
238 self
.mac_status
[mac_address
]["retries"] = 0
239 self
.mac_status
[mac_address
]["next_reading"] = (int(now
)/3600 +1)* 36000 # 10 hores
240 if self
.mac_status
[mac_address
]["next_reading"] < next_iteration
:
241 next_iteration
= self
.mac_status
[mac_address
]["next_reading"]
242 print self
.name
, "mac %s >> %s" % (mac_address
, content
)
245 self
.mac_status
[mac_address
]["retries"] +=1
246 #next iteration is every 2sec at the beginning; every 5sec after a minute, every 1min after a 5min
247 if now
- self
.mac_status
[mac_address
]["active"] > 120:
248 #modify Database to tell openmano that we can not get dhcp from the machine
249 if not self
.mac_status
[mac_address
].get("ip"):
250 self
.db_lock
.acquire()
251 r
,c
= self
.db
.update_rows("ports", {"ip_address": "0.0.0.0"}, {"mac": mac_address
})
252 self
.db_lock
.release()
253 self
.mac_status
[mac_address
]["ip"] = "0.0.0.0"
254 print self
.name
, "mac %s >> set to 0.0.0.0 because of timeout" % (mac_address
)
256 if now
- self
.mac_status
[mac_address
]["active"] > 60:
257 self
.mac_status
[mac_address
]["next_reading"] = (int(now
)/6 +1)* 6
258 elif now
- self
.mac_status
[mac_address
]["active"] > 300:
259 self
.mac_status
[mac_address
]["next_reading"] = (int(now
)/60 +1)* 60
261 self
.mac_status
[mac_address
]["next_reading"] = (int(now
)/2 +1)* 2
263 if self
.mac_status
[mac_address
]["next_reading"] < next_iteration
:
264 next_iteration
= self
.mac_status
[mac_address
]["next_reading"]
265 return next_iteration
267 def get_fake_ip(self
):
268 fake_ip
= "192.168.%d.%d" % (random
.randint(1,254), random
.randint(1,254) )
270 #check not already provided
272 for mac_address
in self
.mac_status
:
273 if self
.mac_status
[mac_address
]["ip"] == fake_ip
:
280 #EXAMPLE of bash script that must be available at the DHCP server for "isc-dhcp-server" type
281 # $ cat ./get_dhcp_lease.sh
284 # ($1=="lease" && $3=="{"){ lease=$2; active="no"; found="no" }
285 # ($1=="binding" && $2=="state" && $3=="active;"){ active="yes" }
286 # ($1=="hardware" && $2=="ethernet" && $3==tolower("'$1';")){ found="yes" }
287 # ($1=="client-hostname"){ name=$2 }
288 # ($1=="}"){ if (active=="yes" && found=="yes"){ target_lease=lease; target_name=name}}
289 # END{printf("%s", target_lease)} #print target_name
290 # ' /var/lib/dhcp/dhcpd.leases