components/openstack/neutron/files/evs/migrate/migrate-evs-to-ovs
changeset 6848 8e252a37ed0d
parent 6847 57069587975f
child 6849 f9a2279efa0d
equal deleted inserted replaced
6847:57069587975f 6848:8e252a37ed0d
     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()