<?php

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

class device_ceos extends device
{
    public function createEthernets($quantity)
    {
        $ethernets = [];
        for ($i = 0; $i < $quantity; $i++) {
            if (!isset($this->ethernets[$i])) {
                if ($i == 0) {
                    $n = "Mgmt/eth0";
                } else {
                    $n = "et" . $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["console"])) {
            $this->console = (string) $p["console"];
        }
        if (isset($p["ETBA"])) {
            $this->ETBA = (string) $p["ETBA"];
        }
        if (isset($p["EOS_PLATFORM"])) {
            $this->EOS_PLATFORM = (string) $p["EOS_PLATFORM"];
        }

        if (isset($p["eth0_dhcp"])) {
            $this->eth0_dhcp = (int) $p["eth0_dhcp"];
        }
        if (isset($p["eth0_ip"])) {
            $this->eth0_ip = (string) $p["eth0_ip"];
        }
        if (isset($p["console_2nd"])) {
            $this->console_2nd = (string) $p["console_2nd"];
        }
        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,
            "console" => $this->console,
            "console_2nd" => $this->console_2nd,
            "ETBA" => $this->ETBA,
            "EOS_PLATFORM" => $this->EOS_PLATFORM,
            "eth0_dhcp" => $this->eth0_dhcp,
            "eth0_ip" => $this->eth0_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 == "ssh") {
                $connPort = 22;
            } else {
                $connPort = 23;
            }
        } else {
            $connPort = (int) $this->map_port;
        }
        if ($this->map_port_2nd == "") {
            if ($this->console_2nd == "ssh") {
                $connPort2nd = 22;
            } 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 = "";
                $consoleCmd2nd = "";
            } elseif ($connPort !== 23 && $connPort2nd !== 23) {
                $consoleCmd =
                    "--net=bridge -p " .
                    $this->getPort() .
                    ":" .
                    $connPort .
                    " -p " .
                    $this->getSecondPort() .
                    ":" .
                    $connPort2nd;
            } elseif ($connPort == 23 && $connPort2nd !== 23) {
                $consoleCmd2nd =
                    " -p --net=bridge " .
                    $this->getSecondPort() .
                    ":" .
                    $connPort2nd;
            } elseif ($connPort !== 23 && $connPort2nd == 23) {
                $consoleCmd2nd =
                    "--net=bridge -p " . $this->getPort() . ":" . $connPort;
            }

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

            $setenv =
                " /sbin/init systemd.setenv=INTFTYPE=eth systemd.setenv=ETBA=" .
                $this->ETBA .
                " systemd.setenv=SKIP_ZEROTOUCH_BARRIER_IN_SYSDBINIT=1 systemd.setenv=CEOS=1 systemd.setenv=EOS_PLATFORM=" .
                $this->EOS_PLATFORM .
                " systemd.setenv=container=docker systemd.setenv=MAPETH0=1 systemd.setenv=MGMT_INTF=eth0 ";

