Howto REST-API nutzen

  • Moin zusammen,

    ich bin neu bei Netcup und hab derzeit 2 nagelneue RS 2000 G12 Rootserver sowie ein VLAN mit 1 GBit.

    Beide rootserver sind frisch aus Datenübernahmegründen noch mit einem Debian bookworm installiert worden und haben eine DRBD-Partition, d.h. das wird ein active-passive-Setup.

    Das schwenken der IP und des DRBD samt Filesystem klappt serverseitig schon gut, allerdings muss noch das Routing Netcup-seitig angepasst werden und die Rest API scheint eine solche Funktion zu bieten.

    Gibt es eigentlich schon ein simples Beispiel auf Basis curl, wie man mittels Rest-Api z.B. eine Netcup-failover-IP zuordnet?

    Ich scheitere derzeit schon an der Authentifizierung bzw. bin auch verwirrt bzgl. der zu nutzenden Daten…

    UserID ist nach meinem Verständnis die Kundennummer vom SCP-Login.

    Aber was nimmt man nun als Password?

    Im SCP finde ich noch unter Optionen „REST API“, wo man die IP-Adressen einschränken kann.

    Ansonsten kann man im SCP unter Optionen noch ein Password für einen Webservice setzen, wo ich allerdings fürchte, dass es sich dabei um WDSL handelt.

    Und dann gibt es noch im CCP unter Stammdaten/_Api noch Api-Passwörter und Api-Keys generieren - da denke ich aber, dass das nicht die Rest-API für die Rootserver meint, da von Domainregistrierung gesprochen wird.


    Weiteres Vorgehen wäre nach meinem Verständnis:

    /api/v1/servers zwecks Ermittlung ServerID

    /api/v1/users/{userid}/failoverips/v4 zwecks Ermittlung id Failover-IP

    /api/v1/users/{userid}/failoverips/v4/(id failover-ip) zwecks setzen der Route (im Data dann die ServerID)

  • Ich scheitere derzeit schon an der Authentifizierung bzw. bin auch verwirrt bzgl. der zu nutzenden Daten…

    UserID ist nach meinem Verständnis die Kundennummer vom SCP-Login.

    Bei der REST-API-Dokumentation im SCP gibt es oben auf der Seite einen Bereich Authentifizierung zum Aufklappen. Dort ist u. a. beschrieben, wie Du die User-ID abrufen kannst (welche nicht die Kundennummer ist).

  • Sooo, wenn ich hier schon dusselige Fragen stelle und hilfreiche Antworten bekomme kann ich auch mal mit einem Ergebnis um die Ecke kommen...

    Das folgende Skript ändert über die Netcup-REST-API das Routing einer VirtualIP auf den jeweiligen Server.

    Dank an terence, da war dann schonmal ein Lichtschein am Ende des Tunnels.
    Credits gehen auch an den des Autor von GitHub - montilo/NetcupHomeIPUpdater: Updates Netcup Firewall Rules with Home-IP · GitHub für das Beispiel für die Authentifizierung (--setup und --whoami) sowie an Google Gemini für die Untersttützung.

    Ich muss dazu sagen, dass ich zwar schon viel Software geschrieben habe, aber mich noch nie zuvor mit Python-Programmierung beschäftigt habe - letztlich hat es nur ein paar Stunden gedauert.

    Freue mich auf Anregungen und Verbesserungsvorschläge.

    ---

    Bedienungsanleitung


    1. Voraussetzungen installieren

    Das Skript benötigt python3, das Modul requests für die API-Kommunikation und PyYAML für das Einlesen der Konfiguration.

    Bash


    Code
    # Systempakete aktualisieren (optional)
    apt update && apt install python3 python3-pip -y
    
    # Notwendige Bibliotheken installieren
    pip3 install requests pyyaml

    2. Die Datei config.yaml erstellen

    Erstelle im selben Verzeichnis wie dein Skript eine Datei namens config.yaml. Du kannst diesen Inhalt als Vorlage kopieren:

    YAML

    Code
    # Pfad, in dem das dauerhafte Login-Token gespeichert wird
    refresh_token_file: "~/.netcup_refresh_token"
    
    # Deine interne Netcup User-ID (sollte durch --setup automatisch befüllt werden, sonst --whoami)
    user_id: 0
    
    # Standard-Werte für den schnellen Wechsel mit --assign (optional)
    vip_id: "123.123.123.123"   # Deine Failover-IP oder deren interne ID
    server_id: "v....."    # Dein Ziel-Servername oder dessen interne ID

    3. Ersteinrichtung (Authentifizierung)

    Bevor das Skript IPs schalten kann, muss es sich bei Netcup autorisieren.

    1. Führe das Skript mit dem Setup-Flag aus:

      Bash

      Code
      python3 NETCUP_FailOverIPV4.py --setup
    2. Das Skript gibt dir eine URL und einen Code aus.
    3. Öffne die URL im Browser, logge dich in dein Netcup SCP ein und bestätige den Zugriff.
    4. Gehe zurück zum Terminal und drücke Enter.
    5. Wichtig: Das Skript versucht nun automatisch deine user_id aus deinen letzten Tasks zu lesen und in die config.yaml zu schreiben.

    4. Testen der Konfiguration

    Um sicherzugehen, dass alles funktioniert, kannst du deine Daten abfragen:

    • Wer bin ich? (Zeigt User-ID und Name):

      Bash

      Code
      python3 NETCUP_FailOverIPV4.py --whoami
    • Failover-IPs auflisten:

      Bash

      Code
      python3 NETCUP_FailOverIPV4.py --list-vips
    • Server-IDs finden:

      Bash

      Code
      python3 NETCUP_FailOverIPV4.py --list-servers

    5. Anwendung im Failover-Fall

    Wenn alles konfiguriert ist, reichen folgende Befehle:

    • Manueller Wechsel (überschreibt Config):

      Bash

      Code
      python3 NETCUP_FailOverIPV4.py --assign 1.2.3.4 MeinZielServer
    • Automatischer Wechsel (nutzt vip_id/server_id aus der Config):

      Bash

      Code
      python3 NETCUP_FailOverIPV4.py --assign

    Sicherheitshinweise:

    • Dateiberechtigungen: Da das Skript ein Authentifizierungs-Token speichert, sollte der Zugriff eingeschränkt sein:

      Bash

      Code
      chmod 600 ~/.netcup_refresh_token
      chmod 600 config.yaml
    • Rate-Limit: Beachte, dass durch die eingebaute Sperre von 301 Sekunden (ca. 5 Minuten) ein "Ping-Pong" zwischen zwei Servern verhindert wird. Sollte ein Wechsel fehlschlagen, musst du diese Zeit abwarten, bevor das Skript die API erneut anspricht.

    ---

    Skript im Anhang...

  • Danke fürs Teilen!

    Leute, die (zusätzlich) IPv6 verwenden, könnten das Skript vermutlich einfach duplizieren und die folgenden Zeilen entsprechend anpassen (ohne Gewähr - ich hab das Skript nur kurz überflogen):

    Code
     ➤ grep -n -i v4 NETCUP_FailOverIPV4.py                                    
    4:Zuweisung von IPv4 Failover-Adressen via PATCH.
    114:   url = f"{API_BASE}/users/{uid}/failoverips/v4"
    156:   url = f"{API_BASE}/users/{uid}/failoverips/v4/{target_ip.get('id')}"

    Die config-Datei müsste natürlich auch dupliziert werden.

    EDIT: Es muss an einigen Stellen wohl auch noch ip durch networkPrefix ersetzt werden, da die Datenstrukturen der Operationen für IPv4 und IPv6 unterschiedlich sind. Eventuell gibt es noch weiteren Anpassungsbedarf.

    [Suche] RS Piccolo gesucht

    2 Mal editiert, zuletzt von terence (19. März 2026 um 19:36)

  • Beim Ausführen habe ich folgenden Fehler bekommen:

    Code
     ➤ python3 NETCUP_FailOverIPV4.py --whoami
     File "/home/sam/bin/nc-failover-ipv4/NETCUP_FailOverIPV4.py", line 159
       resp = requests.patch(url, 
                                 ^
    SyntaxError: invalid non-printable character U+00A0

    Ließ sich durch Löschen des Zeichens aber leicht beheben.


    Bei list-servers würde ich noch den nickname des Server hinzufügen (Zeile 204):

    Code
    print(f"{s.get('id'):<15} {s.get('name')}\t{s.get('nickname')}")
  • Ist es korrekt, daß ich "Zusätzliche IPv4-Adressen" bei GET /api/v1/users/{userId}/failoverips/v4 zurückbekomme? Kann man die auch über die API umhängen? Das kostet doch normalerweise eine Bearbeitungsgebühr, oder nicht?

  • terence Afaik gibt es einen Sonderfall, der kostenlos ist: Wenn der vServer gekündigt wurde, dem sie zugewiesen war, ist sie wieder frei zuweisbar.

    Vielleicht taucht sie deshalb auf? Weil technisch gesehen ist das dann wahrscheinlich die gleiche Schnittstelle im SCP.

    "Wer nur noch Enten sieht, hat die Kontrolle über seine Server verloren." (Netzentenfund)

    Einmal editiert, zuletzt von KB19 (19. März 2026 um 20:45)

  • Vielleicht liefert der Endpunkt einfach alle Failover- und Zusatzadressen zurück. Erstere habe ich nicht, so daß ich nicht prüfen kann, ob es einen Unterschied gibt. Bei meinen Adressen kommt "editable": false zurück, was möglicherweise bedeutet, daß man sie nicht per API zuweisen kann (weil es Zusatzadressen sind - einem Server zugewiesen sind sie nicht). Leider ist das nicht weiter dokumentiert. Mehr als "Get all failover IPv4s of this user." steht da nicht.

    [Suche] RS Piccolo gesucht

    Einmal editiert, zuletzt von terence (19. März 2026 um 22:17)

  • Ich kann das mit den Zusatzadressen leider nicht prüfen, ich nutze derzeit nur Failover...

    Am ehesten kann man noch so agieren, dass nur die VirtualIPs mit "editable = true" ausgegeben werden - dann sieht man die Zusatzadressen bei --list-vips nicht.

    Was die Nicknames angeht - die kann ich zusätzlich bei --list-servers ausgeben, aber nur als Info.

    Nur der Netcup-Servernamen und die ServerID sind aus meiner Sicht so eindeutlig, dass man sie als eineindeutigen Parameter für das Setzen des Routings verwenden kann, wobei auch der Servername letztlich intern auf die ServerID zurückgeführt wird. Die Nicknames können theoretisch mehrfach existieren oder?

  • Am ehesten kann man noch so agieren, dass nur die VirtualIPs mit "editable = true" ausgegeben werden - dann sieht man die Zusatzadressen bei --list-vips nicht.

    Macht vermutlich Sinn, wenn editable das Unterscheidungskriterium ist und man die Zusatzadressen nicht per API zuordnen kann. Ggf. könnte man die Entscheidung auch dem Benutzer überlassen mittels eines Übergabeparameters und/oder eines Eintrags in der Konfigurationsdatei.

    Die Nicknames können theoretisch mehrfach existieren oder?

    Ja, habe ich eben kurz ausprobiert.

    [Suche] RS Piccolo gesucht

    Einmal editiert, zuletzt von terence (20. März 2026 um 18:34)

  • Soooo, jetzt wird bei --list-servers auch der Nickname angezeigt. Der Nickname ist nur zur Info gedacht, kann also bei assign nicht als Referenz genutzt werden, da nicht eineindeutig.

    Bei --list-vips werden jetzt nur noch die VirtualIPs angezeigt, nicht die zusätzlichen IPs (editable: false). Ich denke, man in solchen Fällen auch über das SCP die zusätzliche IP zuordnen und braucht dafür nicht ein Skript, dass eigentlich für Failover-Situationen gedacht ist.

  • Aktuell bastel ich auf der gleichen Basis noch an einer Fence-Funktion...
    Soll ein POWEROFF auslösen, nach meinem Verständnis der Doku geht das über /servers/{server_id}, und dann setzen von "state" auf "OFF" und "stateOption" auf "POWEROFF".

    Sieht im Moment so aus:

    ----------------------------------------------------------------
    def fence_server(token: str, server_id: str):
    url = f"{API_BASE}/servers/{server_id}"
    log("WARNING", f"Sende Fencing-Signal (OFF / POWEROFF) an Server {server_id}...")

    try:
    # PATCH Request
    resp = requests.patch(url,
    headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
    json={"state": "OFF", "stateOption": "POWEROFF"},
    timeout=30)

    if resp.status_code in (200, 202, 204):
    log("SUCCESS", f"Server {server_id} erfolgreich ausgeschaltet (Status: {resp.status_code}).")
    sys.exit(0)
    else:
    log("ERROR", f"API Fehler {resp.status_code}: {resp.text}")
    sys.exit(1)
    except Exception as e:
    log("ERROR", f"Fencing Exception: {e}")
    sys.exit(1)
    ---

    Das Token ist da, die server_id ist die interne, auf die ich auch die FailoverIP routen würde.
    Ich bekomme allerdings eine Fehlermeldung:

    [2026-03-30 16:56:24] [WARNING] Sende Fencing-Signal (OFF / POWEROFF) an Server 888888...
    [2026-03-30 16:56:24] [ERROR] API Fehler 415: {"code":"error.not.supported","message":"The HTTP header Content-Type is missing or is invalid"}

    Also es kommt von der REST-API ein Fehler 415 zurück, die Meldung ist "{"code":"error.not.supported","message":"The HTTP header Content-Type is missing or is invalid"}"

    Natürlich habe ich den Content-Type gesetzt...

    Hat irgendwer eine Idee?

  • Dank Dir terence, das war genau richtig ;)


    Anschließend bekam ich:

    [2026-03-31 11:03:48] [ERROR] API Fehler 400: {"code":"server.patch.oneattribute","message":"Only one attribute at a time is allowed."}

    Was auf die Unmöglichkeit hinweist, sowohl state = OFF als auch stateOption = POWEROFF in einem Patch-Request gleichzeitig zu setzen.


    stateOption = POWEROFF allein hat er nicht gefressen, aber state = OFF hat den erwünschten Effekt erzielt, d.h. der Server mit der übergebenen ServerID war sofort aus und musste über das SCP neu gestartet werden. Über Stateoption denke ich jetzt erstmal nicht mehr nach.

    ---

    def fence_server(token: str, server_id: str):
    url = f"{API_BASE}/servers/{server_id}"
    log("WARNING", f"Sende Fencing-Signal (OFF) an Server {server_id}...")

    try:
    # PATCH Request exakt wie im funktionierenden IP-Skript gestaltet:
    resp = requests.patch(url,
    headers={"Authorization": f"Bearer {token}", "Content-Type": "application/merge-patch+json"},
    json={"state": "OFF"},
    timeout=30)

    if resp.status_code in (200, 202, 204):
    log("SUCCESS", f"Server {server_id} erfolgreich ausgeschaltet (Status: {resp.status_code}).")
    sys.exit(0)
    else:
    log("ERROR", f"API Fehler {resp.status_code}: {resp.text}")
    sys.exit(1)
    except Exception as e:
    log("ERROR", f"Fencing Exception: {e}")
    sys.exit(1)
    ---

    Das kommt jetzt in die pacemaker-Config (Verzeichnisse, serverID und Servernamen sind geändert), in etwa so:

    Definition für v01 um v02 zu fencen…

    pcs stonith create fence_v02 fence_exec \
    executable="/etc/NETCUP/NETCUP_Fence.py" \
    cmdline_options="--fence 654321" \
    monitor_options="--status" \
    pcmk_host_list="v02" \
    shell_timeout=30 \
    op monitor interval=60s

    Definiton für v02 um v01 zu fencen

    pcs stonith create fence_v01 fence_exec \
    executable="/etc/NETCUP/NETCUP_Fence.py" \
    cmdline_options="--fence 123456" \
    monitor_options="--status" \
    pcmk_host_list="v01" \
    shell_timeout=30 \
    op monitor interval=60s


    pcs constraint location fence_v01 avoids v01
    pcs constraint location fence_v02 avoids v02


    Kontrolle

    pcs stonith status

  • das war genau richtig

    Gerne! Kaum macht man's richtig, funktioniert's. :)

    Was auf die Unmöglichkeit hinweist, sowohl state = OFF als auch stateOption = POWEROFF in einem Patch-Request gleichzeitig zu setzen.

    Laut OAS ist stateOption ein Query-Parameter. Probier' daher mal PATCH .../api/v1/servers/12345?stateOption=POWEROFF mit state im Request Body wie gehabt. Wobei es vermutlich keinen Unterschied macht, da es für state = OFF nur POWEROFF als Option gibt. Bei state = ON macht es da mehr Sinn, die mitzugeben.

    [Suche] RS Piccolo gesucht

    Einmal editiert, zuletzt von terence (31. März 2026 um 22:06)