1 #!/usr/bin/python2.7 |
|
2 # |
|
3 # Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. |
|
4 # |
|
5 # Licensed under the Apache License, Version 2.0 (the "License"); you may |
|
6 # not use this file except in compliance with the License. You may obtain |
|
7 # a copy of the License at |
|
8 # |
|
9 # http://www.apache.org/licenses/LICENSE-2.0 |
|
10 # |
|
11 # Unless required by applicable law or agreed to in writing, software |
|
12 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
|
13 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
|
14 # License for the specific language governing permissions and limitations |
|
15 # under the License. |
|
16 |
|
17 # |
|
18 # This script migrates the network, subnet and port information from EVS DB to |
|
19 # neutron-server DB. It also re-creates routers and floatingips tables with |
|
20 # Neutron's l3 schema. This script needs to be run for the proper upgrade of |
|
21 # Neutron from Havana to Juno release. |
|
22 # |
|
23 |
|
24 import ConfigParser |
|
25 import time |
|
26 |
|
27 from oslo.config import cfg |
|
28 from oslo.db import exception as excp |
|
29 from oslo.db import options as db_options |
|
30 import rad.bindings.com.oracle.solaris.rad.evscntl as evsc |
|
31 import rad.connect as radcon |
|
32 import sqlalchemy as sa |
|
33 from sqlalchemy import MetaData, sql |
|
34 from sqlalchemy.orm import sessionmaker |
|
35 from sqlalchemy.schema import DropConstraint |
|
36 |
|
37 from neutron import context as ctx |
|
38 from neutron.db import common_db_mixin, model_base |
|
39 from neutron.plugins.evs.migrate import havana_api |
|
40 |
|
41 |
|
42 def create_db_network(nw, engine, ext_ro): |
|
43 ''' Method for creating networks table in the neutron-server DB |
|
44 Input params: |
|
45 @nw - Dictionary with values from EVS DB |
|
46 @engine - SQL engine |
|
47 @ext_ro - External router |
|
48 ''' |
|
49 # Importing locally because these modules end up importing neutron.wsgi |
|
50 # which causes RAD to hang |
|
51 from neutron.db import db_base_plugin_v2 |
|
52 from neutron.db import external_net_db as ext_net |
|
53 model_base.BASEV2.metadata.bind = engine |
|
54 for _none in range(60): |
|
55 try: |
|
56 model_base.BASEV2.metadata.create_all(engine) |
|
57 break |
|
58 except sa.exc.OperationalError as err: |
|
59 # mysql is not ready. sleep for 2 more seconds |
|
60 time.sleep(2) |
|
61 else: |
|
62 print "Unable to connect to MySQL: %s" % err |
|
63 print ("Please verify MySQL is properly configured and online " |
|
64 "before using svcadm(1M) to clear this service.") |
|
65 raise RuntimeError |
|
66 ctxt = ctx.get_admin_context() |
|
67 inst = db_base_plugin_v2.NeutronDbPluginV2() |
|
68 dup = False |
|
69 try: |
|
70 db_base_plugin_v2.NeutronDbPluginV2.create_network(inst, ctxt, nw) |
|
71 print "\nnetwork=%s added" % nw['network']['name'] |
|
72 if ext_ro: |
|
73 ext_nw = ext_net.ExternalNetwork(network_id=nw['network']['id']) |
|
74 session = sessionmaker() |
|
75 session.configure(bind=engine) |
|
76 s = session() |
|
77 s.add(ext_nw) |
|
78 s.commit() |
|
79 except excp.DBDuplicateEntry: |
|
80 print "\nnetwork '%s' already exists" % nw['network']['name'] |
|
81 dup = True |
|
82 return dup |
|
83 |
|
84 |
|
85 def create_db_subnet(sub): |
|
86 ''' Method for creating subnets table in the neutron-server DB |
|
87 Input params: |
|
88 @sub - Dictionary with values from EVS DB |
|
89 ''' |
|
90 # Importing locally because this module ends up importing neutron.wsgi |
|
91 # which causes RAD to hang |
|
92 from neutron.db import db_base_plugin_v2 |
|
93 ctxt = ctx.get_admin_context() |
|
94 inst = db_base_plugin_v2.NeutronDbPluginV2() |
|
95 try: |
|
96 db_base_plugin_v2.NeutronDbPluginV2.create_subnet(inst, ctxt, sub) |
|
97 print "\nsubnet=%s added" % sub['subnet']['id'] |
|
98 except excp.DBDuplicateEntry: |
|
99 print "\nsubnet '%s' already exists" % sub['subnet']['id'] |
|
100 |
|
101 |
|
102 def create_db_port(port): |
|
103 ''' Method for creating ports table in the neutron-server DB |
|
104 Input params: |
|
105 @port - Dictionary with values from EVS DB |
|
106 ''' |
|
107 # Importing locally because this module ends up importing neutron.wsgi |
|
108 # which causes RAD to hang |
|
109 from neutron.db import db_base_plugin_v2 |
|
110 ctxt = ctx.get_admin_context() |
|
111 inst = db_base_plugin_v2.NeutronDbPluginV2() |
|
112 try: |
|
113 db_base_plugin_v2.NeutronDbPluginV2.create_port(inst, ctxt, port) |
|
114 print "\nport=%s added" % port['port']['id'] |
|
115 except excp.DBDuplicateEntry: |
|
116 print "\nport '%s' already exists" % port['port']['id'] |
|
117 |
|
118 |
|
119 def main(): |
|
120 print "Start Migration." |
|
121 |
|
122 # Connect to EVS controller |
|
123 config = ConfigParser.RawConfigParser() |
|
124 config.readfp(open("/etc/neutron/plugins/evs/evs_plugin.ini")) |
|
125 if config.has_option("EVS", 'evs_controller'): |
|
126 config_suh = config.get("EVS", 'evs_controller') |
|
127 else: |
|
128 config_suh = 'ssh://evsuser@localhost' |
|
129 suh = config_suh.split('://') |
|
130 if len(suh) != 2 or suh[0] != 'ssh' or not suh[1].strip(): |
|
131 raise SystemExit(_("Specified evs_controller is invalid")) |
|
132 uh = suh[1].split('@') |
|
133 if len(uh) != 2 or not uh[0].strip() or not uh[1].strip(): |
|
134 raise SystemExit(_("'user' and 'hostname' need to be specified " |
|
135 "for evs_controller")) |
|
136 try: |
|
137 rc = radcon.connect_ssh(uh[1], user=uh[0]) |
|
138 except: |
|
139 raise SystemExit(_("Cannot connect to EVS Controller")) |
|
140 try: |
|
141 evs_contr = rc.get_object(evsc.EVSController()) |
|
142 except: |
|
143 raise SystemExit(_("Could not retrieve EVS info from EVS Controller")) |
|
144 |
|
145 config.readfp(open("/etc/neutron/neutron.conf")) |
|
146 if config.has_option("database", 'connection'): |
|
147 SQL_CONNECTION = config.get("database", 'connection') |
|
148 else: |
|
149 SQL_CONNECTION = 'sqlite:////var/lib/neutron/neutron.sqlite' |
|
150 |
|
151 conf = cfg.CONF |
|
152 db_options.set_defaults(cfg.CONF, |
|
153 connection=SQL_CONNECTION, |
|
154 sqlite_db='', max_pool_size=10, |
|
155 max_overflow=20, pool_timeout=10) |
|
156 |
|
157 neutron_engine = sa.create_engine(SQL_CONNECTION) |
|
158 router_port_ids = {} |
|
159 |
|
160 evsinfo = evs_contr.getEVSInfo() |
|
161 for e in evsinfo: |
|
162 ext_ro = False |
|
163 for p in e.props: |
|
164 if p.name == 'OpenStack:router:external' and p.value == 'True': |
|
165 ext_ro = True |
|
166 # Populate networks table |
|
167 n = { |
|
168 'tenant_id': e.tenantname, |
|
169 'id': e.uuid, |
|
170 'name': e.name, |
|
171 'status': 'ACTIVE', |
|
172 'admin_state_up': True, |
|
173 'shared': False |
|
174 } |
|
175 nw = {'network': n} |
|
176 dup = create_db_network(nw, neutron_engine, ext_ro) |
|
177 if dup: |
|
178 continue # No need to iterate over subnets and ports |
|
179 |
|
180 # Populate subnets table |
|
181 if not e.ipnets: |
|
182 continue |
|
183 for i in e.ipnets: |
|
184 cidr = None |
|
185 gateway_ip = None |
|
186 enable_dhcp = None |
|
187 dns = [] |
|
188 host = [] |
|
189 start = [] |
|
190 for p in i.props: |
|
191 if p.name == 'subnet': |
|
192 cidr = p.value |
|
193 elif p.name == 'defrouter': |
|
194 gateway_ip = p.value |
|
195 elif p.name == 'OpenStack:enable_dhcp': |
|
196 enable_dhcp = p.value == 'True' |
|
197 elif p.name == 'OpenStack:dns_nameservers': |
|
198 dns = p.value.split(',') |
|
199 elif p.name == 'OpenStack:host_routes': |
|
200 hh = p.value.split(',') |
|
201 for h in range(0, len(hh), 2): |
|
202 d = {hh[h]: hh[h+1]} |
|
203 host.append(d) |
|
204 elif p.name == 'pool': |
|
205 ss = p.value.split(',') |
|
206 for s in ss: |
|
207 if '-' in s: |
|
208 d = {'start': s.split('-')[0], |
|
209 'end': s.split('-')[1]} |
|
210 start.append(d) |
|
211 else: |
|
212 d = {'start': s, 'end': s} |
|
213 start.append(d) |
|
214 ip_version = 4 if i.ipvers == evsc.IPVersion.IPV4 else 6 |
|
215 |
|
216 if i.name.startswith(i.uuid[:8]): |
|
217 # Skip autogenerated names |
|
218 name = None |
|
219 else: |
|
220 name = i.name |
|
221 s = { |
|
222 'tenant_id': i.tenantname, |
|
223 'id': i.uuid, |
|
224 'name': name, |
|
225 'network_id': e.uuid, |
|
226 'ip_version': ip_version, |
|
227 'cidr': cidr, |
|
228 'gateway_ip': gateway_ip, |
|
229 'enable_dhcp': enable_dhcp, |
|
230 'shared': False, |
|
231 'allocation_pools': start, |
|
232 'dns_nameservers': dns, |
|
233 'host_routes': host |
|
234 } |
|
235 |
|
236 sub = {'subnet': s} |
|
237 create_db_subnet(sub) |
|
238 |
|
239 # Populate ports table |
|
240 if not e.vports: |
|
241 continue |
|
242 for j in e.vports: |
|
243 device_owner = '' |
|
244 device_id = '' |
|
245 mac_address = None |
|
246 ipaddr = None |
|
247 for v in j.props: |
|
248 if v.name == 'OpenStack:device_owner': |
|
249 device_owner = v.value |
|
250 if v.value in ('network:router_interface', |
|
251 'network:router_gateway'): |
|
252 router_port_ids[j.uuid] = v.value |
|
253 elif v.name == 'OpenStack:device_id': |
|
254 device_id = v.value |
|
255 elif v.name == 'macaddr': |
|
256 mac_address = v.value |
|
257 elif v.name == 'ipaddr': |
|
258 ipaddr = v.value.split('/')[0] |
|
259 if j.name.startswith(j.uuid[:8]): |
|
260 # Skip autogenerated names |
|
261 name = None |
|
262 else: |
|
263 name = j.name |
|
264 |
|
265 p = { |
|
266 'tenant_id': j.tenantname, |
|
267 'id': j.uuid, |
|
268 'name': name, |
|
269 'network_id': e.uuid, |
|
270 'mac_address': mac_address, |
|
271 'admin_state_up': True, |
|
272 'status': 'ACTIVE', |
|
273 'device_id': device_id, |
|
274 'device_owner': device_owner, |
|
275 'fixed_ips': [{'subnet_id': e.ipnets[0].uuid, |
|
276 'ip_address': ipaddr}] |
|
277 } |
|
278 port = {'port': p} |
|
279 create_db_port(port) |
|
280 |
|
281 # Change the schema of the floatingips and routers tables by doing |
|
282 # the following: |
|
283 # Fetch the floatingip, router entry using EVS API, |
|
284 # Temporarily store the information, |
|
285 # Delete floatingip, router entry, |
|
286 # Remove floatingip, router as a constraint from existing tables, |
|
287 # Drop the routers, floatingips table, |
|
288 # Add router, floatingip entry using Neutron API |
|
289 |
|
290 # Importing locally because this module ends up importing neutron.wsgi |
|
291 # which causes RAD to hang |
|
292 from neutron.db import l3_db |
|
293 havana_api.configure_db() |
|
294 session = havana_api.get_session() |
|
295 |
|
296 # Fetch the floatingip entry using EVS API |
|
297 query = session.query(havana_api.FloatingIP) |
|
298 floatingips = query.all() |
|
299 fl = [] |
|
300 if floatingips: |
|
301 for f in floatingips: |
|
302 fi = { |
|
303 'id': f['id'], |
|
304 'floating_ip_address': f['floating_ip_address'], |
|
305 'floating_network_id': f['floating_network_id'], |
|
306 'floating_port_id': f['floating_port_id'], |
|
307 'fixed_port_id': f['fixed_port_id'], |
|
308 'fixed_ip_address': f['fixed_ip_address'], |
|
309 'tenant_id': f['tenant_id'], |
|
310 'router_id': f['router_id'], |
|
311 } |
|
312 fl.append(fi) |
|
313 |
|
314 # Delete floatingip entry |
|
315 ctxt = ctx.get_admin_context() |
|
316 ctxt = havana_api.get_evs_context(ctxt) |
|
317 with ctxt.session.begin(subtransactions=True): |
|
318 cm_db_inst = common_db_mixin.CommonDbMixin() |
|
319 query = common_db_mixin.CommonDbMixin._model_query(cm_db_inst, |
|
320 ctxt, |
|
321 havana_api. |
|
322 FloatingIP) |
|
323 for fip in query: |
|
324 ctxt.session.delete(fip) |
|
325 |
|
326 # Fetch the router entry using EVS API |
|
327 query = session.query(havana_api.Router) |
|
328 routers = [] |
|
329 try: |
|
330 routers = query.all() |
|
331 except sa.exc.OperationalError: |
|
332 pass |
|
333 if routers: |
|
334 for r in routers: |
|
335 router_id = r['id'] |
|
336 rt = { |
|
337 'tenant_id': r['tenant_id'], |
|
338 'id': r['id'], |
|
339 'name': r['name'], |
|
340 'admin_state_up': r['admin_state_up'], |
|
341 'gw_port_id': r['gw_port_id'], |
|
342 'status': 'ACTIVE' |
|
343 } |
|
344 |
|
345 # Delete router entry |
|
346 ctxt = ctx.get_admin_context() |
|
347 ctxt = havana_api.get_evs_context(ctxt) |
|
348 with ctxt.session.begin(subtransactions=True): |
|
349 cm_db_inst = common_db_mixin.CommonDbMixin() |
|
350 query = common_db_mixin.CommonDbMixin._model_query(cm_db_inst, |
|
351 ctxt, |
|
352 havana_api. |
|
353 Router) |
|
354 router = query.filter(havana_api.Router.id == router_id).one() |
|
355 ctxt.session.delete(router) |
|
356 |
|
357 engine = sa.create_engine(SQL_CONNECTION) |
|
358 meta = MetaData() |
|
359 conn = engine.connect() |
|
360 trans = conn.begin() |
|
361 meta.reflect(engine) |
|
362 |
|
363 # Remove router as a constraint from existing tables, |
|
364 # Drop the routers table to remove old schema |
|
365 for t in meta.tables.values(): |
|
366 for fk in t.foreign_keys: |
|
367 if fk.column.table.name == "routers": |
|
368 if fk.constraint.name: |
|
369 engine.execute(DropConstraint(fk.constraint)) |
|
370 for t in meta.tables.values(): |
|
371 if t.name == "routers": |
|
372 t.drop(bind=conn) |
|
373 |
|
374 # Remove floatingip as a constraint from existing tables, |
|
375 # Drop the floatingip table to remove old schema |
|
376 for t in meta.tables.values(): |
|
377 for fk in t.foreign_keys: |
|
378 if fk.column.table.name == "floatingips": |
|
379 if fk.constraint.name: |
|
380 engine.execute(DropConstraint(fk.constraint)) |
|
381 for t in meta.tables.values(): |
|
382 if t.name == "floatingips": |
|
383 t.drop(bind=conn) |
|
384 conn.close() |
|
385 |
|
386 # Add the routers and floatingips using the schema in l3_db.py |
|
387 |
|
388 setattr(l3_db.Router, 'enable_snat', sa.Column(sa.Boolean, |
|
389 default=True, server_default=sql.true(), nullable=False)) |
|
390 neutron_engine = sa.create_engine(SQL_CONNECTION) |
|
391 model_base.BASEV2.metadata.bind = neutron_engine |
|
392 model_base.BASEV2.metadata.create_all(neutron_engine) |
|
393 if routers: |
|
394 ctxt = ctx.get_admin_context() |
|
395 with ctxt.session.begin(subtransactions=True): |
|
396 router_db = l3_db.Router(id=router_id, |
|
397 tenant_id=r['tenant_id'], |
|
398 name=rt['name'], |
|
399 admin_state_up=rt['admin_state_up'], |
|
400 gw_port_id=rt['gw_port_id'], |
|
401 status="ACTIVE") |
|
402 ctxt.session.add(router_db) |
|
403 print "\nrouter=%s updated" % rt['name'] |
|
404 with ctxt.session.begin(subtransactions=True): |
|
405 for i, j in router_port_ids.iteritems(): |
|
406 router_port = l3_db.RouterPort( |
|
407 port_id=i, |
|
408 router_id=router_id, |
|
409 port_type=j) |
|
410 ctxt.session.add(router_port) |
|
411 |
|
412 if floatingips: |
|
413 ctxt = ctx.get_admin_context() |
|
414 with ctxt.session.begin(subtransactions=True): |
|
415 for i in fl: |
|
416 fl_db = l3_db.FloatingIP( |
|
417 id=i['id'], |
|
418 floating_ip_address=i['floating_ip_address'], |
|
419 floating_network_id=i['floating_network_id'], |
|
420 floating_port_id=i['floating_port_id'], |
|
421 fixed_port_id=i['fixed_port_id'], |
|
422 fixed_ip_address=i['fixed_ip_address'], |
|
423 router_id=i['router_id'], |
|
424 tenant_id=i['tenant_id']) |
|
425 ctxt.session.add(fl_db) |
|
426 print "\nfloatingip=%s updated" % i['floating_ip_address'] |
|
427 |
|
428 print "\nEnd Migration." |
|
429 |
|
430 |
|
431 if __name__ == '__main__': |
|
432 main() |
|