From: Juha Kosonen Date: Wed, 8 Jan 2020 17:05:54 +0000 (+0000) Subject: Merge "Replace logging with services layer" X-Git-Tag: 3.0.0~16 X-Git-Url: https://gerrit.akraino.org/r/gitweb?p=validation.git;a=commitdiff_plain;h=817ef6bf62837a2a8a2507b0e6e4c9f7ff372240;hp=81ee38efa20715143ffc46ec84c872cdf7901d56 Merge "Replace logging with services layer" --- diff --git a/.coafile b/.coafile index 50e636c..fd4a0cc 100644 --- a/.coafile +++ b/.coafile @@ -1,6 +1,7 @@ [all] ignore = .tox/**, .py35/**, + .py36/**, .git/**, .gitignore, .gitreview, diff --git a/.gitignore b/.gitignore index 2e45b8c..92f8161 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +bluval/__pycache__ docker/manifest-tool *.sw? diff --git a/bluval/Dockerfile b/bluval/Dockerfile new file mode 100644 index 0000000..7125c73 --- /dev/null +++ b/bluval/Dockerfile @@ -0,0 +1,36 @@ +############################################################################## +# Copyright (c) 2019 AT&T, ENEA Nokia and others # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you maynot use this file except in compliance with the License. # +# # +# You may obtain a copy of the License at # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################## + +# ref: https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#use-multi-stage-builds +FROM python:3.6-alpine3.10 + +RUN apk --no-cache add --update \ + gcc \ + git \ + docker-cli \ + libc6-compat \ + libc-dev \ + libffi \ + libffi-dev \ + make \ + openssl-dev + +# Build binaries +RUN git clone https://gerrit.akraino.org/r/validation /opt/akraino/validation +RUN pip3 install -r /opt/akraino/validation/bluval/requirements.txt + +WORKDIR /opt/akraino/validation +ENTRYPOINT ["python3", "-B", "bluval/blucon.py"] diff --git a/bluval/blucon.py b/bluval/blucon.py index 7fc8002..0d5d7ca 100644 --- a/bluval/blucon.py +++ b/bluval/blucon.py @@ -57,11 +57,11 @@ def invoke_docker(bluprint, layer): """Start docker container for given layer """ volume_list = get_volumes('common') + get_volumes(layer) - cmd = ("docker run" + volume_list + _SUBNET + + cmd = ("docker run --rm" + volume_list + _SUBNET + " akraino/validation:{0}-latest" " /bin/sh -c" " 'cd /opt/akraino/validation " - "&& python bluval/bluval.py -l {0} {1} {2}'" + "&& python -B bluval/bluval.py -l {0} {1} {2}'" .format(layer, ("-o" if _OPTIONAL_ALSO else ""), bluprint)) args = [cmd] diff --git a/bluval/blucon.sh b/bluval/blucon.sh new file mode 100755 index 0000000..afe54db --- /dev/null +++ b/bluval/blucon.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +############################################################################## +# Copyright (c) 2019 AT&T, ENEA Nokia and others # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you maynot use this file except in compliance with the License. # +# # +# You may obtain a copy of the License at # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################## + +if [ -z "$AKRAINO_HOME" ] +then + echo "AKRAINO_HOME not available. Setting..." + this_file="$(readlink -f $0)" + bluval_dir="$(dirname $this_file)" + validation_dir="$(dirname $bluval_dir)" + parent_dir="$(dirname $validation_dir)" + export AKRAINO_HOME="$parent_dir" +fi +echo "AKRAINO_HOME=$AKRAINO_HOME" + +if [ "$#" -eq 0 ] +then + echo 'Usage: sh blucon.sh [OPTIONS] BLUEPRINT + + Invokes blucon.py and passes parameters as it is. + You can pass all the parameters blucon.py accepts, + and as of now here is the list + + Options: + -l, --layer TEXT + -n, --network TEXT + -o, --optional_also + --help Show this message and exit.' + + exit 1 +fi + +echo "Building docker image" +image_tag=$( (git branch || echo "* local") | grep "^\*" | awk '{print $2}') +docker build -t akraino/validation:blucon-$image_tag $AKRAINO_HOME/validation/bluval + +set -x + +docker run --rm \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v $AKRAINO_HOME/results:/opt/akraino/results \ + -v $AKRAINO_HOME/validation:/opt/akraino/validation \ + akraino/validation:blucon-$image_tag "$@" diff --git a/bluval/bluval-kni.yaml b/bluval/bluval-kni.yaml new file mode 100644 index 0000000..75f941b --- /dev/null +++ b/bluval/bluval-kni.yaml @@ -0,0 +1,39 @@ +--- +############################################################################## +# Copyright (c) 2019 Red Hat. # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); you may # +# not use this file except in compliance with the License. # +# # +# You may obtain a copy of the License at # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################## +blueprint: + name: kni + layers: + - hardware + - os + - k8s + # Any hardware some basic tests + hardware: + - + name: bios_version + skip: "False" + what: bios_version + os: + - + name: ltp + what: ltp + - + name: cyclictest + what: cyclictest + k8s: + - + name: conformance + what: conformance diff --git a/bluval/bluval-rec.yaml b/bluval/bluval-rec.yaml index b611dd6..8bc3203 100644 --- a/bluval/bluval-rec.yaml +++ b/bluval/bluval-rec.yaml @@ -35,6 +35,11 @@ blueprint: name: cyclictest what: cyclictest optional: "True" + - + name: lynis + what: lynis + optional: "True" + docker: &docker_base - name: docker_bench diff --git a/bluval/bluval-unicycle.yaml b/bluval/bluval-unicycle.yaml new file mode 100644 index 0000000..02e6b31 --- /dev/null +++ b/bluval/bluval-unicycle.yaml @@ -0,0 +1,52 @@ +--- +############################################################################## +# Copyright (c) 2019 AT&T Intellectual Property. # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); you may # +# not use this file except in compliance with the License. # +# # +# You may obtain a copy of the License at # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################## +blueprint: + name: unicycle + layers: + - hardware + - os + - docker + - k8s + # Any hardware some basic tests + hardware: &hardware_unicycle + - + name: hp_baremetal + what: hp_baremetal + os: &os_unicycle + - + name: ltp + what: ltp + - + name: cyclictest + what: cyclictest + optional: "True" + docker: &docker_unicycle + - + name: docker_bench + what: docker_bench + k8s: &k8s_unicycle + - + name: conformance + what: conformance + - + name: etcd_ha + what: etcd_ha + optional: "True" + openstack: &openstack_unicycle + - + name: tempest + what: tempest diff --git a/bluval/bluval.py b/bluval/bluval.py index feefa5e..c79c830 100644 --- a/bluval/bluval.py +++ b/bluval/bluval.py @@ -59,11 +59,12 @@ def run_testcase(testcase): variables_file = mypath.parents[1].joinpath("tests/variables.yaml") variables_dict = yaml.safe_load(variables_file.open()) variables_dict['log_path'] = str(results_path) - variables_file.write_text(str(variables_dict)) + variables_updated_file = mypath.parents[1].joinpath("tests/variables_updated.yaml") + variables_updated_file.write_text(str(variables_dict)) # run the test - args = ["robot", "-V", str(variables_file), "-d", - str(results_path), str(test_path)] + args = ["robot", "-V", str(variables_updated_file), "-d", str(results_path), + "-b", "debug.log", str(test_path)] print('Executing testcase {}'.format(name)) print('show_stopper {}'.format(show_stopper)) diff --git a/bluval/requirements.txt b/bluval/requirements.txt index bdd48f9..7784bcd 100644 --- a/bluval/requirements.txt +++ b/bluval/requirements.txt @@ -1,2 +1,7 @@ pyyaml click +robotframework +robotframework-httplibrary +robotframework-jsonlibrary +robotframework-requests +robotframework-sshlibrary diff --git a/bluval/volumes.yaml b/bluval/volumes.yaml index 0e062ea..6c48e65 100644 --- a/bluval/volumes.yaml +++ b/bluval/volumes.yaml @@ -23,7 +23,7 @@ volumes: # location of the ssh key to access the cluster - ssh_key_file: + ssh_key_dir: local: '' target: '/root/.ssh' # location of the k8s access files (config file, certificates, keys) @@ -42,6 +42,10 @@ volumes: results_dir: local: '' target: '/opt/akraino/results' + # location on where to store openrc file + openrc: + local: '' + target: '/root/openrc' # parameters that will be passed to the container at each layer layers: @@ -51,17 +55,21 @@ layers: - blueprint_dir - results_dir hardware: - - ssh_key_file + - ssh_key_dir os: - - ssh_key_file + - ssh_key_dir networking: - - ssh_key_file + - ssh_key_dir + docker: + - ssh_key_dir k8s: - - ssh_key_file + - ssh_key_dir - kube_config_dir k8s_networking: - - ssh_key_File + - ssh_key_dir - kube_config_dir + openstack: + - openrc sds: sdn: vim: diff --git a/docker/os/Dockerfile b/docker/os/Dockerfile index 19947ac..58bed23 100644 --- a/docker/os/Dockerfile +++ b/docker/os/Dockerfile @@ -49,12 +49,16 @@ RUN make autotools && \ make -j $(getconf _NPROCESSORS_ONLN) 2>&1 | tee ../build-log.txt && \ make install 2>&1 | tee ../install-log.txt RUN tar czvf /opt/akraino/ltp.tar.gz /opt/ltp +WORKDIR /root/src +RUN git clone https://github.com/CISOfy/lynis && tar czvf /opt/akraino/lynis-remote.tar.gz ./lynis # Copy binaries into the final container and install robot framework FROM ubuntu:18.04 COPY --from=build /wheels /wheels COPY --from=build /opt/akraino/validation /opt/akraino/validation COPY --from=build /opt/akraino/ltp.tar.gz /opt/akraino/ltp.tar.gz +COPY --from=build /opt/akraino/lynis-remote.tar.gz /opt/akraino/lynis-remote.tar.gz + RUN apt-get update && apt-get -y install \ python3-pip python3.6 && \ diff --git a/tests/docker/docker_bench/docker_bench.resource b/tests/docker/docker_bench/docker_bench.resource index 674087f..8a93e3d 100644 --- a/tests/docker/docker_bench/docker_bench.resource +++ b/tests/docker/docker_bench/docker_bench.resource @@ -18,6 +18,7 @@ *** Settings *** Library BuiltIn +Library Collections Library OperatingSystem Library Process Library SSHLibrary @@ -43,12 +44,18 @@ Upload Test Software To Nodes Copy Test Software To All Nodes Run Test Software On Nodes - :FOR ${node} IN @{nodes} - \ Execute Command ssh ${SSH_OPTS} ${node} "cd ${NODEDIR}; sudo ./docker-bench-security.sh -b -l bench.log" - \ Execute Command scp ${SSH_OPTS} ${node}:${NODEDIR}/bench.log ${DESTDIR}/docker-bench-${node}.log - \ Execute Command scp ${SSH_OPTS} ${node}:${NODEDIR}/bench.log.json ${DESTDIR}/docker-bench-${node}.json - \ SSHLibrary.Get File ${DESTDIR}/docker-bench-${node}.log ${REPORTDIR}/ - \ SSHLibrary.Get File ${DESTDIR}/docker-bench-${node}.json ${REPORTDIR}/ + FOR ${node} IN @{nodes} + Start Command ssh ${SSH_OPTS} ${node} "cd ${NODEDIR}; sudo ./docker-bench-security.sh -b -l bench.log" + END + @{tmp}= Copy List ${nodes} + Reverse List ${tmp} + FOR ${node} IN @{tmp} + Read Command Output return_stdout=False + Execute Command scp ${SSH_OPTS} ${node}:${NODEDIR}/bench.log ${DESTDIR}/docker-bench-${node}.log + Execute Command scp ${SSH_OPTS} ${node}:${NODEDIR}/bench.log.json ${DESTDIR}/docker-bench-${node}.json + SSHLibrary.Get File ${DESTDIR}/docker-bench-${node}.log ${REPORTDIR}/ + SSHLibrary.Get File ${DESTDIR}/docker-bench-${node}.json ${REPORTDIR}/ + END Get Node Addresses ${stdout}= Execute Command @@ -57,11 +64,13 @@ Get Node Addresses Set Test Variable @{nodes} Copy Test Software To All Nodes - :FOR ${node} IN @{nodes} - \ Execute Command ssh ${SSH_OPTS} ${node} "mkdir -p ${NODEDIR}" - \ Execute Command scp ${SSH_OPTS} -rp ${DESTDIR}/. ${node}:${NODEDIR} + FOR ${node} IN @{nodes} + Execute Command ssh ${SSH_OPTS} ${node} "mkdir -p ${NODEDIR}" + Execute Command scp ${SSH_OPTS} -rp ${DESTDIR}/. ${node}:${NODEDIR} + END Remove Test Software From Nodes - :FOR ${node} IN @{nodes} - \ Execute Command ssh ${SSH_OPTS} ${node} "rm -rf ${NODEDIR}" + FOR ${node} IN @{nodes} + Execute Command ssh ${SSH_OPTS} ${node} "rm -rf ${NODEDIR}" + END Execute Command rm -rf ${DESTDIR} diff --git a/tests/hardware/redfish/redfish.resource b/tests/hardware/redfish/redfish.resource index 433f9db..87516ed 100644 --- a/tests/hardware/redfish/redfish.resource +++ b/tests/hardware/redfish/redfish.resource @@ -17,6 +17,7 @@ *** Settings *** +Library Collections Library JSONLibrary Library OperatingSystem Library Process @@ -29,31 +30,64 @@ ${REDFISHDIR} ${TEMPDIR}/Redfish *** Keywords *** Update Config File - ${conf}= Load JSON From File ${REDFISHDIR}/framework_conf.json + [Arguments] ${config_file} + ${conf}= Load JSON From File ${config_file} ${conf}= Update Value To Json ${conf} $.password ${BMC_PASSWORD} ${conf}= Convert JSON To String ${conf} - Create File ${REDFISHDIR}/framework_conf.json ${conf} + Create File ${config_file} ${conf} Run Suite Against Target Node [Arguments] ${ip} - ${result}= Run Process python test_framework.py - ... --directory ${REDFISHDIR} + Start Process python test_framework.py + ... --directory ${REDFISHDIR}/${ip} ... --rhost ${ip} ... --user ${BMC_USER} ... --interpreter python ... --secure Always - ... cwd=${REDFISHDIR} - Copy Files ${REDFISHDIR}/reports/output-*/results*.json ${REPORTDIR}/${ip} - Copy Files ${REDFISHDIR}/output-*/*.html ${REPORTDIR}/${ip} - Should Be Equal As Integers ${result.rc} 0 - Should Not Contain ${result.stderr} FAILED${\n} (Failures= + ... cwd=${REDFISHDIR}/${ip} + ... alias=${ip} + Process Should Be Running -Run Usecase Checkers Suite +Install Usecase Checkers Test Suite + @{BMC_IP}= Remove Duplicates ${BMC_IP} + Set Test Variable @{BMC_IP} + FOR ${ip} IN @{BMC_IP} + Copy Directory /opt/akraino/Redfish-Test-Framework + ... ${REDFISHDIR}/${ip} + Copy Directory /opt/akraino/Redfish-Usecase-Checkers + ... ${REDFISHDIR}/${ip}/Redfish-Usecase-Checkers + Create Directory ${REDFISHDIR}/${ip}/reports + Update Config File ${REDFISHDIR}/${ip}/framework_conf.json + END + +Uninstall Test Suite + Remove Directory ${REDFISHDIR} recursive=True + +Start Suite + @{ips}= Create List + Set Test Variable @{ips} FOR ${ip} IN @{BMC_IP} - Copy Directory /opt/akraino/Redfish-Test-Framework ${REDFISHDIR} - Copy Directory /opt/akraino/Redfish-Usecase-Checkers ${REDFISHDIR}/Redfish-Usecase-Checkers - Create Directory ${REDFISHDIR}/reports - Update Config File Run Suite Against Target Node ${ip} - Remove Directory ${REDFISHDIR} recursive=True + Append To List ${ips} ${ip} + END + +Suite Finished + @{tmp}= Copy List ${ips} + FOR ${ip} IN @{tmp} + ${result}= Wait For Process ${ip} timeout=1ms + Continue For Loop If '${result}' == '${NONE}' + Remove Values From List ${ips} ${ip} + Copy Files ${REDFISHDIR}/${ip}/reports/output-*/results*.json ${REPORTDIR}/${ip} + Copy Files ${REDFISHDIR}/${ip}/output-*/*.html ${REPORTDIR}/${ip} + END + Should Be Empty ${ips} + +Wait Until Suite Finishes + Wait Until Keyword Succeeds 45m 15s Suite Finished + +Check Suite Results + FOR ${ip} IN @{BMC_IP} + ${result}= Get Process Result ${ip} + Should Be Equal As Integers ${result.rc} 0 + Should Not Contain ${result.stderr} FAILED${\n} (Failures= END diff --git a/tests/hardware/redfish/redfish.robot b/tests/hardware/redfish/redfish.robot index 0ff6f31..8ab0064 100644 --- a/tests/hardware/redfish/redfish.robot +++ b/tests/hardware/redfish/redfish.robot @@ -20,8 +20,14 @@ Documentation Redfish Test Framework is a tool and a model for organizing ... and running a set of Redfish interoperability test Resource redfish.resource +Test Teardown Run Keywords +... Terminate All Processes +... Uninstall Test Suite *** Test Cases *** Validate Common Use Cases - Run Usecase Checkers Suite + [Setup] Install Usecase Checkers Test Suite + Start Suite + Wait Until Suite Finishes + Check Suite Results diff --git a/tests/k8s/etcd_ha/etcd_ha.resource b/tests/k8s/etcd_ha/etcd_ha.resource index 1486b9e..c3b7e42 100644 --- a/tests/k8s/etcd_ha/etcd_ha.resource +++ b/tests/k8s/etcd_ha/etcd_ha.resource @@ -25,7 +25,7 @@ Library OperatingSystem *** Variables *** ${ETCD_VERSION} 3 -${SSH_KEYFILE} ${HOME}/.ssh/id_rsa +${SSH_KEYFILE} /root/.ssh/id_rsa *** Keywords *** Open Connection And Log In diff --git a/tests/os/cyclictest/cyclictest.robot b/tests/os/cyclictest/cyclictest.robot index f7b6709..d272c77 100644 --- a/tests/os/cyclictest/cyclictest.robot +++ b/tests/os/cyclictest/cyclictest.robot @@ -26,9 +26,7 @@ Suite Teardown Close All Connections *** Variables *** ${LOG} ${LOG_PATH}${/}${SUITE_NAME.replace(' ','_')}.log -#${USERNAME} mm747b -#${HOME} /home/${USERNAME} -#${HOST} aknode109 +${SSH_KEYFILE} /root/.ssh/id_rsa *** Test Cases *** @@ -41,5 +39,5 @@ Latency Test *** Keywords *** Open Connection And Log In Open Connection ${HOST} - Login With Public Key ${USERNAME} ${HOME}/.ssh/id_rsa + Login With Public Key ${USERNAME} ${SSH_KEYFILE} diff --git a/tests/os/lynis/lynis.robot b/tests/os/lynis/lynis.robot new file mode 100644 index 0000000..8d0069a --- /dev/null +++ b/tests/os/lynis/lynis.robot @@ -0,0 +1,64 @@ +############################################################################## +# Copyright (c) 2019 AT&T Intellectual Property. # +# Copyright (c) 2019 Nokia. # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you maynot use this file except in compliance with the License. # +# # +# You may obtain a copy of the License at # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################## + +*** Settings *** +Documentation Validation, Auditing Hardening Compliance +Library SSHLibrary +Library OperatingSystem +Library BuiltIn +Library Process +Suite Setup Run Keywords +... Open Connection And Log In +... Install Lynis +Test Teardown Download Logs +Suite Teardown Run Keywords +... Uninstall Lynis +... Close All Connections + +*** Variables *** +${FULL_SUITE} ${SUITE_NAME.replace(' ','_')} + +*** Test Cases *** +Run Lynis Audit System + [Documentation] Run Lynis + ${log} = Set Variable ${OUTPUT DIR}${/}${FULL_SUITE}.${TEST NAME.replace(' ','_')}.log + ${stdout} ${rc} = Execute Command cd lynis && sudo ./lynis audit system --quick return_rc=True + Append To File ${log} ${stdout}${\n} + Should Be Equal As Integers ${rc} 0 + + +*** Keywords *** +Open Connection And Log In + Open Connection ${HOST} + Login With Public Key ${USERNAME} ${SSH_KEYFILE} + +Install Lynis + [Documentation] Install Lynis + Put File /opt/akraino/lynis-remote.tar.gz + Execute Command tar xzf lynis-remote.tar.gz && sudo chown -R 0:0 lynis + +Uninstall Lynis + [Documentation] Uninstall Lynis + Execute Command rm lynis-remote.tar.gz + Execute Command rm -rf ~/lynis /var/log/lynis.log /var/log/lynis-report.dat sudo=True + +Download Logs + [Documentation] Downloading logs and removing them + SSHLibrary.Get File /var/log/lynis.log ${OUTPUT DIR}/lynis.log + Execute Command rm /var/log/lynis.log sudo=True + SSHLibrary.Get File /var/log/lynis-report.dat ${OUTPUT DIR}/lynis-report.dat + Execute Command rm /var/log/lynis-report.dat sudo=True \ No newline at end of file diff --git a/tests/variables.yaml b/tests/variables.yaml index 1da77ef..4a68a51 100644 --- a/tests/variables.yaml +++ b/tests/variables.yaml @@ -17,17 +17,25 @@ # This file provides variables required by robot testcases # This file can be passed to robot testcases as follows -# $ robot -v varables.yaml +# $ robot -V variables.yaml # # All keys are converted UPPERCASE before submitting to robot. YAML notation is # smallcase and Robot variables notation is UPPERCASE so industry is following # this. +# +# bluval.py takes this file and updates it to new file and passes to +# robot framework +# + ### Input variables cluster's master host -host: aknode109 # cluster's master host address -username: mm747b # user credentials -home: /home/mm747b # Public keys location -ssh_keyfile: ~/.ssh/id_rsa # Identity file for authentication +host: 172.28.17.206 # cluster's master host address +username: cloudadmin # login name to connect to cluster +ssh_keyfile: /root/.ssh/id_rsa # Identity file for authentication + +### bluval.py adds/modifies following, before passing to robot. +### while debugging from CLI user has to modify these +# log_path: /opt/akraino/results// ### Input variables for bios_version_dell.robot sysinfo: PowerEdge R740xd