diff --git a/.gitmodules b/.gitmodules index f8d10f3af4ab..a8c060a95749 100644 --- a/.gitmodules +++ b/.gitmodules @@ -132,4 +132,7 @@ url = https://github.com/Marvell-switching/sonic-platform-marvell.git [submodule "platform/vpp"] path = platform/vpp - url = https://github.com/sonic-net/sonic-platform-vpp.git \ No newline at end of file + url = https://github.com/sonic-net/sonic-platform-vpp.git +[submodule "src/sonic-framework/gnoi"] + path = src/sonic-framework/gnoi + url = https://github.com/openconfig/gnoi diff --git a/dockers/docker-framework/Dockerfile.j2 b/dockers/docker-framework/Dockerfile.j2 new file mode 100644 index 000000000000..9e38da9ce4c6 --- /dev/null +++ b/dockers/docker-framework/Dockerfile.j2 @@ -0,0 +1,34 @@ +{% from "dockers/dockerfile-macros.j2" import install_debian_packages, install_python_wheels, copy_files %} +FROM docker-config-engine-bookworm-{{DOCKER_USERNAME}}:{{DOCKER_USERTAG}} + +ARG docker_container_name +RUN [ -f /etc/rsyslog.conf ] && sed -ri "s/%syslogtag%/$docker_container_name#%syslogtag%/;" /etc/rsyslog.conf + +## Make apt-get non-interactive +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && \ + apt-get install -f -y \ + libdbus-1-3 \ + libdbus-c++-1-0v5 + +{% if docker_framework_debs.strip() -%} +# Copy locally-built Debian package dependencies +{{ copy_files("debs/", docker_framework_debs.split(' '), "/debs/") }} + +# Install locally-built Debian packages and implicitly install their dependencies +{{ install_debian_packages(docker_framework_debs.split(' ')) }} +{%- endif %} + +RUN apt-get clean -y && \ + apt-get autoclean - && \ + apt-get autoremove -y && \ + rm -rf /debs /var/lib/apt/lists/* /tmp/* ~/.cache/ + +COPY ["start.sh", "/usr/bin/"] +COPY ["supervisord.conf", "/etc/supervisor/conf.d/"] +COPY ["files/supervisor-proc-exit-listener", "/usr/bin"] +# COPY ["git_commits", "/usr"] + + +ENTRYPOINT ["/usr/local/bin/supervisord"] diff --git a/dockers/docker-framework/framework.sh b/dockers/docker-framework/framework.sh new file mode 100755 index 000000000000..68e72417fd2a --- /dev/null +++ b/dockers/docker-framework/framework.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + + +exec /usr/local/bin/framework --logtostderr diff --git a/dockers/docker-framework/start.sh b/dockers/docker-framework/start.sh new file mode 100755 index 000000000000..1235a4e5a671 --- /dev/null +++ b/dockers/docker-framework/start.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + + +mkdir -p /var/sonic +echo "# Config files managed by sonic-config-engine" > /var/sonic/config_status diff --git a/dockers/docker-framework/supervisord.conf b/dockers/docker-framework/supervisord.conf new file mode 100644 index 000000000000..05ca0f4bc272 --- /dev/null +++ b/dockers/docker-framework/supervisord.conf @@ -0,0 +1,54 @@ +[supervisord] +logfile_maxbytes=1MB +logfile_backups=2 +loglevel=warn +nodaemon=true + +[eventlistener:dependent-startup] +command=python3 -m supervisord_dependent_startup --log-level warn +autostart=true +autorestart=unexpected +stdout_logfile=syslog +stderr_logfile=syslog +startretries=0 +exitcodes=0,3 +events=PROCESS_STATE +buffer_size=50 + +[eventlistener:supervisor-proc-exit-listener] +command=/usr/bin/supervisor-proc-exit-listener --container-name framework +events=PROCESS_STATE_EXITED +autostart=true +autorestart=unexpected +stdout_logfile=syslog +stderr_logfile=syslog + +[program:rsyslogd] +command=/usr/sbin/rsyslogd -n -iNONE +priority=1 +autostart=false +autorestart=unexpected +stdout_logfile=syslog +stderr_logfile=syslog +dependent_startup=true + +[program:start] +command=/usr/bin/start.sh +priority=2 +autostart=false +autorestart=false +startsecs=0 +stdout_logfile=syslog +stderr_logfile=syslog +dependent_startup=true +dependent_startup_wait_for=rsyslogd:running + +[program:rebootbackend] +command=/usr/bin/rebootbackend +priority=3 +autostart=false +autorestart=true +stdout_logfile=syslog +stderr_logfile=syslog +dependent_startup=true +dependent_startup_wait_for=start:exited diff --git a/files/build/versions/dockers/docker-framework/versions-deb-bookworm b/files/build/versions/dockers/docker-framework/versions-deb-bookworm new file mode 100644 index 000000000000..d3c4068c849f --- /dev/null +++ b/files/build/versions/dockers/docker-framework/versions-deb-bookworm @@ -0,0 +1,229 @@ +adduser==4.118+deb11u1 +apt==2.2.4 +apt-utils==2.2.4 +base-files==11.1+deb11u9 +base-passwd==3.5.51 +bash==5.1-2+deb11u1 +bsdutils==1:2.36.1-8+deb11u2 +ca-certificates==20210119 +coreutils==8.32-4+b1 +curl==7.74.0-1.3+deb11u11 +dash==0.5.11+git20200708+dd9ef66-5 +debconf==1.5.77 +debian-archive-keyring==2021.1.1+deb11u1 +debianutils==4.11.2 +diffutils==1:3.7-5 +dpkg==1.20.13 +dpkg-dev==1.20.13 +e2fsprogs==1.46.2-2 +findutils==4.8.0-1 +framework==1.0.0 +framework-dbg==1.0.0 +gcc-10-base==10.2.1-6 +gcc-9-base==9.3.0-22 +gdb==10.1-1.7 +gdbserver==10.1-1.7 +gpgv==2.2.27-2+deb11u2 +grep==3.6-1+deb11u1 +gzip==1.10-4+deb11u1 +hostname==3.23 +init-system-helpers==1.60 +iproute2==5.10.0-4 +jq==1.6-2.1 +less==551-2+deb11u2 +libabsl20200923==0~20200923.3-2 +libacl1==2.2.53-10 +libapt-pkg6.0==2.2.4 +libatomic1==10.2.1-6 +libattr1==1:2.4.48-6 +libaudit-common==1:3.0-2 +libaudit1==1:3.0-2 +libbabeltrace1==1.5.8-1+b3 +libblkid1==2.36.1-8+deb11u2 +libboost-regex1.74.0==1.74.0-9 +libboost-serialization1.74.0==1.74.0-9 +libbpf0==1:0.3-3 +libbrotli1==1.0.9-2+b2 +libbsd0==0.11.3-1+deb11u1 +libbz2-1.0==1.0.8-4 +libc-ares2==1.17.1-1+deb11u3 +libc-bin==2.31-13+deb11u10 +libc6==2.31-13+deb11u10 +libcap-ng0==0.7.9-2.2+b1 +libcap2==1:2.44-1 +libcap2-bin==1:2.44-1 +libcbor0==0.5.0+dfsg-2 +libcom-err2==1.46.2-2 +libcrypt1==1:4.4.18-4 +libcurl3-gnutls==7.74.0-1.3+deb11u11 +libcurl4==7.74.0-1.3+deb11u11 +libdaemon0==0.14-7.1 +libdb5.3==5.3.28+dfsg1-0.8 +libdbus-1-3==1.12.28-0+deb11u1 +libdbus-c++-1-0v5==0.9.0-8.2 +libdebconfclient0==0.260 +libdebuginfod1==0.183-1 +libdw1==0.183-1 +libecore1==1.25.1-1 +libedit2==3.1-20191231-2+b1 +libeina1a==1.25.1-1 +libelf1==0.183-1 +libestr0==0.1.10-2.1+b1 +libexpat1==2.2.10-2+deb11u5 +libext2fs2==1.46.2-2 +libfastjson4==0.99.9-1 +libffi7==3.3-6 +libfido2-1==1.6.0-2 +libgcc-s1==10.2.1-6 +libgcrypt20==1.8.7-6 +libgdbm-compat4==1.19-2 +libgdbm6==1.19-2 +libglib2.0-0==2.66.8-1+deb11u3 +libgmp10==2:6.2.1+dfsg-1+deb11u1 +libgnutls30==3.7.1-5+deb11u4 +libgpg-error0==1.38-2 +libgpm2==1.20.7-8 +libgrpc++1==1.30.2-3 +libgrpc10==1.30.2-3 +libgssapi-krb5-2==1.18.3-6+deb11u4 +libhiredis0.14==0.14.1-1 +libhiredis0.14-dbgsym==0.14.1-1 +libhogweed6==3.7.3-1 +libicu67==67.1-7 +libidn2-0==2.3.0-5 +libipt2==2.0.3-1 +libjansson4==2.13.1-1.1 +libjemalloc2==5.2.1-3 +libjq1==1.6-2.1 +libjs-jquery==3.5.1+dfsg+~3.5.5-7 +libk5crypto3==1.18.3-6+deb11u4 +libkeyutils1==1.6.1-2 +libkrb5-3==1.18.3-6+deb11u4 +libkrb5support0==1.18.3-6+deb11u4 +libldap-2.4-2==2.4.57+dfsg-3+deb11u1 +liblognorm5==2.0.5-1.1 +liblua5.1-0==5.1.5-8.1+b3 +liblz4-1==1.9.3-2 +liblzf1==3.6-3 +liblzma5==5.2.5-2.1~deb11u1 +libmd0==1.0.3-3 +libmnl0==1.0.4-3 +libmount1==2.36.1-8+deb11u2 +libmpdec3==2.5.1-1 +libmpfr6==4.1.0-3 +libncurses6==6.2+20201114-2+deb11u2 +libncursesw6==6.2+20201114-2+deb11u2 +libnettle8==3.7.3-1 +libnghttp2-14==1.43.0-1+deb11u1 +libnl-3-200==3.5.0-1 +libnl-cli-3-200==3.5.0-1 +libnl-genl-3-200==3.5.0-1 +libnl-nf-3-200==3.5.0-1 +libnl-route-3-200==3.5.0-1 +libnorm1==1.5.9+dfsg-2 +libnsl2==1.3.0-2 +libonig5==6.9.6-1.1 +libp11-kit0==0.23.22-1 +libpam-modules==1.4.0-9+deb11u1 +libpam-modules-bin==1.4.0-9+deb11u1 +libpam-runtime==1.4.0-9+deb11u1 +libpam0g==1.4.0-9+deb11u1 +libpcre2-8-0==10.36-2+deb11u1 +libpcre3==2:8.39-13 +libperl5.32==5.32.1-4+deb11u3 +libpgm-5.3-0==5.3.128~dfsg-2 +libprocps8==2:3.3.17-5 +libprotobuf-dev==3.21.12-3 +libprotobuf-lite32==3.21.12-3 +libprotobuf32==3.21.12-3 +libprotoc32==3.21.12-3 +libpsl5==0.21.0-1.2 +libpython3-stdlib==3.9.2-3 +libpython3.9==3.9.2-1 +libpython3.9-minimal==3.9.2-1 +libpython3.9-stdlib==3.9.2-1 +libreadline8==8.1-1 +librtmp1==2.4+20151223.gitfa8646d.1-2+b2 +libsasl2-2==2.1.27+dfsg-2.1+deb11u1 +libsasl2-modules-db==2.1.27+dfsg-2.1+deb11u1 +libseccomp2==2.5.1-1+deb11u1 +libselinux1==3.1-3 +libsemanage-common==3.1-1 +libsemanage1==3.1-1+b2 +libsepol1==3.1-1 +libsmartcols1==2.36.1-8+deb11u2 +libsodium23==1.0.18-1 +libsource-highlight-common==3.1.9-3 +libsource-highlight4v5==3.1.9-3+b1 +libsqlite3-0==3.34.1-3 +libss2==1.46.2-2 +libssh2-1==1.9.0-2 +libssl1.1==1.1.1w-0+deb11u1 +libstdc++6==10.2.1-6 +libswsscommon==1.0.0 +libswsscommon-dbgsym==1.0.0 +libsystemd0==247.3-7+deb11u4 +libtasn1-6==4.16.0-2+deb11u1 +libtinfo6==6.2+20201114-2+deb11u2 +libtirpc-common==1.3.1-1+deb11u1 +libtirpc3==1.3.1-1+deb11u1 +libudev1==247.3-7+deb11u4 +libunistring2==0.9.10-4 +libunwind8==1.3.2-2 +libuuid1==2.36.1-8+deb11u2 +libwrap0==7.6.q-31 +libxtables12==1.8.7-1 +libxxhash0==0.8.0-2 +libyang==1.0.73 +libyang-cpp==1.0.73 +libzmq5==4.3.4-1+deb11u1 +libzstd1==1.4.8+dfsg-2.1 +login==1:4.8.1-1 +logsave==1.46.2-2 +lsb-base==11.1.0 +lua-bitop==1.0.2-5 +lua-cjson==2.1.0+dfsg-2.1 +mawk==1.3.4.20200120-2 +media-types==4.0.0 +mount==2.36.1-8+deb11u2 +ncurses-base==6.2+20201114-2+deb11u2 +ncurses-bin==6.2+20201114-2+deb11u2 +net-tools==1.60+git20181103.0eebece-1 +netbase==6.3 +openssh-client==1:8.4p1-5+deb11u3 +openssl==1.1.1w-0+deb11u1 +passwd==1:4.8.1-1 +perl==5.32.1-4+deb11u3 +perl-base==5.32.1-4+deb11u3 +perl-modules-5.32==5.32.1-4+deb11u3 +procps==2:3.3.17-5 +protobuf-compiler==3.21.12-3 +python-is-python3==3.9.2-1 +python3==3.9.2-3 +python3-distutils==3.9.2-1 +python3-lib2to3==3.9.2-1 +python3-minimal==3.9.2-3 +python3-swsscommon==1.0.0 +python3-yang==1.0.73 +python3.9==3.9.2-1 +python3.9-minimal==3.9.2-1 +readline-common==8.1-1 +redis-tools==5:6.0.16-1+deb11u2 +rsyslog==8.2302.0-1~bpo11+1 +sed==4.7-1 +socat==1.7.4.1-3 +sonic-build-hooks==1.0 +sonic-db-cli==1.0.0 +sonic-eventd==1.0.0-0 +sshpass==1.09-1+b1 +strace==5.10-1 +sysvinit-utils==2.96-7+deb11u1 +tar==1.34+dfsg-1+deb11u1 +tzdata==2024a-0+deb11u1 +util-linux==2.36.1-8+deb11u2 +vim==2:8.2.2434-3+deb11u1 +vim-common==2:8.2.2434-3+deb11u1 +vim-runtime==2:8.2.2434-3+deb11u1 +vim-tiny==2:8.2.2434-3+deb11u1 +xxd==2:8.2.2434-3+deb11u1 +zlib1g==1:1.2.11.dfsg-2+deb11u2 diff --git a/files/build/versions/dockers/docker-framework/versions-py3 b/files/build/versions/dockers/docker-framework/versions-py3 new file mode 100644 index 000000000000..d326d329b6ef --- /dev/null +++ b/files/build/versions/dockers/docker-framework/versions-py3 @@ -0,0 +1,26 @@ +async-timeout==4.0.3 +bitarray==1.5.3 +ijson==2.6.1 +ipaddress==1.0.23 +j2cli==0.3.10 +jinja2==3.1.4 +jsondiff==2.0.0 +lxml==4.9.1 +markupsafe==2.1.5 +natsort==6.2.1 +netaddr==0.8.0 +pip==24.0 +pyang==2.6.0 +pyangbind==0.8.1 +pyyaml==5.4.1 +redis==4.5.4 +redis-dump-load==1.1 +regex==2024.5.10 +setuptools==58.1.0 +six==1.16.0 +supervisor==4.2.1 +supervisord-dependent-startup==1.4.0 +tabulate==0.8.2 +toposort==1.10 +wheel==0.40.0 +xmltodict==0.12.0 diff --git a/files/build/versions/dockers/docker-sonic-vs/versions-deb-bookworm b/files/build/versions/dockers/docker-sonic-vs/versions-deb-bookworm new file mode 100644 index 000000000000..3567db934fff --- /dev/null +++ b/files/build/versions/dockers/docker-sonic-vs/versions-deb-bookworm @@ -0,0 +1,140 @@ +arping==2.21-2 +bash-completion==1:2.11-2 +bridge-utils==1.7-1 +bzip2==1.0.8-4 +comerr-dev==2.1-1.46.2-2 +conntrack==1:1.4.6-2 +cron==3.0pl1-137 +dbus==1.12.28-0+deb11u1 +dirmngr==2.2.27-2+deb11u2 +dmsetup==2:1.02.175-2.1 +ethtool==1:5.9-1 +fontconfig-config==2.13.1-4.2 +fonts-dejavu-core==2.37-2 +fonts-font-awesome==5.0.10+really4.7.0~dfsg-4.1 +fonts-lato==2.0-2.1 +framework==1.0.0 +frr==8.5.4-sonic-0 +gettext-base==0.21-4 +gir1.2-glib-2.0==1.66.1-1+b1 +gnupg==2.2.27-2+deb11u2 +gnupg-l10n==2.2.27-2+deb11u2 +gnupg-utils==2.2.27-2+deb11u2 +gpg==2.2.27-2+deb11u2 +gpg-agent==2.2.27-2+deb11u2 +gpg-wks-client==2.2.27-2+deb11u2 +gpg-wks-server==2.2.27-2+deb11u2 +gpgconf==2.2.27-2+deb11u2 +gpgsm==2.2.27-2+deb11u2 +grub-common==2.06-3~deb11u6 +grub2-common==2.06-3~deb11u6 +icu-devtools==67.1-7 +ifupdown==0.8.36 +iproute2==6.1.0-3~bpo11+1 +iptables==1.8.7-1 +krb5-multidev==1.18.3-6+deb11u5+fips +libapparmor1==2.13.6-10 +libassuan0==2.5.3-7.1 +libblkid-dev==2.36.1-8+deb11u2 +libblkid1==2.36.1-8+deb11u2 +libbsd-dev==0.11.3-1+deb11u1 +libc-ares2==1.17.1-1+deb11u3 +libc-dev-bin==2.31-13+deb11u10 +libc6-dev==2.31-13+deb11u10 +libcbor0==0.5.0+dfsg-2 +libcrypt-dev==1:4.4.18-4 +libdevmapper1.02.1==2:1.02.175-2.1 +libdouble-conversion3==3.1.5-6.1 +libedit2==3.1-20191231-2+b1 +libefiboot1==37-6 +libefivar1==37-6 +libfido2-1==1.6.0-2 +libfreetype6==2.10.4+dfsg-1+deb11u1 +libfreetype6-dev==2.10.4+dfsg-1+deb11u1 +libfuse2==2.9.9-5 +libgirepository-1.0-1==1.66.1-1+b1 +libglib2.0-0==2.66.8-1+deb11u4 +libglib2.0-data==2.66.8-1+deb11u4 +libgssapi-krb5-2==1.18.3-6+deb11u5+fips +libgssrpc4==1.18.3-6+deb11u5+fips +libicu-dev==67.1-7 +libicu67==67.1-7 +libip4tc2==1.8.7-1 +libip6tc2==1.8.7-1 +libjs-sphinxdoc==3.4.3-2 +libjs-underscore==1.9.1~dfsg-3 +libjson-c5==0.15-2+deb11u1 +libjudydebian1==1.0.5-5+b2 +libk5crypto3==1.18.3-6+deb11u4 +libkadm5clnt-mit12==1.18.3-6+deb11u5+fips +libkadm5srv-mit12==1.18.3-6+deb11u5+fips +libkdb5-10==1.18.3-6+deb11u5 +libkrb5-3==1.18.3-6+deb11u5+fips +libkrb5-dev==1.18.3-6+deb11u5+fips +libkrb5support0==1.18.3-6+deb11u5+fips +libksba8==1.5.0-3+deb11u2 +libmd-dev==1.0.3-3 +libmount1==2.36.1-8+deb11u2 +libnet1==1.1.6+dfsg-3.1 +libnetfilter-conntrack3==1.0.8-3 +libnfnetlink0==1.0.1-3+b1 +libnftnl11==1.1.9-1 +libnorm-dev==1.5.9+dfsg-2 +libnpth0==1.6-3 +libnsl-dev==1.3.0-2 +libpcap0.8==1.10.0-2 +libpcre2-16-0==10.36-2+deb11u1 +libpgm-dev==5.3.128~dfsg-2 +libpng16-16==1.6.37-3 +libpopt0==1.18-2 +libpython2-stdlib==2.7.18-3 +libpython2.7-minimal==2.7.18-8+deb11u1 +libpython2.7-stdlib==2.7.18-8+deb11u1 +libqt5core5a==5.15.2+dfsg-9+deb11u1 +libqt5dbus5==5.15.2+dfsg-9+deb11u1 +libqt5network5==5.15.2+dfsg-9+deb11u1 +libsaivs==1.0.0 +libsodium-dev==1.0.18-1 +libssl1.1==1.1.1w-0+deb11u1 +libsystemd0==247.3-7+deb11u2 +libteam-utils==1.31-1 +libtirpc-dev==1.3.1-1+deb11u1 +libunwind8==1.3.2-2 +libuuid1==2.36.1-8+deb11u2 +libxml2==2.9.10+dfsg-6.7+deb11u4 +libxml2-dev==2.9.10+dfsg-6.7+deb11u4 +libyang2==2.0.112-6 +libzmq3-dev==4.3.4-1+deb11u1 +linux-libc-dev==5.10.221-1 +logrotate==3.18.0-2+deb11u2 +lsof==4.93.2+dfsg-1.1 +mailcap==3.69 +mime-support==3.66 +ndisc6==1.0.4-2 +netbase==6.3 +openssh-client==1:8.4p1-5+deb11u3 +openssh-server==1:8.4p1-5+deb11u3 +openssh-sftp-server==1:8.4p1-5+deb11u3 +openssl==1.1.1w-0+deb11u1 +pinentry-curses==1.1.0-4 +psmisc==23.4-2 +python-ply==3.11-4 +python2==2.7.18-3 +python2-minimal==2.7.18-3 +python2.7==2.7.18-8+deb11u1 +python2.7-minimal==2.7.18-8+deb11u1 +python3-scapy==2.4.4-4 +redis-server==5:6.0.16-1+deb11u2 +runit-helper==2.10.3 +sensible-utils==0.0.14 +shared-mime-info==2.0-1 +sonic-device-data==1.0-1 +sonic-host-services-data==1.0-1 +sonic-utilities-data==1.0-1 +sphinx-rtd-theme-common==0.5.1+dfsg-1 +syncd-vs==1.0.0 +tcpdump==4.99.0-2+deb11u1 +ucf==3.0043 +uuid-dev==2.36.1-8+deb11u2 +x11-common==1:7.7+22 +xz-utils==5.2.5-2.1~deb11u1 diff --git a/files/build/versions/dockers/docker-sonic-vs/versions-deb-bullseye b/files/build/versions/dockers/docker-sonic-vs/versions-deb-bullseye index 9086a97ef363..3567db934fff 100644 --- a/files/build/versions/dockers/docker-sonic-vs/versions-deb-bullseye +++ b/files/build/versions/dockers/docker-sonic-vs/versions-deb-bullseye @@ -13,6 +13,7 @@ fontconfig-config==2.13.1-4.2 fonts-dejavu-core==2.37-2 fonts-font-awesome==5.0.10+really4.7.0~dfsg-4.1 fonts-lato==2.0-2.1 +framework==1.0.0 frr==8.5.4-sonic-0 gettext-base==0.21-4 gir1.2-glib-2.0==1.66.1-1+b1 diff --git a/files/build_templates/framework.service.j2 b/files/build_templates/framework.service.j2 new file mode 100644 index 000000000000..eb2bf3225e16 --- /dev/null +++ b/files/build_templates/framework.service.j2 @@ -0,0 +1,22 @@ +[Unit] +Description=Framework Container +Requires=database.service +After=database.service swss.service +BindsTo=sonic.target +After=sonic.target +Before=ntp-config.service +StartLimitIntervalSec=1200 +StartLimitBurst=3 + + +[Service] +User={{ sonicadmin_user }} +ExecStartPre=/usr/bin/{{docker_container_name}}.sh start +ExecStart=/usr/bin/{{docker_container_name}}.sh wait +ExecStop=/usr/bin/{{docker_container_name}}.sh stop +Restart=always +RestartSec=30 + + +[Install] +WantedBy=sonic.target diff --git a/files/build_templates/init_cfg.json.j2 b/files/build_templates/init_cfg.json.j2 index 1d1c1817b49d..446755fb99da 100644 --- a/files/build_templates/init_cfg.json.j2 +++ b/files/build_templates/init_cfg.json.j2 @@ -63,6 +63,7 @@ }, {%- set features = [("bgp", "{% if not DEVICE_RUNTIME_METADATA['ETHERNET_PORTS_PRESENT'] or ('CHASSIS_METADATA' in DEVICE_RUNTIME_METADATA and DEVICE_RUNTIME_METADATA['CHASSIS_METADATA']['module_type'] in ['supervisor']) %}disabled{% else %}enabled{% endif %}", false, "enabled"), ("database", "always_enabled", false, "always_enabled"), + ("framework", "enabled", false, "enabled"), ("lldp", "enabled", true, "enabled"), ("pmon", "enabled", "{% if 'type' in DEVICE_METADATA['localhost'] and DEVICE_METADATA['localhost']['type'] == 'SpineRouter' %}False{% else %}True{% endif %}", "enabled"), ("snmp", "enabled", true, "enabled"), @@ -92,7 +93,7 @@ {%- elif include_system_eventd == "y" %} {% do features.append(("eventd", "enabled", false, "enabled")) %} {%- endif %} - "FEATURE": { + "FEATURE": { {# delayed field if set, will start the feature systemd .timer unit instead of .service unit #} {%- for feature, state, delayed, autorestart in features %} "{{feature}}": { diff --git a/platform/vs/docker-sonic-vs.mk b/platform/vs/docker-sonic-vs.mk index 3e66e94546ad..b3e46c7e9ae0 100644 --- a/platform/vs/docker-sonic-vs.mk +++ b/platform/vs/docker-sonic-vs.mk @@ -11,7 +11,8 @@ $(DOCKER_SONIC_VS)_DEPENDS += $(SYNCD_VS) \ $(LIBYANG_CPP) \ $(LIBYANG_PY3) \ $(SONIC_UTILITIES_DATA) \ - $(SONIC_HOST_SERVICES_DATA) + $(SONIC_HOST_SERVICES_DATA) \ + $(FRAMEWORK) $(DOCKER_SONIC_VS)_PYTHON_WHEELS += $(SONIC_PY_COMMON_PY3) \ $(SONIC_PLATFORM_COMMON_PY3) \ @@ -24,7 +25,8 @@ ifeq ($(INSTALL_DEBUG_TOOLS), y) $(DOCKER_SONIC_VS)_DEPENDS += $(LIBSWSSCOMMON_DBG) \ $(LIBSAIREDIS_DBG) \ $(LIBSAIVS_DBG) \ - $(SYNCD_VS_DBG) + $(SYNCD_VS_DBG) \ + $(FRAMEWORK_DBG) endif ifeq ($(SONIC_ROUTING_STACK), frr) diff --git a/platform/vs/docker-sonic-vs/start.sh b/platform/vs/docker-sonic-vs/start.sh index f7dbde8dcff7..89a4b89d493e 100755 --- a/platform/vs/docker-sonic-vs/start.sh +++ b/platform/vs/docker-sonic-vs/start.sh @@ -180,6 +180,8 @@ supervisorctl start tunnelmgrd supervisorctl start fabricmgrd +supervisorctl start rebootbackend + # Start arp_update when VLAN exists VLAN=`sonic-cfggen -d -v 'VLAN.keys() | join(" ") if VLAN'` if [ "$VLAN" != "" ]; then diff --git a/rules/docker-framework.dep b/rules/docker-framework.dep new file mode 100644 index 000000000000..238e16db8b09 --- /dev/null +++ b/rules/docker-framework.dep @@ -0,0 +1,10 @@ +DPATH := $($(DOCKER_FRAMEWORK)_PATH) +DEP_FILES := $(SONIC_COMMON_FILES_LIST) rules/docker-framework.mk rules/docker-framework.dep +DEP_FILES += $(SONIC_COMMON_BASE_FILES_LIST) +DEP_FILES += $(shell git ls-files $(DPATH)) + +$(DOCKER_FRAMEWORK)_CACHE_MODE := GIT_CONTENT_SHA +$(DOCKER_FRAMEWORK)_DEP_FLAGS := $(SONIC_COMMON_FLAGS_LIST) +$(DOCKER_FRAMEWORK)_DEP_FILES := $(DEP_FILES) + +$(eval $(call add_dbg_docker,$(DOCKER_FRAMEWORK),$(DOCKER_FRAMEWORK_DBG))) diff --git a/rules/docker-framework.mk b/rules/docker-framework.mk new file mode 100644 index 000000000000..9da73a1bb118 --- /dev/null +++ b/rules/docker-framework.mk @@ -0,0 +1,35 @@ +# docker image for framework + +DOCKER_FRAMEWORK_STEM = docker-framework +DOCKER_FRAMEWORK = $(DOCKER_FRAMEWORK_STEM).gz +DOCKER_FRAMEWORK_DBG = $(DOCKER_FRAMEWORK_STEM)-$(DBG_IMAGE_MARK).gz + +$(DOCKER_FRAMEWORK)_PATH = $(DOCKERS_PATH)/$(DOCKER_FRAMEWORK_STEM) + +$(DOCKER_FRAMEWORK)_DEPENDS += $(FRAMEWORK) +$(DOCKER_FRAMEWORK)_DBG_DEPENDS = $($(DOCKER_CONFIG_ENGINE_BOOKWORM)_DBG_DEPENDS) +$(DOCKER_FRAMEWORK)_DBG_DEPENDS += $(FRAMEWORK_DBG) $(LIBSWSSCOMMON_DBG) +$(DOCKER_FRAMEWORK)_DBG_IMAGE_PACKAGES = $($(DOCKER_CONFIG_ENGINE_BOOKWORM)_DBG_IMAGE_PACKAGES) + +$(DOCKER_FRAMEWORK)_LOAD_DOCKERS += $(DOCKER_CONFIG_ENGINE_BOOKWORM) +$(DOCKER_FRAMEWORK)_LOAD_DOCKERS += $($(DOCKER_CONFIG_ENGINE_BOOKWORM)_LOAD_DOCKERS) + +$(DOCKER_FRAMEWORK)_VERSION = 1.0.0 +$(DOCKER_FRAMEWORK)_PACKAGE_NAME = framework + +SONIC_DOCKER_IMAGES += $(DOCKER_FRAMEWORK) +SONIC_INSTALL_DOCKER_IMAGES += $(DOCKER_FRAMEWORK) + +SONIC_DOCKER_DBG_IMAGES += $(DOCKER_FRAMEWORK_DBG) +SONIC_INSTALL_DOCKER_DBG_IMAGES += $(DOCKER_FRAMEWORK_DBG) + +$(DOCKER_FRAMEWORK)_CONTAINER_NAME = framework +$(DOCKER_FRAMEWORK)_RUN_OPT += -v /var/run/dbus:/var/run/dbus:rw +$(DOCKER_FRAMEWORK)_RUN_OPT += -v /etc/sonic:/etc/sonic:ro +$(DOCKER_FRAMEWORK)_GIT_REPOSITORIES += "sonic-swss" +$(DOCKER_FRAMEWORK)_GIT_REPOSITORIES += "sonic-swss-common" + +$(DOCKER_FRAMEWORK)_FILES += $(SUPERVISOR_PROC_EXIT_LISTENER_SCRIPT) + +SONIC_BOOKWORM_DOCKERS += $(DOCKER_FRAMEWORK) +SONIC_BOOKWORM_DBG_DOCKERS += $(DOCKER_FRAMEWORK_DBG) diff --git a/rules/framework.dep b/rules/framework.dep new file mode 100644 index 000000000000..76aa6d2aff2b --- /dev/null +++ b/rules/framework.dep @@ -0,0 +1,13 @@ + +SPATH := $($(FRAMEWORK)_SRC_PATH) +DEP_FILES := $(SONIC_COMMON_FILES_LIST) rules/framework.mk rules/framework.dep +DEP_FILES += $(SONIC_COMMON_BASE_FILES_LIST) +SMDEP_PATHS := $(SPATH) $(SPATH)/gnoi +$(foreach path, $(SMDEP_PATHS), $(eval $(path) :=$(filter-out $(SMDEP_PATHS),$(addprefix $(path)/, \ + $(shell cd $(path) && git ls-files | grep -v " "))))) + +$(FRAMEWORK)_CACHE_MODE := GIT_CONTENT_SHA +$(FRAMEWORK)_DEP_FLAGS := $(SONIC_COMMON_FLAGS_LIST) $(ENABLE_ASAN) +$(FRAMEWORK)_DEP_FILES := $(DEP_FILES) +$(FRAMEWORK)_SMDEP_FILES := $(foreach path, $(SMDEP_PATHS), $($(path))) +$(FRAMEWORK)_SMDEP_PATHS := $(SPATH) diff --git a/rules/framework.mk b/rules/framework.mk new file mode 100644 index 000000000000..8c9bb19826f0 --- /dev/null +++ b/rules/framework.mk @@ -0,0 +1,19 @@ +# framework package + +FRAMEWORK = framework_1.0.0_$(CONFIGURED_ARCH).deb +$(FRAMEWORK)_SRC_PATH = $(SRC_PATH)/sonic-framework +$(FRAMEWORK)_DEPENDS += $(LIBSWSSCOMMON_DEV) \ + $(PROTOBUF) $(PROTOBUF_LITE) $(PROTOBUF_DEV) $(PROTOBUF_COMPILER) + +$(FRAMEWORK)_RDEPENDS += $(LIBSWSSCOMMON) $(PROTOBUF) +SONIC_DPKG_DEBS += $(FRAMEWORK) + +FRAMEWORK_DBG = framework-dbg_1.0.0_$(CONFIGURED_ARCH).deb +$(FRAMEWORK_DBG)_DEPENDS += $(FRAMEWORK) +$(FRAMEWORK_DBG)_RDEPENDS += $(FRAMEWORK) +$(eval $(call add_derived_package,$(FRAMEWORK),$(FRAMEWORK_DBG))) + +# The .c, .cpp, .h & .hpp files under src/{$DBG_SRC_ARCHIVE list} +# are archived into debug one image to facilitate debugging. +# +DBG_SRC_ARCHIVE += sonic-framework diff --git a/sonic-slave-bookworm/Dockerfile.j2 b/sonic-slave-bookworm/Dockerfile.j2 index 4e2c5e1eb424..1abf2d5a21ba 100644 --- a/sonic-slave-bookworm/Dockerfile.j2 +++ b/sonic-slave-bookworm/Dockerfile.j2 @@ -430,6 +430,8 @@ RUN apt-get update && apt-get install -y eatmydata && eatmydata apt-get install # For audisp-tacplus libauparse-dev \ auditd \ +# For framework container build + libdbus-c++-dev \ # For protobuf protobuf-compiler \ libprotobuf-dev \ diff --git a/sonic-slave-bullseye/Dockerfile.j2 b/sonic-slave-bullseye/Dockerfile.j2 index 06f8cb1e65a9..801c13a8e456 100644 --- a/sonic-slave-bullseye/Dockerfile.j2 +++ b/sonic-slave-bullseye/Dockerfile.j2 @@ -422,6 +422,8 @@ RUN apt-get update && apt-get install -y eatmydata && eatmydata apt-get install # For audisp-tacplus libauparse-dev \ auditd \ +# For framework container build + libdbus-c++-dev \ # For protobuf dh-elpa \ xmlto \ diff --git a/src/sonic-framework/Makefile.am b/src/sonic-framework/Makefile.am new file mode 100644 index 000000000000..997709cc44b7 --- /dev/null +++ b/src/sonic-framework/Makefile.am @@ -0,0 +1,26 @@ +AUTOMAKE_OPTIONS = subdir-objects +INCLUDES = -I ../sonic-swss-common/common -Ibuild/gen -Ibuild/gen/github.com/openconfig/gnoi +BUILT_SOURCES = rebootbackend_protobuf_compilation rebootbackend_dbus_compilation + +.NOTPARALLEL: compile_protobufs +compile_protobufs: rebootbackend_protobuf_compilation rebootbackend_dbus_compilation + +rebootbackend_protobuf_compilation: + mkdir -p build/gen + /usr/bin/protoc --cpp_out=build/gen \ + --proto_path=github.com/openconfig/gnoi=gnoi \ + gnoi/types/types.proto \ + gnoi/common/common.proto \ + gnoi/system/system.proto + +rebootbackend_dbus_compilation: build/gen/librebootgnoi.la + /usr/bin/dbusxx-xml2cpp rebootbackend/gnoi_reboot.xml \ + --proxy=rebootbackend/gnoi_reboot_dbus.h + +lib_LTLIBRARIES = build/gen/librebootgnoi.la +build_gen_librebootgnoi_la_SOURCES = \ + build/gen/github.com/openconfig/gnoi/system/system.pb.cc \ + build/gen/github.com/openconfig/gnoi/types/types.pb.cc \ + build/gen/github.com/openconfig/gnoi/common/common.pb.cc + +SUBDIRS = rebootbackend diff --git a/src/sonic-framework/autogen.sh b/src/sonic-framework/autogen.sh new file mode 100755 index 000000000000..4a5ffda82dde --- /dev/null +++ b/src/sonic-framework/autogen.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +libtoolize --force --copy && +autoreconf --force --install -I m4 +rm -Rf autom4te.cache diff --git a/src/sonic-framework/configure.ac b/src/sonic-framework/configure.ac new file mode 100644 index 000000000000..3a315230495f --- /dev/null +++ b/src/sonic-framework/configure.ac @@ -0,0 +1,84 @@ +AC_INIT([sonic-swss],[1.0]) +AC_CONFIG_SRCDIR([]) +AC_CONFIG_AUX_DIR(config) +AM_CONFIG_HEADER(config.h) +AM_INIT_AUTOMAKE([foreign]) +AC_LANG_C +AC_LANG([C++]) +AC_PROG_CC +AC_PROG_CXX +AC_PROG_LIBTOOL +AC_HEADER_STDC + +AC_CHECK_LIB([hiredis], [redisConnect],, + AC_MSG_ERROR([libhiredis is not installed.])) + +PKG_CHECK_MODULES([JANSSON], [jansson]) + +AC_ARG_ENABLE(debug, +[ --enable-debug Compile with debugging flags], +[case "${enableval}" in + yes) debug=true ;; + no) debug=false ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-debug) ;; +esac],[debug=false]) +AM_CONDITIONAL(DEBUG, test x$debug = xtrue) + +AC_ARG_WITH(extra-inc, +[ --with-extra-inc=DIR + prefix where extra includes are installed], +[AC_SUBST(CPPFLAGS, "$CPPFLAGS -I${withval} -I${withval}/swss")]) + +AC_ARG_WITH(extra-lib, +[ --with-extra-lib=DIR + prefix where extra libraries are installed], +[AC_SUBST(LDFLAGS, "$LDFLAGS -L${withval}")]) + +CFLAGS_COMMON="-std=c++14 -Wall -fPIC -Wno-write-strings -I/usr/include/swss" + +CFLAGS_COMMON+=" -Werror" +CFLAGS_COMMON+=" -Wno-reorder" +CFLAGS_COMMON+=" -Wcast-align" +CFLAGS_COMMON+=" -Wcast-qual" +#TODO (b/314850353): Re-enable conversion errors with updated protoc compiler. +#CFLAGS_COMMON+=" -Wconversion" +CFLAGS_COMMON+=" -Wdisabled-optimization" +CFLAGS_COMMON+=" -Wextra" +CFLAGS_COMMON+=" -Wfloat-equal" +CFLAGS_COMMON+=" -Wformat=2" +CFLAGS_COMMON+=" -Wformat-nonliteral" +CFLAGS_COMMON+=" -Wformat-security" +CFLAGS_COMMON+=" -Wformat-y2k" +CFLAGS_COMMON+=" -Wimport" +CFLAGS_COMMON+=" -Winit-self" +CFLAGS_COMMON+=" -Winvalid-pch" +CFLAGS_COMMON+=" -Wlong-long" +CFLAGS_COMMON+=" -Wmissing-field-initializers" +CFLAGS_COMMON+=" -Wmissing-format-attribute" +CFLAGS_COMMON+=" -Wno-aggregate-return" +CFLAGS_COMMON+=" -Wno-padded" +CFLAGS_COMMON+=" -Wno-switch-enum" +CFLAGS_COMMON+=" -Wno-unused-parameter" +CFLAGS_COMMON+=" -Wpacked" +CFLAGS_COMMON+=" -Wpointer-arith" +CFLAGS_COMMON+=" -Wredundant-decls" +CFLAGS_COMMON+=" -Wstack-protector" +CFLAGS_COMMON+=" -Wstrict-aliasing=3" +CFLAGS_COMMON+=" -Wswitch" +CFLAGS_COMMON+=" -Wswitch-default" +CFLAGS_COMMON+=" -Wunreachable-code" +CFLAGS_COMMON+=" -Wunused" +CFLAGS_COMMON+=" -Wvariadic-macros" +CFLAGS_COMMON+=" -Wno-switch-default" +CFLAGS_COMMON+=" -Wno-long-long" +CFLAGS_COMMON+=" -Wno-redundant-decls" + +AC_SUBST(CFLAGS_COMMON) + +AC_CONFIG_FILES([ + Makefile + rebootbackend/Makefile + tests/Makefile +]) + +AC_OUTPUT diff --git a/src/sonic-framework/debian/changelog b/src/sonic-framework/debian/changelog new file mode 100644 index 000000000000..93dae9ae8a70 --- /dev/null +++ b/src/sonic-framework/debian/changelog @@ -0,0 +1,6 @@ +sonic (1.0.0) stable; urgency=medium + + * Initial release. + + -- Runming Wu Wed, 20 Sep 2023 12:00:00 -0800 + diff --git a/src/sonic-framework/debian/compat b/src/sonic-framework/debian/compat new file mode 100644 index 000000000000..52a8c6d10ce8 --- /dev/null +++ b/src/sonic-framework/debian/compat @@ -0,0 +1 @@ +10 diff --git a/src/sonic-framework/debian/control b/src/sonic-framework/debian/control new file mode 100644 index 000000000000..cb588a8ce73c --- /dev/null +++ b/src/sonic-framework/debian/control @@ -0,0 +1,18 @@ +Source: sonic +Maintainer: Runming Wu +Section: net +Priority: optional +Build-Depends: dh-exec (>=0.3), debhelper (>= 9), autotools-dev +Standards-Version: 1.0.0 + +Package: framework +Architecture: any +Depends: ${shlibs:Depends} +Description: This package contains framework service. + +Package: framework-dbg +Architecture: any +Section: debug +Priority: extra +Depends: framework (=${binary:Version}) +Description: debugging symbols for framework diff --git a/src/sonic-framework/debian/rules b/src/sonic-framework/debian/rules new file mode 100755 index 000000000000..5d0fe39d9c98 --- /dev/null +++ b/src/sonic-framework/debian/rules @@ -0,0 +1,38 @@ +#!/usr/bin/make -f +# See debhelper(7) (uncomment to enable) +# output every command that modifies files on the build system. +#export DH_VERBOSE = 1 + +# see EXAMPLES in dpkg-buildflags(1) and read /usr/share/dpkg/* +DPKG_EXPORT_BUILDFLAGS = 1 +include /usr/share/dpkg/default.mk + +# see FEATURE AREAS in dpkg-buildflags(1) +#export DEB_BUILD_MAINT_OPTIONS = hardening=+all + +# see ENVIRONMENT in dpkg-buildflags(1) +# package maintainers to append CFLAGS +#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic +# package maintainers to append LDFLAGS +#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed + + +# main packaging script based on dh7 syntax +%: + dh $@ --with autotools-dev + +# dh_make generated override targets +# This is example for Cmake (See https://bugs.debian.org/641051 ) +#override_dh_auto_configure: +# dh_auto_configure -- \ +# -DCMAKE_LIBRARY_PATH=$(DEB_HOST_MULTIARCH) + +override_dh_auto_configure: + dh_auto_configure -- $(configure_opts) + +override_dh_auto_install: + dh_auto_install --destdir=debian/framework + +override_dh_strip: + dh_strip -pframework --dbg-package=framework-dbg + diff --git a/src/sonic-framework/gnoi b/src/sonic-framework/gnoi new file mode 160000 index 000000000000..73a1e7675c5f --- /dev/null +++ b/src/sonic-framework/gnoi @@ -0,0 +1 @@ +Subproject commit 73a1e7675c5f963e7810bd3828203f2758eb47e8 diff --git a/src/sonic-framework/package.cfg b/src/sonic-framework/package.cfg new file mode 100644 index 000000000000..e80d873ab346 --- /dev/null +++ b/src/sonic-framework/package.cfg @@ -0,0 +1,15 @@ +{ + "description": "This package contains SONiC framework.", + "version": "1.0.0", + "maintainer": "Runming Wu ", + "build_depends": [ ], + "packages": [ + { + "name": "sonic-framework", + "dependencies": [ + ], + "files": [ + ] + } + ] +} diff --git a/src/sonic-framework/rebootbackend/Makefile.am b/src/sonic-framework/rebootbackend/Makefile.am new file mode 100644 index 000000000000..9ab36da44bf9 --- /dev/null +++ b/src/sonic-framework/rebootbackend/Makefile.am @@ -0,0 +1,17 @@ +AUTOMAKE_OPTIONS = subdir-objects +INCLUDES = -I $(top_srcdir) -I/usr/include/dbus-c++-1/ -I$(top_srcdir)/build/gen -I$(top_srcdir)/build/gen/github.com/openconfig/gnoi -I ../../sonic-swss-common/common + +bin_PROGRAMS = rebootbackend + +if DEBUG +DBGFLAGS = -ggdb -DDEBUG +else +DBGFLAGS = -g +endif + +rebootbackend_SOURCES = rebootbackend.cpp rebootbe.cpp interfaces.cpp \ + reboot_thread.cpp + +rebootbackend_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_ASAN) +rebootbackend_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_ASAN) +rebootbackend_LDADD = $(LDFLAGS_ASAN) -lswsscommon -ldbus-c++-1 -lpthread -lprotobuf $(top_srcdir)/build/gen/librebootgnoi.la diff --git a/src/sonic-framework/rebootbackend/gnoi_reboot.xml b/src/sonic-framework/rebootbackend/gnoi_reboot.xml new file mode 100644 index 000000000000..63af1963db4b --- /dev/null +++ b/src/sonic-framework/rebootbackend/gnoi_reboot.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/sonic-framework/rebootbackend/interfaces.cpp b/src/sonic-framework/rebootbackend/interfaces.cpp new file mode 100644 index 000000000000..b74ecb99079e --- /dev/null +++ b/src/sonic-framework/rebootbackend/interfaces.cpp @@ -0,0 +1,72 @@ +#include "interfaces.h" + +#include // DBus + +#include "reboot_interfaces.h" + +constexpr char kRebootBusName[] = "org.SONiC.HostService.gnoi_reboot"; +constexpr char kRebootPath[] = "/org/SONiC/HostService/gnoi_reboot"; + +constexpr char kContainerShutdownBusName[] = + "org.SONiC.HostService.gnoi_container_shutdown"; +constexpr char kContainerShutdownPath[] = + "/org/SONiC/HostService/gnoi_container_shutdown"; + +// DBus::BusDispatcher dispatcher; +DBus::Connection& HostServiceDbus::getConnection(void) { + static DBus::Connection* connPtr = nullptr; + if (connPtr == nullptr) { + static DBus::BusDispatcher dispatcher; + DBus::default_dispatcher = &dispatcher; + + static DBus::Connection conn = DBus::Connection::SystemBus(); + connPtr = &conn; + } + return *connPtr; +} + +DbusInterface::DbusResponse HostServiceDbus::Reboot( + const std::string& jsonRebootRequest) { + int32_t status; + + GnoiDbusReboot reboot_client(getConnection(), kRebootBusName, kRebootPath); + std::string retString; + std::vector options; + options.push_back(jsonRebootRequest); + try { + reboot_client.issue_reboot(options, status, retString); + } catch (DBus::Error& ex) { + return DbusResponse{ + DbusStatus::DBUS_FAIL, + "HostServiceDbus::Reboot: failed to call reboot host service"}; + } + + // gnoi_reboot.py returns 0 for success, 1 for failure + if (status == 0) { + // Successful reboot response is an empty string. + return DbusResponse{DbusStatus::DBUS_SUCCESS, ""}; + } + return DbusResponse{DbusStatus::DBUS_FAIL, retString}; +} + +DbusInterface::DbusResponse HostServiceDbus::RebootStatus( + const std::string& jsonStatusRequest) { + GnoiDbusReboot reboot_client(getConnection(), kRebootBusName, kRebootPath); + int32_t status; + std::string retString; + + try { + reboot_client.get_reboot_status(status, retString); + } catch (DBus::Error& ex) { + return DbusResponse{ + DbusStatus::DBUS_FAIL, + "HostServiceDbus::RebootStatus: failed to call reboot status " + "host service"}; + } + + // gnoi_reboot.py returns 0 for success, 1 for failure + if (status == 0) { + return DbusResponse{DbusStatus::DBUS_SUCCESS, retString}; + } + return DbusResponse{DbusStatus::DBUS_FAIL, retString}; +} diff --git a/src/sonic-framework/rebootbackend/interfaces.h b/src/sonic-framework/rebootbackend/interfaces.h new file mode 100644 index 000000000000..10382af1483f --- /dev/null +++ b/src/sonic-framework/rebootbackend/interfaces.h @@ -0,0 +1,39 @@ +#pragma once +#include + +#include + +#include "gnoi_reboot_dbus.h" // auto generated gnoi_reboot_proxy +#include "reboot_interfaces.h" + +/* Reboot is a request to the reboot sonic host service to request a reboot +from the platform. This takes as an argument a string based json formatted +Reboot request from +system.proto.’https://github.com/openconfig/gnoi/blob/73a1e7675c5f963e7810bd3828203f2758eb47e8/system/system.proto#L107 +*/ + +class GnoiDbusReboot : public org::SONiC::HostService::gnoi_reboot_proxy, + public DBus::IntrospectableProxy, + public DBus::ObjectProxy { + public: + GnoiDbusReboot(DBus::Connection& connection, const char* dbus_bus_name_p, + const char* dbus_obj_name_p) + : DBus::ObjectProxy(connection, dbus_obj_name_p, dbus_bus_name_p) {} +}; + +/* DbusResponse consists of STATUS: success/fail: i.e. was the dbus request +successful DbusResponse.json_string: string based json formatted RebootResponse +defined here: +https://github.com/openconfig/gnoi/blob/73a1e7675c5f963e7810bd3828203f2758eb47e8/system/system.proto#L119 +*/ + +class HostServiceDbus : public DbusInterface { + public: + DbusInterface::DbusResponse Reboot( + const std::string& json_reboot_request) override; + DbusInterface::DbusResponse RebootStatus( + const std::string& json_status_request) override; + + private: + static DBus::Connection& getConnection(void); +}; diff --git a/src/sonic-framework/rebootbackend/reboot_common.h b/src/sonic-framework/rebootbackend/reboot_common.h new file mode 100644 index 000000000000..b2f6848c43f4 --- /dev/null +++ b/src/sonic-framework/rebootbackend/reboot_common.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#include "status_code_util.h" + +namespace rebootbackend { + +extern bool sigterm_requested; +struct NotificationResponse { + swss::StatusCode status; + std::string json_string; +}; + +} // namespace rebootbackend diff --git a/src/sonic-framework/rebootbackend/reboot_interfaces.h b/src/sonic-framework/rebootbackend/reboot_interfaces.h new file mode 100644 index 000000000000..599652fa5347 --- /dev/null +++ b/src/sonic-framework/rebootbackend/reboot_interfaces.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +class DbusInterface { + public: + enum class DbusStatus { + DBUS_SUCCESS, + DBUS_FAIL, + }; + + struct DbusResponse { + DbusStatus status; + std::string json_string; + }; + + virtual ~DbusInterface() = default; + virtual DbusResponse Reboot(const std::string& jsonRebootRequest) = 0; + virtual DbusResponse RebootStatus(const std::string& jsonStatusRequest) = 0; +}; diff --git a/src/sonic-framework/rebootbackend/reboot_thread.cpp b/src/sonic-framework/rebootbackend/reboot_thread.cpp new file mode 100644 index 000000000000..14f3d4522d9e --- /dev/null +++ b/src/sonic-framework/rebootbackend/reboot_thread.cpp @@ -0,0 +1,291 @@ +#include "reboot_thread.h" + +#include + +#include + +#include "dbconnector.h" +#include "logger.h" +#include "notificationproducer.h" +#include "reboot_common.h" +#include "reboot_interfaces.h" +#include "select.h" +#include "selectableevent.h" +#include "selectabletimer.h" +#include "subscriberstatetable.h" +#include "system/system.pb.h" +#include "timestamp.h" + +namespace rebootbackend { + +using namespace ::gnoi::system; +using steady_clock = std::chrono::steady_clock; +using Progress = ::rebootbackend::RebootThread::Progress; +namespace gpu = ::google::protobuf::util; + +bool sigterm_requested = false; + +RebootThread::RebootThread(DbusInterface &dbus_interface, + swss::SelectableEvent &m_finished) + : m_db("STATE_DB", 0), + m_finished(m_finished), + m_dbus_interface(dbus_interface) {} + +void RebootThread::Stop(void) { + SWSS_LOG_ENTER(); + // Notify reboot thread that stop has been requested. + m_stop.notify(); +} + +bool RebootThread::Join(void) { + SWSS_LOG_ENTER(); + + if (!m_thread.joinable()) { + SWSS_LOG_ERROR("RebootThread::Join called, but not joinable"); + return false; + } + + try { + m_thread.join(); + m_status.set_inactive(); + return true; + } catch (const std::system_error &e) { + SWSS_LOG_ERROR("Exception calling join: %s", e.what()); + return false; + } +} + +RebootStatusResponse RebootThread::GetResponse(void) { + return m_status.get_response(); +} + +bool RebootThread::HasRun() { return m_status.get_reboot_count() > 0; } + +Progress RebootThread::platform_reboot_select(swss::Select &s, + swss::SelectableTimer &l_timer) { + SWSS_LOG_ENTER(); + + while (true) { + swss::Selectable *sel; + int select_ret; + select_ret = s.select(&sel); + + if (select_ret == swss::Select::ERROR) { + SWSS_LOG_NOTICE("Error: %s!", strerror(errno)); + } else if (select_ret == swss::Select::OBJECT) { + if (sel == &m_stop) { + // SIGTERM expected after platform reboot request + SWSS_LOG_NOTICE( + "m_stop rx'd (SIGTERM) while waiting for platform reboot"); + return Progress::EXIT_EARLY; + } else if (sel == &l_timer) { + return Progress::PROCEED; + } + } + } +} + +Progress RebootThread::wait_for_platform_reboot(swss::Select &s) { + SWSS_LOG_ENTER(); + + // Sleep for a long time: 260 seconds. + // During this time platform should kill us as part of reboot. + swss::SelectableTimer l_timer( + timespec{.tv_sec = m_reboot_timeout, .tv_nsec = 0}); + s.addSelectable(&l_timer); + + l_timer.start(); + + Progress progress = platform_reboot_select(s, l_timer); + + l_timer.stop(); + s.removeSelectable(&l_timer); + return progress; +} + +void RebootThread::do_reboot(void) { + SWSS_LOG_ENTER(); + + swss::Select s; + s.addSelectable(&m_stop); + + // Check if stop was requested before Selectable was setup + if (sigterm_requested) { + SWSS_LOG_ERROR("sigterm_requested was raised, exiting"); + return; + } + + if (m_request.method() == RebootMethod::COLD) { + do_cold_reboot(s); + } else if (m_request.method() == RebootMethod::WARM) { + do_warm_reboot(s); + } else { + // This shouldn't be possible. Reference check_start_preconditions() + SWSS_LOG_ERROR("Received unrecognized method type = %s", + RebootMethod_Name(m_request.method()).c_str()); + } +} + +RebootThread::Progress RebootThread::send_dbus_reboot_request() { + SWSS_LOG_ENTER(); + SWSS_LOG_NOTICE("Sending reboot request to platform"); + + std::string json_string; + gpu::Status status = gpu::MessageToJsonString(m_request, &json_string); + if (!status.ok()) { + std::string error_string = "unable to convert reboot protobuf to json: " + + status.message().as_string(); + log_error_and_set_non_retry_failure(error_string); + return Progress::EXIT_EARLY; + } + + // Send the reboot request to the reboot host service via dbus. + DbusInterface::DbusResponse dbus_response = + m_dbus_interface.Reboot(json_string); + + if (dbus_response.status == DbusInterface::DbusStatus::DBUS_FAIL) { + log_error_and_set_non_retry_failure(dbus_response.json_string); + return Progress::EXIT_EARLY; + } + return Progress::PROCEED; +} + +void RebootThread::do_cold_reboot(swss::Select &s) { + SWSS_LOG_ENTER(); + SWSS_LOG_NOTICE("Sending cold reboot request to platform"); + if (send_dbus_reboot_request() == Progress::EXIT_EARLY) { + return; + } + + // Wait for platform to reboot. If we return, reboot failed. + if (wait_for_platform_reboot(s) == Progress::EXIT_EARLY) { + return; + } + + // We shouldn't be here. Platform reboot should've killed us. + log_error_and_set_non_retry_failure("platform failed to reboot"); + + return; +} + +void RebootThread::do_warm_reboot(swss::Select &s) { + SWSS_LOG_ENTER(); + SWSS_LOG_NOTICE("Sending warm reboot request to platform"); + if (send_dbus_reboot_request() == Progress::EXIT_EARLY) { + return; + } + + // Wait for warm reboot. If we return, reboot failed. + if (wait_for_platform_reboot(s) == Progress::EXIT_EARLY) { + return; + } + + // We shouldn't be here. Platform reboot should've killed us. + log_error_and_set_non_retry_failure("failed to warm reboot"); + + return; +} + +void RebootThread::reboot_thread(void) { + SWSS_LOG_ENTER(); + + do_reboot(); + + // Notify calling thread that reboot thread has exited. + // Calling thread will call Join(): join and set thread status to inactive. + m_finished.notify(); +} + +bool RebootThread::check_start_preconditions(const RebootRequest &request, + NotificationResponse &response) { + // We have to join a previous executing thread before restarting. + // Active is cleared in Join. + if (m_status.get_active()) { + response.json_string = "RebootThread: can't Start while active"; + response.status = swss::StatusCode::SWSS_RC_IN_USE; + } else if (request.method() != RebootMethod::COLD && + request.method() != RebootMethod::WARM) { + response.json_string = "RebootThread: Start rx'd unsupported method"; + response.status = swss::StatusCode::SWSS_RC_INVALID_PARAM; + } else if (request.method() == RebootMethod::WARM) { + if (m_status.get_last_reboot_status() == + RebootStatus_Status::RebootStatus_Status_STATUS_FAILURE) { + // If the last reboot failed with a non-retriable failure, don't retry. + // But, we will allow a cold boot to recover. + response.json_string = + "RebootThread: last WARM reboot failed with non-retriable failure"; + response.status = swss::StatusCode::SWSS_RC_FAILED_PRECONDITION; + } + } else if (request.delay() != 0) { + response.json_string = "RebootThread: delayed start not supported"; + response.status = swss::StatusCode::SWSS_RC_INVALID_PARAM; + } + + if (response.status == swss::StatusCode::SWSS_RC_SUCCESS) { + return true; + } + + SWSS_LOG_ERROR("%s", response.json_string.c_str()); + // Log the reboot request contents. + gpu::Status status; + std::string json_request; + status = gpu::MessageToJsonString(request, &json_request); + if (status.ok()) { + SWSS_LOG_ERROR("check_start_preconditions: RebootRequest = %s", + json_request.c_str()); + } else { + SWSS_LOG_ERROR( + "check_start_preconditions: error calling MessageToJsonString"); + } + return false; +} + +NotificationResponse RebootThread::Start(const RebootRequest &request) { + SWSS_LOG_ENTER(); + + NotificationResponse response = {.status = swss::StatusCode::SWSS_RC_SUCCESS, + .json_string = ""}; + + // Confirm we're not running, method is supported and we're not delayed. + if (!check_start_preconditions(request, response)) { + // Errors logged in check_start_preconditions. + return response; + } + + m_request = request; + + // From this point errors will be reported via RebootStatusRequest. + m_status.set_start_status(request.method(), request.message()); + + try { + m_thread = std::thread(&RebootThread::reboot_thread, this); + } catch (const std::system_error &e) { + std::string error_string = "Exception launching reboot thread: "; + error_string += e.what(); + log_error_and_set_failure_as_retriable(error_string); + + // Notify calling thread that thread has finished. + // Calling thread MUST call Join, which will join and clear active bit. + m_finished.notify(); + } + return response; +} + +void RebootThread::log_error_and_set_non_retry_failure( + const std::string error_string) { + SWSS_LOG_ENTER(); + SWSS_LOG_ERROR("%s", error_string.c_str()); + m_status.set_completed_status( + RebootStatus_Status::RebootStatus_Status_STATUS_FAILURE, error_string); +} + +void RebootThread::log_error_and_set_failure_as_retriable( + const std::string error_string) { + SWSS_LOG_ENTER(); + SWSS_LOG_ERROR("%s", error_string.c_str()); + m_status.set_completed_status( + RebootStatus_Status::RebootStatus_Status_STATUS_RETRIABLE_FAILURE, + error_string); +} + +} // namespace rebootbackend diff --git a/src/sonic-framework/rebootbackend/reboot_thread.h b/src/sonic-framework/rebootbackend/reboot_thread.h new file mode 100644 index 000000000000..ee36e3cd01c5 --- /dev/null +++ b/src/sonic-framework/rebootbackend/reboot_thread.h @@ -0,0 +1,218 @@ +#pragma once + +#include +#include +#include + +#include "dbconnector.h" +#include "notificationproducer.h" +#include "reboot_common.h" +#include "reboot_interfaces.h" +#include "select.h" +#include "selectableevent.h" +#include "selectabletimer.h" +#include "subscriberstatetable.h" +#include "system/system.pb.h" + +namespace rebootbackend { + +// Hold/manage the contents of a RebootStatusResponse as defined +// in system.proto +// Thread-safe: expectation is one thread will write and multiple +// threads can read. +class ThreadStatus { + public: + ThreadStatus() { + m_proto_status.set_active(false); + + // Reason for reboot as specified in message from a RebootRequest. + // This is "message" in RebootRequest. + m_proto_status.set_reason(""); + + // Number of reboots since active. + m_proto_status.set_count(0); + + // RebootMethod is type of of reboot: cold, warm, fast from a + // RebootRequest + m_proto_status.set_method(gnoi::system::RebootMethod::UNKNOWN); + + // Status can be UNKNOWN, SUCCESS, RETRIABLE_FAILURE or FAILURE. + m_proto_status.mutable_status()->set_status( + gnoi::system::RebootStatus_Status::RebootStatus_Status_STATUS_UNKNOWN); + + // In the event of error: message is human readable error explanation. + m_proto_status.mutable_status()->set_message(""); + } + + void set_start_status(const gnoi::system::RebootMethod &method, + const std::string &reason) { + m_mutex.lock(); + + m_proto_status.set_active(true); + m_proto_status.set_reason(reason); + m_proto_status.set_count(m_proto_status.count() + 1); + m_proto_status.set_method(method); + m_proto_status.mutable_status()->set_status( + gnoi::system::RebootStatus_Status::RebootStatus_Status_STATUS_UNKNOWN); + m_proto_status.mutable_status()->set_message(""); + + // set when to time reboot starts + std::chrono::nanoseconds ns = + std::chrono::system_clock::now().time_since_epoch(); + m_proto_status.set_when(ns.count()); + + m_mutex.unlock(); + } + + bool get_active(void) { + m_mutex.lock(); + bool ret = m_proto_status.active(); + m_mutex.unlock(); + return ret; + } + + void set_completed_status(const gnoi::system::RebootStatus_Status &status, + const std::string &message) { + m_mutex.lock(); + + // Status should only be updated while reboot is active + if (m_proto_status.active()) { + m_proto_status.mutable_status()->set_status(status); + m_proto_status.mutable_status()->set_message(message); + } + + m_mutex.unlock(); + } + + void set_inactive(void) { + m_mutex.lock(); + m_proto_status.set_active(false); + m_mutex.unlock(); + } + + int get_reboot_count() { + const std::lock_guard lock(m_mutex); + return m_proto_status.count(); + } + + gnoi::system::RebootStatus_Status get_last_reboot_status(void) { + gnoi::system::RebootStatusResponse response = get_response(); + return response.status().status(); + } + + gnoi::system::RebootStatusResponse get_response(void) { + m_mutex.lock(); + // make a copy + gnoi::system::RebootStatusResponse lstatus = m_proto_status; + m_mutex.unlock(); + + if (lstatus.active()) { + // RebootStatus isn't applicable if we're active + lstatus.mutable_status()->set_status( + gnoi::system::RebootStatus_Status:: + RebootStatus_Status_STATUS_UNKNOWN); + lstatus.mutable_status()->set_message(""); + } else { + // When is only valid while we're active (since delayed + // start isn't supported). Value is set when reboot begins. + lstatus.set_when(0); + } + + return lstatus; + } + + private: + std::mutex m_mutex; + gnoi::system::RebootStatusResponse m_proto_status; +}; + +// RebootThread performs reboot actions leading up to a platform +// request to reboot. +// thread-compatible: expectation is Stop, Start and Join will be +// called from the same thread. +class RebootThread { + public: + enum class Status { SUCCESS, FAILURE, KEEP_WAITING }; + enum class Progress { PROCEED, EXIT_EARLY }; + + // interface: dbus reboot host service access + // m_finished: let launching task know thread has finished + RebootThread(DbusInterface &dbus_interface, + swss::SelectableEvent &m_finished); + + NotificationResponse Start(const gnoi::system::RebootRequest &request); + + // Request thread stop/exit. Only used when platform is shutting down + // all containers/processes. + void Stop(void); + + // Called by launching task after notification sent to m_finished. + bool Join(void); + + // Return Status of last reboot attempt + gnoi::system::RebootStatusResponse GetResponse(); + + // Returns true if the RebootThread has been started since the last reboot, + // and false otherwise. + bool HasRun(); + + private: + void reboot_thread(void); + void do_reboot(void); + Progress send_dbus_reboot_request(); + void do_cold_reboot(swss::Select &s); + void do_warm_reboot(swss::Select &s); + + // Inner loop select handler to wait for platform reboot. + // wait for timeout + // wait for a stop request (sigterm) + // Returns: + // EXIT_EARLY: an issue occurred that stops WARM + // PROCEED: if reboot timeout expired + Progress platform_reboot_select(swss::Select &s, + swss::SelectableTimer &l_timer); + + // Wait for platform to reboot while waiting for possible stop + // Returns: + // EXIT_EARLY: an issue occurred that stops WARM + // PROCEED: if reboot timeout expired + Progress wait_for_platform_reboot(swss::Select &s); + + // Log error string, set status to RebootStatus_Status_STATUS_FAILURE + // Set status message to error_string. + void log_error_and_set_non_retry_failure(const std::string error_string); + + // Log error string, set status to + // RebootStatus_Status_STATUS_RETRIABLE_FAILURE Set status message to + // error_string. + void log_error_and_set_failure_as_retriable(const std::string error_string); + + // Request is input only. + // Response is ouput only. + // Return true if preconditions met, false otherwise. + bool check_start_preconditions(const gnoi::system::RebootRequest &request, + NotificationResponse &response); + std::thread m_thread; + + // Signal m_finished to let main thread know weve completed. + // Main thread should call Join. + swss::SelectableEvent &m_finished; + + // m_stop signalled by main thread on sigterm: cleanup and exit. + swss::SelectableEvent m_stop; + DbusInterface &m_dbus_interface; + swss::DBConnector m_db; + ThreadStatus m_status; + gnoi::system::RebootRequest m_request; + + // Wait for system to reboot: allow unit test to shorten. + // TODO: there is a plan to make these timer values + // available in CONFIG_DB + static constexpr uint32_t kRebootTime = 260; + long m_reboot_timeout = kRebootTime; + + friend class RebootBETestWithoutStop; + friend class RebootThreadTest; +}; + +} // namespace rebootbackend diff --git a/src/sonic-framework/rebootbackend/rebootbackend.cpp b/src/sonic-framework/rebootbackend/rebootbackend.cpp new file mode 100644 index 000000000000..d0962f63869b --- /dev/null +++ b/src/sonic-framework/rebootbackend/rebootbackend.cpp @@ -0,0 +1,10 @@ +#include "interfaces.h" +#include "reboot_interfaces.h" +#include "rebootbe.h" + +int main(int argc, char** argv) { + HostServiceDbus dbus_interface; + ::rebootbackend::RebootBE rebootbe(dbus_interface); + rebootbe.Start(); + return 0; +} diff --git a/src/sonic-framework/rebootbackend/rebootbe.cpp b/src/sonic-framework/rebootbackend/rebootbe.cpp new file mode 100644 index 000000000000..3bf119bd30ec --- /dev/null +++ b/src/sonic-framework/rebootbackend/rebootbe.cpp @@ -0,0 +1,276 @@ +#include "rebootbe.h" + +#include +#include + +#include +#include +#include + +#include "logger.h" +#include "notificationconsumer.h" +#include "notificationproducer.h" +#include "reboot_common.h" +#include "reboot_interfaces.h" +#include "select.h" +#include "status_code_util.h" +#include "warm_restart.h" + +namespace rebootbackend { + +namespace gpu = ::google::protobuf::util; + +RebootBE::RebootBE(DbusInterface &dbus_interface) + : m_db("STATE_DB", 0), + m_RebootResponse(&m_db, REBOOT_RESPONSE_NOTIFICATION_CHANNEL), + m_NotificationConsumer(&m_db, REBOOT_REQUEST_NOTIFICATION_CHANNEL), + m_dbus(dbus_interface), + m_RebootThread(dbus_interface, m_RebootThreadFinished) { + swss::Logger::linkToDbNative("rebootbackend"); +} + +RebootBE::RebManagerStatus RebootBE::GetCurrentStatus() { + const std::lock_guard lock(m_StatusMutex); + return m_CurrentStatus; +} + +void RebootBE::SetCurrentStatus(RebManagerStatus newStatus) { + const std::lock_guard lock(m_StatusMutex); + m_CurrentStatus = newStatus; +} + +void RebootBE::Start() { + SWSS_LOG_ENTER(); + SWSS_LOG_NOTICE("--- Starting rebootbackend ---"); + + swss::Select s; + s.addSelectable(&m_NotificationConsumer); + s.addSelectable(&m_Done); + s.addSelectable(&m_RebootThreadFinished); + + if (swss::WarmStart::isWarmStart()) { + SetCurrentStatus(RebManagerStatus::WARM_INIT_WAIT); + } else { + SWSS_LOG_NOTICE("Warm restart not enabled"); + } + + SWSS_LOG_NOTICE("RebootBE entering operational loop"); + while (true) { + swss::Selectable *sel; + int ret; + + ret = s.select(&sel); + if (ret == swss::Select::ERROR) { + SWSS_LOG_NOTICE("Error: %s!", strerror(errno)); + } else if (ret == swss::Select::OBJECT) { + if (sel == &m_NotificationConsumer) { + DoTask(m_NotificationConsumer); + } else if (sel == &m_RebootThreadFinished) { + HandleRebootFinish(); + } else if (sel == &m_Done) { + HandleDone(); + break; + } + } + } + return; +} + +void RebootBE::Stop() { + SWSS_LOG_ENTER(); + m_Done.notify(); + return; +} + +bool RebootBE::RetrieveNotificationData( + swss::NotificationConsumer &consumer, + RebootBE::NotificationRequest &request) { + SWSS_LOG_ENTER(); + + request.op = ""; + request.retString = ""; + + std::string data; + std::vector values; + consumer.pop(request.op, data, values); + + for (auto &fv : values) { + if (DATA_TUPLE_KEY == fvField(fv)) { + request.retString = fvValue(fv); + return true; + } + } + return false; +} + +// Send a response on the Reboot_Response_Channel notification channel.. +// Key is one of: Reboot, RebootStatus, or CancelReboot +// code is swss::StatusCode, hopefully SWSS_RC_SUCCESS. +// message is json formatted RebootResponse, RebootStatusResponse +// or CancelRebootResponse as defined in system.proto +void RebootBE::SendNotificationResponse(const std::string key, + const swss::StatusCode code, + const std::string message) { + SWSS_LOG_ENTER(); + + std::vector ret_values; + ret_values.push_back(swss::FieldValueTuple(DATA_TUPLE_KEY, message)); + + m_RebootResponse.send(key, swss::statusCodeToStr(code), ret_values); +} + +NotificationResponse RebootBE::HandleRebootRequest( + const std::string &jsonRebootRequest) { + using namespace gpu; + + SWSS_LOG_ENTER(); + + // On success an emtpy string is returned. RebootResponse in system.proto + // is an empty proto. + NotificationResponse response = {.status = swss::StatusCode::SWSS_RC_SUCCESS, + .json_string = ""}; + + gnoi::system::RebootRequest request; + Status status = gpu::JsonStringToMessage(jsonRebootRequest, &request); + + if (!status.ok()) { + std::string error_string = + "unable to convert json to rebootRequest protobuf: " + + status.message().as_string(); + SWSS_LOG_ERROR("%s", error_string.c_str()); + SWSS_LOG_ERROR("json = |%s|", jsonRebootRequest.c_str()); + response.status = swss::StatusCode::SWSS_RC_INTERNAL, + response.json_string = error_string; + return response; + } + + if (!RebootAllowed(request.method())) { + response.status = swss::StatusCode::SWSS_RC_IN_USE; + response.json_string = + "Reboot not allowed at this time. Reboot or " + "post-warmboot in progress"; + SWSS_LOG_WARN("%s", response.json_string.c_str()); + return response; + } + + SWSS_LOG_NOTICE("Forwarding request to RebootThread: %s", + request.DebugString().c_str()); + response = m_RebootThread.Start(request); + if (response.status == swss::StatusCode::SWSS_RC_SUCCESS) { + if (request.method() == gnoi::system::RebootMethod::COLD) { + SetCurrentStatus(RebManagerStatus::COLD_REBOOT_IN_PROGRESS); + } else if (request.method() == gnoi::system::RebootMethod::WARM) { + SetCurrentStatus(RebManagerStatus::WARM_REBOOT_IN_PROGRESS); + } + } + return response; +} + +bool RebootBE::RebootAllowed(const gnoi::system::RebootMethod rebMethod) { + RebManagerStatus current_status = GetCurrentStatus(); + switch (current_status) { + case RebManagerStatus::COLD_REBOOT_IN_PROGRESS: + case RebManagerStatus::WARM_REBOOT_IN_PROGRESS: { + return false; + } + case RebManagerStatus::WARM_INIT_WAIT: { + return rebMethod == gnoi::system::RebootMethod::COLD; + } + case RebManagerStatus::IDLE: { + return true; + } + default: { + return true; + } + } +} + +NotificationResponse RebootBE::HandleStatusRequest( + const std::string &jsonStatusRequest) { + SWSS_LOG_ENTER(); + + gnoi::system::RebootStatusResponse reboot_response = + m_RebootThread.GetResponse(); + + std::string json_reboot_response_string; + google::protobuf::util::Status status = + gpu::MessageToJsonString(reboot_response, &json_reboot_response_string); + + NotificationResponse response; + if (status.ok()) { + response.status = swss::StatusCode::SWSS_RC_SUCCESS; + response.json_string = json_reboot_response_string; + } else { + std::string error_string = + "unable to convert reboot status response protobuf to json: " + + status.message().as_string(); + SWSS_LOG_ERROR("%s", error_string.c_str()); + response.status = swss::StatusCode::SWSS_RC_INTERNAL; + response.json_string = error_string; + } + + return response; +} + +NotificationResponse RebootBE::HandleCancelRequest( + const std::string &jsonCancelRequest) { + SWSS_LOG_ENTER(); + + NotificationResponse response; + + // CancelReboot isn't supported: not needed until/unless delayed support + // is added: return unimplemented. + response.status = swss::StatusCode::SWSS_RC_UNIMPLEMENTED; + response.json_string = "Cancel reboot isn't supported"; + SWSS_LOG_WARN("%s", response.json_string.c_str()); + return response; +} + +void RebootBE::DoTask(swss::NotificationConsumer &consumer) { + SWSS_LOG_ENTER(); + + NotificationResponse response; + RebootBE::NotificationRequest request; + + if (!RetrieveNotificationData(consumer, request)) { + // Response is simple string (not json) on error. + response.json_string = + "MESSAGE not present in reboot notification request message, op = " + + request.op; + SWSS_LOG_ERROR("%s", response.json_string.c_str()); + response.status = swss::StatusCode::SWSS_RC_INVALID_PARAM; + } else if (request.op == REBOOT_KEY) { + response = HandleRebootRequest(request.retString); + } else if (request.op == REBOOT_STATUS_KEY) { + response = HandleStatusRequest(request.retString); + } else if (request.op == CANCEL_REBOOT_KEY) { + response = HandleCancelRequest(request.retString); + } else { + // Response is simple string (not json) on error. + response.json_string = + "Unrecognized op in reboot request, op = " + request.op; + SWSS_LOG_ERROR("%s", response.json_string.c_str()); + response.status = swss::StatusCode::SWSS_RC_INVALID_PARAM; + } + SendNotificationResponse(request.op, response.status, response.json_string); +} + +void RebootBE::HandleRebootFinish() { + SWSS_LOG_ENTER(); + SWSS_LOG_WARN( + "Receieved notification that reboot has finished. This probably means " + "something is wrong"); + m_RebootThread.Join(); + SetCurrentStatus(RebManagerStatus::IDLE); +} + +void RebootBE::HandleDone() { + SWSS_LOG_INFO("RebootBE received signal to stop"); + + if (m_RebootThread.GetResponse().active()) { + m_RebootThread.Stop(); + m_RebootThread.Join(); + } +} + +} // namespace rebootbackend diff --git a/src/sonic-framework/rebootbackend/rebootbe.h b/src/sonic-framework/rebootbackend/rebootbe.h new file mode 100644 index 000000000000..b24db5e7a20b --- /dev/null +++ b/src/sonic-framework/rebootbackend/rebootbe.h @@ -0,0 +1,94 @@ +#pragma once +#include "dbconnector.h" +#include "notificationconsumer.h" +#include "notificationproducer.h" +#include "reboot_common.h" +#include "reboot_interfaces.h" +#include "reboot_thread.h" +#include "selectableevent.h" +#include "status_code_util.h" + +namespace rebootbackend { + +constexpr char REBOOT_REQUEST_NOTIFICATION_CHANNEL[] = "Reboot_Request_Channel"; +constexpr char REBOOT_RESPONSE_NOTIFICATION_CHANNEL[] = + "Reboot_Response_Channel"; +constexpr char REBOOT_KEY[] = "Reboot"; +constexpr char REBOOT_STATUS_KEY[] = "RebootStatus"; +constexpr char CANCEL_REBOOT_KEY[] = "CancelReboot"; +constexpr char DATA_TUPLE_KEY[] = "MESSAGE"; + +class RebootBE { + public: + enum class RebManagerStatus { + WARM_INIT_WAIT, + IDLE, + COLD_REBOOT_IN_PROGRESS, + WARM_REBOOT_IN_PROGRESS + }; + + struct NotificationRequest { + std::string op; + std::string retString; + }; + + RebootBE(DbusInterface &interface); + + // To get the current reboot status. + RebManagerStatus GetCurrentStatus(); + + // Checks for the notification and takes appropriate action. + void Start(); + + // Notifies completion status of reboot. + void Stop(); + + private: + std::mutex m_StatusMutex; + RebManagerStatus m_CurrentStatus = RebManagerStatus::IDLE; + swss::SelectableEvent m_Done; + + swss::DBConnector m_db; + swss::NotificationProducer m_RebootResponse; + swss::NotificationConsumer m_NotificationConsumer; + + DbusInterface &m_dbus; + + // Signalled by reboot thread when thread completes. + swss::SelectableEvent m_RebootThreadFinished; + RebootThread m_RebootThread; + + void SetCurrentStatus(RebManagerStatus newStatus); + + // Reboot_Request_Channel notifications should all contain {"MESSAGE" : Data} + // in the notification Data field. + // Return true if "MESSAGE" is found, false otherwise. + // Set message_value to the Data string if found, "" otherwise. + // consumer is input: this is the consumer from which we pop + // reboot/cancel/status requests. + // request is output: this the request recevied from consumer + bool RetrieveNotificationData(swss::NotificationConsumer &consumer, + NotificationRequest &request); + NotificationResponse HandleRebootRequest( + const std::string &jsonRebootRequest); + NotificationResponse HandleStatusRequest( + const std::string &jsonStatusRequest); + NotificationResponse HandleCancelRequest( + const std::string &jsonCancelRequest); + void SendNotificationResponse(const std::string key, + const swss::StatusCode code, + const std::string message); + + // Returns true if a reboot is allowed at this time given the current + // warm manager state and reboot type, and false otherwise. + bool RebootAllowed(const gnoi::system::RebootMethod rebMethod); + + void DoTask(swss::NotificationConsumer &consumer); + + void HandleRebootFinish(); + void HandleDone(); + + friend class RebootBETestWithoutStop; +}; + +} // namespace rebootbackend diff --git a/src/sonic-framework/tests/Makefile.am b/src/sonic-framework/tests/Makefile.am new file mode 100644 index 000000000000..37334fd0eff6 --- /dev/null +++ b/src/sonic-framework/tests/Makefile.am @@ -0,0 +1,57 @@ +AUTOMAKE_OPTIONS = subdir-objects +INCLUDES = -I $(top_srcdir) -I $(top_srcdir)/rebootbackend -I$(top_srcdir)/build/gen -I$(top_srcdir)/build/gen/github.com/openconfig/gnoi -I ../../sonic-swss-common/common + +TESTS = tests tests_asan tests_tsan tests_usan + +noinst_PROGRAMS = tests tests_asan tests_tsan tests_usan + +if DEBUG +DBGFLAGS = -ggdb -DDEBUG +else +DBGFLAGS = -g -DNDEBUG +endif + +CFLAGS_GTEST = +LDADD_GTEST = -lgtest -lgtest_main -lgmock -lgmock_main +CFLAGS_COVERAGE = --coverage -fprofile-arcs -ftest-coverage +LDADD_COVERAGE = -lgcov +CFLAGS_ASAN = -fsanitize=address +CFLAGS_TSAN = -fsanitize=thread +CFLAGS_USAN = -fsanitize=undefined + +testsdir = /usr/include + +tests_HEADERS = /usr/include/gmock/gmock.h + +tests_SOURCES = rebootbe_test.cpp \ + $(top_srcdir)/rebootbackend/rebootbe.cpp \ + reboot_thread_test.cpp \ + $(top_srcdir)/rebootbackend/reboot_thread.cpp \ + test_main.cpp + +tests_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_COVERAGE) $(CFLAGS_SAI) +tests_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_COVERAGE) $(CFLAGS_SAI) +tests_LDADD = $(LDADD_GTEST) $(LDADD_COVERAGE) -lswsscommon -lpthread \ + -lprotobuf $(top_srcdir)/build/gen/librebootgnoi.la + +tests_asan_SOURCES = $(tests_SOURCES) +tests_asan_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_ASAN) $(CFLAGS_SAI) +tests_asan_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_ASAN) $(CFLAGS_SAI) +tests_asan_LDFLAGS = $(CFLAGS_ASAN) +tests_asan_LDADD = $(LDADD_GTEST) -lswsscommon -lpthread -lprotobuf \ + $(top_srcdir)/build/gen/librebootgnoi.la + + +tests_tsan_SOURCES = $(tests_SOURCES) +tests_tsan_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_TSAN) $(CFLAGS_SAI) +tests_tsan_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_TSAN) $(CFLAGS_SAI) +tests_tsan_LDFLAGS = $(CFLAGS_TSAN) +tests_tsan_LDADD = $(LDADD_GTEST) -lswsscommon -lpthread -lprotobuf \ + $(top_srcdir)/build/gen/librebootgnoi.la + +tests_usan_SOURCES = $(tests_SOURCES) +tests_usan_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_USAN) $(CFLAGS_SAI) +tests_usan_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_USAN) $(CFLAGS_SAI) +tests_usan_LDFLAGS = $(CFLAGS_USAN) +tests_usan_LDADD = $(LDADD_GTEST) -lswsscommon -lpthread -lprotobuf \ + $(top_srcdir)/build/gen/librebootgnoi.la diff --git a/src/sonic-framework/tests/mock_reboot_interfaces.h b/src/sonic-framework/tests/mock_reboot_interfaces.h new file mode 100644 index 000000000000..f6833b42c0b0 --- /dev/null +++ b/src/sonic-framework/tests/mock_reboot_interfaces.h @@ -0,0 +1,18 @@ +#pragma once +#include + +#include "reboot_interfaces.h" +#include "selectableevent.h" +#include "system/system.pb.h" + +namespace rebootbackend { + +class MockDbusInterface : public DbusInterface { + public: + MOCK_METHOD(DbusInterface::DbusResponse, Reboot, (const std::string &), + (override)); + MOCK_METHOD(DbusInterface::DbusResponse, RebootStatus, (const std::string &), + (override)); +}; + +} // namespace rebootbackend diff --git a/src/sonic-framework/tests/reboot_thread_test.cpp b/src/sonic-framework/tests/reboot_thread_test.cpp new file mode 100644 index 000000000000..b040914f79ef --- /dev/null +++ b/src/sonic-framework/tests/reboot_thread_test.cpp @@ -0,0 +1,286 @@ +#include "reboot_thread.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include "mock_reboot_interfaces.h" +#include "reboot_common.h" +#include "reboot_interfaces.h" +#include "select.h" +#include "selectableevent.h" +#include "status_code_util.h" +#include "system/system.pb.h" +#include "timestamp.h" + +namespace rebootbackend { + +namespace gpu = ::google::protobuf::util; +using Progress = ::rebootbackend::RebootThread::Progress; +using RebootThread = ::rebootbackend::RebootThread; +using ::testing::_; +using ::testing::ExplainMatchResult; +using ::testing::HasSubstr; +using ::testing::NiceMock; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::StrictMock; + +MATCHER_P2(IsStatus, status, message, "") { + return (arg.status().status() == status && + ExplainMatchResult(message, arg.status().message(), result_listener)); +} + +class RebootStatusTest : public ::testing::Test { + protected: + RebootStatusTest() : m_status() {} + ThreadStatus m_status; +}; + +TEST_F(RebootStatusTest, TestInit) { + gnoi::system::RebootStatusResponse response = m_status.get_response(); + + EXPECT_FALSE(response.active()); + EXPECT_THAT(response.reason(), StrEq("")); + EXPECT_EQ(response.count(), 0); + EXPECT_EQ(response.method(), gnoi::system::RebootMethod::UNKNOWN); + EXPECT_EQ( + response.status().status(), + gnoi::system::RebootStatus_Status::RebootStatus_Status_STATUS_UNKNOWN); + EXPECT_THAT(response.status().message(), StrEq("")); + + EXPECT_FALSE(m_status.get_active()); +} + +TEST_F(RebootStatusTest, TestGetStatus) { + std::chrono::nanoseconds curr_ns = + std::chrono::high_resolution_clock::now().time_since_epoch(); + + m_status.set_start_status(gnoi::system::RebootMethod::COLD, "reboot because"); + + gnoi::system::RebootStatusResponse response = m_status.get_response(); + EXPECT_EQ( + response.status().status(), + gnoi::system::RebootStatus_Status::RebootStatus_Status_STATUS_UNKNOWN); + + m_status.set_completed_status( + gnoi::system::RebootStatus_Status::RebootStatus_Status_STATUS_SUCCESS, + "anything"); + + response = m_status.get_response(); + + // message should be empty while reboot is active + EXPECT_THAT(response.status().message(), StrEq("")); + + uint64_t reboot_ns = response.when(); + EXPECT_TRUE(reboot_ns > (uint64_t)curr_ns.count()); + + m_status.set_inactive(); + response = m_status.get_response(); + EXPECT_THAT(response.status().message(), StrEq("anything")); + EXPECT_EQ( + response.status().status(), + gnoi::system::RebootStatus_Status::RebootStatus_Status_STATUS_SUCCESS); + EXPECT_EQ(0, response.when()); +} + +class RebootThreadTest : public ::testing::Test { + protected: + RebootThreadTest() + : m_dbus_interface(), + m_db("STATE_DB", 0), + m_config_db("CONFIG_DB", 0), + m_reboot_thread(m_dbus_interface, m_finished) { + sigterm_requested = false; + } + + void overwrite_reboot_timeout(uint32_t timeout_seconds) { + m_reboot_thread.m_reboot_timeout = timeout_seconds; + } + + gnoi::system::RebootStatusResponse get_response(void) { + return m_reboot_thread.m_status.get_response(); + } + + void set_start_status(const gnoi::system::RebootMethod &method, + const std::string &reason) { + return m_reboot_thread.m_status.set_start_status(method, reason); + } + + void set_completed_status(const gnoi::system::RebootStatus_Status &status, + const std::string &message) { + return m_reboot_thread.m_status.set_completed_status(status, message); + } + + void force_inactive(void) { return m_reboot_thread.m_status.set_inactive(); } + + void force_active(void) { return m_reboot_thread.m_status.set_inactive(); } + + void do_reboot(void) { return m_reboot_thread.do_reboot(); } + + void wait_for_finish(swss::Select &s, swss::SelectableEvent &finished, + long timeout_seconds) { + swss::Selectable *sel; + + int ret = s.select(&sel, timeout_seconds * 1000); + EXPECT_EQ(ret, swss::Select::OBJECT); + EXPECT_EQ(sel, &finished); + } + + Progress wait_for_platform_reboot(swss::Select &s) { + return m_reboot_thread.wait_for_platform_reboot(s); + } + + swss::SelectableEvent &return_m_stop_reference() { + return m_reboot_thread.m_stop; + } + + swss::DBConnector m_db; + swss::DBConnector m_config_db; + NiceMock m_dbus_interface; + swss::SelectableEvent m_finished; + RebootThread m_reboot_thread; +}; + +MATCHER_P2(Status, status, message, "") { + return (arg.status().status() == status && arg.status().message() == message); +} + +TEST_F(RebootThreadTest, TestStop) { + EXPECT_CALL(m_dbus_interface, Reboot(_)) + .Times(1) + .WillOnce(Return(DbusInterface::DbusResponse{ + DbusInterface::DbusStatus::DBUS_SUCCESS, ""})); + gnoi::system::RebootRequest request; + request.set_method(gnoi::system::RebootMethod::COLD); + overwrite_reboot_timeout(2); + m_reboot_thread.Start(request); + m_reboot_thread.Stop(); + m_reboot_thread.Join(); + gnoi::system::RebootStatusResponse response = m_reboot_thread.GetResponse(); + EXPECT_THAT( + response, + IsStatus( + gnoi::system::RebootStatus_Status::RebootStatus_Status_STATUS_UNKNOWN, + "")); +} + +TEST_F(RebootThreadTest, TestCleanExit) { + EXPECT_CALL(m_dbus_interface, Reboot(_)) + .Times(1) + .WillOnce(Return(DbusInterface::DbusResponse{ + DbusInterface::DbusStatus::DBUS_SUCCESS, ""})); + + overwrite_reboot_timeout(1); + + swss::Select s; + s.addSelectable(&m_finished); + + gnoi::system::RebootRequest request; + request.set_method(gnoi::system::RebootMethod::COLD); + request.set_message("time to reboot"); + m_reboot_thread.Start(request); + wait_for_finish(s, m_finished, 5); + + // Status should be active until we call join + gnoi::system::RebootStatusResponse response = get_response(); + EXPECT_TRUE(response.active()); + EXPECT_THAT(response.reason(), StrEq("time to reboot")); + EXPECT_EQ(response.count(), 1); + + EXPECT_THAT(response.status().message(), StrEq("")); + + m_reboot_thread.Join(); + + response = get_response(); + EXPECT_FALSE(response.active()); + EXPECT_THAT(response.status().message(), StrEq("platform failed to reboot")); +} + +TEST_F(RebootThreadTest, TestJoinWithoutStart) { + bool ret = m_reboot_thread.Join(); + EXPECT_FALSE(ret); +} + +// Call Start a second time while first thread is still executing. +TEST_F(RebootThreadTest, TestStartWhileRunning) { + EXPECT_CALL(m_dbus_interface, Reboot(_)) + .Times(1) + .WillOnce(Return(DbusInterface::DbusResponse{ + DbusInterface::DbusStatus::DBUS_SUCCESS, ""})); + + overwrite_reboot_timeout(2); + + gnoi::system::RebootRequest request; + request.set_method(gnoi::system::RebootMethod::COLD); + request.set_message("time to reboot"); + m_reboot_thread.Start(request); + + // First thread is still running ... + NotificationResponse response = m_reboot_thread.Start(request); + EXPECT_EQ(response.status, swss::StatusCode::SWSS_RC_IN_USE); + EXPECT_THAT(response.json_string, + StrEq("RebootThread: can't Start while active")); + + bool ret = m_reboot_thread.Join(); + EXPECT_TRUE(ret); +} + +// Call Start a second time after first thread completed +// but before first thread was joined. +// Second start should fail. +TEST_F(RebootThreadTest, TestStartWithoutJoin) { + EXPECT_CALL(m_dbus_interface, Reboot(_)) + .Times(1) + .WillOnce(Return(DbusInterface::DbusResponse{ + DbusInterface::DbusStatus::DBUS_SUCCESS, ""})); + + overwrite_reboot_timeout(1); + + swss::Select s; + s.addSelectable(&m_finished); + + gnoi::system::RebootRequest request; + request.set_method(gnoi::system::RebootMethod::COLD); + request.set_message("time to reboot"); + m_reboot_thread.Start(request); + wait_for_finish(s, m_finished, 3); + + // First thread has stopped: we need to join before + // restart will succeed + NotificationResponse response = m_reboot_thread.Start(request); + EXPECT_EQ(response.status, swss::StatusCode::SWSS_RC_IN_USE); + + // This should join the first start. + bool ret = m_reboot_thread.Join(); + EXPECT_TRUE(ret); +} + +TEST_F(RebootThreadTest, TestUnsupportedRebootType) { + gnoi::system::RebootRequest request; + request.set_method(gnoi::system::RebootMethod::POWERDOWN); + + NotificationResponse response = m_reboot_thread.Start(request); + EXPECT_EQ(response.status, swss::StatusCode::SWSS_RC_INVALID_PARAM); + EXPECT_EQ(response.json_string, + "RebootThread: Start rx'd unsupported method"); +} + +TEST_F(RebootThreadTest, TestInvalidMethodfDoReboot) { + set_start_status(gnoi::system::RebootMethod::POWERUP, "time to reboot"); + do_reboot(); + force_inactive(); + gnoi::system::RebootStatusResponse response = m_reboot_thread.GetResponse(); + EXPECT_THAT( + response, + IsStatus( + gnoi::system::RebootStatus_Status::RebootStatus_Status_STATUS_UNKNOWN, + "")); +} + +} // namespace rebootbackend diff --git a/src/sonic-framework/tests/rebootbe_test.cpp b/src/sonic-framework/tests/rebootbe_test.cpp new file mode 100644 index 000000000000..0ac1460faa7b --- /dev/null +++ b/src/sonic-framework/tests/rebootbe_test.cpp @@ -0,0 +1,374 @@ +#include "rebootbe.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "mock_reboot_interfaces.h" +#include "reboot_common.h" +#include "select.h" +#include "status_code_util.h" +#include "system/system.pb.h" +#include "timestamp.h" + +namespace rebootbackend { + +#define ONE_SECOND (1) +#define TWO_SECONDS (2) +#define TENTH_SECOND_MS (100) +#define SELECT_TIMEOUT_250_MS (250) + +namespace gpu = ::google::protobuf::util; +using namespace gnoi::system; + +using ::testing::_; +using ::testing::AllOf; +using ::testing::AtLeast; +using ::testing::ExplainMatchResult; +using ::testing::HasSubstr; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::NiceMock; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::StrictMock; + +MATCHER_P2(IsStatus, status, message, "") { + return (arg.status().status() == status && + ExplainMatchResult(message, arg.status().message(), result_listener)); +} + +MATCHER_P3(ActiveCountMethod, active, count, method, "") { + return (arg.active() == active && arg.count() == (uint32_t)count && + arg.method() == method); +} + +class RebootBETestWithoutStop : public ::testing::Test { + protected: + RebootBETestWithoutStop() + : m_dbus_interface(), + m_db("STATE_DB", 0), + m_config_db("CONFIG_DB", 0), + m_rebootbeRequestChannel(&m_db, REBOOT_REQUEST_NOTIFICATION_CHANNEL), + m_rebootbeReponseChannel(&m_db, REBOOT_RESPONSE_NOTIFICATION_CHANNEL), + m_rebootbe(m_dbus_interface) { + sigterm_requested = false; + + m_s.addSelectable(&m_rebootbeReponseChannel); + + // Make the tests log to stdout, instead of syslog. + swss::Table logging_table(&m_config_db, CFG_LOGGER_TABLE_NAME); + logging_table.hset("rebootbackend", swss::DAEMON_LOGOUTPUT, "STDOUT"); + swss::Logger::restartLogger(); + } + virtual ~RebootBETestWithoutStop() = default; + + void start_rebootbe() { + m_rebootbe_thread = + std::make_unique(&RebootBE::Start, &m_rebootbe); + } + + void set_mock_defaults() { + ON_CALL(m_dbus_interface, Reboot(_)) + .WillByDefault(Return(DbusInterface::DbusResponse{ + DbusInterface::DbusStatus::DBUS_SUCCESS, ""})); + } + + void overwrite_reboot_timeout(uint32_t timeout_seconds) { + m_rebootbe.m_RebootThread.m_reboot_timeout = timeout_seconds; + } + + void send_stop_reboot_thread() { m_rebootbe.m_RebootThread.Stop(); } + + void SendRebootRequest(const std::string &op, const std::string &data, + const std::string &field, const std::string &value) { + std::vector values; + values.push_back(swss::FieldValueTuple{field, value}); + + m_rebootbeRequestChannel.send(op, data, values); + } + + void SendRebootViaProto(RebootRequest &request) { + std::string json_string; + gpu::MessageToJsonString(request, &json_string); + + SendRebootRequest("Reboot", "StatusCode", DATA_TUPLE_KEY, json_string); + } + + void SendRebootStatusRequest(void) { + SendRebootRequest("RebootStatus", "StatusCode", DATA_TUPLE_KEY, + "json status request"); + } + + void start_reboot_via_rpc( + RebootRequest &request, + swss::StatusCode expected_result = swss::StatusCode::SWSS_RC_SUCCESS) { + SendRebootViaProto(request); + while (true) { + int ret; + swss::Selectable *sel; + ret = m_s.select(&sel, SELECT_TIMEOUT_250_MS); + if (ret != swss::Select::OBJECT) continue; + if (sel != &m_rebootbeReponseChannel) continue; + break; + } + std::string op, data; + std::vector ret_values; + m_rebootbeReponseChannel.pop(op, data, ret_values); + + EXPECT_THAT(op, StrEq("Reboot")); + EXPECT_THAT(data, StrEq(swss::statusCodeToStr(expected_result))); + } + + gnoi::system::RebootStatusResponse do_reboot_status_rpc() { + SendRebootStatusRequest(); + while (true) { + int ret; + swss::Selectable *sel; + ret = m_s.select(&sel, SELECT_TIMEOUT_250_MS); + if (ret != swss::Select::OBJECT) continue; + if (sel != &m_rebootbeReponseChannel) continue; + break; + } + std::string op, data; + std::vector ret_values; + m_rebootbeReponseChannel.pop(op, data, ret_values); + + EXPECT_THAT(op, StrEq("RebootStatus")); + EXPECT_EQ(data, swss::statusCodeToStr(swss::StatusCode::SWSS_RC_SUCCESS)); + + std::string json_response; + for (auto &fv : ret_values) { + if (DATA_TUPLE_KEY == fvField(fv)) { + json_response = fvValue(fv); + } + } + gnoi::system::RebootStatusResponse response; + gpu::JsonStringToMessage(json_response, &response); + return response; + } + + void GetNotificationResponse(swss::NotificationConsumer &consumer, + std::string &op, std::string &data, + std::vector &values) { + swss::Select s; + s.addSelectable(&consumer); + swss::Selectable *sel; + s.select(&sel, SELECT_TIMEOUT_250_MS); + + consumer.pop(op, data, values); + } + + NotificationResponse handle_reboot_request(std::string &json_request) { + return m_rebootbe.HandleRebootRequest(json_request); + } + + // Mock interfaces. + NiceMock m_dbus_interface; + + // DB connectors + swss::DBConnector m_db; + swss::DBConnector m_config_db; + + // Reboot thread signaling. + swss::NotificationProducer m_rebootbeRequestChannel; + swss::Select m_s; + swss::NotificationConsumer m_rebootbeReponseChannel; + + // Module under test. + std::unique_ptr m_rebootbe_thread; + RebootBE m_rebootbe; +}; + +class RebootBETest : public RebootBETestWithoutStop { + protected: + ~RebootBETest() { + m_rebootbe.Stop(); + m_rebootbe_thread->join(); + } +}; + +// Test fixture to skip through the startup sequence into the main loop. +// Param indicates if RebootBE should be initialized into a state where the +// system came up in warmboot. +class RebootBEAutoStartTest : public RebootBETest, + public ::testing::WithParamInterface { + protected: + RebootBEAutoStartTest() { + start_rebootbe(); + + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + EXPECT_EQ(m_rebootbe.GetCurrentStatus(), RebootBE::RebManagerStatus::IDLE); + } +}; + +// Normal operation testing. +TEST_P(RebootBEAutoStartTest, NonExistentMessage) { + swss::NotificationConsumer consumer(&m_db, + REBOOT_RESPONSE_NOTIFICATION_CHANNEL); + + // No "MESSAGE" in field/values + SendRebootRequest("Reboot", "StatusCode", "field1", "field1_value"); + EXPECT_EQ(m_rebootbe.GetCurrentStatus(), RebootBE::RebManagerStatus::IDLE); + + std::string op, data; + std::vector ret_values; + GetNotificationResponse(consumer, op, data, ret_values); + + EXPECT_THAT(op, StrEq("Reboot")); + EXPECT_THAT( + data, + StrEq(swss::statusCodeToStr(swss::StatusCode::SWSS_RC_INVALID_PARAM))); +} + +TEST_P(RebootBEAutoStartTest, TestCancelReboot) { + swss::NotificationConsumer consumer(&m_db, + REBOOT_RESPONSE_NOTIFICATION_CHANNEL); + + SendRebootRequest("CancelReboot", "StatusCode", DATA_TUPLE_KEY, + "json cancelreboot request"); + EXPECT_EQ(m_rebootbe.GetCurrentStatus(), RebootBE::RebManagerStatus::IDLE); + + std::string op, data; + std::vector ret_values; + GetNotificationResponse(consumer, op, data, ret_values); + + EXPECT_THAT(op, StrEq("CancelReboot")); + EXPECT_THAT( + data, + StrEq(swss::statusCodeToStr(swss::StatusCode::SWSS_RC_UNIMPLEMENTED))); +} + +TEST_P(RebootBEAutoStartTest, TestUnrecognizedOP) { + swss::NotificationConsumer consumer(&m_db, + REBOOT_RESPONSE_NOTIFICATION_CHANNEL); + + SendRebootRequest("NonOp", "StatusCode", DATA_TUPLE_KEY, "invalid op code"); + EXPECT_EQ(m_rebootbe.GetCurrentStatus(), RebootBE::RebManagerStatus::IDLE); + + std::string op, data; + std::vector ret_values; + GetNotificationResponse(consumer, op, data, ret_values); + + EXPECT_THAT(op, StrEq("NonOp")); + EXPECT_THAT( + data, + StrEq(swss::statusCodeToStr(swss::StatusCode::SWSS_RC_INVALID_PARAM))); +} + +TEST_P(RebootBEAutoStartTest, TestColdRebootDbusToCompletion) { + DbusInterface::DbusResponse dbus_response{ + DbusInterface::DbusStatus::DBUS_SUCCESS, ""}; + EXPECT_CALL(m_dbus_interface, Reboot(_)) + .Times(3) + .WillRepeatedly(Return(dbus_response)); + + overwrite_reboot_timeout(1); + RebootRequest request; + request.set_method(RebootMethod::COLD); + start_reboot_via_rpc(request); + + std::this_thread::sleep_for(std::chrono::milliseconds(TENTH_SECOND_MS)); + EXPECT_EQ(m_rebootbe.GetCurrentStatus(), + RebootBE::RebManagerStatus::COLD_REBOOT_IN_PROGRESS); + sleep(TWO_SECONDS); + + EXPECT_EQ(m_rebootbe.GetCurrentStatus(), RebootBE::RebManagerStatus::IDLE); + gnoi::system::RebootStatusResponse response = do_reboot_status_rpc(); + EXPECT_THAT(response, ActiveCountMethod(false, 1, RebootMethod::COLD)); + EXPECT_THAT(response, + IsStatus(RebootStatus_Status::RebootStatus_Status_STATUS_FAILURE, + "platform failed to reboot")); + + start_reboot_via_rpc(request); + sleep(TWO_SECONDS); + + start_reboot_via_rpc(request); + sleep(TWO_SECONDS); + + response = do_reboot_status_rpc(); + // Verifiy count is 3 after three reboot attempts. + EXPECT_THAT(response, ActiveCountMethod(false, 3, RebootMethod::COLD)); + EXPECT_THAT(response, + IsStatus(RebootStatus_Status::RebootStatus_Status_STATUS_FAILURE, + "platform failed to reboot")); +} + +TEST_P(RebootBEAutoStartTest, TestColdBootSigterm) { + sigterm_requested = true; + set_mock_defaults(); + overwrite_reboot_timeout(1); + + RebootRequest request; + request.set_method(RebootMethod::COLD); + start_reboot_via_rpc(request); + + sleep(ONE_SECOND); + + EXPECT_EQ(m_rebootbe.GetCurrentStatus(), RebootBE::RebManagerStatus::IDLE); + gnoi::system::RebootStatusResponse second_resp = do_reboot_status_rpc(); + EXPECT_THAT(second_resp, ActiveCountMethod(false, 1, RebootMethod::COLD)); + EXPECT_THAT( + second_resp, + IsStatus(RebootStatus_Status::RebootStatus_Status_STATUS_UNKNOWN, "")); +} + +TEST_P(RebootBEAutoStartTest, TestColdBootDbusError) { + // Return FAIL from dbus reboot call. + DbusInterface::DbusResponse dbus_response{ + DbusInterface::DbusStatus::DBUS_FAIL, "dbus reboot failed"}; + EXPECT_CALL(m_dbus_interface, Reboot(_)) + .Times(1) + .WillOnce(Return(dbus_response)); + + RebootRequest request; + request.set_method(RebootMethod::COLD); + start_reboot_via_rpc(request); + + sleep(TWO_SECONDS); + + EXPECT_EQ(m_rebootbe.GetCurrentStatus(), RebootBE::RebManagerStatus::IDLE); + gnoi::system::RebootStatusResponse second_resp = do_reboot_status_rpc(); + EXPECT_THAT(second_resp, ActiveCountMethod(false, 1, RebootMethod::COLD)); + EXPECT_THAT(second_resp, + IsStatus(RebootStatus_Status::RebootStatus_Status_STATUS_FAILURE, + "dbus reboot failed")); +} + +TEST_P(RebootBEAutoStartTest, TestStopDuringColdBoot) { + set_mock_defaults(); + + RebootRequest request; + request.set_method(RebootMethod::COLD); + start_reboot_via_rpc(request); + std::this_thread::sleep_for(std::chrono::milliseconds(TENTH_SECOND_MS)); + EXPECT_EQ(m_rebootbe.GetCurrentStatus(), + RebootBE::RebManagerStatus::COLD_REBOOT_IN_PROGRESS); + + send_stop_reboot_thread(); + std::this_thread::sleep_for(std::chrono::milliseconds(TENTH_SECOND_MS)); + EXPECT_EQ(m_rebootbe.GetCurrentStatus(), RebootBE::RebManagerStatus::IDLE); + + gnoi::system::RebootStatusResponse response = do_reboot_status_rpc(); + EXPECT_THAT(response, ActiveCountMethod(false, 1, RebootMethod::COLD)); + EXPECT_THAT( + response, + IsStatus(RebootStatus_Status::RebootStatus_Status_STATUS_UNKNOWN, "")); +} + +TEST_P(RebootBEAutoStartTest, TestInvalidJsonRebootRequest) { + std::string json_request = "abcd"; + NotificationResponse response = handle_reboot_request(json_request); + EXPECT_EQ(swss::StatusCode::SWSS_RC_INTERNAL, response.status); +} + +INSTANTIATE_TEST_SUITE_P(TestWithStartupWarmbootEnabledState, + RebootBEAutoStartTest, testing::Values(true, false)); + +} // namespace rebootbackend diff --git a/src/sonic-framework/tests/test_main.cpp b/src/sonic-framework/tests/test_main.cpp new file mode 100644 index 000000000000..693d88f181ed --- /dev/null +++ b/src/sonic-framework/tests/test_main.cpp @@ -0,0 +1,7 @@ + +#include "gtest/gtest.h" + +int main(int argc, char* argv[]) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file