Merged branch 'nginx_conf' into 'main', ignoring conflicts and keeping 'nginx_conf' changes
testing/og-dhcp-API/pipeline/head There was a failure building this commit Details

ogdhcp-log opengnsys_devel-0.0.17
Luis Gerardo Romero Garcia 2024-11-13 19:35:06 +01:00
parent 3ca7ddd20b
commit d58744c595
18 changed files with 2784 additions and 903 deletions

7
.gitignore vendored
View File

@ -31,3 +31,10 @@
.phpunit.result.cache
/phpunit.xml
###< symfony/phpunit-bridge ###
.venv
ogdhcp.code-workspace
output.xml
log.html
report.html
__pycache__

View File

@ -4,17 +4,16 @@
"minimum-stability": "stable",
"prefer-stable": true,
"require": {
"php": ">=7.2.0",
"php": ">=8.1",
"ext-ctype": "*",
"ext-iconv": "*",
"doctrine/annotations": "^1.6",
"doctrine/doctrine-bundle": "^2.0",
"doctrine/doctrine-migrations-bundle": "^3.0",
"doctrine/orm": "^2.7",
"nelmio/api-doc-bundle": "^4.9",
"phpdocumentor/reflection-docblock": "^5.0",
"phpstan/phpdoc-parser": "^0.4",
"zircote/swagger-php": "3.*",
"symfony/runtime": "5.*",
"symfony/asset": "5.*",
"symfony/console": "5.*",
"symfony/doctrine-messenger": "5.*",
@ -32,6 +31,7 @@
"symfony/process": "5.*",
"symfony/property-access": "5.*",
"symfony/property-info": "5.*",
"symfony/runtime": "5.*",
"symfony/security-bundle": "5.*",
"symfony/serializer": "5.*",
"symfony/string": "5.*",
@ -41,7 +41,8 @@
"symfony/web-link": "5.*",
"symfony/yaml": "5.*",
"twig/extra-bundle": "^2.12|^3.0",
"twig/twig": "^2.12|^3.0"
"twig/twig": "^2.12|^3.0",
"zircote/swagger-php": "3.*"
},
"require-dev": {
"phpunit/phpunit": "^8.5",

View File

@ -12,4 +12,6 @@ return [
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
App\DhcpBundle\DhcpBundle::class => ['all' => true],
Nelmio\ApiDocBundle\NelmioApiDocBundle::class => ['all' => true],
];

View File

@ -0,0 +1,48 @@
nelmio_api_doc:
documentation:
info:
title: OgDHCP API
description: OgDHCP API documentation
version: 1.0.0
components:
schemas:
Host:
type: object
properties:
hostname:
type: string
description: The hostname of the device
example: "pc11"
hw-address:
type: string
description: The hardware address (MAC)
example: "56:6f:c7:4f:00:4f"
ip-address:
type: string
description: The IP address assigned to the host
example: "192.168.5.11"
Subnet:
type: object
properties:
id:
type: integer
description: The ID of the subnet
subnet:
type: string
description: The name of the subnet
next-server:
type: string
description: The next server in the subnet
boot-file-name:
type: string
description: The boot file name for the subnet
reservations:
type: array
items:
$ref: '#/components/schemas/Host'
description: The reservations in the subnet
areas: # to filter documented areas
path_patterns:
- ^/ogdhcp/ # Accepts routes under /api except /api/doc

View File

@ -1,3 +1,7 @@
#index:
# path: /
# controller: App\Controller\DefaultController::index
app.swagger_ui:
path: /ogdhcp/api/doc
methods: GET
defaults: { _controller: nelmio_api_doc.controller.swagger_ui }

View File

@ -4,7 +4,7 @@
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
parameters:
backup_dir: '%kernel.project_dir%/etc/kea/backup'
services:
# default configuration for services in *this* file
_defaults:

View File

@ -2,6 +2,7 @@
<VirtualHost *:80>
ServerName localhost
ServerAlias api-test
DocumentRoot /opt/ogdhcp/public
<Directory /opt/ogdhcp/public>
@ -21,7 +22,7 @@
</IfModule>
<FilesMatch \.php$>
SetHandler "proxy:unix:/run/php/php8.1-fpm.sock|fcgi://localhost/"
SetHandler "proxy:unix:/run/php/php8.3-fpm.sock|fcgi://localhost/"
</FilesMatch>
ErrorLog ${APACHE_LOG_DIR}/ogdhcp_error.log

View File

@ -0,0 +1,42 @@
server {
listen 8081;
server_name __SERVERIP__ localhost; # IP del servidor
# Raíz del documento para el proyecto Symfony
root __PUBLICDIR__;
# Bloque para manejar las solicitudes a /ogdhcp
location /ogdhcp {
try_files $uri $uri/ /index.php?$query_string;
# Aumentar el tiempo de espera por el install ogdhcp (si es necesario)
proxy_read_timeout 600;
proxy_connect_timeout 600;
proxy_send_timeout 600;
send_timeout 600;
}
# Bloque para manejar las solicitudes a index.php
location ~ ^/index.php(/|$) {
include fastcgi_params;
fastcgi_pass unix:/run/php/php__PHPVERSION__-fpm-ogdhcp.sock;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param DOCUMENT_ROOT $document_root;
internal;
}
# Bloque para devolver 404 en cualquier solicitud a archivos PHP que no sean index.php
location ~ \.php$ {
return 404;
}
# Logs de error y acceso para el proyecto Symfony
error_log /var/log/nginx/ogdhcp_error.log;
access_log /var/log/nginx/ogdhcp_access.log;
# Manejo de la ruta para la documentación de la API (Swagger)
location /ogdhcp/api/doc {
try_files $uri /index.php?$query_string;
}
}

View File

@ -0,0 +1,7 @@
{
"interfaces": ["eth0", "eth1"],
"ogbootIP": "172.17.8.37",
"ogDhcpIP": "172.17.8.37",
"ogDhcp_Dir": "/opt/opengnsys/ogdhcp"
}

View File

@ -10,7 +10,13 @@ function globalSetup() {
current_dir=$(dirname "$0")
PROGRAMDIR=$(readlink -e "$current_dir")
PROGRAMNAME=$(basename "$0")
OPENGNSYS_CLIENT_USER="ogdhcp"
OPENGNSYS_CLIENT_USER="opengnsys"
current_dir=$(dirname "$0")
PROGRAMDIR=$(readlink -e "$current_dir")
# Ruta del archivo config_ogdhcp.json proporcionado por el usuario
CONFIG_FILE="$PROGRAMDIR/config_ogdhcp.json"
# Comprobar si se ha descargado el paquete comprimido (REMOTE=0) o sólo el instalador (REMOTE=1).
if [ -d "$PROGRAMDIR/../installer" ]; then
@ -27,7 +33,7 @@ function globalSetup() {
# Directorios de instalación y destino de OpenGnsys.
WORKDIR=/tmp/ogdhcp_installer
INSTALL_TARGET=/opt/ogdhcp
INSTALL_TARGET=$(jq -r '.ogDhcp_Dir' "$CONFIG_FILE")
PATH=$PATH:$INSTALL_TARGET/bin
if command -v service &>/dev/null; then
@ -41,18 +47,14 @@ function globalSetup() {
ENABLESERVICE="eval update-rc.d \$service defaults"
DISABLESERVICE="eval update-rc.d \$service disable"
APACHESERV=apache2
APACHECFGDIR=/etc/apache2
APACHESITESDIR=sites-available
APACHEOGSITE=ogdhcp
APACHEUSER="www-data"
APACHEGROUP="www-data"
APACHEENABLEMODS="a2enmod headers ssl rewrite proxy_fcgi fastcgi actions alias"
APACHEENABLESSL="a2ensite default-ssl"
APACHEENABLEOG="a2ensite $APACHEENABLEOG"
APACHEMAKECERT="make-ssl-cert generate-default-snakeoil --force-overwrite"
PHPFPMSERV=php7.2-fpm
# Variables globales
DEFAULTDEV=""
NGINX_TEMPLATE="$INSTALL_TARGET/etc/nginxServer.conf.tmpl"
NGINX_OUTPUT="/etc/nginx/sites-available/ogdhcp.conf"
NGINX_CONF_PATH="/etc/nginx/nginx.conf"
PHP_FPM_CONF_PATH="/etc/php/__PHPVERSION__/fpm/pool.d/www.conf"
NEW_FPM_CONF_PATH="/etc/php/__PHPVERSION__/fpm/pool.d/ogdhcp.conf"
SOCKET_PATH="/run/php/php__PHPVERSION__-fpm-ogdhcp.sock"
# Registro de incidencias.
OGLOGFILE="$INSTALL_TARGET/var/log/${PROGRAMNAME%.sh}.log"
@ -80,47 +82,32 @@ function checkDependencies() {
php-bcmath
composer
unzip
apache2
libapache2-mod-php
kea-dhcp4-server
kea-common
kea-ctrl-agent
jq
net-tools
nginx
)
export DEBIAN_FRONTEND=noninteractive
apt-get update -y
# Comprobar cada dependencia
for dep in "${DEPENDENCIES[@]}"; do
if ! dpkg -s "$dep" >/dev/null 2>&1; then
echoAndLog "$dep is not installed. Installing..."
sudo apt-get install -y "$dep"
apt-get install -y --no-install-recommends "$dep"
else
echoAndLog "$dep is already installed."
fi
done
sed -i '/ConditionFileNotEmpty=\/etc\/kea\/kea-api-password/d' /usr/lib/systemd/system/kea-ctrl-agent.service
chown -R _kea:_kea /etc/kea
systemctl daemon-reload
systemctl restart kea-ctrl-agent.service
echoAndLog "Dependencies checked."
}
# Función para instalar los paquetes necesarios para KEA-DHCP
install_kea() {
sudo apt-get install -y isc-kea-common isc-kea-ctrl-agent isc-kea-dhcp4-server isc-kea-dhcp6-server isc-kea-admin
}
# Función para instalar Composer
install_composer() {
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
}
# Función para instalar Swagger UI
install_swagger() {
sudo apt-get install -y unzip
wget https://github.com/swagger-api/swagger-ui/archive/master.zip
unzip master.zip -d /var/www/html/
sudo mv /var/www/html/swagger-ui-master /var/www/html/swagger-ui
}
# Obtiene el código fuente del proyecto desde el repositorio de GitHub.
function downloadCode() {
@ -189,22 +176,32 @@ function createDirs() {
# Cambiar permisos de usuario
echoAndLog "Changing user permission"
chown -R "$OPENGNSYS_CLIENT_USER:$OPENGNSYS_CLIENT_USER" "$INSTALL_TARGET"
# Copiar .env
cp -a "$WORKDIR/ogdhcp/.env" "${path_opengnsys_base}/.env"
}
function create_ogdhcp_project {
# Cambia al usuario ogdhcp y crea el proyecto Symfony
# Crea el usuario ogdhcp si no existe
local path_opengnsys_base="$1"
composer create-project symfony/website-skeleton "$path_opengnsys_base"
pushd "$path_opengnsys_base" || return
# Elimina el archivo composer.lock
rm composer.lock
popd || return
echoAndLog "Esqueleto de la aplicación creado y archivo composer.lock eliminado."
}
if ! id -u opengnsys &>/dev/null; then
echoAndLog "${FUNCNAME}(): creating opengnsys user"
useradd opengnsys 2>/dev/null
if [ $? -ne 0 ]; then
errorAndLog "${FUNCNAME}(): error creating opengnsys user"
return 1
fi
fi
# Crea el directorio path_opengnsys_base con el usuario opengnsys
echoAndLog "${FUNCNAME}(): creating directory $path_opengnsys_base with opengnsys user"
sudo mkdir -p "$path_opengnsys_base"
sudo chown opengnsys:opengnsys $path_opengnsys_base
if [ $? -ne 0 ]; then
errorAndLog "${FUNCNAME}(): error while creating directory $path_opengnsys_base"
return 1
fi
echoAndLog "Directory $path_opengnsys_base created with opengnsys user"
}
function copyServerFiles() {
if [ $# -ne 1 ]; then
@ -220,9 +217,6 @@ function copyServerFiles() {
#public
src
etc
templates
tests
vendor
.env
composer.json
composer.lock
@ -234,9 +228,6 @@ function copyServerFiles() {
#public
src
etc
templates
tests
vendor
.env
composer.json
composer.lock
@ -271,53 +262,14 @@ function copyServerFiles() {
popd || return
}
function downloadComposer() {
echoAndLog "Downloading composer.phar..."
# Crear el directorio de trabajo si no existe
mkdir -p "$WORKDIR/ogdhcp/bin" || return
# Cambiar al directorio de trabajo
pushd "$WORKDIR/ogdhcp/bin" || return
# Descargar composer.phar
curl -sS https://getcomposer.org/installer | php
# Comprobar si la descarga fue exitosa
if [ ! -f composer.phar ]; then
errorAndLog "Failed to download composer.phar"
popd
return 1
fi
# Crear el directorio de destino si no existe
mkdir -p "/opt/ogdhcp/bin" || return
# Mover composer.phar a /opt/ogdhcp/bin
mv composer.phar "/opt/ogdhcp/bin/"
# Comprobar si el movimiento fue exitoso
if [ ! -f "/opt/ogdhcp/bin/composer.phar" ]; then
errorAndLog "Failed to move composer.phar to /opt/ogdhcp/bin"
popd
return 1
fi
# Volver al directorio original
popd || return
echoAndLog "composer.phar downloaded and moved to /opt/ogdhcp/bin"
return 0
}
function runComposer() {
echoAndLog "Running composer.phar to install dependencies..."
# Cambiar al directorio donde está composer.phar
pushd /opt/ogdhcp/bin || return
local path_opengnsys_base="$1"
pushd $path_opengnsys_base
pwd
# Ejecutar composer.phar
sudo -u "$OPENGNSYS_CLIENT_USER" php composer.phar install
sudo -u "$OPENGNSYS_CLIENT_USER" composer --no-interaction install
# Comprobar si la ejecución fue exitosa
if [ $? -ne 0 ]; then
@ -326,92 +278,270 @@ function runComposer() {
return 1
fi
# Volver al directorio original
popd || return
echoAndLog "composer.phar ran successfully and dependencies were installed"
return 0
}
function install_swagger_ui {
# Define la URL del archivo de Swagger UI que quieres descargar
swagger_ui_url="https://github.com/swagger-api/swagger-ui/archive/refs/heads/master.zip"
# Define la ruta donde quieres descomprimir Swagger UI
swagger_ui_path="/tmp/swagger-ui"
get_first_network_interface_with_traffic() {
while read -r line; do
if [[ "$line" == *:* ]]; then
interface=$(echo "$line" | cut -d ':' -f 1 | xargs)
if [[ "$interface" != "lo" ]]; then
received_bytes=$(echo "$line" | awk '{print $2}')
transmitted_bytes=$(echo "$line" | awk '{print $10}')
if (( received_bytes > 0 || transmitted_bytes > 0 )); then
DEFAULTDEV="$interface"
break
fi
fi
fi
done < /proc/net/dev
}
comment_auth_kea() {
KEA_CTRL_AGENT_CONF="/etc/kea/kea-ctrl-agent.conf"
# Verificar si el bloque de "authentication" ya está comentado
if grep -q '^[^#]*"authentication": {' "$KEA_CTRL_AGENT_CONF"; then
echo "Comentando el bloque de autenticación en $KEA_CTRL_AGENT_CONF..."
# Define la ruta de destino para los archivos de Swagger UI
destination_path="/opt/ogdhcp/public"
# Comentar solo el bloque de authentication desde la apertura hasta la línea con '},'
sed -i '/"authentication": {/,/^[[:space:]]*},/ {
s/^\([[:space:]]*\)\([^#]\)/\1#\2/
}' "$KEA_CTRL_AGENT_CONF"
# Crea los directorios si no existen
mkdir -p "$swagger_ui_path"
mkdir -p "$destination_path"
echo "Bloque de autenticación comentado correctamente."
else
echo "El bloque de autenticación ya está comentado."
fi
# Descarga el archivo de Swagger UI
wget "$swagger_ui_url" -O /tmp/swagger-ui.zip
# Verificar si el bloque fue comentado correctamente
if grep -q '^#\s*"authentication": {' "$KEA_CTRL_AGENT_CONF"; then
echo "Confirmación: Bloque de autenticación comentado correctamente."
# Descomprime el archivo de Swagger UI en la ruta especificada
unzip /tmp/swagger-ui.zip -d "$swagger_ui_path"
# Reiniciar el servicio de Kea Control Agent para aplicar los cambios
echo "Reiniciando el agente de Kea Control Agent..."
sudo systemctl restart kea-ctrl-agent.service
# Copia los archivos de Swagger UI al directorio de destino
cp -r "$swagger_ui_path"/swagger-ui-master/dist/* "$destination_path"
# Elimina el archivo descargado y el directorio temporal
rm /tmp/swagger-ui.zip
rm -r "$swagger_ui_path"
/opt/ogdhcp/vendor/bin/openapi /opt/ogdhcp/src/DhcpBundle/Controller/ -o "$destination_path/swagger.json"
echo "Swagger UI instalado en $destination_path."
if systemctl is-active --quiet kea-ctrl-agent.service; then
echo "El agente de Kea Control Agent se ha reiniciado correctamente."
else
echo "Error: No se pudo reiniciar el agente de Kea Control Agent."
fi
else
echo "Error: No se pudo comentar correctamente el bloque de autenticación."
fi
}
# Función para obtener la dirección IP de una interfaz
get_ip_address() {
local interface="$1"
ip -4 addr show "$interface" | grep -oP "(?<=inet\s)\d+(\.\d+){3}"
}
function installWebConsoleApacheConf() {
if [ $# -ne 2 ]; then
errorAndLog "${FUNCNAME}(): invalid number of parameters"
# Función para obtener la versión de PHP instalada
get_php_fpm_version() {
php -v | grep -oP "PHP \K\d+\.\d+"
}
add_write_permission_apparmor() {
APPARMOR_PROFILE="/etc/apparmor.d/usr.sbin.kea-dhcp4"
# Comprobar si las líneas existen
if grep -q "/etc/kea/ r," "$APPARMOR_PROFILE" && grep -q "/etc/kea/** r," "$APPARMOR_PROFILE"; then
echo "Modificando permisos en $APPARMOR_PROFILE..."
# Modificar las líneas /etc/kea/ r, y /etc/kea/** r, añadiendo w
sed -i 's#/etc/kea/ r,#/etc/kea/ rw,#g' "$APPARMOR_PROFILE"
sed -i 's#/etc/kea/\*\* r,#/etc/kea/** rw,#g' "$APPARMOR_PROFILE"
echo "Permisos de escritura añadidos correctamente a $APPARMOR_PROFILE."
else
echo "Las líneas no fueron encontradas o ya están modificadas."
fi
# Recargar el perfil de AppArmor para aplicar los cambios
echo "Recargando el perfil de AppArmor para kea-dhcp4..."
sudo apparmor_parser -r "$APPARMOR_PROFILE"
if [ $? -eq 0 ]; then
echo "El perfil de AppArmor se recargó correctamente."
else
echo "Error al recargar el perfil de AppArmor."
fi
}
# Función para configurar Nginx
setup_nginx() {
local path_opengnsys_base="$1"
local public_dir="$path_opengnsys_base/public"
#ip_address_server=$(get_ip_address "$DEFAULTDEV")
if [[ ! -f "$CONFIG_FILE" ]]; then
echo "Error: El archivo de configuración no se encontró."
exit 1
fi
local path_opengnsys_base="$1"
local path_apache2_confd="$2"
local OGHDPCDIR="${path_opengnsys_base}/public"
local sockfile
ip_address_server=$(jq -r '.ogDhcpIP' "$CONFIG_FILE")
if [ ! -d "$path_apache2_confd" ]; then
errorAndLog "${FUNCNAME}(): path to apache2 conf.d can not found, verify your server installation"
return 1
if [[ -z "$ip_address_server" ]]; then
echo "Error: No se pudo obtener la dirección IP del servidor desde el archivo de configuración."
exit 1
fi
php_version=$(get_php_fpm_version)
if [[ -z "$php_version" ]]; then
echo "Error: No se pudo obtener la versión de PHP."
exit 1
fi
mkdir -p "$path_apache2_confd/{sites-available,sites-enabled}"
echoAndLog "${FUNCNAME}(): creating apache2 config file.."
# Activar PHP-FPM.
echoAndLog "${FUNCNAME}(): configuring PHP-FPM"
service="$PHPFPMSERV"
$ENABLESERVICE; $STARTSERVICE
sockfile=$(find /run/php -name "php*.sock" -type s -print 2>/dev/null | tail -1)
# Activar módulos de Apache.
$APACHEENABLEMODS
# Generar configuración de consola web a partir del archivo de plantilla.
if [ -n "$sockfile" ]; then
sed -e "s,OGHDPCDIR,$OGHDPCDIR,g" \
-e "s,proxy:fcgi:.*,proxy:unix:${sockfile%% *}|fcgi://localhost\",g" \
"$WORKDIR/ogdhcp/etc/apache.conf.tmpl" > "$path_apache2_confd/$APACHESITESDIR/${APACHEOGSITE}.conf"
else
sed -e "s,OGHDPCDIR,$OGHDPCDIR,g" \
"$WORKDIR/ogdhcp/server/etc/apache.conf.tmpl" > "$path_apache2_confd/$APACHESITESDIR/${APACHEOGSITE}.conf"
# Leer y modificar la plantilla de configuración de nginx
if [[ ! -f "$NGINX_TEMPLATE" ]]; then
echo "Error: La plantilla de Nginx no se encontró."
exit 1
fi
$APACHEENABLEOG
if [ $? -ne 0 ]; then
errorAndLog "${FUNCNAME}(): config file can't be linked to apache conf, verify your server installation"
return 1
fi
echoAndLog "${FUNCNAME}(): config file created and linked, restarting apache daemon"
service="$APACHESERV"
$ENABLESERVICE; $STARTSERVICE
return 0
nginx_content=$(<"$NGINX_TEMPLATE")
nginx_content="${nginx_content//__SERVERIP__/$ip_address_server}"
nginx_content="${nginx_content//__PHPVERSION__/$php_version}"
nginx_content="${nginx_content//__PUBLICDIR__/$public_dir}"
# Crear el archivo de configuración de Nginx
echo "$nginx_content" > "$NGINX_OUTPUT"
echo "Archivo de configuración de Nginx creado en $NGINX_OUTPUT."
# Crear el enlace simbólico
ln -sf "$NGINX_OUTPUT" /etc/nginx/sites-enabled/ogdhcp.conf
echo "Enlace simbólico creado en /etc/nginx/sites-enabled/ogdhcp.conf."
# Modificar nginx.conf para ejecutar como opengnsys
sed -i 's/user www-data;/user opengnsys;/g' "$NGINX_CONF_PATH"
echo "Nginx configurado para ejecutarse como opengnsys."
# Reiniciar Nginx
systemctl restart nginx.service
echo "Servicio Nginx reiniciado."
}
# Función para modificar el archivo de configuración PHP-FPM
modify_php_fpm_config() {
php_version=$(get_php_fpm_version)
if [[ -z "$php_version" ]]; then
echo "Error: No se pudo obtener la versión de PHP."
exit 1
fi
php_fpm_conf_path="/etc/php/$php_version/fpm/pool.d/www.conf"
new_fpm_conf_path="/etc/php/$php_version/fpm/pool.d/ogdhcp.conf"
socket_path="/run/php/php$php_version-fpm-ogdhcp.sock"
# Verificar si el archivo new_fpm_conf_path ya existe
if [[ -f "$new_fpm_conf_path" ]]; then
echo "El archivo $new_fpm_conf_path ya existe. No se realizarán modificaciones."
return
fi
# Copiar el archivo www.conf a opengnsys.conf
cp "$php_fpm_conf_path" "$new_fpm_conf_path"
# Modificar el archivo ogdhcp.conf
sed -i 's/\[www\]/[ogdhcp]/g' "$new_fpm_conf_path"
sed -i 's/user = www-data/user = opengnsys/g' "$new_fpm_conf_path"
sed -i 's/group = www-data/group = opengnsys/g' "$new_fpm_conf_path"
sed -i "s|listen =.*|listen = $socket_path|g" "$new_fpm_conf_path"
sed -i 's/listen.owner = www-data/listen.owner = opengnsys/g' "$new_fpm_conf_path"
sed -i 's/listen.group = www-data/listen.group = opengnsys/g' "$new_fpm_conf_path"
# Reiniciar PHP-FPM
systemctl restart php"$php_version"-fpm.service
echo "PHP-FPM reiniciado."
# Verificar la creación del socket
if [[ -S "$socket_path" ]]; then
echo "Socket PHP-FPM $socket_path creado correctamente."
else
echo "Error: El socket PHP-FPM $socket_path no se ha creado."
exit 1
fi
}
configure_kea() {
# Verificar si jq está instalado
if ! command -v jq &> /dev/null; then
echo "jq no está instalado. Por favor, instala jq para continuar."
exit 1
fi
# Verificar si el archivo de configuración existe
if [ ! -f "$CONFIG_FILE" ]; then
echo "El archivo $CONFIG_FILE no se encuentra. Asegúrate de que esté disponible antes de la instalación."
exit 1
fi
# Leer los parámetros del archivo JSON usando jq
INTERFACES=$(jq -r '.interfaces[]' "$CONFIG_FILE")
OGBOOT_IP=$(jq -r '.ogbootIP' "$CONFIG_FILE")
# Crear la configuración mínima de Kea DHCP
KEA_CONFIG="/etc/kea/kea-dhcp4.conf"
# Hacer una copia de seguridad del archivo kea-dhcp4.conf si ya existe
if [ -f "$KEA_CONFIG" ]; then
cp "$KEA_CONFIG" "$KEA_CONFIG.backup"
echo "Se ha creado una copia de seguridad del archivo de configuración actual en $KEA_CONFIG.backup"
fi
# Generar la configuración mínima para Kea DHCP
cat > "$KEA_CONFIG" << EOL
{
"Dhcp4": {
"interfaces-config": {
"interfaces": [ $(
for interface in $INTERFACES; do
echo "\"$interface\""
done | paste -sd "," -
) ]
},
"client-classes": [
{
"name": "UEFI-64",
"test": "not substring(option[60].hex,0,20) == 'PXEClient:Arch:00000'",
"boot-file-name": "ipxe.efi",
"next-server": "$OGBOOT_IP"
},
{
"name": "Legacy",
"test": "substring(option[60].hex,0,20) == 'PXEClient:Arch:00000'",
"boot-file-name": "undionly.kpxe",
"next-server": "$OGBOOT_IP"
}
],
"control-socket": {
"socket-name": "/run/kea/kea4-ctrl-socket",
"socket-type": "unix"
}
}
}
EOL
echo "Se ha generado la configuración mínima de Kea DHCP en $KEA_CONFIG"
# Reiniciar el servicio de Kea DHCP y verificar si se reinicia correctamente
echo "Reiniciando el servicio Kea DHCP..."
sudo systemctl restart kea-dhcp4-server.service
# Comprobar el estado del servicio Kea DHCP
if systemctl is-active --quiet kea-dhcp4-server.service; then
echo "Kea DHCP reiniciado correctamente."
else
echo "Error al reiniciar Kea DHCP."
exit 1
fi
}
#####################################################################
####### Algunas funciones útiles de propósito general:
#####################################################################
@ -459,7 +589,6 @@ mkdir -p $WORKDIR
pushd $WORKDIR
checkDependencies
install_kea
# Si es necesario, descarga el repositorio de código en directorio temporal
if [ $REMOTE -eq 1 ]; then
downloadCode $GIT_REPO
@ -492,18 +621,39 @@ if [ $? -ne 0 ]; then
exit 1
fi
downloadComposer
runComposer
install_swagger_ui
# Creando configuración de Apache.
installWebConsoleApacheConf $INSTALL_TARGET $APACHECFGDIR
comment_auth_kea
if [ $? -ne 0 ]; then
errorAndLog "Error configuring Apache for OpenGnsys Admin"
errorAndLog "Error while commenting auth block!"
exit 1
fi
sudo apt-get update
runComposer ${INSTALL_TARGET}
setup_nginx $INSTALL_TARGET
if [ $? -ne 0 ]; then
errorAndLog "Error configuring Nginx for OpenGnsys Admin"
exit 1
fi
modify_php_fpm_config
if [ $? -ne 0 ]; then
errorAndLog "Error configuring PHP-FPM for OpenGnsys Admin"
exit 1
fi
add_write_permission_apparmor
if [ $? -ne 0 ]; then
errorAndLog "Error adding write permission to AppArmor profile"
exit 1
fi
configure_kea
if [ $? -ne 0 ]; then
errorAndLog "Error configuring Kea DHCP initial configuration"
exit 1
fi
# install_kea
# install_php
# install_composer
@ -512,4 +662,4 @@ sudo apt-get update
# Ahora puedes clonar e instalar el componente ogDhcp
# git clone <URL del repositorio de ogDhcp>
# cd <directorio de ogDhcp>
# composer install
# composer install

View File

@ -0,0 +1,25 @@
#!/bin/bash
set -x
URL_REPO="https://ognproject.evlt.uma.es/gitea/opengnsys/ogdhcp.git"
BRANCH=${OGDHCP_BRANCH:-"main"}
DOWNLOADDIR=${OGDHCP_DOWNLOADDIR:-"/tmp/ogdhcp"}
apt install -y git vim
git config --global http.sslVerify false
git clone -b $BRANCH $URL_REPO $DOWNLOADDIR
cd $DOWNLOADDIR/installer
ogBoot_ServerIP=${1:-"172.17.8.82"}
ogDhcp_ServerIP=${2:-"172.17.8.37"}
ogDhcp_Dir=${3:-"/opt/opengnsys/ogdhcp"}
cat > config_ogdhcp.json <<EOF
{
"interfaces": ["eth0", "eth1"],
"ogbootIP": "$ogBoot_ServerIP",
"ogDhcpIP": "$ogDhcp_ServerIP",
"ogDhcp_Dir": "$ogDhcp_Dir"
}
EOF
chmod 755 ogdhcp_installer.sh
./ogdhcp_installer.sh

File diff suppressed because it is too large Load Diff

View File

@ -28,7 +28,8 @@ class CurlKeaService
} else {
return "Error: Comando no válido";
}
if (($command == 'config-set' || $command == 'config-write') && $create_backup) {
//f (($command == 'config-set' || $command == 'config-write') && $create_backup) {
if ($command == 'config-set' && $create_backup) {
$this->backupConfig();
}
$jsonData = json_encode($requestData);

118
tests/API-dhcp/Jenkinsfile vendored 100644
View File

@ -0,0 +1,118 @@
pipeline {
agent {
node {
label 'jenkins-slave'
}
}
options {
// Deshabilita ejecuciones concurrentes
disableConcurrentBuilds()
}
environment {
ESXI_PASS = credentials('VI_PASSWORD')
QINDEL_PASS = credentials('jenkins-user-slave-password')
PATH = "/home/qindel/bin/ovftool:${env.PATH}"
}
stages {
stage('Prepare environment') {
steps {
dir ('tests/API-dhcp') {
echo "Install vagrant plugin"
sh '''
if ! vagrant plugin list | grep -q vagrant-vmware-esxi; then
echo "Vagrant plugin vagrant-vmware-esxi not found. Installing..."
vagrant plugin install vagrant-vmware-esxi
else
echo "Vagrant plugin vagrant-vmware-esxi is already installed."
fi
'''
echo "Deploy API server for DHCP with Vagrant"
sh 'vagrant up --provider=vmware_esxi --provision'
echo 'Create Python venv to work with robotframework'
sh '''
python3 -m venv robotframework
. robotframework/bin/activate
pip install -r requirements.txt
'''
}
}
}
stage('Check Installation') {
steps {
dir ('tests/API-dhcp') {
echo "Get IP of API server for DHCP with Vagrant"
sh '''
set -e
new_ip=$(vagrant ssh -c "hostname -I" | tr -d '\r\n' | sed 's/^[^0-9]*//' | sed 's/[[:space:]]*$//')
echo $new_ip > ip.txt
echo "$QINDEL_PASS" | sudo -S bash -c "echo '$new_ip api-test' >> /etc/hosts"
echo "IP: $new_ip"
curl -X 'GET' "http://$new_ip/ogdhcp/v1/subnets" -H 'accept: /'
'''
script {
def new_ip = readFile('ip.txt').trim()
env.NEW_IP = new_ip
}
}
}
}
stage('Run API tests') {
steps {
dir ('tests/API-dhcp') {
echo 'Running API tests'
sh '''
. robotframework/bin/activate
robot --variable BASE_URL:http://${NEW_IP}/ogdhcp/v1 -d results/ robot/
'''
}
// Aquí incluirías los comandos para ejecutar tus pruebas
}
}
}
post {
success {
// Si el trabajo ha sido exitoso, destruir la máquina de Vagrant
echo "El trabajo ha finalizado con éxito. Destruyendo máquina Vagrant..."
dir ('tests/API-dhcp') {
sh 'vagrant destroy -f'
}
}
always {
// Recoger los resultados de los tests
dir ('tests/API-dhcp') {
robot outputPath: 'results',
outputFileName: 'output.xml',
logFileName: 'log.html',
reportFileName: 'report.html',
passThreshold: 100.0,
unstableThreshold: 75.0
}
// Siempre se ejecutará, independientemente del resultado
script {
// Elimina la entrada del /etc/hosts
sh "echo '$QINDEL_PASS' | sudo -S sed -i '/api-test/d' /etc/hosts"
def recipientEmail = env.BUILD_USER_EMAIL ?: getCommitterEmail()
mail to: recipientEmail,
subject: "Jenkins Job Completed: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
body: """
El job '${env.JOB_NAME}' con número de build ${env.BUILD_NUMBER} ha finalizado.
Estado: ${currentBuild.currentResult}
Revisa los detalles del build en: ${env.BUILD_URL}
"""
}
}
}
}
def getCommitterEmail() {
return sh(
script: 'git log -1 --pretty=format:"%ae"',
returnStdout: true
).trim()
}

163
tests/API-dhcp/Vagrantfile vendored 100644
View File

@ -0,0 +1,163 @@
#
# Fully documented Vagrantfile available
# in the wiki: https://github.com/josenk/vagrant-vmware-esxi/wiki
$script = <<SCRIPT
SERVER_NAME=localhost
echo "Provisioning with shell script..."
cd /vagrant/installer
chmod +x ogdhcp_installer.sh && ./ogdhcp_installer.sh
SCRIPT
Vagrant.configure('2') do |config|
config.vm.box = 'dummy'
VM_TEMPLATE = 'template-ubuntu24'
# Use rsync and NFS synced folders. (or use the option to disable them)
# https://www.vagrantup.com/docs/synced-folders/
#config.vm.synced_folder('.', '/vagrant', type: 'rsync')
config.vm.synced_folder('../../', '/vagrant', type: 'rsync')
# Vagrant can configure additional network interfaces using a static IP or
# DHCP. Use public_network or private_network to manually set a static IP and
# optionally netmask. ESXi doesn't use the concept of public or private
# networks so both are valid here. The primary network interface is considered the
# "vagrant management" interface and cannot be changed and this plugin
# supports 10 NICS, so you can specify 9 entries here!
#
# https://www.vagrantup.com/docs/networking/public_network.html
# https://www.vagrantup.com/docs/networking/private_network.html
#
# *** Invalid settings could cause 'vagrant up' to fail ***
#config.vm.network 'private_network', ip: '192.168.10.170', netmask: '255.255.255.0'
#config.vm.network 'private_network', ip: '192.168.11.170'
#config.vm.network 'public_network', ip: '192.168.12.170'
#
# Provider (esxi) settings
#
config.vm.provision 'shell', inline: $script
config.vm.provider :vmware_esxi do |esxi|
# REQUIRED! ESXi hostname/IP
esxi.esxi_hostname = 'esxi-jenkins.evlt.uma.es'
# ESXi username
esxi.esxi_username = 'root'
# IMPORTANT! Set ESXi password.
# 1) 'prompt:'
# 2) 'file:' or 'file:my_secret_file'
# 3) 'env:' or 'env:my_secret_env_var'
# 4) 'key:' or key:~/.ssh/some_ssh_private_key'
# 5) or esxi.esxi_password = 'my_esxi_password'
#
esxi.esxi_password = 'env:ESXI_PASS'
# SSH port.
#esxi.esxi_hostport = 22
# HIGHLY RECOMMENDED! ESXi Virtual Network
# You should specify an ESXi Virtual Network! If it's not specified, the
# default is to use the first found. You can specify up to 10 virtual
# networks using an array format.
esxi.esxi_virtual_network = ['vLan_742']
# OPTIONAL. Specify a Disk Store
#esxi.esxi_disk_store = 'DS_001'
# OPTIONAL. Resource Pool
# Vagrant will NOT create a Resource pool it for you.
esxi.esxi_resource_pool = '/'
# Optional. Specify a VM to clone instead of uploading a box.
# Vagrant can use any stopped VM as the source 'box'. The VM must be
# registered, stopped and must have the vagrant insecure ssh key installed.
# If the VM is stored in a resource pool, it must be specified.
# See wiki: https://github.com/josenk/vagrant-vmware-esxi/wiki/How-to-clone_from_vm
esxi.clone_from_vm = VM_TEMPLATE
# OPTIONAL. Guest VM name to use.
# The Default will be automatically generated.
#esxi.guest_name = 'Custom-Guest-VM_Name'
# OPTIONAL. When automatically naming VMs, use this prefix.
#esxi.guest_name_prefix = 'V-'
# OPTIONAL. Set the guest username login. The default is 'vagrant'.
#esxi.guest_username = 'vagrant'
# OPTIONAL. Memory size override
#esxi.guest_memsize = '2048'
# OPTIONAL. Virtual CPUs override
#esxi.guest_numvcpus = '2'
# OPTIONAL & RISKY. Specify up to 10 MAC addresses
# The default is ovftool to automatically generate a MAC address.
# You can specify an array of MAC addresses using upper or lower case,
# separated by colons ':'.
#esxi.guest_mac_address = ['00:50:56:aa:bb:cc', '00:50:56:01:01:01','00:50:56:02:02:02','00:50:56:BE:AF:01' ]
# OPTIONAL & RISKY. Specify a guest_nic_type
# The validated list of guest_nic_types are 'e1000', 'e1000e', 'vmxnet',
# 'vmxnet2', 'vmxnet3', 'Vlance', and 'Flexible'.
#esxi.guest_nic_type = 'e1000'
# OPTIONAL. Specify a disk type.
# If unspecified, it will be set to 'thin'. Otherwise, you can set to
# 'thin', 'thick', or 'eagerzeroedthick'
#esxi.guest_disk_type = 'thick'
# OPTIONAL. Boot disk size.
# If unspecified, the boot disk size will be the same as the original
# box. You can specify a larger boot disk size in GB. The extra disk space
# will NOT automatically be available to your OS. You will need to
# create or modify partitions, LVM and/or filesystems.
#esxi.guest_boot_disk_size = 50
# OPTIONAL. Create additional storage for guests.
# You can specify an array of up to 13 virtual disk sizes (in GB) that you
# would like the provider to create once the guest has been created. You
# can optionally specify the size and datastore using a hash.
#esxi.guest_storage = [ 10, 20, { size: 30, datastore: 'datastore1' } ]
# OPTIONAL. specify snapshot options.
#esxi.guest_snapshot_includememory = 'true'
#esxi.guest_snapshot_quiesced = 'true'
# RISKY. guest_guestos
# https://github.com/josenk/vagrant-vmware-esxi/wiki/VMware-ESXi-6.5-guestOS-types
#esxi.guest_guestos = 'centos-64'
# OPTIONAL. guest_virtualhw_version
# ESXi 6.7 supports these versions. 4,7,8,9,10,11,12,13 & 14.
#esxi.guest_virtualhw_version = '9'
# OPTIONAL. Guest Autostart
# Guest VM will autostart when esxi host is booted. 'true' or 'false'(default)
#esxi.guest_autostart = 'false'
# RISKY. guest_custom_vmx_settings
#esxi.guest_custom_vmx_settings = [['vhv.enable','TRUE'], ['floppy0.present','TRUE']]
# OPTIONAL. local_lax
#esxi.local_lax = 'true'
# OPTIONAL. Guest IP Caching
#esxi.local_use_ip_cache = 'True'
# DANGEROUS! Allow Overwrite
# If unspecified, the default is to produce an error if overwriting
# VMs and packages.
#esxi.local_allow_overwrite = 'True'
# Advanced Users.
# If set to 'True', all WARNINGS will produce a FAILURE and Vagrant will stop.
#esxi.local_failonwarning = 'True'
# Plugin debug output.
# Please send any bug reports with this debug output...
#esxi.debug = 'true'
end
end

View File

@ -0,0 +1,374 @@
#!/usr/bin/env python
from flask import Flask, jsonify, request
import ipaddress
import json
app = Flask(__name__)
def convert_to_cidr(address, mask):
"""
Convert an IP address and subnet mask to CIDR notation.
Args:
address (str): The IP address in dotted decimal format (e.g., '192.168.1.1').
mask (str): The subnet mask in dotted decimal format (e.g., '255.255.255.0').
Returns:
str: The CIDR notation of the network (e.g., '192.168.1.1/24').
None: If there is an error in conversion, returns None and prints an error message.
Raises:
ValueError: If the provided address or mask is invalid.
"""
# Convertir dirección y máscara a formato CIDR
try:
# Convertir la máscara de red de formato largo a formato corto
# Cada octeto se convierte a su representación binaria y se cuenta el número de bits '1'
cidr_mask = ipaddress.IPv4Network(f"0.0.0.0/{mask}").prefixlen
red_objeto = f"{address}/{cidr_mask}"
return red_objeto
except ValueError as e:
print(f"Error al convertir a CIDR: {e}")
return None
subnets_collection = [{"id": 1,"subnet": "192.168.1.0/24", "next-server": "192.168.1.1", "boot-file-name": "pxelinux.0", "reservations": [] }]
# Endpoint GET /ogdhcp/v1/status
"""
Endpoint to get the status of the DHCP service.
This endpoint returns a JSON response with the current status of the DHCP service,
including disk usage, subnets configuration, and the status of various services.
Returns:
Response: A JSON response with the following structure:
"total": str, # Total disk space
"used": str, # Used disk space
"available": str, # Available disk space
"percentage": str # Percentage of disk space used
"id": int, # Subnet ID
"subnet": str, # Subnet address
"pools": [
"pool": str # IP address pool range
"reservations": [
"ip-address": str, # Reserved IP address
"hw-address": str # Hardware address associated with the reservation
]
...
"kea-ctrl-agent": str, # Status of kea-ctrl-agent service
"kea-dhcp4": str, # Status of kea-dhcp4 service
"nginx": str # Status of nginx service
HTTP Status Code:
200: If the request was successful.
"""
@app.route('/ogdhcp/v1/status', methods=['GET'])
def get_status():
# Simular respuesta de éxito
return jsonify({
"success": "Mensaje éxito",
"message": {
"disk_usage": {
"total": "20G",
"used": "15G",
"available": "5G",
"percentage": "75%"
},
"subnets": [
{
"id": 1,
"subnet": "192.168.1.0/24",
"pools": [{"pool": "192.168.1.10-192.168.1.100"}],
"reservations": [{"ip-address": "192.168.1.20", "hw-address": "00:0c:29:6b:5e:71"}]
},
{
"id": 2,
"subnet": "10.0.0.0/24",
"pools": [{"pool": "10.0.0.10-10.0.0.100"}],
"reservations": []
}
],
"services_status": {
"kea-ctrl-agent": "active",
"kea-dhcp4": "active",
"nginx": "active"
}
}
}), 200
# Endpoint GET /ogdhcp/v1/subnets
@app.route('/ogdhcp/v1/subnets', methods=['GET'])
def get_subnets():
# Simular respuesta de éxito
return jsonify({
"success": "Subredes obtenidas correctamente",
"message": subnets_collection
}), 200
# Endpoint POST /ogdhcp/v1/subnets
"""
Create a new subnet.
Endpoint: /ogdhcp/v1/subnets
Method: POST
Request Body (JSON):
{
"id": <str>, # Unique identifier for the subnet
"address": <str>, # IP address of the subnet
"mask": <str>, # Subnet mask
"nextServer": <str>, # (Optional) Next server IP address
"bootFileName": <str> # (Optional) Boot file name
}
Responses:
200 OK:
{
"message": <new_subnet>
}
400 Bad Request:
- "Invalid JSON"
- "Missing 'id', 'address' or 'mask' key"
- {
"error": "Error: La subred con el id '<id>' ya existe."
}
- {
"error": "Error: La subred con la dirección '<subnet>' ya existe."
}
- "Invalid data format"
Description:
This endpoint allows the creation of a new subnet. It expects a JSON payload with the subnet details.
The function checks if the provided JSON is valid and contains the required keys ('id', 'address', 'mask').
It also ensures that the subnet ID and subnet address do not already exist in the collection.
If the validation passes, the new subnet is added to the collection and a success response is returned.
"""
@app.route('/ogdhcp/v1/subnets', methods=['POST'])
def create_subnet():
try:
new_subnet = json.loads(request.data)
new_subnet["next-server"] = new_subnet.get("nextServer")
new_subnet["boot-file-name"] = new_subnet.get("bootFileName")
except json.JSONDecodeError:
return "Invalid JSON", 400
if isinstance(new_subnet, dict):
if "id" in new_subnet and "address" in new_subnet and "mask" in new_subnet:
new_subnet_cidr = convert_to_cidr(new_subnet["address"], new_subnet["mask"])
# Comprobar si el id ya existe en la colección
for subnet in subnets_collection:
if subnet.get("id") == new_subnet["id"]:
return jsonify({
"error": f"Error: La subred con el id '{new_subnet['id']}' ya existe."
}), 400
if subnet.get("subnet") == new_subnet_cidr:
return jsonify({
"error": f"Error: La subred con la dirección '{new_subnet_cidr}' ya existe."
}), 400
# Si el id no existe, continuar con la creación de la subred
new_subnet["subnet"] = convert_to_cidr(new_subnet["address"], new_subnet["mask"])
subnets_collection.append(new_subnet)
return jsonify({
"success": "Subred agregada correctamente",
"message": new_subnet
}), 200
else:
return "Missing 'id', 'address' or 'mask' key", 400
else:
return "Invalid data format", 400
# Endpoint DELETE /ogdhcp/v1/subnets/<subnetId>
"""
Deletes a subnet from the subnets_collection based on the provided subnetId.
Args:
subnetId (int): The ID of the subnet to be deleted.
Returns:
Response: A JSON response indicating the success or failure of the deletion.
- If the subnet is successfully deleted, returns a JSON response with a success message and HTTP status 200.
- If the subnet with the given ID does not exist, returns a JSON response with an error message and HTTP status 404.
"""
@app.route('/ogdhcp/v1/subnets/<int:subnetId>', methods=['DELETE'])
def delete_subnet(subnetId):
subnet_to_delete = None
for subnet in subnets_collection:
if int(subnet["id"]) == subnetId:
subnet_to_delete = subnet
break
if subnet_to_delete:
print (f"Subnet Collection {subnets_collection}")
print (f"Subnet to delete {subnet_to_delete}")
subnets_collection.remove(subnet_to_delete)
print (f"Subnet Collection {subnets_collection}")
return jsonify({
"success": "Subred eliminada correctamente"
}), 200
else:
return jsonify({
"error": f"Error: La subred con el id '{subnetId}' no existe"
}), 404
# Endpoint PUT /ogdhcp/v1/subnets/<subnetId>
"""
Updates a subnet with the given subnetId based on the provided JSON data in the request.
Args:
subnetId (str): The ID of the subnet to be updated.
Returns:
Response: A JSON response indicating the success or failure of the update operation.
- On success: Returns a JSON response with a success message and the updated subnet data, with a status code of 200.
- On failure: Returns a JSON response with an error message and a status code of 400.
Raises:
json.JSONDecodeError: If the request data is not valid JSON.
The function performs the following steps:
1. Parses the JSON data from the request.
2. Validates the presence of required fields ('address' and 'mask') in the JSON data.
3. Converts the 'address' and 'mask' fields to a 'subnet' field in CIDR notation.
4. Searches for the subnet with the given subnetId in the subnets_collection.
5. Updates the subnet fields ('subnet', 'nextServer', 'bootFileName') if they are present in the JSON data.
6. Returns a success response if the subnet is found and updated.
7. Returns an error response if the subnet is not found.
"""
@app.route('/ogdhcp/v1/subnets/<int:subnetId>', methods=['PUT'])
def update_subnet(subnetId):
if subnetId == 0:
return jsonify({
"error": "Error al guardar la configuración en Kea DHCP: Unable to save configuration"
}), 400
try:
modify_data = json.loads(request.data)
except json.JSONDecodeError:
return "Invalid JSON", 400
print ("Modify data", modify_data)
print ("Modify data type", type(modify_data))
if modify_data.get("address") != None and modify_data.get("mask") == None:
print ("Address", modify_data.get("address"))
return jsonify({
"error": f"Error: La máscara de red es requerida con el parametro 'address'"
}), 400
if modify_data.get("mask") != None and modify_data.get("address") == None:
return jsonify({
"error": f"Error: La dirección de red es requerida con el parametro 'mask'"
}), 400
subnet_to_update = None
for subnet in subnets_collection:
# Casting subnet["id"] to int to avoid type mismatch
if str(subnet["id"]) == str(subnetId):
# subnet_to_update = subnet
if modify_data.get("subnet"):
subnet["subnet"] = modify_data["subnet"]
if modify_data.get("nextServer"):
subnet["next-server"] = modify_data["nextServer"]
if modify_data.get("bootFileName"):
subnet["boot-file-name"] = modify_data["bootFileName"]
subnet_to_update = subnet
break
if subnet_to_update:
# subnet_to_update.update(modify_data)
print ("Subnet to update", subnet_to_update)
return jsonify({
"success": "Subred modificada correctamente",
"message": subnet_to_update
}), 200
else:
# Si no se encuentra la subred, devolver un error
print (f"Subnet Collection {subnets_collection}")
response = jsonify({
"error": f"Error: La subred con el id '{subnetId}' no existe"
})
response.status_code = 404
response.headers["Content-Type"] = "application/json"
return response
# Endpoint GET /ogdhcp/v1/subnets/<subnetId>/hosts
@app.route('/ogdhcp/v1/subnets/<int:subnetId>/hosts', methods=['GET'])
def get_hosts(subnetId):
# Simular respuesta de éxito
return jsonify({
"success": "Hosts retrieved successfully",
"message": [
{"ip-address": "192.168.1.10", "hw-address": "00:0c:29:6b:5e:71", "hostname": "host1"},
{"ip-address": "192.168.1.20", "hw-address": "00:0c:29:6b:5e:72", "hostname": "host2"}
]
}), 200
# Endpoint POST /ogdhcp/v1/subnets/<subnetId>/hosts
@app.route('/ogdhcp/v1/subnets/<int:subnetId>/hosts', methods=['POST'])
def create_host(subnetId):
# Simular respuesta de éxito
return jsonify({
"success": "Host agregado correctamente",
"message": {
"id": 1,
"subnet": "192.168.1.0/24",
"next-server": "192.168.1.1",
"boot-file-name": "pxelinux.0",
"reservations": [
{"hostname": "pc11", "hw-address": "56:6f:c7:4f:00:4f", "ip-address": "172.30.4.11"}
]
}
}), 200
# Endpoint DELETE /ogdhcp/v1/subnets/<subnetId>/hosts
@app.route('/ogdhcp/v1/subnets/<int:subnetId>/hosts', methods=['DELETE'])
def delete_host(subnetId):
# Simular respuesta de éxito
return jsonify({
"success": "Host eliminado correctamente",
"message": {
"id": 1,
"subnet": "192.168.1.0/24",
"next-server": "192.168.1.1",
"boot-file-name": "pxelinux.0",
"reservations": [
{"hostname": "host2", "hw-address": "00:0c:29:6b:5e:72", "ip-address": "172.30.4.12"}
]
}
}), 200
# Endpoint PUT /ogdhcp/v1/subnets/<subnetId>/hosts
@app.route('/ogdhcp/v1/subnets/<int:subnetId>/hosts', methods=['PUT'])
def update_host(subnetId):
# Simular respuesta de éxito
return jsonify({
"success": "Host actualizado correctamente",
"message": {
"id": 1,
"subnet": "192.168.1.0/24",
"next-server": "192.168.1.1",
"boot-file-name": "pxelinux.0",
"reservations": [
{"hostname": "pc11", "hw-address": "56:6f:c7:4f:01:01", "ip-address": "192.168.1.11"},
{"hostname": "host2", "hw-address": "00:0c:29:6b:5e:72", "ip-address": "192.168.1.12"}
]
}
}), 200
# Endpoint POST /ogdhcp/v1/backup
@app.route('/ogdhcp/v1/backup', methods=['POST'])
def backup_config():
# Simular respuesta de éxito
return jsonify({
"success": "Configuración cargada correctamente"
}), 200
if __name__ == '__main__':
app.run(debug=True, port=8006)

View File

@ -0,0 +1,2 @@
robotframework
robotframework-requests

View File

@ -0,0 +1,215 @@
*** Settings ***
Documentation This is a basic skeleton for a Robot Framework test suite.
Library Collections
Library RequestsLibrary
*** Variables ***
${BASE_URL} http://localhost:8006/ogdhcp/v1
${headers} Create Dictionary Content-Type application/json
${modified_subnet} Create Dictionary mask=255.255.192.0 address=192.168.1.0 nextServer=192.168.1.1 bootFileName=pxelinux.0
${wrong_subnet_data_netmask} Create Dictionary mask=333.333.333.333 address=192.168.1.0 nextServer=192.168.1.1 bootFileName=pxelinux.0
${wrong_subnet_data_address} Create Dictionary mask=255.255.255.0 address=444.168.1.0 nextServer=192.168.1.1 bootFileName=pxelinux.0
${wrong_subnet_data_server} Create Dictionary mask=255.255.255.0 address=192.168.1.0 nextServer=555.168.1.1 bootFileName=pxelinux.0
${invalid_net_id} 999
${valid_net_id} 2
${subnet_error} 0
*** Test Cases ***
Get Status of the DHCP server
[Documentation] Get status of the dhcp server and check services status
[Tags] status
${response}= GET ${BASE_URL}/status
Status Should Be 200 ${response}
Log ${response.json()}
# Convertir la respuesta a JSON usando ${response.json()}
${json}= Convert to Dictionary ${response.json()}
Dictionary Should Contain Key ${json} success
Dictionary Should Contain Key ${json} message
Should Contain ${response.json()['message']} disk_usage
Should Contain ${response.json()['message']} subnets
Should Contain ${response.json()['message']['disk_usage']} total
Get All Subnets
[Documentation] Este test verifica que la API retorna las subredes correctamente con el código 200.
[Tags] subnets
${response}= GET ${BASE_URL}/subnets
# Verificar código de estado HTTP
Should Be Equal As Numbers ${response.status_code} 200
${json}= Convert To Dictionary ${response.json()}
# Verificar que la respuesta contiene las claves 'success' y 'message'
Dictionary Should Contain Key ${json} success
Dictionary Should Contain Key ${json} message
# Validar el mensaje de éxito
Should Be Equal ${json['success']} Subredes obtenidas correctamente
# Verificar que 'message' es una lista
# Should Be True ${json['message']} is list
# Verificar que cada subred en 'message' tiene los campos esperados
FOR ${subred} IN @{json['message']}
Dictionary Should Contain Key ${subred} id
Dictionary Should Contain Key ${subred} subnet
END
Post a new subnet
[Documentation] Post a new subnet
[Tags] subnets
${new_subnet} Create Dictionary
... subnetId=${valid_net_id}
... mask=255.255.255.0
... address=192.168.2.0
... nextServer=192.168.2.1
... bootFileName=pxelinux.0
${headers}= Create Dictionary Content-Type=application/json
${response}= POST ${BASE_URL}/subnets json=${new_subnet} headers=${headers}
Status Should Be 200 ${response}
${json}= Convert to Dictionary ${response.json()}
Dictionary Should Contain Key ${json} success
Dictionary Should Contain Key ${json} message
Should Contain ${json["success"]} Subred agregada correctamente
Should Be Equal As Numbers ${json["message"]["id"]} ${valid_net_id}
Post a new subnet with existing id
[Documentation] Post a new subnet with invalid data
[Tags] subnets
${new_subnet} Create Dictionary
... subnetId=2
... mask=255.255.255.0
... address=192.168.3.0
... nextServer=192.168.3.1
... bootFileName=pxelinux.0
${headers}= Create Dictionary Content-Type=application/json
${response}= POST ${BASE_URL}/subnets json=${new_subnet} headers=${headers} expected_status=400
Status Should Be 400 ${response}
${json}= Convert to Dictionary ${response.json()}
Dictionary Should Contain Key ${json} error
Should Contain ${response.json()["error"]} La subred con el id '${valid_net_id}' ya existe.
Post a new subnet with existing address
[Documentation] Post a new subnet with invalid data
[Tags] subnets
${headers}= Create Dictionary Content-Type=application/json
${new_subnet_invalid} Create Dictionary
... subnetId=3
... mask=255.255.255.0
... address=192.168.1.0
... nextServer=192.168.1.1
... bootFileName=pxelinux.0
${response}= POST ${BASE_URL}/subnets json=${new_subnet_invalid} headers=${headers} expected_status=400
Status Should Be 400 ${response}
${json}= Convert to Dictionary ${response.json()}
Dictionary Should Contain Key ${json} error
Should Contain ${response.json()['error']} La subred con la dirección '192.168.1.0/24' ya existe
Modify an existing subnet by id
[Documentation] Modify a subnet by id
[Tags] subnets
${headers}= Create Dictionary Content-Type=application/json
${modified_subnet}= Create Dictionary
... mask=255.255.192.0
... address=192.168.1.0
... nextServer=192.168.1.1
... bootFileName=pxelinux.0
# Modificar la subred con id=2
${response}= PUT ${BASE_URL}/subnets/2 json=${modified_subnet} headers=${headers} expected_status=200
Status Should Be 200 ${response}
${json}= Convert to Dictionary ${response.json()}
# Verificar que la respuesta contiene clave 'success'
Dictionary Should Contain Key ${json} success
# Validar el contenido del mensaje de éxito
Should Be Equal ${json['success']} Subred modificada correctamente
Should Be Equal ${json['message']['id']} 2
Modify a subnet with invalid id
[Documentation] Este test verifica que la modificación de una subred falla cuando el ID no existe.
[Tags] subnets
${headers}= Create Dictionary Content-Type=application/json
${modificar_subred}= Create Dictionary mask=255.255.192.0 address=192.168.1.0 nextServer=192.168.1.1 bootFileName=pxelinux.0
${response}= PUT ${BASE_URL}/ogdhcp/v1/subnets/${invalid_net_id} json=${modificar_subred} headers=${headers} expected_status=404
# Verificar código de estado HTTP
Should Be Equal As Numbers ${response.status_code} 404
Modify subnet with error to save configuration
[Documentation] Este test verifica que la modificación falla cuando hay un error al guardar la configuración en Kea DHCP.
[Tags] subnets
${headers}= Create Dictionary Content-Type=application/json
${modificar_subred}= Create Dictionary subnet="192.168.1.0" mask="255.255.255.0" nextServer="192.168.1.1" bootFileName="pxelinux.0"
${response}= PUT ${BASE_URL}/subnets/${subnet_error} json=${modificar_subred} headers=${headers} expected_status=400
# Verificar código de estado HTTP
Should Be Equal As Numbers ${response.status_code} 400
${json}= Convert To Dictionary ${response.json()}
# Verificar que la respuesta contiene el error esperado
Should Be Equal ${json['error']} Error al guardar la configuración en Kea DHCP: Unable to save configuration
Modify subnet with invalid configuration address without netmask
[Documentation] Este test verifica que la modificación falla cuando hay un error en la configuración de Kea DHCP.
[Tags] subnets
${headers}= Create Dictionary Content-Type=application/json
${modificar_subred}= Create Dictionary address="192.168.1.0" nextServer="192.168.1.1" bootFileName="pxelinux.0"
${response}= PUT ${BASE_URL}/subnets/2 json=${modificar_subred} headers=${headers} expected_status=400
# Verificar código de estado HTTP
Should Be Equal As Numbers ${response.status_code} 400
${json}= Convert To Dictionary ${response.json()}
# Verificar que la respuesta contiene el error esperado
Should Be Equal ${json['error']} Falta un parámetro requerido: mask
Modify subnet with invalid configuration netmask without address
[Documentation] Este test verifica que la modificación falla cuando hay un error en la configuración de Kea DHCP.
[Tags] subnets
${headers}= Create Dictionary Content-Type=application/json
${modificar_subred}= Create Dictionary mask="255.255.255.0" nextServer="192.168.1.1" bootFileName="pxelinux.0"
${response}= PUT ${BASE_URL}/subnets/2 json=${modificar_subred} headers=${headers} expected_status=400
# Verificar código de estado HTTP
Should Be Equal As Numbers ${response.status_code} 400
${json}= Convert To Dictionary ${response.json()}
# Verificar que la respuesta contiene el error esperado
Should Be Equal ${json['error']} Falta un parámetro requerido: address
Delete subnet by id
[Documentation] Este test verifica que la subred se elimina correctamente con el código 200.
[Tags] subnets
${response}= DELETE ${BASE_URL}/subnets/${valid_net_id}
# Verificar código de estado HTTP
Should Be Equal As Numbers ${response.status_code} 200
${json}= Convert To Dictionary ${response.json()}
# Verificar que la respuesta contiene la clave 'success'
Dictionary Should Contain Key ${json} success
# Validar el mensaje de éxito
Should Be Equal ${json['success']} Subred eliminada correctamente
Delete subnet - Error: wrong subnet Id
[Documentation] Este test verifica que la eliminación falla si la subred con el ID no existe.
[Tags] subnets
${response}= DELETE ${BASE_URL}/subnets/${invalid_net_id} expected_status=404
# Verificar código de estado HTTP
Should Be Equal As Numbers ${response.status_code} 404
${json}= Convert To Dictionary ${response.json()}
# Verificar que la respuesta contiene la clave 'error'
Dictionary Should Contain Key ${json} error
# Validar el mensaje de error
Should Be Equal ${json['error']} La subred con el id '999' no existe