<?php

/**
 *
 * @author LIN
 * @copyright pnetlab.com
 * @link https://www.pnetlab.com/
 *
 */

class device_docker extends device
{
    public function createEthernets($quantity)
    {
        $ethernets = [];
        for ($i = 0; $i < $quantity; $i++) {
            if (!isset($this->ethernets[$i])) {
                $n = "eth" . $i; // Interface name
                try {
                    $ethernets[$i] = new Interfc(
                        $this,
                        [
                            "name" => $n,
                            "type" => "ethernet",
                        ],
                        $i
                    );
                } catch (Exception $e) {
                    error_log(
                        date("M d H:i:s ") .
                            "ERROR: " .
                            $GLOBALS["messages"][40020]
                    );
                    error_log(date("M d H:i:s ") . (string) $e);
                    return 40020;
                }
            } else {
                $ethernets[$i] = $this->ethernets[$i];
            }
        }
        $this->ethernets = $ethernets;
        return $this->ethernets;
    }

    public function editParams($p)
    {
        if (isset($p["docker_options"])) {
            $this->docker_options = (string) $p["docker_options"];
        }

        if (isset($p["username"])) {
            $this->username = (string) $p["username"];
        }

        if (isset($p["password"])) {
            $this->password = (string) $p["password"];
        }

        if (isset($p["console"])) {
            $this->console = (string) $p["console"];
        }

        if (isset($p["console_2nd"])) {
            $this->console_2nd = (string) $p["console_2nd"];
        }

        if (isset($p["map_port"])) {
            $this->map_port = (string) $p["map_port"];
        }

        if (isset($p["map_port_2nd"])) {
            $this->map_port_2nd = (string) $p["map_port_2nd"];
        }

        if (isset($p["eth1_dhcp"])) {
            $this->eth1_dhcp = (int) $p["eth1_dhcp"];
        }

        if (isset($p["eth1_ip"])) {
            $this->eth1_ip = (string) $p["eth1_ip"];
        }

        if (isset($p["eth2_dhcp"])) {
            $this->eth2_dhcp = (int) $p["eth2_dhcp"];
        }

        if (isset($p["eth2_ip"])) {
            $this->eth2_ip = (string) $p["eth2_ip"];
        }

        if (isset($p["eth3_dhcp"])) {
            $this->eth3_dhcp = (int) $p["eth3_dhcp"];
        }

        if (isset($p["eth3_ip"])) {
            $this->eth3_ip = (string) $p["eth3_ip"];
        }

        if (isset($p["default_route"])) {
            $this->default_route = (string) $p["default_route"];
        }

        if (isset($p["DNS"])) {
            $this->DNS = (string) $p["DNS"];
        }

        parent::editParams($p);
    }

    public function getParams()
    {
        $params = parent::getParams();
        return array_replace($params, [
            "docker_options" => $this->docker_options,
            "username" => $this->username,
            "password" => $this->password,
            "console" => $this->console,
            "console_2nd" => $this->console_2nd,
            "map_port" => $this->map_port,
            "map_port_2nd" => $this->map_port_2nd,
            "eth1_dhcp" => $this->eth1_dhcp,
            "eth1_ip" => $this->eth1_ip,
            "eth2_dhcp" => $this->eth2_dhcp,
            "eth2_ip" => $this->eth2_ip,
            "eth3_dhcp" => $this->eth3_dhcp,
            "eth3_ip" => $this->eth3_ip,
            "default_route" => $this->default_route,
            "DNS" => $this->DNS,
        ]);
    }

