# Copyright 2016 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from distutils import spawn import os import fixtures import psutil import tenacity from neutron.agent.linux import utils class DaemonProcessFixture(fixtures.Fixture): def __init__(self, temp_dir): super(DaemonProcessFixture, self).__init__() self.temp_dir = temp_dir def _get_pid_from_pidfile(self, pidfile): with open(os.path.join(self.temp_dir, pidfile), 'r') as pidfile_f: pid = pidfile_f.read().strip() try: return int(pid) except ValueError: raise RuntimeError( "Pidfile %(pidfile)s contains %(pid)s that " "is not a pid" % {'pidfile': pidfile, 'pid': pid} ) class OvnNorthd(DaemonProcessFixture): def __init__(self, temp_dir, ovn_nb_db, ovn_sb_db, protocol='unix', debug=True): super(OvnNorthd, self).__init__(temp_dir) self.ovn_nb_db = ovn_nb_db self.ovn_sb_db = ovn_sb_db self.protocol = protocol self.unixctl_path = os.path.join(self.temp_dir, 'ovn_northd.ctl') self.log_file_path = os.path.join(self.temp_dir, 'ovn_northd.log') self.debug = debug if self.protocol == 'ssl': self.private_key = os.path.join(self.temp_dir, 'ovn-privkey.pem') self.certificate = os.path.join(self.temp_dir, 'ovn-cert.pem') self.ca_cert = os.path.join(self.temp_dir, 'controllerca', 'cacert.pem') def _setUp(self): self.addCleanup(self.stop) self.start() def start(self): # start the ovn-northd ovn_northd_cmd = [ spawn.find_executable('ovn-northd'), '-vconsole:off', '--detach', '--ovnnb-db=%s' % self.ovn_nb_db, '--ovnsb-db=%s' % self.ovn_sb_db, '--no-chdir', '--unixctl=%s' % self.unixctl_path, '--log-file=%s' % (self.log_file_path)] if self.protocol == 'ssl': ovn_northd_cmd.append('--private-key=%s' % self.private_key) ovn_northd_cmd.append('--certificate=%s' % self.certificate) ovn_northd_cmd.append('--ca-cert=%s' % self.ca_cert) if self.debug: ovn_northd_cmd.append('--verbose') obj, _ = utils.create_process(ovn_northd_cmd) obj.communicate() def stop(self): try: stop_cmd = ['ovs-appctl', '-t', self.unixctl_path, 'exit'] utils.execute(stop_cmd) except Exception: pass class OvsdbServer(DaemonProcessFixture): def __init__(self, temp_dir, ovs_dir, ovn_nb_db=True, ovn_sb_db=False, protocol='unix', debug=True): super(OvsdbServer, self).__init__(temp_dir) self.ovs_dir = ovs_dir self.ovn_nb_db = ovn_nb_db self.ovn_sb_db = ovn_sb_db # The value of the protocol must be unix or tcp or ssl self.protocol = protocol self.ovsdb_server_processes = [] self.private_key = os.path.join(self.temp_dir, 'ovn-privkey.pem') self.certificate = os.path.join(self.temp_dir, 'ovn-cert.pem') self.ca_cert = os.path.join(self.temp_dir, 'controllerca', 'cacert.pem') self.debug = debug def _setUp(self): if self.ovn_nb_db: self.ovsdb_server_processes.append( {'db_path': os.path.join(self.temp_dir, 'ovn_nb.db'), 'schema_path': os.path.join(self.ovs_dir, 'ovn-nb.ovsschema'), 'remote_path': os.path.join(self.temp_dir, 'ovnnb_db.sock'), 'protocol': self.protocol, 'remote_ip': '127.0.0.1', 'remote_port': '0', 'pidfile': 'ovn-nb.pid', 'unixctl_path': os.path.join(self.temp_dir, 'ovnnb_db.ctl'), 'log_file_path': os.path.join(self.temp_dir, 'ovn_nb.log'), 'db_type': 'nb', 'connection': 'db:OVN_Northbound,NB_Global,connections', 'ctl_cmd': 'ovn-nbctl'}) if self.ovn_sb_db: self.ovsdb_server_processes.append( {'db_path': os.path.join(self.temp_dir, 'ovn_sb.db'), 'schema_path': os.path.join(self.ovs_dir, 'ovn-sb.ovsschema'), 'remote_path': os.path.join(self.temp_dir, 'ovnsb_db.sock'), 'protocol': self.protocol, 'remote_ip': '127.0.0.1', 'remote_port': '0', 'pidfile': 'ovn-sb.pid', 'unixctl_path': os.path.join(self.temp_dir, 'ovnsb_db.ctl'), 'log_file_path': os.path.join(self.temp_dir, 'ovn_sb.log'), 'db_type': 'sb', 'connection': 'db:OVN_Southbound,SB_Global,connections', 'ctl_cmd': 'ovn-sbctl'}) self.addCleanup(self.stop) self.start() def _init_ovsdb_pki(self): os.chdir(self.temp_dir) pki_init_cmd = [spawn.find_executable('ovs-pki'), 'init', '-d', self.temp_dir, '-l', os.path.join(self.temp_dir, 'pki.log'), '--force'] utils.execute(pki_init_cmd) pki_req_sign = [spawn.find_executable('ovs-pki'), 'req+sign', 'ovn', 'controller', '-d', self.temp_dir, '-l', os.path.join(self.temp_dir, 'pki.log'), '--force'] utils.execute(pki_req_sign) def delete_dbs(self): for ovsdb in self.ovsdb_server_processes: try: os.remove(ovsdb['db_path']) except OSError: pass def start(self): pki_done = False for ovsdb_process in self.ovsdb_server_processes: # create the db from the schema using ovsdb-tool ovsdb_tool_cmd = [spawn.find_executable('ovsdb-tool'), 'create', ovsdb_process['db_path'], ovsdb_process['schema_path']] utils.execute(ovsdb_tool_cmd) # start the ovsdb-server ovsdb_server_cmd = [ spawn.find_executable('ovsdb-server'), '-vconsole:off', '--detach', '--pidfile=%s' % os.path.join( self.temp_dir, ovsdb_process['pidfile']), '--log-file=%s' % (ovsdb_process['log_file_path']), '--remote=punix:%s' % (ovsdb_process['remote_path']), '--remote=%s' % (ovsdb_process['connection']), '--unixctl=%s' % (ovsdb_process['unixctl_path']), '--detach'] if ovsdb_process['protocol'] == 'ssl': if not pki_done: pki_done = True self._init_ovsdb_pki() ovsdb_server_cmd.append('--private-key=%s' % self.private_key) ovsdb_server_cmd.append('--certificate=%s' % self.certificate) ovsdb_server_cmd.append('--ca-cert=%s' % self.ca_cert) ovsdb_server_cmd.append(ovsdb_process['db_path']) if self.debug: ovsdb_server_cmd.append('--verbose') obj, _ = utils.create_process(ovsdb_server_cmd) obj.communicate() conn_cmd = [spawn.find_executable(ovsdb_process['ctl_cmd']), '--db=unix:%s' % ovsdb_process['remote_path'], 'set-connection', 'p%s:%s:%s' % (ovsdb_process['protocol'], ovsdb_process['remote_port'], ovsdb_process['remote_ip']), '--', 'set', 'connection', '.', 'inactivity_probe=60000'] @tenacity.retry(wait=tenacity.wait_exponential(multiplier=0.1), stop=tenacity.stop_after_delay(3), reraise=True) def _set_connection(): utils.execute(conn_cmd) @tenacity.retry( wait=tenacity.wait_exponential(multiplier=0.1), stop=tenacity.stop_after_delay(10), reraise=True) def get_ovsdb_remote_port_retry(pid): process = psutil.Process(pid) for connect in process.connections(): if connect.status == 'LISTEN': return connect.laddr[1] raise Exception(_("Could not find LISTEN port.")) if ovsdb_process['protocol'] != 'unix': _set_connection() pid = self._get_pid_from_pidfile(ovsdb_process['pidfile']) ovsdb_process['remote_port'] = \ get_ovsdb_remote_port_retry(pid) def stop(self): for ovsdb_process in self.ovsdb_server_processes: try: stop_cmd = ['ovs-appctl', '-t', ovsdb_process['unixctl_path'], 'exit'] utils.execute(stop_cmd) except Exception: pass def get_ovsdb_connection_path(self, db_type='nb'): for ovsdb_process in self.ovsdb_server_processes: if ovsdb_process['db_type'] == db_type: if ovsdb_process['protocol'] == 'unix': return 'unix:' + ovsdb_process['remote_path'] else: return '%s:%s:%s' % (ovsdb_process['protocol'], ovsdb_process['remote_ip'], ovsdb_process['remote_port'])