1 #!/usr/bin/python2.7 |
|
2 # |
|
3 # Copyright (c) 2016, 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 # There are four aspects to migrate an OpenStack cloud running Neutron EVS |
|
19 # to Neutron ML2 + OVS and they are enumerated below. This script needs to |
|
20 # be run on each of the nodes that is either -- compute, controller, or |
|
21 # network -- and the script infers the role of the node based on the SMF |
|
22 # services running and does one or more of the operations enumerated below. |
|
23 # |
|
24 # 1. Populate Neutron ML2 tables |
|
25 # ------------------------------ |
|
26 # Neutron ML2 plugin uses a different set of tables to manage various |
|
27 # network types and mechanism drivers underneath it. The names of these |
|
28 # tables start with ml2_* and the content of these tables will need to be |
|
29 # inferred from other Neutron tables and from EVS controller |
|
30 # |
|
31 # 2. Update existing configuration files |
|
32 # -------------------------------------- |
|
33 # Following files need to be updated for various Neutron services. |
|
34 # - /etc/neutron/neutron.conf |
|
35 # - change core_plugin option to neutron.plugins.ml2.plugin.Ml2Plugin |
|
36 # |
|
37 # - /etc/neutron/dhcp_agent.ini |
|
38 # - change interface_driver option to \ |
|
39 # neutron.agent.solaris.interface.SolarisOVSInterfaceDriver |
|
40 # - set ovs_integration_bridge to br_int0 |
|
41 # |
|
42 # - /etc/neutron/l3_agent.ini |
|
43 # - change interface_driver option to \ |
|
44 # neutron.agent.solaris.interface.SolarisOVSInterfaceDriver |
|
45 # - set ovs_integration_bridge to br_int0 |
|
46 # - set external_network_bridge to br_ex0 |
|
47 # - add service tenant's neutron user credentials to communicate with |
|
48 # neutron-server |
|
49 # |
|
50 # Following files need to be updated on every node where nova-compute runs. |
|
51 # - /etc/nova/nova.conf |
|
52 # The only change to this file is to add an ovs_bridge |
|
53 # option set to 'br_int0' (default OVS bridge to which various VNICs |
|
54 # (Neutron ports) are added) |
|
55 # |
|
56 # 3. Create new configuration files |
|
57 # --------------------------------- |
|
58 # Following new file needs to be created on the node running neutron-server. |
|
59 # - /etc/neutron/plugins/ml2/ml2_conf.ini |
|
60 # |
|
61 # Following new file needs to be created on every node running either |
|
62 # nova-compute, neutron-dhcp-agent, or neutron-l3-agent. |
|
63 # - /etc/neutron/plugins/openvswitch/ovs_neutron_plugin.ini |
|
64 # |
|
65 # The majority of the contents of the file is inferred from EVS controller |
|
66 # |
|
67 # 4. Migrate all the VMs from EVS to OVS |
|
68 # -------------------------------------- |
|
69 # The anets of each VM, spawned in Neutron EVS cloud, has one global(tenant) |
|
70 # and two anet(evs and vport) properites that are EVS specific. We will need |
|
71 # to clear those properties. Before we do that, we will need to first fetch |
|
72 # the information (MAC address, lower-link, and such) from EVS controller |
|
73 # for a given anet which is uniquely identified by <tenant, evs, vport> and |
|
74 # explicitly set corresponding anet properties. This step needs to be |
|
75 # repeated for other EVS based anets, if any, in the VM. |
|
76 # |
|
77 |
|
78 import argparse |
|
79 from collections import OrderedDict |
|
80 from datetime import datetime |
|
81 import iniparse |
|
82 import netaddr as na |
|
83 import netifaces as ni |
|
84 import os |
|
85 import pwd |
|
86 import re |
|
87 from shutil import copy2, move |
|
88 import signal |
|
89 import socket |
|
90 import sqlalchemy as sa |
|
91 from subprocess import check_output, check_call, CalledProcessError, PIPE |
|
92 import sys |
|
93 import uuid |
|
94 |
|
95 import rad.bindings.com.oracle.solaris.rad.evscntl_1 as evscntl |
|
96 import rad.bindings.com.oracle.solaris.rad.zonemgr_1 as zonemgr |
|
97 import rad.client as radcli |
|
98 import rad.connect as radcon |
|
99 |
|
100 from oslo_db.sqlalchemy import session |
|
101 from neutronclient.v2_0 import client as neutron_client |
|
102 from neutron.extensions import portbindings |
|
103 from neutron.openstack.common import uuidutils |
|
104 |
|
105 # SMF services |
|
106 SVC_NOVA_COMPUTE = 'nova-compute:default' |
|
107 SVC_NEUTRON_SERVER = 'neutron-server:default' |
|
108 SVC_DHCP_AGENT = 'neutron-dhcp-agent:default' |
|
109 SVC_L3_AGENT = 'neutron-l3-agent:default' |
|
110 SVC_METADATA_AGENT = 'neutron-metadata-agent:default' |
|
111 SVC_OVS_AGENT = 'neutron-openvswitch-agent:default' |
|
112 SVC_VSWITCH_SERVER = 'vswitch-server:default' |
|
113 SVC_OVSDB_SERVER = 'ovsdb-server:default' |
|
114 SVC_NEUTRON_UPGRADE = 'neutron-upgrade:default' |
|
115 |
|
116 |
|
117 ALL_SVCS = [SVC_NEUTRON_SERVER, SVC_DHCP_AGENT, SVC_L3_AGENT, SVC_NOVA_COMPUTE] |
|
118 curnode_svcs = [] |
|
119 |
|
120 # conf files |
|
121 NEUTRON_CONF = '/etc/neutron/neutron.conf' |
|
122 ML2_INI = '/etc/neutron/plugins/ml2/ml2_conf.ini' |
|
123 OVS_INI = '/etc/neutron/plugins/openvswitch/ovs_neutron_plugin.ini' |
|
124 EVS_INI = '/etc/neutron/plugins/evs/evs_plugin.ini' |
|
125 DHCP_INI = '/etc/neutron/dhcp_agent.ini' |
|
126 L3_INI = '/etc/neutron/l3_agent.ini' |
|
127 METADATA_INI = '/etc/neutron/metadata_agent.ini' |
|
128 NOVA_CONF = '/etc/nova/nova.conf' |
|
129 |
|
130 # constants |
|
131 ML2_PLUGIN = 'neutron.plugins.ml2.plugin.Ml2Plugin' |
|
132 OVS_INTFC_DRIVER = 'neutron.agent.solaris.interface.OVSInterfaceDriver' |
|
133 OVS_INT_BRIDGE = 'br_int0' |
|
134 OVS_EXT_BRIDGE = 'br_ex0' |
|
135 VXLAN_UPLINK_PORT = 'ovs.vxlan1' |
|
136 FLAT_PHYS_NET = 'flatnet' |
|
137 EXT_VLAN_PHYS_NET = 'extnet' |
|
138 RABBITMQ_DEFAULT_USERID = 'guest' |
|
139 RABBITMQ_DEFAULT_PASSWORD = 'guest' |
|
140 L2_TYPE_VLAN = 'vlan' |
|
141 L2_TYPE_VXLAN = 'vxlan' |
|
142 L2_TYPE_FLAT = 'flat' |
|
143 UID_NEUTRON = 84 |
|
144 UID_NOVA = 85 |
|
145 |
|
146 # file ownership |
|
147 file_owner = { |
|
148 NEUTRON_CONF: UID_NEUTRON, |
|
149 ML2_INI: UID_NEUTRON, |
|
150 OVS_INI: UID_NEUTRON, |
|
151 EVS_INI: UID_NEUTRON, |
|
152 DHCP_INI: UID_NEUTRON, |
|
153 L3_INI: UID_NEUTRON, |
|
154 METADATA_INI: UID_NEUTRON, |
|
155 NOVA_CONF: UID_NOVA |
|
156 } |
|
157 |
|
158 # LOGGING LEVELS |
|
159 LOG_DEBUG = 'DEBUG:' |
|
160 LOG_INFO = 'INFO:' |
|
161 LOG_WARN = 'WARN:' |
|
162 LOG_ERROR = 'ERROR:' |
|
163 |
|
164 HOSTNAME = socket.gethostname().split('.')[0] |
|
165 |
|
166 evsutil = None |
|
167 l2type = None |
|
168 external_network_datalink = None |
|
169 external_network_name = None |
|
170 external_network_vid = None |
|
171 bridge_mappings = {} |
|
172 neutron_conn = {} |
|
173 |
|
174 |
|
175 def log_msg(level, msg, oneliner=True): |
|
176 if oneliner: |
|
177 msg = msg.replace('\n', ' ') |
|
178 msg = re.sub(r'\s\s+', ' ', msg) |
|
179 print level, msg |
|
180 |
|
181 |
|
182 class ZoneConfig(object): |
|
183 """ZoneConfig - context manager for access zone configurations. |
|
184 Automatically opens the configuration for a zone and commits any changes |
|
185 before exiting |
|
186 """ |
|
187 def __init__(self, zone): |
|
188 """zone is a zonemgr object representing either a kernel zone or |
|
189 non-global zone. |
|
190 """ |
|
191 self.zone = zone |
|
192 self.editing = False |
|
193 |
|
194 def __enter__(self): |
|
195 """enables the editing of the zone.""" |
|
196 try: |
|
197 self.zone.editConfig() |
|
198 self.editing = True |
|
199 return self |
|
200 except: |
|
201 raise |
|
202 |
|
203 def __exit__(self, exc_type, exc_val, exc_tb): |
|
204 """looks for any kind of exception before exiting. If one is found, |
|
205 cancel any configuration changes and reraise the exception. If not, |
|
206 commit the new configuration. |
|
207 """ |
|
208 if exc_type is not None and self.editing: |
|
209 # We received some kind of exception. Cancel the config and raise. |
|
210 self.zone.cancelConfig() |
|
211 raise |
|
212 else: |
|
213 # commit the config |
|
214 try: |
|
215 self.zone.commitConfig() |
|
216 except: |
|
217 raise |
|
218 |
|
219 def get_resources(self, resource_type): |
|
220 """Get list of resources of specified type |
|
221 """ |
|
222 try: |
|
223 return self.zone.getResources(zonemgr.Resource(resource_type)) |
|
224 except: |
|
225 raise |
|
226 |
|
227 def set_resource_prop(self, resource, prop, value, rsc_filter=None): |
|
228 """sets a property for an existing resource. |
|
229 """ |
|
230 try: |
|
231 if isinstance(resource, basestring): |
|
232 resource = zonemgr.Resource(resource, rsc_filter) |
|
233 self.zone.setResourceProperties(resource, |
|
234 [zonemgr.Property(prop, value)]) |
|
235 except: |
|
236 raise |
|
237 |
|
238 def clear_resource_props(self, resource, props, rsc_filter=None): |
|
239 """Clear property values of a given resource |
|
240 """ |
|
241 try: |
|
242 if isinstance(resource, basestring): |
|
243 resource = zonemgr.Resource(resource, rsc_filter) |
|
244 self.zone.clearResourceProperties(resource, props) |
|
245 except: |
|
246 raise |
|
247 |
|
248 def lookup_resource_property(self, resource, prop, rsc_filter=None): |
|
249 """Lookup specified property from specified Solaris Zone resource.""" |
|
250 try: |
|
251 if isinstance(resource, basestring): |
|
252 resource = zonemgr.Resource(resource, rsc_filter) |
|
253 val = self.zone.getResourceProperties(resource, [prop]) |
|
254 except radcli.ObjectError: |
|
255 return None |
|
256 except Exception: |
|
257 raise |
|
258 return val[0].value if val else None |
|
259 |
|
260 |
|
261 class ZoneUtil(object): |
|
262 """Zone utility functions like getting list of zones, zone names etc. |
|
263 """ |
|
264 def __init__(self): |
|
265 self.rc = radcon.connect_unix() |
|
266 |
|
267 def get_zone_by_name(self, name): |
|
268 """Return a Solaris Zones object via RAD by name.""" |
|
269 try: |
|
270 zone = self.rc.get_object( |
|
271 zonemgr.Zone(), radcli.ADRGlobPattern({'name': name})) |
|
272 except radcli.NotFoundError: |
|
273 return None |
|
274 except Exception: |
|
275 raise |
|
276 return zone |
|
277 |
|
278 def _get_zone_objects(self): |
|
279 """Return a list of all Solaris Zones objects via RAD.""" |
|
280 return self.rc.list_objects(zonemgr.Zone()) |
|
281 |
|
282 def get_zone_names(self): |
|
283 """Return the names of all the instances known to the virtualization |
|
284 layer, as a list. |
|
285 """ |
|
286 instances_list = [] |
|
287 for zone in self._get_zone_objects(): |
|
288 instances_list.append(self.rc.get_object(zone).name) |
|
289 return instances_list |
|
290 |
|
291 |
|
292 class EVSUtil(): |
|
293 """Use to access EVS info. |
|
294 """ |
|
295 def __init__(self): |
|
296 ctl_locn = self._get_evs_controller() |
|
297 try: |
|
298 self.rad_uri = radcon.RadURI(ctl_locn) |
|
299 except ValueError as err: |
|
300 raise SystemExit(_("Specified evs_controller is invalid: %s"), err) |
|
301 try: |
|
302 self._rc = self.rad_uri.connect() |
|
303 except: |
|
304 raise SystemExit(_("Cannot connect to EVS Controller")) |
|
305 try: |
|
306 self._evs_contr = self._rc.get_object(evscntl.EVSController()) |
|
307 except: |
|
308 raise SystemExit(_("Failed to get EVS Controller")) |
|
309 self.l2type = self._evs_contr.getProperty('l2-type')[0].current_value |
|
310 self._evsinfo = None |
|
311 self._vportinfo = None |
|
312 self._l2rangeinfo = None |
|
313 self._evs_cache = {} |
|
314 # _global_vlanrange_to_nw_uplink does not contain host specific entries |
|
315 # and is of the form: |
|
316 # {comma separated vlanrange strings: (physical n/w name, uplink port)} |
|
317 self._global_vlanrange_to_nw_uplink = {} |
|
318 # _local_vlanrange_to_uplink contains only this host specific entries |
|
319 # and is of the form: |
|
320 # {comma separated vlanrange strings: uplink port} |
|
321 self._local_vlanrange_to_uplink = {} |
|
322 # global uplink port for flatnet |
|
323 self._global_flat_nw_uplink = None |
|
324 # local uplink port for flatnet |
|
325 self._local_flat_nw_uplink = None |
|
326 |
|
327 def _get_evs_controller(self): |
|
328 if (set(curnode_svcs) & |
|
329 set([SVC_NOVA_COMPUTE, SVC_DHCP_AGENT, SVC_L3_AGENT])): |
|
330 try: |
|
331 evsc = check_output(['/usr/sbin/evsadm', 'show-prop', '-co', |
|
332 'value', '-p', 'controller']).strip() |
|
333 except: |
|
334 raise SystemExit(_("Could not determine EVS Controller " |
|
335 "RAD URI")) |
|
336 return evsc.strip() |
|
337 |
|
338 assert SVC_NEUTRON_SERVER in curnode_svcs |
|
339 # get evs_controller from EVS_INI |
|
340 config = iniparse.ConfigParser() |
|
341 config.readfp(open(EVS_INI)) |
|
342 try: |
|
343 evsc = config.get("EVS", "evs_controller") |
|
344 except: |
|
345 return 'ssh://evsuser@localhost' |
|
346 return evsc.strip() |
|
347 |
|
348 @property |
|
349 def evsinfo(self): |
|
350 if not self._evsinfo: |
|
351 self._evsinfo = self._evs_contr.getEVSInfo() |
|
352 return self._evsinfo |
|
353 |
|
354 @property |
|
355 def vportinfo(self): |
|
356 if not self._vportinfo: |
|
357 self._vportinfo = self._evs_contr.getVPortInfo() |
|
358 return self._vportinfo |
|
359 |
|
360 @property |
|
361 def l2rangeinfo(self): |
|
362 if not self._l2rangeinfo: |
|
363 self._l2rangeinfo = self._evs_contr.getL2TypeIdRange() |
|
364 return self._l2rangeinfo |
|
365 |
|
366 @property |
|
367 def global_flat_nw_uplink(self): |
|
368 if not self._global_flat_nw_uplink: |
|
369 self.get_global_vlanrange_nw_uplink_map() |
|
370 return self._global_flat_nw_uplink |
|
371 |
|
372 @property |
|
373 def local_flat_nw_uplink(self): |
|
374 if not self._local_flat_nw_uplink: |
|
375 self.get_local_vlanrange_uplink_map() |
|
376 return self._local_flat_nw_uplink |
|
377 |
|
378 def _get_vport(self, tenant_name, evs_uuid, vport_uuid): |
|
379 pat = radcli.ADRGlobPattern({'tenant': tenant_name, |
|
380 'evsuuid': evs_uuid, |
|
381 'uuid': vport_uuid}) |
|
382 adrnames = self._rc.list_objects(evscntl.VPort(), pat) |
|
383 if not adrnames: |
|
384 return None |
|
385 return self._rc.get_object(adrnames[0]) |
|
386 |
|
387 def get_macaddr(self, tenant_name, evs_uuid, vport_uuid): |
|
388 vport = self._get_vport(tenant_name, evs_uuid, vport_uuid) |
|
389 return vport.getProperty('macaddr')[0].current_value |
|
390 |
|
391 def _get_evs(self, tenant_name, evs_uuid): |
|
392 if evs_uuid in self._evs_cache: |
|
393 return self._evs_cache[evs_uuid] |
|
394 pat = radcli.ADRGlobPattern({'tenant': tenant_name, |
|
395 'uuid': evs_uuid}) |
|
396 adrnames = self._rc.list_objects(evscntl.EVS(), pat) |
|
397 if not adrnames: |
|
398 return None |
|
399 evs = self._rc.get_object(adrnames[0]) |
|
400 self._evs_cache[evs_uuid] = evs |
|
401 return evs |
|
402 |
|
403 def _vid_in_vidrange(self, vid, vidrange): |
|
404 # vidrange is of the form 1-5,10-20,30-35 |
|
405 vlan_ranges = vidrange.split(',') |
|
406 for vlan_range_str in vlan_ranges: |
|
407 vlan_range = vlan_range_str.split("-") |
|
408 vlan_start = int(vlan_range[0]) |
|
409 if len(vlan_range) == 2: |
|
410 vlan_end = int(vlan_range[1]) + 1 |
|
411 else: |
|
412 vlan_end = vlan_start + 1 |
|
413 if vid in xrange(vlan_start, vlan_end): |
|
414 return True |
|
415 return False |
|
416 |
|
417 def get_global_vlanrange_nw_uplink_map(self): |
|
418 if self._global_vlanrange_to_nw_uplink: |
|
419 return self._global_vlanrange_to_nw_uplink |
|
420 i = 1 |
|
421 extnet_found = False |
|
422 for l2ri in self.l2rangeinfo: |
|
423 if l2ri.host or l2ri.name != 'uplink-port': |
|
424 continue |
|
425 uplink_port = l2ri.value |
|
426 for range_prop in l2ri.range: |
|
427 if range_prop.name != 'vlan-range': |
|
428 if range_prop.name == 'flat-range': |
|
429 self._global_flat_nw_uplink = uplink_port |
|
430 continue |
|
431 vlanrange = range_prop.value |
|
432 phys_nw = '' |
|
433 if external_network_vid and not extnet_found: |
|
434 extnet_found = self._vid_in_vidrange(external_network_vid, |
|
435 vlanrange) |
|
436 if extnet_found: |
|
437 phys_nw = EXT_VLAN_PHYS_NET |
|
438 if not phys_nw: |
|
439 phys_nw = 'physnet' + str(i) |
|
440 i += 1 |
|
441 self._global_vlanrange_to_nw_uplink[vlanrange] = (phys_nw, |
|
442 uplink_port) |
|
443 return self._global_vlanrange_to_nw_uplink |
|
444 |
|
445 def get_local_vlanrange_uplink_map(self): |
|
446 if self._local_vlanrange_to_uplink: |
|
447 return self._local_vlanrange_to_uplink |
|
448 for l2ri in self.l2rangeinfo: |
|
449 if not l2ri.host: |
|
450 continue |
|
451 l2ri_hostname = l2ri.host.split('.')[0] |
|
452 if l2ri_hostname != HOSTNAME or l2ri.name != 'uplink-port': |
|
453 continue |
|
454 uplink_port = l2ri.value |
|
455 for range_prop in l2ri.range: |
|
456 if range_prop.name != 'vlan-range': |
|
457 if range_prop.name == 'flat-range': |
|
458 self._local_flat_nw_uplink = uplink_port |
|
459 continue |
|
460 vlanrange = range_prop.value |
|
461 self._local_vlanrange_to_uplink[vlanrange] = uplink_port |
|
462 return self._local_vlanrange_to_uplink |
|
463 |
|
464 def _get_vlanrange_dict_val(self, vlanrangedict, vlanid): |
|
465 """Each key in vlanrangedict is of the form |
|
466 'vid_start_1-vid_end_1,vid_start_2-vid_end_2'.. |
|
467 This method parses the keys and finds the one which contains the |
|
468 required vlanid and returns its corresponding dictionary value. |
|
469 """ |
|
470 for vlan_ranges_str, value in vlanrangedict.iteritems(): |
|
471 if self._vid_in_vidrange(vlanid, vlan_ranges_str): |
|
472 return value |
|
473 |
|
474 def get_uplink_port(self, tenant_name, evs_uuid): |
|
475 """ For VXLAN the uplink port is always ovs.vxlan1. |
|
476 For flat, we can return local or global uplink port after executing |
|
477 get_local_vlanrange_uplink_map() or get_global_vlanrange_uplink_map(). |
|
478 For vlan, to find we first find the vlan-id associated |
|
479 with this evs. Then check which l2range object contains this vlan-id |
|
480 for this host and get the corresponding uplink-port. |
|
481 """ |
|
482 if l2type == L2_TYPE_VXLAN: |
|
483 return VXLAN_UPLINK_PORT |
|
484 elif l2type == L2_TYPE_FLAT: |
|
485 if self.local_flat_nw_uplink: |
|
486 return self.local_flat_nw_uplink |
|
487 return self.global_flat_nw_uplink |
|
488 assert l2type == L2_TYPE_VLAN |
|
489 evs = self._get_evs(tenant_name, evs_uuid) |
|
490 vlanid = int(evs.getProperty('vlanid')[0].current_value) |
|
491 val = self._get_vlanrange_dict_val( |
|
492 self.get_local_vlanrange_uplink_map(), vlanid) |
|
493 if val: |
|
494 return val |
|
495 val = self._get_vlanrange_dict_val( |
|
496 self.get_global_vlanrange_nw_uplink_map(), vlanid)[1] |
|
497 return val |
|
498 |
|
499 def get_vni_range_list(self): |
|
500 vni_ranges_list = [] |
|
501 for l2ri in self.l2rangeinfo: |
|
502 if l2ri.host: |
|
503 continue |
|
504 for range_prop in l2ri.range: |
|
505 if range_prop.name != 'vxlan-range': |
|
506 continue |
|
507 vni_ranges_list += range_prop.value.split(',') |
|
508 return vni_ranges_list |
|
509 |
|
510 def get_vxlan_addrs_and_uplinks(self): |
|
511 local_vxlan_addr, local_uplink_port = '', '' |
|
512 global_vxlan_addr, global_uplink_port = '', '' |
|
513 for l2ri in self.l2rangeinfo: |
|
514 if l2ri.host: |
|
515 if l2ri.host.split('.')[0] != HOSTNAME: |
|
516 # Don't care about other hosts' configurations |
|
517 continue |
|
518 if l2ri.name == 'vxlan-addr': |
|
519 local_vxlan_addr = l2ri.value |
|
520 # if we found -h vxlan-addr, we don't need the other values |
|
521 break |
|
522 elif l2ri.name == 'uplink-port': |
|
523 for range_prop in l2ri.range: |
|
524 if range_prop.name == 'vxlan-range': |
|
525 local_uplink_port = l2ri.value |
|
526 break |
|
527 else: |
|
528 if l2ri.name == 'vxlan_addr' and l2ri.value != '0.0.0.0': |
|
529 global_vxlan_addr = l2ri.value |
|
530 else: |
|
531 for range_prop in l2ri.range: |
|
532 if range_prop.name == 'vxlan-range': |
|
533 global_uplink_port = l2ri.value |
|
534 break |
|
535 if local_vxlan_addr and local_uplink_port and global_vxlan_addr \ |
|
536 and global_uplink_port: |
|
537 break |
|
538 return (local_vxlan_addr, local_uplink_port, global_vxlan_addr, |
|
539 global_uplink_port) |
|
540 |
|
541 |
|
542 def get_db_connection(): |
|
543 config = iniparse.ConfigParser() |
|
544 config.readfp(open(NEUTRON_CONF)) |
|
545 if config.has_option('database', 'connection'): |
|
546 connection = config.get('database', 'connection') |
|
547 else: |
|
548 raise SystemExit(_("Connection url for target DB not found.")) |
|
549 return connection |
|
550 |
|
551 |
|
552 class DBEVSToMl2(object): |
|
553 def __init__(self): |
|
554 self._table_names = ['ml2_network_segments', 'ml2_vxlan_allocations', |
|
555 'ml2_vlan_allocations', 'ml2_port_binding_levels', |
|
556 'ml2_port_bindings', 'router_extra_attributes'] |
|
557 self._vif_type = portbindings.VIF_TYPE_OVS |
|
558 self._driver_type = 'openvswitch' |
|
559 # _vlan_xrange_to_nw is a list of tuples to hold the mapping from |
|
560 # vlan-id to physical_network. The tuple format is |
|
561 # (xrange(vid_range_start, vid_range_end), physical_network). |
|
562 self._vlan_xrange_to_nw = [] |
|
563 |
|
564 def __call__(self): |
|
565 connection = get_db_connection() |
|
566 engine = session.create_engine(connection) |
|
567 metadata = sa.MetaData() |
|
568 self._check_db_schema_version(engine, metadata) |
|
569 # Autoload the ports table to ensure that foreign keys to it and |
|
570 # the network table can be created for the new tables. |
|
571 sa.Table('ports', metadata, autoload=True, autoload_with=engine) |
|
572 metadata.create_all(engine) |
|
573 self._clear_tables(engine, metadata) |
|
574 self._get_vlanrange_mapping() |
|
575 self._migrate_network_segments(engine, metadata) |
|
576 self._migrate_vlan_allocations(engine) |
|
577 self._migrate_vxlan_allocations(engine) |
|
578 self._migrate_port_bindings(engine, metadata) |
|
579 self._add_router_extra_attributes(engine, metadata) |
|
580 |
|
581 def _check_db_schema_version(self, engine, metadata): |
|
582 """Check that current version of the db schema is supported.""" |
|
583 supported_schema_version = 'kilo' |
|
584 version_table = sa.Table( |
|
585 'alembic_version', metadata, autoload=True, autoload_with=engine) |
|
586 versions = [v[0] for v in engine.execute(version_table.select())] |
|
587 if not versions: |
|
588 raise ValueError(_("Missing version in alembic_versions table")) |
|
589 elif len(versions) > 1: |
|
590 raise ValueError(_("Multiple versions in alembic_versions table:" |
|
591 " %s") % versions) |
|
592 current_version = versions[0] |
|
593 if current_version != supported_schema_version: |
|
594 raise SystemError(_("Unsupported database schema %(current)s. " |
|
595 "Please migrate your database to one of " |
|
596 " following versions: %(supported)s") |
|
597 % {'current': current_version, |
|
598 'supported': supported_schema_version} |
|
599 ) |
|
600 |
|
601 def _clear_tables(self, engine, metadata): |
|
602 for tbl_name in self._table_names: |
|
603 sa.Table(tbl_name, metadata, autoload=True, autoload_with=engine) |
|
604 tbl = metadata.tables[tbl_name] |
|
605 engine.execute(tbl.delete()) |
|
606 |
|
607 def _get_vlanrange_mapping(self): |
|
608 vlanrange_to_nw_uplink = evsutil.get_global_vlanrange_nw_uplink_map() |
|
609 # mapping from vlan-id to physical_network |
|
610 for vlan_ranges_str, (nw, _) in vlanrange_to_nw_uplink.iteritems(): |
|
611 vlan_ranges = vlan_ranges_str.split(',') |
|
612 for vlan_range_str in vlan_ranges: |
|
613 vlan_range = vlan_range_str.split("-") |
|
614 vlan_start = int(vlan_range[0]) |
|
615 if len(vlan_range) == 2: |
|
616 vlan_end = int(vlan_range[1]) + 1 |
|
617 else: |
|
618 vlan_end = vlan_start + 1 |
|
619 self._vlan_xrange_to_nw.append((xrange(vlan_start, vlan_end), |
|
620 nw)) |
|
621 |
|
622 def _get_phys_net(self, l2type, vid): |
|
623 if l2type == L2_TYPE_VLAN: |
|
624 for vid_range, phys in self._vlan_xrange_to_nw: |
|
625 if vid in vid_range: |
|
626 return phys |
|
627 elif l2type == L2_TYPE_FLAT: |
|
628 return FLAT_PHYS_NET |
|
629 return None |
|
630 |
|
631 def _add_router_extra_attributes(self, engine, metadata): |
|
632 routers = engine.execute("SELECT id FROM routers") |
|
633 routers = list(routers) |
|
634 records = [] |
|
635 for router in routers: |
|
636 router_ext_attr = {} |
|
637 router_ext_attr['router_id'] = router[0] |
|
638 router_ext_attr['distributed'] = 0 |
|
639 router_ext_attr['service_router'] = 0 |
|
640 router_ext_attr['ha'] = 0 |
|
641 router_ext_attr['ha_vr_id'] = 0 |
|
642 records.append(router_ext_attr) |
|
643 |
|
644 if records: |
|
645 sa.Table('router_extra_attributes', metadata, autoload=True, |
|
646 autoload_with=engine) |
|
647 router_ea = metadata.tables['router_extra_attributes'] |
|
648 engine.execute(router_ea.insert(), records) |
|
649 |
|
650 def _migrate_network_segments(self, engine, metadata): |
|
651 records = [] |
|
652 for evsinfo in evsutil.evsinfo: |
|
653 segment = dict(id=uuidutils.generate_uuid()) |
|
654 segment['network_id'] = evsinfo.uuid |
|
655 segment['segmentation_id'] = None |
|
656 for prop in evsinfo.props: |
|
657 if prop.name == 'l2-type': |
|
658 segment['network_type'] = prop.value |
|
659 elif prop.name == 'vlanid' or prop.name == 'vni': |
|
660 segment['segmentation_id'] = int(prop.value) |
|
661 phys_net = self._get_phys_net(segment['network_type'], |
|
662 segment['segmentation_id']) |
|
663 segment['physical_network'] = phys_net |
|
664 records.append(segment) |
|
665 if records: |
|
666 sa.Table('ml2_network_segments', metadata, autoload=True, |
|
667 autoload_with=engine) |
|
668 ml2_network_segments = metadata.tables['ml2_network_segments'] |
|
669 engine.execute(ml2_network_segments.insert(), records) |
|
670 |
|
671 def _migrate_vxlan_allocations(self, engine): |
|
672 vnis = [] |
|
673 for evsinfo in evsutil.evsinfo: |
|
674 pdict = dict((prop.name, prop.value) for prop in evsinfo.props) |
|
675 if L2_TYPE_VXLAN not in pdict.values(): |
|
676 continue |
|
677 vnis.append(int(pdict['vni'])) |
|
678 records = [dict(vxlan_vni=vni, allocated=True) for vni in vnis] |
|
679 if records: |
|
680 metadata = sa.MetaData() |
|
681 sa.Table('ml2_vxlan_allocations', metadata, autoload=True, |
|
682 autoload_with=engine) |
|
683 vxlan_allocations = metadata.tables['ml2_vxlan_allocations'] |
|
684 engine.execute(vxlan_allocations.insert(), records) |
|
685 |
|
686 def _migrate_vlan_allocations(self, engine): |
|
687 vid_allocated_map = OrderedDict() |
|
688 # initially set 'allocated' to False for all vids |
|
689 for vid_range, _ in self._vlan_xrange_to_nw: |
|
690 for vid in vid_range: |
|
691 vid_allocated_map[vid] = False |
|
692 for evsinfo in evsutil.evsinfo: |
|
693 pdict = dict((prop.name, prop.value) for prop in evsinfo.props) |
|
694 if L2_TYPE_VLAN not in pdict.values(): |
|
695 continue |
|
696 vid = int(pdict['vlanid']) |
|
697 vid_allocated_map[vid] = True |
|
698 records = [ |
|
699 dict(physical_network=self._get_phys_net(L2_TYPE_VLAN, vid), |
|
700 vlan_id=vid, allocated=alloc) |
|
701 for vid, alloc in vid_allocated_map.iteritems() |
|
702 ] |
|
703 if records: |
|
704 metadata = sa.MetaData() |
|
705 sa.Table('ml2_vlan_allocations', metadata, autoload=True, |
|
706 autoload_with=engine) |
|
707 vlan_allocations = metadata.tables['ml2_vlan_allocations'] |
|
708 engine.execute(vlan_allocations.insert(), records) |
|
709 |
|
710 def _get_port_segment_map(self, engine): |
|
711 port_segments = engine.execute(""" |
|
712 SELECT ports_network.port_id, ml2_network_segments.id AS segment_id |
|
713 FROM ml2_network_segments, ( |
|
714 SELECT ports.id AS port_id, ports.network_id |
|
715 FROM ports |
|
716 ) AS ports_network |
|
717 WHERE ml2_network_segments.network_id = ports_network.network_id |
|
718 """) |
|
719 return dict(x for x in port_segments) |
|
720 |
|
721 def _migrate_port_bindings(self, engine, metadata): |
|
722 ml2_bindings = [] |
|
723 ml2_binding_levels = [] |
|
724 port_segment_map = self._get_port_segment_map(engine) |
|
725 metadata = sa.MetaData() |
|
726 for vportinfo in evsutil.vportinfo: |
|
727 binding = {} |
|
728 binding['port_id'] = vportinfo.uuid |
|
729 binding['host'] = vportinfo.hostname |
|
730 if vportinfo.hostname: |
|
731 binding['vif_type'] = self._vif_type |
|
732 binding['vif_details'] = '{"port_filter": false, ' \ |
|
733 '"ovs_hybrid_plug": false}' |
|
734 ml2_bindings.append(binding) |
|
735 binding_level = {} |
|
736 binding_level['port_id'] = vportinfo.uuid |
|
737 binding_level['host'] = vportinfo.hostname |
|
738 binding_level['level'] = 0 |
|
739 binding_level['driver'] = self._driver_type |
|
740 segment_id = port_segment_map.get(binding_level['port_id']) |
|
741 if segment_id: |
|
742 binding_level['segment_id'] = segment_id |
|
743 ml2_binding_levels.append(binding_level) |
|
744 else: |
|
745 binding['vif_type'] = 'unbound' |
|
746 binding['vif_details'] = '' |
|
747 ml2_bindings.append(binding) |
|
748 if ml2_bindings: |
|
749 sa.Table('ml2_port_bindings', metadata, autoload=True, |
|
750 autoload_with=engine) |
|
751 ml2_port_bindings = metadata.tables['ml2_port_bindings'] |
|
752 engine.execute(ml2_port_bindings.insert(), ml2_bindings) |
|
753 if ml2_binding_levels: |
|
754 sa.Table('ml2_port_binding_levels', metadata, autoload=True, |
|
755 autoload_with=engine) |
|
756 ml2_port_binding_lvls = metadata.tables['ml2_port_binding_levels'] |
|
757 engine.execute(ml2_port_binding_lvls.insert(), ml2_binding_levels) |
|
758 |
|
759 |
|
760 class NovaVmEVSToOVS(object): |
|
761 def _zc_get_evs_vport_vals(self, zc, anet_rsc): |
|
762 """Get mac-address and lower-link for this anet from evs. |
|
763 """ |
|
764 mac_addr, uplink_port = None, None |
|
765 tenant_name = zc.lookup_resource_property('global', 'tenant') |
|
766 evs_uuid = zc.lookup_resource_property(anet_rsc, 'evs') |
|
767 vport_uuid = zc.lookup_resource_property(anet_rsc, 'vport') |
|
768 if not evs_uuid or not vport_uuid: |
|
769 return mac_addr, uplink_port |
|
770 mac_addr = evsutil.get_macaddr(tenant_name, evs_uuid, vport_uuid) |
|
771 uplink_port = evsutil.get_uplink_port(tenant_name, evs_uuid) |
|
772 return mac_addr, uplink_port |
|
773 |
|
774 def migrate(self, zone): |
|
775 """Update zonecfg by deleting evs-specific and adding ovs-specific conf |
|
776 """ |
|
777 installed_port_uuids = [] |
|
778 with ZoneConfig(zone) as zc: |
|
779 brand = zc.lookup_resource_property('global', 'brand') |
|
780 anet_update_failed = False |
|
781 for anet_rsc in zc.get_resources('anet'): |
|
782 mac_addr, lower_link = self._zc_get_evs_vport_vals(zc, |
|
783 anet_rsc) |
|
784 if not mac_addr or not lower_link: |
|
785 anet_update_failed = True |
|
786 msg = "Failed to get ovs info for zone" |
|
787 log_msg(LOG_ERROR, msg) |
|
788 continue |
|
789 if zone.state == 'installed': |
|
790 vport_uuid = zc.lookup_resource_property(anet_rsc, 'vport') |
|
791 if vport_uuid: |
|
792 installed_port_uuids.append(vport_uuid) |
|
793 fname = 'id' if brand == 'solaris-kz' else 'linkname' |
|
794 fvalue = zc.lookup_resource_property(anet_rsc, fname) |
|
795 zc.clear_resource_props(anet_rsc, ['evs', 'vport']) |
|
796 rsc_filter = [zonemgr.Property(fname, fvalue)] |
|
797 zc.set_resource_prop('anet', 'mac-address', mac_addr, |
|
798 rsc_filter) |
|
799 zc.set_resource_prop('anet', 'lower-link', lower_link, |
|
800 rsc_filter) |
|
801 |
|
802 if not anet_update_failed: |
|
803 zc.clear_resource_props('global', ['tenant']) |
|
804 return installed_port_uuids |
|
805 |
|
806 |
|
807 class ConfigEVSToOVS(): |
|
808 def __init__(self): |
|
809 # These are the configuration changes that are fixed, i.e., don't |
|
810 # require extra computation. The data structure format is: |
|
811 # _fixed = {config_file: [(section, param_name, param_value),]} |
|
812 self._fixed = { |
|
813 NEUTRON_CONF: [('DEFAULT', 'core_plugin', ML2_PLUGIN)], |
|
814 ML2_INI: [('ml2_type_flat', 'flat_networks', 'flatnet')], |
|
815 DHCP_INI: [('DEFAULT', 'interface_driver', OVS_INTFC_DRIVER), |
|
816 ('DEFAULT', 'ovs_integration_bridge', OVS_INT_BRIDGE)], |
|
817 L3_INI: [('DEFAULT', 'interface_driver', OVS_INTFC_DRIVER), |
|
818 ('DEFAULT', 'ovs_integration_bridge', OVS_INT_BRIDGE), |
|
819 ('DEFAULT', 'external_network_bridge', OVS_EXT_BRIDGE)], |
|
820 NOVA_CONF: [('neutron', 'ovs_bridge', OVS_INT_BRIDGE)] |
|
821 } |
|
822 # Config changes that are fixed depending on the l2-type |
|
823 if l2type == L2_TYPE_VXLAN: |
|
824 self._fixed[ML2_INI] += [('ml2', 'tenant_network_types', 'vxlan')] |
|
825 self._fixed[OVS_INI] = [('ovs', 'enable_tunneling', 'True'), |
|
826 ('agent', 'tunnel_types', 'vxlan')] |
|
827 elif l2type == L2_TYPE_VLAN: |
|
828 self._fixed[ML2_INI] += [('ml2', 'tenant_network_types', 'vlan')] |
|
829 else: |
|
830 assert l2type == L2_TYPE_FLAT |
|
831 self._fixed[ML2_INI] += [('ml2', 'tenant_network_types', 'flat')] |
|
832 self._vxlan_local_ip = None |
|
833 |
|
834 def _read_config(self, conf_file): |
|
835 config = iniparse.ConfigParser() |
|
836 config.readfp(open(conf_file)) |
|
837 return config |
|
838 |
|
839 def _write_config(self, conf_file, config): |
|
840 with open(conf_file, 'wb+') as fp: |
|
841 config.write(fp) |
|
842 |
|
843 def _do_fixed(self, conf_file, config): |
|
844 orig_conf_file = conf_file.replace('.migr', '') |
|
845 if orig_conf_file not in self._fixed: |
|
846 return |
|
847 for sec, key, val in self._fixed[orig_conf_file]: |
|
848 config.set(sec, key, val) |
|
849 |
|
850 def _do_ml2_vlan_range(self, config): |
|
851 vlanrange_to_nw_uplink = evsutil.get_global_vlanrange_nw_uplink_map() |
|
852 nw_vlan_str_list = [] |
|
853 for vlan_ranges_str, (nw, _) in vlanrange_to_nw_uplink.iteritems(): |
|
854 vlan_ranges = vlan_ranges_str.split(',') |
|
855 for vlan_range_str in vlan_ranges: |
|
856 vlan_range = vlan_range_str.split("-") |
|
857 vlan_start = vlan_end = vlan_range[0] |
|
858 if len(vlan_range) == 2: |
|
859 vlan_end = vlan_range[1] |
|
860 nw_vlan_str = nw + ":" + vlan_start + ":" + vlan_end |
|
861 nw_vlan_str_list.append(nw_vlan_str) |
|
862 nw_vlan_strs = ",".join(nw_vlan_str_list) |
|
863 config.set('ml2_type_vlan', 'network_vlan_ranges', nw_vlan_strs) |
|
864 |
|
865 def _do_ml2_vni_range(self, config): |
|
866 vni_ranges_list = evsutil.get_vni_range_list() |
|
867 vni_ranges_list = [vr.replace('-', ':') for vr in vni_ranges_list] |
|
868 vni_ranges = ",".join(vni_ranges_list) |
|
869 config.set('ml2_type_vxlan', 'vni_ranges', vni_ranges) |
|
870 |
|
871 def _get_rabbit_host(self, conf_file): |
|
872 config = self._read_config(conf_file) |
|
873 host = 'localhost' |
|
874 if config.has_option('DEFAULT', 'rabbit_host'): |
|
875 host = config.get('DEFAULT', 'rabbit_host') |
|
876 elif config.has_option('oslo_messaging_rabbit', 'rabbit_host'): |
|
877 host = config.get('oslo_messaging_rabbit', 'rabbit_host') |
|
878 |
|
879 port = '5672' |
|
880 if config.has_option('DEFAULT', 'rabbit_port'): |
|
881 port = config.get('DEFAULT', 'rabbit_port') |
|
882 elif config.has_option('oslo_messaging_rabbit', 'rabbit_port'): |
|
883 port = config.get('oslo_messaging_rabbit', 'rabbit_port') |
|
884 |
|
885 hosts = ':'.join([host, port]) |
|
886 if config.has_option('DEFAULT', 'rabbit_hosts'): |
|
887 hosts = config.get('DEFAULT', 'rabbit_hosts') |
|
888 elif config.has_option('oslo_messaging_rabbit', 'rabbit_hosts'): |
|
889 hosts = config.get('oslo_messaging_rabbit', 'rabbit_hosts') |
|
890 |
|
891 userid = RABBITMQ_DEFAULT_USERID |
|
892 if config.has_option('DEFAULT', 'rabbit_userid'): |
|
893 userid = config.get('DEFAULT', 'rabbit_userid') |
|
894 elif config.has_option('oslo_messaging_rabbit', 'rabbit_userid'): |
|
895 userid = config.get('oslo_messaging_rabbit', 'rabbit_userid') |
|
896 |
|
897 passwd = RABBITMQ_DEFAULT_PASSWORD |
|
898 if config.has_option('DEFAULT', 'rabbit_password'): |
|
899 passwd = config.get('DEFAULT', 'rabbit_password') |
|
900 elif config.has_option('oslo_messaging_rabbit', 'rabbit_password'): |
|
901 passwd = config.get('oslo_messaging_rabbit', 'rabbit_password') |
|
902 passwd += '\n' |
|
903 |
|
904 return (host, hosts, userid, passwd) |
|
905 |
|
906 def _do_rabbit_host(self, config): |
|
907 if SVC_NOVA_COMPUTE in curnode_svcs: |
|
908 (host, hosts, userid, passwd) = self._get_rabbit_host(NOVA_CONF) |
|
909 elif set([SVC_DHCP_AGENT, SVC_L3_AGENT]) & set(curnode_svcs): |
|
910 (host, hosts, userid, passwd) = self._get_rabbit_host(NEUTRON_CONF) |
|
911 else: |
|
912 return |
|
913 if not config.has_section('oslo_messaging_rabbit'): |
|
914 config.add_section('oslo_messaging_rabbit') |
|
915 config.set('oslo_messaging_rabbit', 'rabbit_host', host) |
|
916 config.set('oslo_messaging_rabbit', 'rabbit_hosts', hosts) |
|
917 config.set('oslo_messaging_rabbit', 'rabbit_userid', userid) |
|
918 config.set('oslo_messaging_rabbit', 'rabbit_password', passwd) |
|
919 |
|
920 def _get_local_ip(self, if_str='', subnet_str=''): |
|
921 if not if_str and not subnet_str: |
|
922 return None |
|
923 for iface in ni.interfaces(): |
|
924 if if_str: |
|
925 if iface != if_str: |
|
926 continue |
|
927 # Only IPv4 addresses, not considering IPv6 since OVS |
|
928 # doesn't support IPv6 VXLANs |
|
929 for addrinfo in ni.ifaddresses(iface)[ni.AF_INET]: |
|
930 addr = addrinfo['addr'] |
|
931 if subnet_str: |
|
932 if na.IPAddress(addr) in na.IPNetwork(subnet_str): |
|
933 return addr |
|
934 else: |
|
935 if addr != '127.0.0.1': |
|
936 return addr |
|
937 break |
|
938 else: |
|
939 for addrinfo in ni.ifaddresses(iface)[ni.AF_INET]: |
|
940 addr = addrinfo['addr'] |
|
941 if na.IPAddress(addr) in na.IPNetwork(subnet_str): |
|
942 return addr |
|
943 return None |
|
944 |
|
945 def _get_vxlan_local_ip(self): |
|
946 """Returns the local_ip for vxlan_endpoint. It is found as follows: |
|
947 1. If host specific vxlan-addr is present, use it. |
|
948 2. If local uplink-port and global vxlan-addr(subnet) is present, use |
|
949 the first IP address on that uplink-port which is in the subnet. |
|
950 3. If local uplink-port, use the first IP on the uplink-port. |
|
951 4. If global uplink-port and global vxlan-addr(subnet), use first |
|
952 IP address on that uplink-port which is in the subnet. |
|
953 5. If global vxlan-addr is configured only, use the first IP address |
|
954 on any interface that is in the subnet of global vxlan-addr. |
|
955 """ |
|
956 if self._vxlan_local_ip: |
|
957 return self._vxlan_local_ip |
|
958 (laddr, lup, gaddr, gup) = evsutil.get_vxlan_addrs_and_uplinks() |
|
959 if laddr: |
|
960 self._vxlan_local_ip = laddr |
|
961 elif lup: |
|
962 self._vxlan_local_ip = self._get_local_ip(lup, gaddr) |
|
963 else: |
|
964 self._vxlan_local_ip = self._get_local_ip(gup, gaddr) |
|
965 return self._vxlan_local_ip |
|
966 |
|
967 def _do_neutron_credentials(self, config, input_file, section): |
|
968 neutron_cfg = self._read_config(input_file) |
|
969 tenant = None |
|
970 if neutron_cfg.has_option(section, 'admin_tenant_name'): |
|
971 tenant = neutron_cfg.get(section, 'admin_tenant_name') |
|
972 config.set('DEFAULT', 'admin_tenant_name', tenant) |
|
973 user = None |
|
974 if neutron_cfg.has_option(section, 'admin_user'): |
|
975 user = neutron_cfg.get(section, 'admin_user') |
|
976 config.set('DEFAULT', 'admin_user', user) |
|
977 passwd = None |
|
978 if neutron_cfg.has_option(section, 'admin_password'): |
|
979 passwd = neutron_cfg.get(section, 'admin_password') |
|
980 config.set('DEFAULT', 'admin_password', passwd) |
|
981 auth_uri_option = ('auth_uri' if input_file == NEUTRON_CONF else |
|
982 'auth_url') |
|
983 if neutron_cfg.has_option(section, auth_uri_option): |
|
984 auth_url = neutron_cfg.get(section, auth_uri_option) |
|
985 config.set('DEFAULT', 'auth_url', auth_url) |
|
986 if neutron_cfg.has_option(section, 'auth_region'): |
|
987 auth_region = neutron_cfg.get(section, 'auth_region') |
|
988 config.set('DEFAULT', 'auth_region', auth_region) |
|
989 |
|
990 if any('%SERVICE_' in val for val in [tenant, user, passwd]): |
|
991 msg = "Neutron credentials are incomplete in %s" % L3_INI |
|
992 log_msg(LOG_WARN, msg) |
|
993 |
|
994 def _backup_file(self, orig_file): |
|
995 today = datetime.now().strftime("%Y%m%d%H%M%S") |
|
996 new_file = orig_file + '.' + today |
|
997 try: |
|
998 self._copy_file(orig_file, new_file) |
|
999 msg = "Backed up current %s in %s" % (orig_file, new_file) |
|
1000 log_msg(LOG_DEBUG, msg) |
|
1001 except (IOError, OSError): |
|
1002 msg = "Unable to create a backup of %s" % orig_file |
|
1003 log_msg(LOG_WARN, msg) |
|
1004 |
|
1005 def _copy_file(self, orig_file, new_file): |
|
1006 copy2(orig_file, new_file) |
|
1007 uid = file_owner[orig_file] |
|
1008 os.chown(new_file, uid, uid) |
|
1009 |
|
1010 def update_neutron_conf(self): |
|
1011 self._backup_file(NEUTRON_CONF) |
|
1012 msg = "Updating %s" % NEUTRON_CONF |
|
1013 log_msg(LOG_DEBUG, msg) |
|
1014 self._copy_file(NEUTRON_CONF, NEUTRON_CONF + '.migr') |
|
1015 conf_file = NEUTRON_CONF + '.migr' |
|
1016 config = self._read_config(conf_file) |
|
1017 self._do_fixed(conf_file, config) |
|
1018 service_plugins = 'router' |
|
1019 if config.has_option('DEFAULT', 'service_plugins'): |
|
1020 service_plugins = config.get('DEFAULT', 'service_plugins') |
|
1021 if service_plugins: |
|
1022 service_plugins = 'router,' + service_plugins |
|
1023 else: |
|
1024 service_plugins = 'router' |
|
1025 config.set('DEFAULT', 'service_plugins', service_plugins) |
|
1026 self._write_config(conf_file, config) |
|
1027 move(conf_file, NEUTRON_CONF) |
|
1028 |
|
1029 def update_ml2_conf_ini(self): |
|
1030 """ |
|
1031 Reference target configuration state: |
|
1032 [ml2] |
|
1033 type_drivers = flat,vlan,vxlan |
|
1034 tenant_network_types = vlan |
|
1035 mechanism_drivers = openvswitch |
|
1036 [ml2_type_flat] |
|
1037 flat_networks = external |
|
1038 [ml2_type_vlan] |
|
1039 network_vlan_ranges = physnet1:300:400,extnet:240:240 |
|
1040 [ml2_type_gre] |
|
1041 [ml2_type_vxlan] |
|
1042 [securitygroup] |
|
1043 enable_security_group = False |
|
1044 enable_ipset = False |
|
1045 """ |
|
1046 self._backup_file(ML2_INI) |
|
1047 msg = "Updating %s" % ML2_INI |
|
1048 log_msg(LOG_DEBUG, msg) |
|
1049 self._copy_file(ML2_INI, ML2_INI + '.migr') |
|
1050 conf_file = ML2_INI + '.migr' |
|
1051 config = self._read_config(conf_file) |
|
1052 self._do_fixed(conf_file, config) |
|
1053 if l2type == L2_TYPE_VXLAN: |
|
1054 self._do_ml2_vni_range(config) |
|
1055 elif l2type == L2_TYPE_VLAN: |
|
1056 self._do_ml2_vlan_range(config) |
|
1057 self._write_config(conf_file, config) |
|
1058 move(conf_file, ML2_INI) |
|
1059 |
|
1060 def update_ovs_neutron_plugin_ini(self, bmap_str): |
|
1061 """ |
|
1062 Reference target configuration state: |
|
1063 [ovs] |
|
1064 integration_bridge = br_int0 |
|
1065 bridge_mappings = physnet1:l3stub0 (for VLAN) |
|
1066 local_ip = A.B.C.D (for VXLAN) |
|
1067 enable_tunneling = True (for VXLAN) |
|
1068 [agent] |
|
1069 root_helper = |
|
1070 tunnel_types = vxlan (for VXLAN) |
|
1071 [securitygroup] |
|
1072 enable_security_group = False |
|
1073 """ |
|
1074 self._backup_file(OVS_INI) |
|
1075 msg = "Updating %s" % OVS_INI |
|
1076 log_msg(LOG_DEBUG, msg) |
|
1077 self._copy_file(OVS_INI, OVS_INI + '.migr') |
|
1078 conf_file = OVS_INI + '.migr' |
|
1079 config = self._read_config(conf_file) |
|
1080 self._do_fixed(conf_file, config) |
|
1081 if l2type == L2_TYPE_VXLAN: |
|
1082 local_ip = self._get_vxlan_local_ip() |
|
1083 if local_ip: |
|
1084 config.set('ovs', 'local_ip', local_ip) |
|
1085 else: |
|
1086 msg = """Could not determine IP address for VXLAN endpoint. |
|
1087 Manually set the local_ip option in ovs_neutron_plugin.ini""" |
|
1088 log_msg(LOG_WARN, msg) |
|
1089 if bmap_str: |
|
1090 config.set('ovs', 'bridge_mappings', bmap_str) |
|
1091 self._do_rabbit_host(config) |
|
1092 self._write_config(conf_file, config) |
|
1093 move(conf_file, OVS_INI) |
|
1094 |
|
1095 def update_dhcp_agent_ini(self): |
|
1096 self._backup_file(DHCP_INI) |
|
1097 msg = "Updating %s" % DHCP_INI |
|
1098 log_msg(LOG_DEBUG, msg) |
|
1099 self._copy_file(DHCP_INI, DHCP_INI + '.migr') |
|
1100 conf_file = DHCP_INI + '.migr' |
|
1101 config = self._read_config(conf_file) |
|
1102 self._do_fixed(conf_file, config) |
|
1103 self._write_config(conf_file, config) |
|
1104 move(conf_file, DHCP_INI) |
|
1105 |
|
1106 def update_l3_agent_ini(self): |
|
1107 self._backup_file(L3_INI) |
|
1108 msg = "Updating %s" % L3_INI |
|
1109 log_msg(LOG_DEBUG, msg) |
|
1110 self._copy_file(L3_INI, L3_INI + '.migr') |
|
1111 conf_file = L3_INI + '.migr' |
|
1112 config = self._read_config(conf_file) |
|
1113 if l2type == L2_TYPE_VLAN: |
|
1114 global external_network_datalink |
|
1115 if config.has_option('DEFAULT', 'external_network_datalink'): |
|
1116 external_network_datalink = \ |
|
1117 config.get('DEFAULT', 'external_network_datalink') |
|
1118 if not external_network_datalink: |
|
1119 external_network_datalink = None |
|
1120 else: |
|
1121 external_network_datalink = 'net0' |
|
1122 self._do_fixed(conf_file, config) |
|
1123 if is_svc_online(SVC_METADATA_AGENT): |
|
1124 self._do_neutron_credentials(config, METADATA_INI, "DEFAULT") |
|
1125 else: |
|
1126 self._do_neutron_credentials(config, NEUTRON_CONF, |
|
1127 "keystone_authtoken") |
|
1128 self._write_config(conf_file, config) |
|
1129 move(conf_file, L3_INI) |
|
1130 |
|
1131 def update_nova_conf(self): |
|
1132 self._backup_file(NOVA_CONF) |
|
1133 msg = "Updating %s" % NOVA_CONF |
|
1134 log_msg(LOG_DEBUG, msg) |
|
1135 self._copy_file(NOVA_CONF, NOVA_CONF + '.migr') |
|
1136 conf_file = NOVA_CONF + '.migr' |
|
1137 config = self._read_config(conf_file) |
|
1138 self._do_fixed(conf_file, config) |
|
1139 self._write_config(conf_file, config) |
|
1140 move(conf_file, NOVA_CONF) |
|
1141 |
|
1142 def update_Open_vSwitch_other_config(self, bmap_str): |
|
1143 bm_str = "other_config:bridge_mappings=" + bmap_str |
|
1144 try: |
|
1145 check_call(['/usr/bin/pfexec', '/usr/sbin/ovs-vsctl', 'set', |
|
1146 'Open_vSwitch', '.', bm_str]) |
|
1147 msg = """Successfully set other_config column in Open_vSwitch table |
|
1148 with value %s.""" % bm_str |
|
1149 log_msg(LOG_DEBUG, msg) |
|
1150 except: |
|
1151 msg = """Failed to set other_config column in Open_vSwitch table |
|
1152 with value %s.""" % bm_str |
|
1153 log_msg(LOG_WARN, msg) |
|
1154 |
|
1155 |
|
1156 def enable_svc(svcname, exit_on_fail=False): |
|
1157 msg = "Enabling service: %s" % svcname |
|
1158 log_msg(LOG_INFO, msg) |
|
1159 cmd = ['/usr/bin/pfexec', '/usr/sbin/svcadm', 'enable', '-s'] |
|
1160 cmd.append(svcname) |
|
1161 try: |
|
1162 check_call(cmd, stdout=PIPE, stderr=PIPE) |
|
1163 except CalledProcessError as err: |
|
1164 msg = """Failed to enable %s: %s. |
|
1165 Please verify "and manually enable the service""" % (svcname, err) |
|
1166 log_msg(LOG_ERROR, msg) |
|
1167 if exit_on_fail: |
|
1168 msg = "Exiting..." |
|
1169 log_msg(LOG_INFO, msg) |
|
1170 sys.exit(1) |
|
1171 |
|
1172 |
|
1173 def disable_svc(svcname): |
|
1174 msg = "Disabling service: %s" % svcname |
|
1175 log_msg(LOG_INFO, msg) |
|
1176 try: |
|
1177 check_call(['/usr/bin/pfexec', '/usr/sbin/svcadm', 'disable', '-s', |
|
1178 svcname], stdout=PIPE, stderr=PIPE) |
|
1179 except CalledProcessError as err: |
|
1180 msg = "Failed to disable %s: %s." % (svcname, err) |
|
1181 log_msg(LOG_ERROR, msg) |
|
1182 |
|
1183 |
|
1184 def nova_evs_to_ovs(migr_conf_obj): |
|
1185 # step-1: disable nova-compute |
|
1186 disable_svc(SVC_NOVA_COMPUTE) |
|
1187 |
|
1188 # step-2: update zones' config |
|
1189 migr_vm = NovaVmEVSToOVS() |
|
1190 determine_neutron_conn_params() |
|
1191 zoneutil = ZoneUtil() |
|
1192 for name in zoneutil.get_zone_names(): |
|
1193 zone = zoneutil.get_zone_by_name(name) |
|
1194 if not zone: |
|
1195 msg = "skipping EVS-OVS migration of VM %s; not found" % name |
|
1196 log_msg(LOG_DEBUG, msg) |
|
1197 continue |
|
1198 if zone.state == 'incomplete': |
|
1199 msg = """skipping EVS-OVS migration of VM %s; It is in 'incomplete' |
|
1200 state""" % name |
|
1201 log_msg(LOG_DEBUG, msg) |
|
1202 continue |
|
1203 with ZoneConfig(zone) as zc: |
|
1204 tenant_name = zc.lookup_resource_property('global', 'tenant') |
|
1205 if not tenant_name: |
|
1206 msg = """skipping EVS-OVS migration of non-openstack |
|
1207 managed VM %s""" % name |
|
1208 log_msg(LOG_DEBUG, msg) |
|
1209 continue |
|
1210 try: |
|
1211 uuid.UUID(tenant_name) |
|
1212 except: |
|
1213 msg = """skipping EVS-OVS migration of non-openstack |
|
1214 managed VM %s""" % name |
|
1215 log_msg(LOG_DEBUG, msg) |
|
1216 continue |
|
1217 msg = "Performing EVS-OVS migration of VM: %s" % name |
|
1218 log_msg(LOG_INFO, msg) |
|
1219 |
|
1220 # step 2.1: migrate zone config |
|
1221 installed_port_uuids = migr_vm.migrate(zone) |
|
1222 # step 2.2: shutdown |
|
1223 if zone.state == 'running': |
|
1224 try: |
|
1225 msg = "Shutting down VM: %s, after modifying zone's config" % \ |
|
1226 name |
|
1227 log_msg(LOG_DEBUG, msg) |
|
1228 zone.shutdown() |
|
1229 except Exception as ex: |
|
1230 msg = """ Failed to shutdown instance %s. The zone's config |
|
1231 has been modified to OVS. Manually start the VM""" % name |
|
1232 log_msg(LOG_WARN, msg) |
|
1233 if installed_port_uuids: |
|
1234 nc = neutron_client.Client( |
|
1235 username=neutron_conn['username'], |
|
1236 password=neutron_conn['password'], |
|
1237 tenant_name=neutron_conn['tenant'], |
|
1238 auth_url=neutron_conn['auth_url']) |
|
1239 for vport_uuid in installed_port_uuids: |
|
1240 port_req_body = {'port': {'binding:host_id': HOSTNAME}} |
|
1241 nc.update_port(vport_uuid, port_req_body) |
|
1242 |
|
1243 # step-3: change nova.conf |
|
1244 migr_conf_obj.update_nova_conf() |
|
1245 |
|
1246 # we will enable the service later |
|
1247 |
|
1248 |
|
1249 def dhcp_evs_to_ovs(migr_conf_obj): |
|
1250 # step-1: disable neutron-dhcp-agent |
|
1251 disable_svc(SVC_DHCP_AGENT) |
|
1252 |
|
1253 # step-2: change dhcp_agent.ini |
|
1254 migr_conf_obj.update_dhcp_agent_ini() |
|
1255 |
|
1256 # we will enable the service later |
|
1257 |
|
1258 |
|
1259 def add_ovs_bridge(bridge_name): |
|
1260 try: |
|
1261 check_call(['/usr/bin/pfexec', '/usr/sbin/ovs-vsctl', '--', |
|
1262 '--may-exist', 'add-br', bridge_name], stdout=PIPE, |
|
1263 stderr=PIPE) |
|
1264 msg = "Created %s ovs bridge" % bridge_name |
|
1265 log_msg(LOG_DEBUG, msg) |
|
1266 if bridge_name == OVS_EXT_BRIDGE: |
|
1267 check_call(['/usr/bin/pfexec', '/usr/sbin/ovs-vsctl', |
|
1268 'br-set-external-id', OVS_EXT_BRIDGE, 'bridge-id', |
|
1269 OVS_EXT_BRIDGE]) |
|
1270 except CalledProcessError as err: |
|
1271 msg = "Failed to create %s ovs bridge: %s" % (bridge_name, err) |
|
1272 log_msg(LOG_ERROR, msg) |
|
1273 |
|
1274 |
|
1275 def l3_evs_to_ovs(migr_conf_obj): |
|
1276 # step-1: disable neutron-l3-agent |
|
1277 disable_svc(SVC_L3_AGENT) |
|
1278 |
|
1279 # step-2: change l3_agent.ini and ovs_neutron_plugin.ini |
|
1280 migr_conf_obj.update_l3_agent_ini() |
|
1281 |
|
1282 # step-3: create external network bridge |
|
1283 add_ovs_bridge(OVS_EXT_BRIDGE) |
|
1284 |
|
1285 # we will enable the service later |
|
1286 |
|
1287 |
|
1288 def neutron_evs_to_ovs(migr_conf_obj): |
|
1289 # step-1: disable neutron-server |
|
1290 disable_svc(SVC_NEUTRON_SERVER) |
|
1291 |
|
1292 # step-2: migrate DB to ml2 |
|
1293 migr_ml2 = DBEVSToMl2() |
|
1294 migr_ml2() |
|
1295 |
|
1296 # step-3: change ml2_conf.ini and neutron.conf |
|
1297 migr_conf_obj.update_ml2_conf_ini() |
|
1298 migr_conf_obj.update_neutron_conf() |
|
1299 |
|
1300 # step-4: enable neutron-server |
|
1301 enable_svc(SVC_NEUTRON_SERVER) |
|
1302 |
|
1303 |
|
1304 def is_svc_online(svc, exit_on_maintenance=False): |
|
1305 try: |
|
1306 state = check_output(['/usr/bin/svcs', '-H', '-o', 'state', svc], |
|
1307 stderr=PIPE) |
|
1308 except: |
|
1309 return False |
|
1310 if exit_on_maintenance and state.strip() == 'maintenance': |
|
1311 msg = """Unable to perform EVS to OVS migration as %s is in maintenance |
|
1312 state. Please fix the errors and clear the svc before running |
|
1313 migration""" % svc |
|
1314 log_msg(LOG_ERROR, msg) |
|
1315 sys.exit(1) |
|
1316 return state.strip() == 'online' |
|
1317 |
|
1318 |
|
1319 def create_backup_be(): |
|
1320 msg = "Creating backup BE" |
|
1321 log_msg(LOG_INFO, msg) |
|
1322 boot_envs = check_output(['/usr/sbin/beadm', 'list', '-H'], |
|
1323 stderr=PIPE) |
|
1324 for be in boot_envs.splitlines(): |
|
1325 be_fields = be.split(';') |
|
1326 if 'N' in be_fields[2]: |
|
1327 curr_be = be_fields[0] |
|
1328 backup_be = curr_be + '-backup-ovs-upgrade' |
|
1329 break |
|
1330 msg = "Active BE is: %s" % curr_be |
|
1331 log_msg(LOG_DEBUG, msg) |
|
1332 try: |
|
1333 check_call(['/usr/sbin/beadm', 'create', backup_be], stdout=PIPE, |
|
1334 stderr=PIPE) |
|
1335 msg = "Created backup BE: " + backup_be |
|
1336 log_msg(LOG_DEBUG, msg) |
|
1337 except: |
|
1338 msg = "Backup BE already exists: " + backup_be |
|
1339 log_msg(LOG_DEBUG, msg) |
|
1340 |
|
1341 |
|
1342 def get_node_svcs(): |
|
1343 global curnode_svcs |
|
1344 for svc in ALL_SVCS: |
|
1345 if is_svc_online(svc): |
|
1346 curnode_svcs.append(svc) |
|
1347 |
|
1348 |
|
1349 def get_default_gateways(): |
|
1350 def_gws = set() |
|
1351 routes = check_output(['/usr/bin/pfexec', '/usr/bin/netstat', |
|
1352 '-arn']).splitlines() |
|
1353 for route in routes: |
|
1354 route = route.strip() |
|
1355 elems = route.split() |
|
1356 if elems and elems[0] == 'default': |
|
1357 def_gws.add(elems[1]) |
|
1358 return def_gws |
|
1359 |
|
1360 |
|
1361 def add_uplink_to_br(uplink, bridge): |
|
1362 def add_ips_and_gws_to_port(port): |
|
1363 if ips: |
|
1364 try: |
|
1365 check_call(['/usr/bin/pfexec', '/usr/sbin/ipadm', 'show-if', |
|
1366 port], stdout=PIPE, stderr=PIPE) |
|
1367 except CalledProcessError: |
|
1368 check_call(['/usr/bin/pfexec', '/usr/sbin/ipadm', 'create-ip', |
|
1369 port], stdout=PIPE) |
|
1370 aconf_configured = False |
|
1371 for ip in ips: |
|
1372 msg = "Adding IP %s to %s" % (ip, port) |
|
1373 log_msg(LOG_DEBUG, msg) |
|
1374 addrtype_addr = ip.split(':') |
|
1375 addrtype, addr = addrtype_addr[0], addrtype_addr[1] |
|
1376 if addrtype == 'static': |
|
1377 check_call(['/usr/bin/pfexec', '/usr/sbin/ipadm', |
|
1378 'create-addr', '-T', addrtype, '-a', addr, port], |
|
1379 stdout=PIPE) |
|
1380 elif addrtype == 'addrconf': |
|
1381 if not aconf_configured: |
|
1382 check_call(['/usr/bin/pfexec', '/usr/sbin/ipadm', |
|
1383 'create-addr', '-T', addrtype, port], |
|
1384 stdout=PIPE) |
|
1385 aconf_configured = True |
|
1386 else: |
|
1387 check_call(['/usr/bin/pfexec', '/usr/sbin/ipadm', |
|
1388 'create-addr', '-T', addrtype, port], stdout=PIPE) |
|
1389 new_gateways = get_default_gateways() |
|
1390 removed_gateways = old_gateways - new_gateways |
|
1391 for gw in removed_gateways: |
|
1392 # simple check for IPv6 address |
|
1393 if ':' in gw: |
|
1394 continue |
|
1395 msg = "Adding default gateway %s" % gw |
|
1396 log_msg(LOG_DEBUG, msg) |
|
1397 check_call(['/usr/bin/pfexec', '/usr/sbin/route', 'add', 'default', |
|
1398 gw], stdout=PIPE) |
|
1399 |
|
1400 msg = "Migrating %s link to OVS bridge: %s" % (uplink, bridge) |
|
1401 log_msg(LOG_DEBUG, msg) |
|
1402 # Store IP and gateway info |
|
1403 ips = [] |
|
1404 old_gateways = get_default_gateways() |
|
1405 try: |
|
1406 ips = check_output(['/usr/bin/pfexec', '/usr/sbin/ipadm', 'show-addr', |
|
1407 '-po', 'type,addr', |
|
1408 uplink], stderr=PIPE).splitlines() |
|
1409 check_call(['/usr/bin/pfexec', '/usr/sbin/ipadm', 'delete-ip', |
|
1410 uplink], stdout=PIPE, stderr=PIPE) |
|
1411 except CalledProcessError as err: |
|
1412 pass |
|
1413 |
|
1414 try: |
|
1415 check_call(['/usr/bin/pfexec', '/usr/sbin/dladm', 'set-linkprop', '-p', |
|
1416 'openvswitch=on', uplink], stdout=PIPE, stderr=PIPE) |
|
1417 except CalledProcessError as err: |
|
1418 msg = """Failed to set openvswitch property=on for %s - link is busy. |
|
1419 Follow the below steps to migrate link to OVS bridge manually. |
|
1420 1. Remove any flows, IP etc. so that link is unused. |
|
1421 2. dladm set-linkprop -p openvswitch=on %s |
|
1422 3. ovs-vsctl -- --may-exist add-port %s %s |
|
1423 4. Replumb IPs, if existed before on %s, on %s.""" % \ |
|
1424 (uplink, uplink, bridge, uplink, uplink, bridge) |
|
1425 log_msg(LOG_ERROR, msg, oneliner=False) |
|
1426 return |
|
1427 |
|
1428 # add uplink to bridge |
|
1429 check_call(['/usr/bin/pfexec', '/usr/sbin/ovs-vsctl', '--', '--may-exist', |
|
1430 'add-port', bridge, uplink]) |
|
1431 try: |
|
1432 add_ips_and_gws_to_port(bridge) |
|
1433 except CalledProcessError as err: |
|
1434 msg = """Failed to configure the IPs(%s) on %s VNIC. Manually |
|
1435 configure the IPs and set default gateway""" % (ips, bridge) |
|
1436 log_msg(LOG_ERROR, msg) |
|
1437 |
|
1438 |
|
1439 def get_uplink_ports_for_int_bridge(): |
|
1440 int_uplinks = set(bridge_mappings.values()) |
|
1441 int_uplinks.discard(external_network_datalink) |
|
1442 return int_uplinks |
|
1443 |
|
1444 |
|
1445 def get_uplink_port_for_ext_bridge(): |
|
1446 if l2type == L2_TYPE_VLAN and external_network_datalink is not None: |
|
1447 return external_network_datalink |
|
1448 return bridge_mappings.get(external_network_name) |
|
1449 |
|
1450 |
|
1451 def determine_neutron_conn_params(): |
|
1452 global neutron_conn |
|
1453 if neutron_conn: |
|
1454 return |
|
1455 config = iniparse.ConfigParser() |
|
1456 if SVC_NOVA_COMPUTE in curnode_svcs: |
|
1457 config.readfp(open(NOVA_CONF)) |
|
1458 neutron_conn['username'] = config.get('neutron', 'admin_username') |
|
1459 neutron_conn['password'] = config.get('neutron', 'admin_password') |
|
1460 neutron_conn['tenant'] = config.get('neutron', 'admin_tenant_name') |
|
1461 neutron_conn['auth_url'] = \ |
|
1462 config.get('keystone_authtoken', 'auth_uri') |
|
1463 else: |
|
1464 config.readfp(open(NEUTRON_CONF)) |
|
1465 neutron_conn['username'] = \ |
|
1466 config.get('keystone_authtoken', 'admin_user') |
|
1467 neutron_conn['password'] = \ |
|
1468 config.get('keystone_authtoken', 'admin_password') |
|
1469 neutron_conn['tenant'] = \ |
|
1470 config.get('keystone_authtoken', 'admin_tenant_name') |
|
1471 neutron_conn['auth_url'] = \ |
|
1472 config.get('keystone_authtoken', 'auth_uri') |
|
1473 |
|
1474 |
|
1475 def determine_external_network_name(): |
|
1476 global external_network_name, external_network_vid |
|
1477 determine_neutron_conn_params() |
|
1478 nc = neutron_client.Client(username=neutron_conn['username'], |
|
1479 password=neutron_conn['password'], |
|
1480 tenant_name=neutron_conn['tenant'], |
|
1481 auth_url=neutron_conn['auth_url']) |
|
1482 search_opts = {'router:external': True} |
|
1483 try: |
|
1484 external_network = nc.list_networks(**search_opts)['networks'] |
|
1485 except: |
|
1486 msg = """Could not get external network information from |
|
1487 neutron-server. Make sure it is online.""" |
|
1488 log_msg(LOG_ERROR, msg) |
|
1489 sys.exit(1) |
|
1490 |
|
1491 if not external_network: |
|
1492 return |
|
1493 external_network = external_network[0] |
|
1494 nw_type = external_network['provider:network_type'] |
|
1495 if nw_type == L2_TYPE_FLAT: |
|
1496 external_network_name = FLAT_PHYS_NET |
|
1497 else: |
|
1498 assert nw_type == L2_TYPE_VLAN |
|
1499 external_network_name = EXT_VLAN_PHYS_NET |
|
1500 external_network_vid = external_network['provider:segmentation_id'] |
|
1501 msg = "External Network name is " + external_network_name |
|
1502 log_msg(LOG_DEBUG, msg) |
|
1503 |
|
1504 |
|
1505 def determine_bridge_mappings(): |
|
1506 global bridge_mappings, external_network_datalink |
|
1507 global_nw_uplink_map = evsutil.get_global_vlanrange_nw_uplink_map() |
|
1508 local_uplink_map = evsutil.get_local_vlanrange_uplink_map() |
|
1509 # Any local uplink ports should have the same vlan-range boundaries |
|
1510 # as the global ones. This is expected in an openstack deployment but |
|
1511 # is not enforced by evs itself. So we raise a warning if we encounter |
|
1512 # a local uplink-port for a vlan-range whose boundaries are different |
|
1513 # from any that are defined globally. |
|
1514 errs = set(local_uplink_map.keys()) - set(global_nw_uplink_map.keys()) |
|
1515 if errs: |
|
1516 errs = ','.join(errs) |
|
1517 msg = """Found the following incorrect vlan_ranges that were not |
|
1518 added to bridge_mappings in ovs_neutron_plugin.ini. Please update |
|
1519 manually if necessary - %s""" % errs |
|
1520 log_msg(LOG_WARN, msg) |
|
1521 for vlanranges_str, (nw, uplink) in global_nw_uplink_map.iteritems(): |
|
1522 uplink = local_uplink_map.get(vlanranges_str, uplink) |
|
1523 bridge_mappings[nw] = uplink |
|
1524 if evsutil.local_flat_nw_uplink: |
|
1525 bridge_mappings[FLAT_PHYS_NET] = evsutil.local_flat_nw_uplink |
|
1526 elif evsutil.global_flat_nw_uplink: |
|
1527 bridge_mappings[FLAT_PHYS_NET] = evsutil.global_flat_nw_uplink |
|
1528 |
|
1529 external_network_datalink = bridge_mappings.get(external_network_name) |
|
1530 if external_network_datalink: |
|
1531 msg = "External Network datalink is " + external_network_datalink |
|
1532 log_msg(LOG_DEBUG, msg) |
|
1533 if bridge_mappings.values().count(external_network_datalink) > 1: |
|
1534 msg = """The external network datalink '%s' cannot be the uplink-port |
|
1535 of any physical network other than external network. Please satisfy |
|
1536 this condition before running migration.""" % external_network_datalink |
|
1537 log_msg(LOG_ERROR, msg) |
|
1538 sys.exit(1) |
|
1539 |
|
1540 # Depending on l2type and whether l3-agent is running on this node, |
|
1541 # bridge_mappings should have the following: |
|
1542 # 1. l3-agent not in node and l2type = vxlan => no bridge mappings. This is |
|
1543 # already handled since determine_bridge_mappings() won't be called for |
|
1544 # this condition. |
|
1545 # 2. l3-agent not in node and l2type = vlan/flat => bridge mappings should |
|
1546 # not have mapping for external network. |
|
1547 # 3. l3-agent in node and l2type = vxlan => bridge mappings should have |
|
1548 # only the mapping for external network. |
|
1549 # 4. l3-agent in node and l2type = vlan/flat => bridge mappings should have |
|
1550 # all the orignial mappings. |
|
1551 if SVC_L3_AGENT not in curnode_svcs: |
|
1552 bridge_mappings.pop(external_network_name, None) |
|
1553 elif l2type == L2_TYPE_VXLAN: |
|
1554 bridge_mappings.clear() |
|
1555 if external_network_datalink: |
|
1556 bridge_mappings[external_network_name] = \ |
|
1557 external_network_datalink |
|
1558 |
|
1559 |
|
1560 def finish(): |
|
1561 msg = "Migration Successful" |
|
1562 log_msg(LOG_INFO, msg) |
|
1563 check_call(['/usr/bin/pfexec', '/usr/sbin/svccfg', '-s', |
|
1564 SVC_NEUTRON_UPGRADE, 'setprop', 'config/evs2ovs', '=', |
|
1565 'astring:', 'done'], stdout=PIPE, stderr=PIPE) |
|
1566 check_call(['/usr/bin/pfexec', '/usr/sbin/svccfg', '-s', |
|
1567 SVC_NEUTRON_UPGRADE, 'refresh'], stdout=PIPE, stderr=PIPE) |
|
1568 msg = "Exiting..." |
|
1569 log_msg(LOG_INFO, msg) |
|
1570 sys.exit() |
|
1571 |
|
1572 |
|
1573 def main(): |
|
1574 # help text |
|
1575 parser = argparse.ArgumentParser( |
|
1576 formatter_class=argparse.RawDescriptionHelpFormatter, description=''' |
|
1577 Migration script to migrate OpenStack Cloud based on EVS to an |
|
1578 OpenStack cloud based on OVS. |
|
1579 |
|
1580 There are four steps to migration: |
|
1581 -- Populate Neutron ML2 tables |
|
1582 -- Replace EVS information in existing configuration files with OVS |
|
1583 (neutron.conf, dhcp_agent.ini, l3_agent.ini, and nova.conf) |
|
1584 -- Add OVS information to new configuration files |
|
1585 (ml2_conf.ini and ovs_neutron_agent.ini) |
|
1586 -- Clear EVS information in Zones and populate the anets for OVS |
|
1587 |
|
1588 The nodes must be migrated in the following order: |
|
1589 -- controller node running neutron-server |
|
1590 -- all of the nodes running neutron-dhcp-agent or neutron-l3-agent |
|
1591 -- all of the compute nodes |
|
1592 |
|
1593 It is advisable to run migration with nohup if using ssh over a link that |
|
1594 is also used by OpenStack. |
|
1595 ''') |
|
1596 parser.parse_args() |
|
1597 |
|
1598 signal.signal(signal.SIGHUP, signal.SIG_IGN) |
|
1599 try: |
|
1600 out = check_output(['/usr/bin/pfexec', '/usr/bin/svcprop', '-p', |
|
1601 'config/evs2ovs', SVC_NEUTRON_UPGRADE], |
|
1602 stderr=PIPE) |
|
1603 if out.strip() == 'done': |
|
1604 msg = "Migration has already run on this node." |
|
1605 log_msg(LOG_INFO, msg) |
|
1606 return |
|
1607 except: |
|
1608 pass |
|
1609 |
|
1610 # get the current node services |
|
1611 get_node_svcs() |
|
1612 if not curnode_svcs: |
|
1613 msg = "Nothing to migrate on this node. Quitting." |
|
1614 log_msg(LOG_INFO, msg) |
|
1615 return |
|
1616 |
|
1617 msg = """The script has determined that following services - %s - are |
|
1618 online and the system will be migrated based on these services.""" % \ |
|
1619 ', '.join(curnode_svcs) |
|
1620 log_msg(LOG_INFO, msg) |
|
1621 |
|
1622 # Create backup BE |
|
1623 create_backup_be() |
|
1624 |
|
1625 # Even if nova-compute is the only svc on this node, make sure neutron |
|
1626 # is also installed. |
|
1627 if not set(curnode_svcs) - set([SVC_NOVA_COMPUTE]): |
|
1628 try: |
|
1629 check_call(['pkg', 'info', 'neutron'], stdout=PIPE, stderr=PIPE) |
|
1630 except: |
|
1631 msg = "cloud/openstack/neutron pkg not found." |
|
1632 log_msg(LOG_ERROR, msg) |
|
1633 msg = """cloud/openstack/neutron pkg needs to be installed on this |
|
1634 node before migration.""" |
|
1635 log_msg(LOG_INFO, msg) |
|
1636 return |
|
1637 |
|
1638 # If nova-compute is running on this node, we can execute everything as |
|
1639 # root. Else, this is a network node and we can execute everything as |
|
1640 # neutron user. |
|
1641 if SVC_NOVA_COMPUTE not in curnode_svcs: |
|
1642 msg = "Changing user to neutron" |
|
1643 log_msg(LOG_DEBUG, msg) |
|
1644 os.setgid(UID_NEUTRON) |
|
1645 os.setuid(UID_NEUTRON) |
|
1646 |
|
1647 global evsutil |
|
1648 evsutil = EVSUtil() |
|
1649 global l2type |
|
1650 l2type = evsutil.l2type |
|
1651 msg = "l2type = %s" % l2type |
|
1652 log_msg(LOG_DEBUG, msg) |
|
1653 migr_conf_obj = ConfigEVSToOVS() |
|
1654 |
|
1655 # step-0: Determine bridge_mappings and ensure external network datalink |
|
1656 # is not serving as uplink port for other physical networks. This is only |
|
1657 # required if l2-type is VLAN or FLAT or if neutron-l3-agent is running on |
|
1658 # this node. |
|
1659 if l2type != L2_TYPE_VXLAN or SVC_L3_AGENT in curnode_svcs: |
|
1660 determine_external_network_name() |
|
1661 determine_bridge_mappings() |
|
1662 |
|
1663 # step-1: Populate ML2 tables and update Neutron and ML2 config files. |
|
1664 if SVC_NEUTRON_SERVER in curnode_svcs: |
|
1665 msg = "Current migration based on svc: %s" % SVC_NEUTRON_SERVER |
|
1666 log_msg(LOG_INFO, msg) |
|
1667 neutron_evs_to_ovs(migr_conf_obj) |
|
1668 # We have already enabled neutron-server. There is nothing else to do |
|
1669 # wrt the service. |
|
1670 curnode_svcs.remove(SVC_NEUTRON_SERVER) |
|
1671 |
|
1672 # We don't need to do anything else if neutron-server is the only service |
|
1673 # we are migrating on this node. |
|
1674 if not curnode_svcs: |
|
1675 finish() |
|
1676 |
|
1677 # step-2: add ovs integration bridge and update conf for |
|
1678 # neutron-openvswitch-agent. |
|
1679 if not is_svc_online(SVC_OVSDB_SERVER, exit_on_maintenance=True): |
|
1680 enable_svc(SVC_OVSDB_SERVER, exit_on_fail=True) |
|
1681 if not is_svc_online(SVC_VSWITCH_SERVER, exit_on_maintenance=True): |
|
1682 enable_svc(SVC_VSWITCH_SERVER, exit_on_fail=True) |
|
1683 add_ovs_bridge(OVS_INT_BRIDGE) |
|
1684 bmap_str = '' |
|
1685 if bridge_mappings: |
|
1686 for nw, uplink in bridge_mappings.iteritems(): |
|
1687 bmap_str += nw + ':' + uplink + ',' |
|
1688 bmap_str = bmap_str.strip(',') |
|
1689 if bmap_str: |
|
1690 msg = "bridge_mappings = " + bmap_str |
|
1691 log_msg(LOG_DEBUG, msg) |
|
1692 migr_conf_obj.update_Open_vSwitch_other_config(bmap_str) |
|
1693 migr_conf_obj.update_ovs_neutron_plugin_ini(bmap_str) |
|
1694 # we will enable the OVS agent later |
|
1695 |
|
1696 # step-3: migrate the other services. |
|
1697 svc_func_map = { |
|
1698 SVC_DHCP_AGENT: dhcp_evs_to_ovs, |
|
1699 SVC_L3_AGENT: l3_evs_to_ovs, |
|
1700 SVC_NOVA_COMPUTE: nova_evs_to_ovs |
|
1701 } |
|
1702 |
|
1703 for svc in curnode_svcs: |
|
1704 msg = "Current migration based on svc: %s" % svc |
|
1705 log_msg(LOG_INFO, msg) |
|
1706 svc_func_map[svc](migr_conf_obj) |
|
1707 |
|
1708 # At this point we have disabled all the services that we are interested |
|
1709 # in. Now we need to add the right uplink-port to the OVS bridges. |
|
1710 if l2type == L2_TYPE_VXLAN: |
|
1711 # check if there are any left over evs-vxlan datalinks |
|
1712 output = check_output(['/usr/sbin/dladm', 'show-vxlan', '-po', 'link'], |
|
1713 stderr=PIPE) |
|
1714 if len(output.strip().splitlines()) != 0: |
|
1715 msg = """There are other VXLAN datalinks present and as a result |
|
1716 OVS agent will go into maintenance. Please remove these datalinks |
|
1717 and clear the OVS agent service.""" |
|
1718 log_msg(LOG_WARN, msg) |
|
1719 else: |
|
1720 assert l2type == L2_TYPE_VLAN or l2type == L2_TYPE_FLAT |
|
1721 int_uplinks = get_uplink_ports_for_int_bridge() |
|
1722 # add the uplink-ports to integration bridge |
|
1723 for uplink in int_uplinks: |
|
1724 add_uplink_to_br(uplink, OVS_INT_BRIDGE) |
|
1725 |
|
1726 # enable all services |
|
1727 enable_svc(SVC_OVS_AGENT) |
|
1728 for svc in curnode_svcs: |
|
1729 if svc == SVC_L3_AGENT: |
|
1730 # add the port to br_ex0 |
|
1731 ext_uplink = get_uplink_port_for_ext_bridge() |
|
1732 if ext_uplink: |
|
1733 add_uplink_to_br(ext_uplink, OVS_EXT_BRIDGE) |
|
1734 enable_svc(svc) |
|
1735 |
|
1736 finish() |
|
1737 |
|
1738 |
|
1739 if __name__ == "__main__": |
|
1740 main() |
|