            $env =
                " -e INTFTYPE=eth -e ETBA=" .
                $this->ETBA .
                " -e SKIP_ZEROTOUCH_BARRIER_IN_SYSDBINIT=1 -e CEOS=1 -e EOS_PLATFORM=" .
                $this->EOS_PLATFORM .
                " -e container=docker --net=none --privileged ";
            $cmd = "docker -H=tcp://127.0.0.1:4243 create ";
            $cmd .=
                $env .
                " " .
                $this->docker_options .
                "  " .
                $consoleCmd .
                " " .
                $consoleCmd2nd .
                " --name=docker" .
                $this->getSession() .
                " " .
                ' -h "' .
                $this->name .
                '" ' .
                $this->image .
                $setenv;
            $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];

            if (file_exists($this->getRunningPath() . "/startup-config")) {
                $cmd =
                    " /usr/bin/docker -H tcp://127.0.0.1:4243 cp " .
                    $this->getRunningPath() .
                    "/startup-config " .
                    $docker_name .
                    ":/mnt/flash/";
                error_log(date("M d H:i:s ") . "INFO: starting " . $cmd);

                exec($cmd, $o, $rc);
            } elseif (
                !file_exists($this->getRunningPath() . "/initial-config")
            ) {
                copy(
                    "/opt/unetlab/startup_configs/Docker/cEOS/initial-config",
                    $this->getRunningPath() . "/initial-config"
                );
                $startup_ceos = $this->getRunningPath() . "/initial-config";
                $file_contents = file_get_contents($startup_ceos);
                $file_contents = str_replace(
                    "hostname ",
                    "hostname  $this->name  ",
                    $file_contents
                );
                file_put_contents(
                    $this->getRunningPath() . "/initial-config",
                    $file_contents
                );
                $cmd =
                    " /usr/bin/docker -H tcp://127.0.0.1:4243 cp " .
                    $this->getRunningPath() .
                    "/initial-config " .
                    $docker_name .
                    ":/mnt/flash/startup-config";
                error_log(date("M d H:i:s ") . "INFO: starting " . $cmd);

                exec($cmd, $o, $rc);
            }

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

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

                $cmd =
                    "ip link add " .
                    $ceos .
                    " type veth peer name " .
                    $vunl;
                error_log(date("M d H:i:s ") . "INFO: starting " . $cmd);
                exec($cmd, $o, $rc);
                $cmd = "ip link set dev " . $vunl . " mtu " . $this->mtu;
                error_log(date("M d H:i:s ") . "INFO: starting " . $cmd);
                exec($cmd, $o, $rc);

                $cmd = "ip link set dev " . $vunl . " up";
                error_log(date("M d H:i:s ") . "INFO: starting " . $cmd);
                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;
                error_log(date("M d H:i:s ") . "INFO: starting " . $cmd);
                exec($cmd, $o, $rc);
                if ($interface_id == 0) {
                    $cmd =
                        "ip link set netns " .
                        $pid .
                        " " .
                        $ceos .
                        " name eth0  " .
                        " address " .
                        $this->createNodeMac($interface_id) .
                        " mtu " .
                        $this->mtu .
                        " up";
                    error_log(date("M d H:i:s ") . "INFO: starting " . $cmd);
                    exec($cmd, $o, $rc);
                } else {
                    $cmd =
                        "ip link set netns " .
                        $pid .
                        " " .
                        $ceos .
                        " 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);
                }
                
                $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());
                    }
                }
            }
            $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);

            $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 ") . " starting  " . $cmd);
            exec($cmd, $o, $rc);
            $cmd =
                "/opt/unetlab/wrappers/nsenter -t " .
                $pid .
                " -m -u -i -n -p -w bash /etc/profile";
            error_log(date("M d H:i:s ") . " starting  " . $cmd);
            exec($cmd, $o, $rc);

            // Start configuration process

            $cmd = exec("sleep 5");

            //DHCP Settings
            if (
                preg_match(
                    "/([0-9]+.[0-9]+.[0-9]+.[0-9]+)\/[0-9]+/",
                    $this->eth0_ip
                )
            ) {
                $cmd =
                    "/opt/unetlab/wrappers/nsenter  -r -m -t " .
                    $pid .
                    " -n /busybox ip a add " .
                    $this->eth0_ip .
                    " dev eth0";
                exec($cmd, $o, $rc);
                error_log(date("M d H:i:s ") . "INFO: importing " . $cmd);
            } elseif ($this->eth0_dhcp) {
                $cmd =
                    "/opt/unetlab/wrappers/nsenter -r -m -t " .
                    $pid .
                    ' -n  /busybox udhcpc NODE="' .
                    $this->name .
                    '" -b -s /udhcpc.script -i eth0';
                // 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 =
                    " 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);
                $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);
            }
            //error_log(date('M d H:i:s ') . 'INFO: run ' . $cmd);
            unlink($this->getRunningPath() . "/startup-config");
            $attachCmd = "Cli";
            $attachCmd_bash = "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() .
                    "/wrapper1.txt 2>&1 &";
                exec($cmd, $o, $rc);
                error_log(date("M d H:i:s ") . "INFO: run " . $cmd);
            }
            if ($this->console == "bash") {
                $cmd =
                    "sudo /opt/unetlab/wrappers/docker_wrapper -P " .
                    $this->getPort() .
                    ' -t "' .
                    $this->name .
                    '" -p ' .
                    $this->getSession() .
                    " -c " .
                    $attachCmd_bash .
                    " > " .
                    $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 == "bash") {
                $cmd =
                    "sudo /opt/unetlab/wrappers/docker_wrapper -P " .
                    $this->getSecondPort() .
                    ' -t "' .
                    $this->name .
                    '" -p ' .
                    $this->getSession() .
                    " -c " .
                    $attachCmd_bash .
                    " > " .
                    $this->getRunningPath() .
                    "/wrapper1.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()
    {
        $docker_name = "docker" . $this->getSession();
        $cmd = exec(
            "docker -H=tcp://127.0.0.1:4243 cp " .
                $docker_name .
                ":/mnt/flash/startup-config " .
                $this->getRunningPath() .
                "/export-config"
        );

        $tmp = $this->getRunningPath() . "/export-config";

        // Now save the config file within the lab
        clearstatcache();

        $fp = fopen($tmp, "r");
        if (!isset($fp)) {
            // Cannot open file
            error_log(
                date("M d H:i:s ") . "ERROR: " . $GLOBALS["messages"][80064]
            );
            return 80064;
        }

        $config_data = fread($fp, filesize($tmp));
        if ($config_data === false || $config_data === "") {
            // Cannot read file
            error_log(
                date("M d H:i:s ") . "ERROR: " . $GLOBALS["messages"][80065]
            );
            return 80065;
        }

        $activeConfig = $this->getActiveConfig();
        if ($activeConfig == "") {
            $this->config_data = $config_data;
        } else {
            $this->multi_config[$activeConfig] = $config_data;
        }

        if (!unlink($tmp)) {
            // Failed to remove tmp file
            error_log(
                date("M d H:i:s ") . "WARNING: " . $GLOBALS["messages"][80070]
            );
        }
        return 0;
    }

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

        return parent::wipe();
    }
}
