diff --git a/README.md b/README.md new file mode 120000 index 0000000..351df1d --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +src/README.md \ No newline at end of file diff --git a/src/README.md b/src/README.md index 555bcd2..f452340 100644 --- a/src/README.md +++ b/src/README.md @@ -1,9 +1,36 @@ # Overview -OVN provides open source network virtualization for Open vSwitch (OVS). +This charm provides the Northbound and Southbound OVSDB Databases and the +Open Virtual Network (OVN) central control daemon (`ovn-northd`). + +> **Note**: The OVN charms are considered preview charms. # Usage +OVN makes use of Public Key Infrastructure (PKI) to authenticate and authorize +control plane communication. The charm requires a Certificate Authority to be +present in the model as represented by the `certificates` relation. + +There is a [OVN overlay bundle](https://github.com/openstack-charmers/openstack-bundles/blob/master/development/overlays/openstack-base-ovn.yaml) +for use in conjunction with the [OpenStack Base bundle](https://github.com/openstack-charmers/openstack-bundles/blob/master/development/openstack-base-bionic-train/bundle.yaml) +which give an example of how you can automate certificate lifecycle management +with the help from [Vault](https://jaas.ai/vault/). + +Please refer to the [Open Virtual Network](https://docs.openstack.org/project-deploy-guide/charm-deployment-guide/latest/app-ovn.html) section of +the [OpenStack Charms Deployment Guide](https://docs.openstack.org/project-deploy-guide/charm-deployment-guide/latest/index.html) +for information about deploying OVN with OpenStack. + +## Network Spaces support + +This charm supports the use of Juju Network Spaces. + +By binding the `ovsdb`, `ovsdb-cms` and `ovsdb-peer` endpoints you can +influence which interface will be used for communication with consumers of +the Southbound DB, Cloud Management Systems (CMS) and cluster internal +communication. + + juju deploy ovn-central --bind "''=oam-space ovsdb=data-space" + # Bugs Please report bugs on [Launchpad](https://bugs.launchpad.net/charm-ovn-central/+filebug). diff --git a/src/lib/charm/openstack/ovn_central.py b/src/lib/charm/openstack/ovn_central.py index 1119cd1..d4c43e2 100644 --- a/src/lib/charm/openstack/ovn_central.py +++ b/src/lib/charm/openstack/ovn_central.py @@ -61,8 +61,26 @@ class OVNCentralCharm(charms_openstack.charm.OpenStackCharm): packages = ['ovn-central'] services = ['ovn-central'] required_relations = ['certificates'] + # NOTE(fnordahl) we replace the package sysv init script with our own + # systemd service files. + # + # The issue that triggered this change is that to be able to pass the + # correct command line arguments to ``ovn-nortrhd`` we need to create + # a ``/etc/openvswitch/ovn-northd-db-params.conf`` which has the side + # effect of profoundly changing the behaviour of the ``ovn-ctl`` tool + # that the ``ovn-central`` init script makes use of. + # + # https://github.com/ovn-org/ovn/blob/dc0e10c068c20c4e59c9c86ecee26baf8ed50e90/utilities/ovn-ctl#L323 + # + # TODO: The systemd service files should be upstreamed and removed from + # the charm restart_map = { '/etc/default/ovn-central': services, + os.path.join(OVS_ETCDIR, 'ovn-northd-db-params.conf'): ['ovn-northd'], + '/lib/systemd/system/ovn-central.service': [], + '/lib/systemd/system/ovn-northd.service': [], + '/lib/systemd/system/ovn-nb-ovsdb.service': [], + '/lib/systemd/system/ovn-sb-ovsdb.service': [], } python_version = 3 source_config_key = 'source' @@ -70,7 +88,7 @@ class OVNCentralCharm(charms_openstack.charm.OpenStackCharm): def install(self): """Extend the default install method. - Mask the ``ovn-central`` service before initial installation. + Mask services before initial installation. This is done to prevent extraneous standalone DB initialization and subsequent upgrade to clustered DB when configuration is rendered. @@ -81,9 +99,12 @@ class OVNCentralCharm(charms_openstack.charm.OpenStackCharm): We also configure source before installing as OpenvSwitch and OVN packages are distributed as part of the UCA. """ - ovn_central_service_file = '/etc/systemd/system/ovn-central.service' - if not os.path.islink(ovn_central_service_file): - os.symlink('/dev/null', ovn_central_service_file) + service_masks = [ + '/etc/systemd/system/ovn-central.service', + ] + for service_file in service_masks: + if not os.path.islink(service_file): + os.symlink('/dev/null', service_file) self.configure_source() super().install() @@ -151,13 +172,21 @@ class OVNCentralCharm(charms_openstack.charm.OpenStackCharm): ovn_key(self.adapters_instance), ovn_cert(self.adapters_instance), ovn_ca_cert(self.adapters_instance)) - self.run('ovs-vsctl', - 'set', - 'open', - '.', - 'external-ids:ovn-remote=ssl:127.0.0.1:{}' - .format(DB_SB_PORT)) - if reactive.is_flag_set('leadership.is_leader'): + if (reactive.is_flag_set('leadership.is_leader') and not + reactive.is_flag_set('leadership.set.ready')): + # This is one-time set up at cluster creation and can only be + # done one the OVSDB cluster leader. + # + # It also has to be done after certificates has been written + # to disk and before we do anything else which is why it is + # co-located with the ``configure_tls`` method. + # + # NOTE: There is one individual OVSDB cluster leader for each + # of the OVSDB databases and throughout a deployment lifetime + # they are not necessarilly the same as the charm leader. + # + # However, at bootstrap time the OVSDB cluster leaders will + # coincide with the charm leader. self.run('ovn-nbctl', 'set-connection', 'pssl:{}'.format(DB_NB_PORT)) @@ -171,5 +200,21 @@ class OVNCentralCharm(charms_openstack.charm.OpenStackCharm): self.run('ovn-sbctl', 'set-connection', 'pssl:{}'.format(DB_SB_PORT)) - self.restart_all() + self.restart_all() break + + def configure_ovn_remote(self, ovsdb_interface): + """Configure the OVN remote setting in the local OVSDB. + + The value is used by command line tools run on this unit. + + :param ovsdb_interface: OVSDB interface instance + :type ovsdb_interface: reactive.Endpoint derived class + :raises: subprocess.CalledProcessError + """ + self.run('ovs-vsctl', + 'set', + 'open', + '.', + 'external-ids:ovn-remote={}' + .format(','.join(ovsdb_interface.db_sb_connection_strs))) diff --git a/src/reactive/ovn_central_handlers.py b/src/reactive/ovn_central_handlers.py index 49df1dc..316d985 100644 --- a/src/reactive/ovn_central_handlers.py +++ b/src/reactive/ovn_central_handlers.py @@ -146,5 +146,6 @@ def render(): ovsdb_peer.cluster_remote_addrs, ovsdb_peer.db_sb_cluster_port)) if ovn_charm.enable_services(): + ovn_charm.configure_ovn_remote(ovsdb_peer) reactive.set_flag('config.rendered') ovn_charm.assess_status() diff --git a/src/templates/ovn-central b/src/templates/ovn-central index 038e2ff..1a5bd21 100644 --- a/src/templates/ovn-central +++ b/src/templates/ovn-central @@ -1,5 +1,5 @@ -# This is a POSIX shell fragment -*- sh -*- - +# This is a systemd EnvironmentFile as documented in systemd.exec(5) +# ############################################################################### # [ WARNING ] # Configuration file maintained by Juju. Local changes may be overwritten. @@ -11,32 +11,26 @@ # OVN_CTL_OPTS: Extra options to pass to ovs-ctl. This is, for example, # a suitable place to specify --ovn-northd-wrapper=valgrind. -OVN_CTL_OPTS=" - --db-nb-file=/var/lib/openvswitch/ovnnb_db.db - --db-nb-cluster-local-addr={{ ovsdb_peer.cluster_local_addr }} - --db-nb-cluster-local-port={{ ovsdb_peer.db_nb_cluster_port }} - --db-nb-cluster-local-proto=ssl -{%if ovsdb_peer and ovsdb_peer.cluster_remote_addrs %} - --ovn-nb-db-ssl-key={{ options.ovn_key }} - --ovn-nb-db-ssl-cert={{ options.ovn_cert }} - --ovn-nb-db-ssl-ca-cert={{ options.ovn_ca_cert }} - --db-nb-cluster-remote-addr={{ ovsdb_peer.cluster_remote_addrs | first }} - --db-nb-cluster-remote-port={{ ovsdb_peer.db_nb_cluster_port }} - --db-nb-cluster-remote-proto=ssl -{% endif %} - --db-sb-file=/var/lib/openvswitch/ovnsb_db.db - --db-sb-cluster-local-addr={{ ovsdb_peer.cluster_local_addr }} - --db-sb-cluster-local-port={{ ovsdb_peer.db_sb_cluster_port }} - --db-sb-cluster-local-proto=ssl -{%if ovsdb_peer and ovsdb_peer.cluster_remote_addrs %} - --ovn-sb-db-ssl-key={{ options.ovn_key }} - --ovn-sb-db-ssl-cert={{ options.ovn_cert }} - --ovn-sb-db-ssl-ca-cert={{ options.ovn_ca_cert }} - --db-sb-cluster-remote-addr={{ ovsdb_peer.cluster_remote_addrs | first }} - --db-nb-cluster-remote-port={{ ovsdb_peer.db_nb_cluster_port }} +OVN_CTL_OPTS=--db-nb-file=/var/lib/openvswitch/ovnnb_db.db \ + --db-nb-cluster-local-addr={{ ovsdb_peer.cluster_local_addr }} \ + --db-nb-cluster-local-port={{ ovsdb_peer.db_nb_cluster_port }} \ + --db-nb-cluster-local-proto=ssl \ + --ovn-nb-db-ssl-key={{ options.ovn_key }} \ + --ovn-nb-db-ssl-cert={{ options.ovn_cert }} \ + --ovn-nb-db-ssl-ca-cert={{ options.ovn_ca_cert }} \ + --db-nb-cluster-remote-addr={{ ovsdb_peer.cluster_remote_addrs | first }} \ + --db-nb-cluster-remote-port={{ ovsdb_peer.db_nb_cluster_port }} \ + --db-nb-cluster-remote-proto=ssl \ + --db-sb-file=/var/lib/openvswitch/ovnsb_db.db \ + --db-sb-cluster-local-addr={{ ovsdb_peer.cluster_local_addr }} \ + --db-sb-cluster-local-port={{ ovsdb_peer.db_sb_cluster_port }} \ + --db-sb-cluster-local-proto=ssl \ + --ovn-sb-db-ssl-key={{ options.ovn_key }} \ + --ovn-sb-db-ssl-cert={{ options.ovn_cert }} \ + --ovn-sb-db-ssl-ca-cert={{ options.ovn_ca_cert }} \ + --db-sb-cluster-remote-addr={{ ovsdb_peer.cluster_remote_addrs | first }} \ + --db-nb-cluster-remote-port={{ ovsdb_peer.db_nb_cluster_port }} \ --db-sb-cluster-remote-proto=ssl -{% endif %} -" # DPDK options are now configured via ovs-vsctl/ovsdb, see: # - /usr/share/doc/openvswitch-common/INSTALL.DPDK.md.gz diff --git a/src/templates/ovn-central.service b/src/templates/ovn-central.service new file mode 100644 index 0000000..072da7f --- /dev/null +++ b/src/templates/ovn-central.service @@ -0,0 +1,24 @@ +############################################################################### +# [ WARNING ] +# Configuration file maintained by Juju. Local changes may be overwritten. +# Configuration managed by ovn-central charm +############################################################################### +[Unit] +Description=Open Virtual Network central components +After=network.target +After=openvswitch-switch.service +Requires=network.target +Requires=openvswitch-switch.service +Requires=ovn-northd.service +# Facilitate spread placement of the DBs if someone should choose to do that +Wants=ovn-nb-ovsdb.service +Wants=ovn-sb-ovsdb.service + +[Service] +Type=oneshot +ExecStart=/bin/true +ExecStop=/bin/true +RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target diff --git a/src/templates/ovn-nb-ovsdb.service b/src/templates/ovn-nb-ovsdb.service new file mode 100644 index 0000000..8dc2e27 --- /dev/null +++ b/src/templates/ovn-nb-ovsdb.service @@ -0,0 +1,20 @@ +############################################################################### +# [ WARNING ] +# Configuration file maintained by Juju. Local changes may be overwritten. +# Configuration managed by ovn-central charm +############################################################################### +[Unit] +Description=Open vSwitch database server for OVN Northbound database +After=network.target openvswitch-switch.service +PartOf=ovn-central.service +DefaultDependencies=no + +[Service] +EnvironmentFile=-/etc/default/ovn-central +Type=forking +PIDFile=/var/run/openvswitch/ovnnb_db.pid +ExecStart=/usr/share/openvswitch/scripts/ovn-ctl start_nb_ovsdb $OVN_CTL_OPTS +ExecStop=/usr/share/openvswitch/scripts/ovn-ctl start_nb_ovsdb +Restart=on-failure +LimitNOFILE=65535 +TimeoutStopSec=15 diff --git a/src/templates/ovn-northd-db-params.conf b/src/templates/ovn-northd-db-params.conf new file mode 100644 index 0000000..11cc2ff --- /dev/null +++ b/src/templates/ovn-northd-db-params.conf @@ -0,0 +1,5 @@ +--ovnnb-db={{ ovsdb_peer.db_nb_connection_strs|join(',') }} +--ovnsb-db={{ ovsdb_peer.db_sb_connection_strs|join(',') }} +-c {{ options.ovn_cert }} +-C {{ options.ovn_ca_cert }} +-p {{ options.ovn_key }} diff --git a/src/templates/ovn-northd.service b/src/templates/ovn-northd.service new file mode 100644 index 0000000..44e125a --- /dev/null +++ b/src/templates/ovn-northd.service @@ -0,0 +1,20 @@ +############################################################################### +# [ WARNING ] +# Configuration file maintained by Juju. Local changes may be overwritten. +# Configuration managed by ovn-central charm +############################################################################### +[Unit] +Description=Open Virtual Network central control daemon +After=network.target ovn-nb-ovsdb.service ovn-sb-ovsdb.service +PartOf=ovn-central.service +DefaultDependencies=no + +[Service] +EnvironmentFile=-/etc/default/ovn-central +Type=forking +PIDFile=/var/run/openvswitch/ovn-northd.pid +ExecStart=/usr/share/openvswitch/scripts/ovn-ctl start_northd --ovn-manage-ovsdb=no $OVN_CTL_OPTS +ExecStop=/usr/share/openvswitch/scripts/ovn-ctl stop_northd +Restart=on-failure +LimitNOFILE=65535 +TimeoutStopSec=15 diff --git a/src/templates/ovn-sb-ovsdb.service b/src/templates/ovn-sb-ovsdb.service new file mode 100644 index 0000000..0487cdb --- /dev/null +++ b/src/templates/ovn-sb-ovsdb.service @@ -0,0 +1,20 @@ +############################################################################### +# [ WARNING ] +# Configuration file maintained by Juju. Local changes may be overwritten. +# Configuration managed by ovn-central charm +############################################################################### +[Unit] +Description=Open vSwitch database server for OVN Southbound database +After=network.target openvswitch-switch.service +PartOf=ovn-central.service +DefaultDependencies=no + +[Service] +EnvironmentFile=-/etc/default/ovn-central +Type=forking +PIDFile=/var/run/openvswitch/ovnsb_db.pid +ExecStart=/usr/share/openvswitch/scripts/ovn-ctl start_sb_ovsdb $OVN_CTL_OPTS +ExecStop=/usr/share/openvswitch/scripts/ovn-ctl start_sb_ovsdb +Restart=on-failure +LimitNOFILE=65535 +TimeoutStopSec=15 diff --git a/unit_tests/test_lib_charm_openstack_ovn_central.py b/unit_tests/test_lib_charm_openstack_ovn_central.py index 0b81446..fe587e4 100644 --- a/unit_tests/test_lib_charm_openstack_ovn_central.py +++ b/unit_tests/test_lib_charm_openstack_ovn_central.py @@ -64,11 +64,17 @@ class TestOVNCentralCharm(Helper): self.patch_object(ovn_central.os, 'symlink') self.patch_target('configure_source') self.target.install() - self.islink.assert_called_once_with( - '/etc/systemd/system/ovn-central.service') - self.symlink.assert_called_once_with( - '/dev/null', - '/etc/systemd/system/ovn-central.service') + calls = [] + for service in self.target.services: + calls.append( + mock.call('/etc/systemd/system/{}.service'.format(service))) + self.islink.assert_has_calls(calls) + calls = [] + for service in self.target.services: + calls.append( + mock.call('/dev/null', + '/etc/systemd/system/{}.service'.format(service))) + self.symlink.assert_has_calls(calls) self.install.assert_called_once_with() self.configure_source.assert_called_once_with() @@ -91,7 +97,10 @@ class TestOVNCentralCharm(Helper): self.assertFalse(self.service_resume.called) self.target.check_if_paused.return_value = (None, None) self.target.enable_services() - self.service_resume.assert_called_once_with('ovn-central') + calls = [] + for service in self.target.services: + calls.append(mock.call(service)) + self.service_resume.assert_has_calls(calls) def test_run(self): self.patch_object(ovn_central.subprocess, 'run') @@ -129,7 +138,7 @@ class TestOVNCentralCharm(Helper): mocked_open.return_value = mocked_file self.target.configure_cert = mock.MagicMock() self.target.run = mock.MagicMock() - self.is_flag_set.return_value = True + self.is_flag_set.side_effect = [True, False] self.target.configure_tls() mocked_open.assert_called_once_with( '/etc/openvswitch/ovn-central.crt', 'w') @@ -146,11 +155,6 @@ class TestOVNCentralCharm(Helper): '/etc/openvswitch/key_host', '/etc/openvswitch/cert_host', '/etc/openvswitch/ovn-central.crt'), - mock.call('ovs-vsctl', - 'set', - 'open', - '.', - 'external-ids:ovn-remote=ssl:127.0.0.1:6642'), mock.call('ovn-nbctl', 'set-connection', 'pssl:6641'), @@ -158,7 +162,7 @@ class TestOVNCentralCharm(Helper): 'set-connection', 'pssl:6642'), ]) - self.is_flag_set.return_value = False + self.is_flag_set.side_effect = [False, True] self.target.run.reset_mock() self.target.configure_tls() self.target.run.assert_has_calls([ @@ -167,9 +171,19 @@ class TestOVNCentralCharm(Helper): '/etc/openvswitch/key_host', '/etc/openvswitch/cert_host', '/etc/openvswitch/ovn-central.crt'), - mock.call('ovs-vsctl', - 'set', - 'open', - '.', - 'external-ids:ovn-remote=ssl:127.0.0.1:6642'), ]) + + def test_configure_ovn_remote(self): + self.patch_target('run') + ovsdb_interface = mock.MagicMock() + ovsdb_interface.db_sb_connection_strs = \ + mock.PropertyMock().return_value = [ + 'ssl:a.b.c.d:6642', + 'ssl:a.b.c.d:6642', + 'ssl:a.b.c.d:6642', + ] + self.target.configure_ovn_remote(ovsdb_interface) + self.run.assert_called_once_with( + 'ovs-vsctl', 'set', 'open', '.', + 'external-ids:ovn-remote=' + 'ssl:a.b.c.d:6642,ssl:a.b.c.d:6642,ssl:a.b.c.d:6642')