    public function prepare()
    {
        $result = parent::prepare();
        if ($result != 0) {
            return $result;
        }

        if ($this->map_port == "") {
            if ($this->console == "vnc") {
                $connPort = 5900;
            } elseif ($this->console == "rdp" || $this->console == "rdp-tls") {
                $connPort = 3389;
            } elseif ($this->console == "ssh") {
                $connPort = 22;
            } elseif ($this->console == "http") {
                $connPort = 80;
            } elseif ($this->console == "https") {
                $connPort = 443;
            } else {
                $connPort = 23;
            }
        } else {
            $connPort = (int) $this->map_port;
        }

        if ($this->map_port_2nd == "") {
            if ($this->console_2nd == "vnc") {
                $connPort2nd = 5900;
            } elseif (
                $this->console_2nd == "rdp" ||
                $this->console_2nd == "rdp-tls"
            ) {
                $connPort2nd = 3389;
            } elseif ($this->console_2nd == "ssh") {
                $connPort2nd = 22;
            } elseif ($this->console_2nd == "http") {
                $connPort2nd = 80;
            } elseif ($this->console_2nd == "https") {
                $connPort2nd = 443;
            } else {
                $connPort2nd = 23;
            }
        } else {
            $connPort2nd = (int) $this->map_port_2nd;
        }

        $cmd =
            'docker -H=tcp://127.0.0.1:4243 inspect --format="{{ .State.Running }}" docker' .
            $this->getSession();
        error_log(date("M d H:i:s ") . "INFO: starting " . $cmd);

        exec($cmd, $o, $rc);
        error_log($rc);
        if ($rc != 0) {
            // Must create docker.io container
            //Check Docker Options
            if ($connPort == 23 && $connPort2nd == 23) {
                $consoleCmd = "";
            } elseif ($connPort !== 23 && $connPort2nd !== 23) {
                $consoleCmd =
                    "--net=bridge -p " .
                    $this->getPort() .
                    ":" .
                    $connPort .
                    " -p " .
                    $this->getSecondPort() .
                    ":" .
                    $connPort2nd;
            } elseif ($connPort == 23 && $connPort2nd !== 23) {
                $consoleCmd2nd =
                    "--net=bridge -p " .
                    $this->getSecondPort() .
                    ":" .
                    $connPort2nd;
            } elseif ($connPort !== 23 && $connPort2nd == 23) {
                $consoleCmd2nd =
                    "--net=bridge -p " . $this->getPort() . ":" . $connPort;
            }

            error_log($connPort2nd);

            if (!isset($this->docker_options)) {
                $this->docker_options = "";
            }

            $cmd =
                "docker -H=tcp://127.0.0.1:4243 create --env GDK_SCALE=2 --env QT_SCALE_FACTOR=1.5 -ti --memory " .
                $this->ram .
                "M ";
            if ($this->cpu > 0) {
                $cmd .= " --cpus=" . $this->cpu . " ";
            }
            $cmd .=
                $this->docker_options .
                "  " .
                $consoleCmd .
                " " .
                $consoleCmd2nd .
                " --name=docker" .
                $this->getSession() .
                ' -h "' .
                $this->name .
                '" ' .
                $this->image;
            $cmd = preg_replace("/\s+/m", " ", $cmd);
            error_log(date("M d H:i:s ") . "INFO: starting " . $cmd);
            exec($cmd, $o, $rc);

            if ($rc != 0) {
                error_log(
                    date("M d H:i:s ") . "ERROR: " . $GLOBALS["messages"][80083]
                );
                return 80083;
            }
        }

        if (!touch($this->getRunningPath() . "/.prepared")) {
            // Cannot write on directory
            error_log(
                date("M d H:i:s ") . "ERROR: " . $GLOBALS["messages"][80044]
            );
            return 80044;
        }

        return 0;
    }


    public function start()
    {
        $result = parent::start();
        if ($result != 0) {
            return $result;
        }
        $docker_name = "docker" . $this->getSession();

        $cmd =
            "docker -H=tcp://127.0.0.1:4243 start docker" . $this->getSession();
        error_log(date("M d H:i:s ") . "INFO: starting " . $cmd);
        exec($cmd, $o, $rc);
        sleep((int) $this->delay);
        if ($rc == 0) {
            $cmd =
                'docker -H=tcp://127.0.0.1:4243 inspect --format "{{ .State.Pid }}" docker' .
                $this->getSession();
            //error_log(date('M d H:i:s ').'INFO: starting '.$cmd);
            exec($cmd, $o, $rc);
            $pid = $o[1];

            foreach ($this->getEthernets() as $interface_id => $interface) {
                $vunl = "vunl" . $this->getSession() . "_" . $interface_id;
                $docker = "docker" . $this->getSession() . "_" . $interface_id;

                $cmd = "ip link delete " . $vunl;
                exec($cmd, $o, $rc);

                $cmd ="ip link add " . $docker ." type veth peer name " .$vunl;
                exec($cmd, $o, $rc);

                $cmd = "ip link set dev " . $vunl . " mtu " . $this->mtu;
                exec($cmd, $o, $rc);

                $cmd = "ip link set dev " . $vunl . " up";
                exec($cmd, $o, $rc);                $network = $this->getNetwork($interface->getNetworkId());

                if (isset($network) && $network->isCloud()) {
                    // Network is a Cloud
                    $net_name = $network->getNType();
                } elseif (
                    $network &&
                    $network->listNetworkTypes() == "internal"
                ) {
                    $net_name = "internal_" . $this->getLabSession();
                    $netName1 = $this->getNetwork(
                        $interface->getNetworkId()
                    )->addSysNetwork();
                } elseif (
                    $network &&
                    $network->listNetworkTypes() == "internal2"
                ) {
                    $net_name = "internal2_" . $this->getLabSession();
                    $netName1 = $this->getNetwork(
                        $interface->getNetworkId()
                    )->addSysNetwork();
                } elseif (
                    $network &&
                    $network->listNetworkTypes() == "internal3"
                ) {
                    $net_name = "internal3_" . $this->getLabSession();
                    $netName1 = $this->getNetwork(
                        $interface->getNetworkId()
                    )->addSysNetwork();
                } elseif (
                    $network &&
                    $network->listNetworkTypes() == "private"
                ) {
                    $net_name = "private_" . $this->getHost();
                    $netName1 = $this->getNetwork(
                        $interface->getNetworkId()
                    )->addSysNetwork();
                } elseif (
                    $network &&
                    $network->listNetworkTypes() == "private2"
                ) {
                    $net_name = "private2_" . $this->getHost();
                    $netName1 = $this->getNetwork(
                        $interface->getNetworkId()
                    )->addSysNetwork();
                } elseif (
                    $network &&
                    $network->listNetworkTypes() == "private3"
                ) {
                    $net_name = "private3_" . $this->getHost();
                    $netName1 = $this->getNetwork(
                        $interface->getNetworkId()
                    )->addSysNetwork();
                } else {
                    $net_name =
                        "vnet" .
                        $this->getLabSession() .
                        "_" .
                        $interface->getNetworkId();
                }

                $cmd = "brctl addif " . $net_name . " " . $vunl;
                exec($cmd, $o, $rc);

                $vlan = $interface->getVlanId();
                if (
                    $interface->getNetworkId() > 0 &&
                    $network->getsmart() == "1"
                ) {
                    $interface->setvlan($vlan, $interface->getNetworkId());
                    if ($network->getvlan8021ad() == "1") {
                        $interface->setvlan8021ad($interface->getNetworkId());
                    } else {
                        $interface->unsetvlan8021ad($interface->getNetworkId());
                    }
                }
                
                // ip link set netns ${PID} docker3_4_5 name eth0 address 22:ce:e0:99:04:05 up
                $cmd =
                    "ip link set netns " .
                    $pid .
                    " " .
                    $docker .
                    " name eth" .
                    $interface_id .
                    " address " .
                    $this->createNodeMac($interface_id) .
                    " mtu " .
                    $this->mtu .
                    " up";
                error_log(date("M d H:i:s ") . "INFO: starting " . $cmd);
                exec($cmd, $o, $rc);
            }
             // Start configuration process

                

            // Start configuration process
            $cmd =
                " docker -H=tcp://127.0.0.1:4243 exec -u root " .
                $docker_name .
                " umount /etc/resolv.conf ";
            error_log(date("M d H:i:s ") . "umount " . $cmd);
            exec($cmd, $o, $rc);

            touch($this->getRunningPath() . "/.lock");
            $configScript =
                $this->config_script != ""
                    ? $this->config_script
                    : (isset($this->tpl["config_script"])
                        ? $this->tpl["config_script"]
                        : "");
            $cmd =
                "nohup /opt/unetlab/config_scripts/" .
                $configScript .
                " -a put -i docker" .
                $this->getSession() .
                " -f " .
                $this->getRunningPath() .
                "/startup-config -t " .
                ($this->delay + 300) .
                " > /dev/null 2>&1 &";
            exec($cmd, $o, $rc);
            error_log(date("M d H:i:s ") . "INFO: importing " . $cmd);

            $cmd =
                " /usr/bin/docker -H tcp://127.0.0.1:4243 cp /opt/unetlab/wrappers/busybox " .
                $docker_name .
                ":/";
            error_log(date("M d H:i:s ") . "importing busybox " . $cmd);
            exec($cmd, $o, $rc);

            $cmd =
                " /usr/bin/docker -H tcp://127.0.0.1:4243 cp /opt/unetlab/wrappers/profile.sh " .
                $docker_name .
                ":/";
            error_log(date("M d H:i:s ") . "importing profile " . $cmd);
            exec($cmd, $o, $rc);

            $cmd =
                " /usr/bin/docker -H tcp://127.0.0.1:4243 cp /opt/unetlab/wrappers/udhcpc.script " .
                $docker_name .
                ":/";
            error_log(date("M d H:i:s ") . "importing udhcpc.script " . $cmd);
            exec($cmd, $o, $rc);

            $cmd =
                " /usr/bin/docker -H tcp://127.0.0.1:4243 cp /opt/unetlab/wrappers/bash-static " .
                $docker_name .
                ":/";
            error_log(date("M d H:i:s ") . "importing bash-static " . $cmd);
            exec($cmd, $o, $rc);

            $cmd =
                "/opt/unetlab/wrappers/nsenter -t " .
                $pid .
                ' -m -u -i -n -p -w /bash-static -c "/busybox ls /bin/bash 2>/dev/null || /busybox ln /bash-static /bin/bash"';
            error_log(
                date("M d H:i:s ") . " linking bash if required  " . $cmd
            );
            exec($cmd, $o, $rc);

            $cmd =
                "/opt/unetlab/wrappers/nsenter -t " .
                $pid .
                " -m -u -i -n -p -w chmod a+x /etc/profile";
            error_log(date("M d H:i:s ") . " setting  " . $cmd);
            exec($cmd, $o, $rc);

            $cmd =
                "/opt/unetlab/wrappers/nsenter -t " .
                $pid .
                " -m -u -i -n -p -w /bash-static /etc/profile";
            error_log(date("M d H:i:s ") . " setting  " . $cmd);
            exec($cmd, $o, $rc);
            $output = exec('ifconfig docker0 | grep "inet "');
            error_log($output);
            if (
                preg_match(
                    "/inet[^0-9]*([0-9]+.[0-9]+.[0-9]+.[0-9]+)/",
                    $output,
                    $match
                )
            ) {
                $docker0 = $match[1];
                $cmd =
                    "/opt/unetlab/wrappers/nsenter -r -m -t " .
                    $pid .
                    " -n /busybox route del default gw " .
                    $docker0;
                exec($cmd, $o, $rc);
                error_log(date("M d H:i:s ") . "INFO: del default gw  " . $cmd);
            }

            if (
                preg_match(
                    "/([0-9]+.[0-9]+.[0-9]+.[0-9]+)\/[0-9]+/",
                    $this->eth1_ip
                )
            ) {
                $cmd =
                    "/opt/unetlab/wrappers/nsenter  -r -m -t " .
                    $pid .
                    " -n /busybox ip a add " .
                    $this->eth1_ip .
                    " dev eth1";
                exec($cmd, $o, $rc);
                error_log(date("M d H:i:s ") . "INFO: importing " . $cmd);
            } elseif ($this->eth1_dhcp) {
                $cmd =
                    "/opt/unetlab/wrappers/nsenter -r -m -t " .
                    $pid .
                    ' -n  /busybox udhcpc NODE="' .
                    $this->name .
                    '" -b -s /udhcpc.script -i eth1';
                // error_log(date('M d H:i:s ') . ' starting  ' . $cmd);
                exec($cmd, $o, $rc);
            }

            if (
                preg_match(
                    "/([0-9]+.[0-9]+.[0-9]+.[0-9]+)\/[0-9]+/",
                    $this->eth2_ip
                )
            ) {
                $cmd =
                    "/opt/unetlab/wrappers/nsenter  -r -m -t " .
                    $pid .
                    " -n /busybox ip a add " .
                    $this->eth2_ip .
                    " dev eth1";
                exec($cmd, $o, $rc);
                // error_log(date('M d H:i:s ') . 'INFO: importing ' . $cmd);
            } elseif ($this->eth2_dhcp) {
                $cmd =
                    "/opt/unetlab/wrappers/nsenter -r -m -t " .
                    $pid .
                    ' -n  /busybox udhcpc NODE="' .
                    $this->name .
                    '" -b -s /udhcpc.script -i eth2';
                //  error_log(date('M d H:i:s ') . ' starting  ' . $cmd);
                exec($cmd, $o, $rc);
            }

            if (
                preg_match(
                    "/([0-9]+.[0-9]+.[0-9]+.[0-9]+)\/[0-9]+/",
                    $this->eth3_ip
                )
            ) {
                $cmd =
                    "/opt/unetlab/wrappers/nsenter  -r -m -t " .
                    $pid .
                    " -n /busybox ip a add " .
                    $this->eth3_ip .
                    " dev eth1";
                exec($cmd, $o, $rc);
                // error_log(date('M d H:i:s ') . 'INFO: importing ' . $cmd);
            } elseif ($this->eth3_dhcp) {
                $cmd =
                    "/opt/unetlab/wrappers/nsenter -r -m -t " .
                    $pid .
                    ' -n  /busybox udhcpc NODE="' .
                    $this->name .
                    '" -b -s /udhcpc.script -i eth3';
                error_log(date("M d H:i:s ") . " starting  " . $cmd);
                exec($cmd, $o, $rc);
            }
            if (preg_match("/([0-9]+.[0-9]+.[0-9]+.[0-9]+)/", $this->DNS)) {
                $cmd =
                    "sudo /opt/unetlab/wrappers/nsenter -r -m -t " .
                    $pid .
                    ' -n /bash-static -c "echo nameserver "' .
                    $this->DNS .
                    '" > /etc/resolv.conf" ';
                exec($cmd, $o, $rc);
                error_log(date("M d H:i:s ") . "INFO: Set Dns Server " . $cmd);
            }

            if (
                preg_match(
                    "/([0-9]+.[0-9]+.[0-9]+.[0-9]+)/",
                    $this->default_route
                )
            ) {
                $cmd =
                    "/opt/unetlab/wrappers/nsenter -r -m -t " .
                    $pid .
                    " -n /busybox route add default gw " .
                    $this->default_route;
                exec($cmd, $o, $rc);
                error_log(date("M d H:i:s ") . "INFO: importing " . $cmd);
            }
            $attachCmd = "sh";
            $cmd =
                "docker -H=tcp://127.0.0.1:4243 exec -i docker" .
                $this->getSession() .
                " ls /bin/bash";
            $o = [];
            exec($cmd, $o, $rc);
            error_log(date("M d H:i:s ") . "INFO: importing " . $cmd);
            if (count($o) > 0) {
                $attachCmd = "/bin/bash";
            }

            if ($this->console == "telnet") {
                $cmd =
                    "sudo /opt/unetlab/wrappers/docker_wrapper -P " .
                    $this->getPort() .
                    ' -t "' .
                    $this->name .
                    '" -p ' .
                    $this->getSession() .
                    " -c " .
                    $attachCmd .
                    " > " .
                    $this->getRunningPath() .
                    "/wrapper.txt 2>&1 &";
                exec($cmd, $o, $rc);
                error_log(date("M d H:i:s ") . "INFO: run " . $cmd);
            } elseif ($this->console_2nd == "telnet") {
                $cmd =
                    "sudo /opt/unetlab/wrappers/docker_wrapper -P " .
                    $this->getSecondPort() .
                    ' -t "' .
                    $this->name .
                    '" -p ' .
                    $this->getSession() .
                    " -c " .
                    $attachCmd .
                    " > " .
                    $this->getRunningPath() .
                    "/wrapper.txt 2>&1 &";
                exec($cmd, $o, $rc);
                error_log(date("M d H:i:s ") . "INFO: run " . $cmd);
            }

            $ethernets = $this->getEthernets();
            $index = 0;
            foreach ($ethernets as $ethernet) {
                $index++;
                if ($index == 1) {
                    continue;
                } // Keep eth0 up for management
                if (count($ethernet->getQuality()) > 0) {
                    $ethernet->applyQuality();
                }
                if ($ethernet->getSuspendStatus() == 1) {
                    $ethernet->applySuspendStatus();
                }
                if ($ethernet->getNetworkId() == 0) {
                    $ethernet->setLinkState("down");
                }
            }
        }

        return 0;
    }

    public function export()
    {
        // Unsupported
        error_log(date("M d H:i:s ") . "ERROR: " . $GLOBALS["messages"][80061]);
        return 80061;
    }

    public function wipe()
    {
        $cmd =
            "sudo /usr/bin/docker -H=tcp://127.0.0.1:4243 rm docker " .
            $this->getSession(). " --force ";
        exec($cmd, $o, $rc);

        return parent::wipe();
    }
}
