diff --git a/board/netconf/rootfs/libexec/infix/yanger b/board/netconf/rootfs/libexec/infix/yanger index 5cbe411c..6bc655e2 100755 --- a/board/netconf/rootfs/libexec/infix/yanger +++ b/board/netconf/rootfs/libexec/infix/yanger @@ -55,6 +55,14 @@ def json_get_yang_origin(addr): return xlate.get(proto, "other") +def getitem(data, key): + """Get sub-object from an object""" + while key: + data = data[key[0]] + key = key[1:] + + return data + def get_proc_value(procfile): """Return contents of /proc file, or None""" try: @@ -438,36 +446,57 @@ def get_bridge_port_stp_state(ifname, iface_out, test): return None +def container_inspect(name, key): + """Call podman inspect {name}, return object at {path} or None""" + cmd = ['podman', 'inspect', name] + raw = run_json_cmd(cmd) + + return getitem(raw[0], key) + def add_container(containers): - """In container-state we list *all* containers, not just ones managed in configuration.""" + """In container-state we list *all* containers, not just ones manged in configuration.""" cmd = ['podman', 'ps', '-a', '--format=json'] raw = run_json_cmd(cmd) for entry in raw: - container = {} - container["name"] = entry["Names"][0] - container["id"] = entry["Id"] - container["image"] = entry["Image"] - container["image_id"] = entry["ImageID"] - container["running"] = entry["State"] == "running" - container["status"] = entry["Status"] + running = entry["State"] == "running" + + container = { + "name": entry["Names"][0], + "id": entry["Id"], + "image": entry["Image"], + "image-id": entry["ImageID"], + "running": running, + "status": entry["Status"] + } # Bonus information, may not be available if entry["Command"]: container["command"] = " ".join(entry["Command"]) - if entry["Networks"]: - container["networks"] = entry["Networks"] + # The 'podman ps' command lists ports even in host mode, but + # that's not applicable, so skip networks and port forwardings + networks = container_inspect(container["name"], ("NetworkSettings", "Networks")) + if "host" in networks: + container["network"] = { "host": True } + else: + container["network"] = { + "interface": [], + "publish": [] + } - if entry["Ports"]: - container["ports"] = [] - for port in entry["Ports"]: - addr = "" - if port["host_ip"]: - addr = f"{port['host_ip']}:" + if entry["Networks"]: + for net in entry["Networks"]: + container["network"]["interface"].append({ "name": net }) - publish = f"{addr}{port['host_port']}:{port['container_port']}/{port['protocol']}" - container["ports"].append(publish) + if running and entry["Ports"]: + for port in entry["Ports"]: + addr = "" + if port["host_ip"]: + addr = f"{port['host_ip']}:" + + pub = f"{addr}{port['host_port']}->{port['container_port']}/{port['protocol']}" + container["network"]["publish"].append(pub) containers.append(container) @@ -825,11 +854,11 @@ if __name__ == "__main__": elif args.model == 'infix-containers': yang_data = { - "infix-containers:container-state": { + "infix-containers:containers": { "container": [] } } - add_container(yang_data['infix-containers:container-state']['container']) + add_container(yang_data['infix-containers:containers']['container']) else: print(f"Unsupported model {args.model}", file=sys.stderr) diff --git a/src/confd/src/infix-containers.c b/src/confd/src/infix-containers.c index 732ac177..aba94699 100644 --- a/src/confd/src/infix-containers.c +++ b/src/confd/src/infix-containers.c @@ -16,14 +16,14 @@ #include "core.h" #define ARPING_MSEC 1000 #define MODULE "infix-containers" -#define CFG_XPATH "/infix-containers:container" +#define CFG_XPATH "/infix-containers:containers" #define INBOX_QUEUE "/run/containers/inbox" #define JOB_QUEUE "/run/containers/queue" #define ACTIVE_QUEUE "/var/lib/containers/active" #define LOGGER "logger -t container -p local1.notice" static const struct srx_module_requirement reqs[] = { - { .dir = YANG_PATH_, .name = MODULE, .rev = "2023-12-14" }, + { .dir = YANG_PATH_, .name = MODULE, .rev = "2024-02-01" }, { NULL } }; @@ -31,7 +31,7 @@ static int add(const char *name, struct lyd_node *cif) { const char *image = lydx_get_cattr(cif, "image"); const char *restart_policy, *string; - struct lyd_node *node; + struct lyd_node *node, *network; FILE *fp, *ap; char *restart = ""; /* Default restart:10 */ @@ -56,9 +56,6 @@ static int add(const char *name, struct lyd_node *cif) if ((string = lydx_get_cattr(cif, "hostname"))) fprintf(fp, " --hostname %s", string); - LYX_LIST_FOR_EACH(lyd_child(cif), node, "publish") - fprintf(fp, " -p %s", lyd_get_value(node)); - if (lydx_is_enabled(cif, "read-only")) fprintf(fp, " --read-only"); @@ -102,30 +99,37 @@ static int add(const char *name, struct lyd_node *cif) fprintf(fp, " -e /run/containers/args/%s.env", name); } - LYX_LIST_FOR_EACH(lyd_child(cif), node, "network") { - struct lyd_node *opt; - const char *name; - int first = 1; + network = lydx_get_descendant(lyd_child(cif), "network", NULL); + if (network) { + if (lydx_is_enabled(network, "host")) { + fprintf(fp, " --net host"); + } else { + LYX_LIST_FOR_EACH(lyd_child(network), node, "interface") { + struct lyd_node *opt; + const char *name; + int first = 1; - name = lydx_get_cattr(node, "name"); - fprintf(fp, " --net %s", name); - LYX_LIST_FOR_EACH(lyd_child(node), opt, "option") { - const char *option = lyd_get_value(opt); + name = lydx_get_cattr(node, "name"); + fprintf(fp, " --net %s", name); + LYX_LIST_FOR_EACH(lyd_child(node), opt, "option") { + const char *option = lyd_get_value(opt); - fprintf(fp, "%s%s", first ? ":" : ",", option); - first = 0; + fprintf(fp, "%s%s", first ? ":" : ",", option); + first = 0; + } + } + + LYX_LIST_FOR_EACH(lyd_child(network), node, "publish") + fprintf(fp, " -p %s", lyd_get_value(node)); } } - if (lydx_is_enabled(cif, "host-network")) - fprintf(fp, " --net host"); - - if ((string = lydx_get_cattr(cif, "entrypoint"))) + if ((string = lydx_get_cattr(cif, "command"))) fprintf(fp, " --entrypoint"); fprintf(fp, " create %s %s", name, image); - if ((string = lydx_get_cattr(cif, "entrypoint"))) + if ((string = lydx_get_cattr(cif, "command"))) fprintf(fp, " %s", string); fprintf(fp, "\n"); @@ -163,15 +167,26 @@ static int add(const char *name, struct lyd_node *cif) static int del(const char *name) { - char fn[strlen(JOB_QUEUE) + strlen(name) + 5]; + const char *queue[] = { + JOB_QUEUE, + INBOX_QUEUE, + ACTIVE_QUEUE, + }; + int rc; /* Remove any pending download/create job first */ - snprintf(fn, sizeof(fn), "%s/%s.sh", JOB_QUEUE, name); - erase(fn); - snprintf(fn, sizeof(fn), "%s/%s.sh", INBOX_QUEUE, name); - erase(fn); + for (size_t i = 0; i < NELEMS(queue); i++) { + char fn[strlen(queue[i]) + strlen(name) + 5]; - return systemf("container delete %s", name); + snprintf(fn, sizeof(fn), "%s/%s.sh", queue[i], name); + erase(fn); + } + + systemf("initctl -nbq disable container:%s", name); + rc = systemf("container delete %s", name); + erasef("/etc/finit.d/available/container:%s.conf", name); + + return rc; } static int change(sr_session_ctx_t *session, uint32_t sub_id, const char *module, @@ -198,16 +213,14 @@ static int change(sr_session_ctx_t *session, uint32_t sub_id, const char *module if (err) goto err_release_data; - cifs = lydx_get_descendant(cfg->tree, "container", "container", NULL); - difs = lydx_get_descendant(diff, "container", "container", NULL); + cifs = lydx_get_descendant(cfg->tree, "containers", "container", NULL); + difs = lydx_get_descendant(diff, "containers", "container", NULL); /* find the modified one, delete or recreate only that */ LYX_LIST_FOR_EACH(difs, dif, "container") { const char *name = lydx_get_cattr(dif, "name"); - ERROR("Change in container %s", name); if (lydx_get_op(dif) == LYDX_OP_DELETE) { - ERROR("OP DELETE container %s", name); del(name); continue; } @@ -215,19 +228,13 @@ static int change(sr_session_ctx_t *session, uint32_t sub_id, const char *module LYX_LIST_FOR_EACH(cifs, cif, "container") { const char *nm = lydx_get_cattr(cif, "name"); - ERROR("container %s vs %s", name, nm); - if (strcmp(name, nm)) { - ERROR("Skipping container %s", nm); + if (strcmp(name, nm)) continue; - } - if (!lydx_is_enabled(cif, "enabled")) { - ERROR("container %s not enabled", nm); + if (!lydx_is_enabled(cif, "enabled")) del(name); - } else { - ERROR("container %s enabled", nm); + else add(name, cif); - } break; } } @@ -250,7 +257,7 @@ static int action(sr_session_ctx_t *session, uint32_t sub_id, const char *xpath, char *cmd, *name, *ptr; char quote; - /* /infix-containers:container/container[name='ntpd']/restart */ + /* /infix-containers:containers/container[name='ntpd']/restart */ strlcpy(buf, xpath, sizeof(buf)); name = strstr(buf, "[name="); @@ -271,7 +278,7 @@ static int action(sr_session_ctx_t *session, uint32_t sub_id, const char *xpath, return SR_ERR_INTERNAL; cmd += 2; - ERROR("CALLING 'container %s %s' (xpath %s)", cmd, name, xpath); + DEBUG("CALLING 'container %s %s' (xpath %s)", cmd, name, xpath); if (systemf("container %s %s", cmd, name)) return SR_ERR_INTERNAL; @@ -309,7 +316,7 @@ void infix_containers_launch(void) ERRNO("Failed moving %s to job queue %s", next, JOB_QUEUE); } - systemf("container -f volume prune"); + systemf("container volume prune -f"); } int infix_containers_init(struct confd *confd) diff --git a/src/confd/yang/infix-containers@2023-12-14.yang b/src/confd/yang/infix-containers@2024-02-01.yang similarity index 72% rename from src/confd/yang/infix-containers@2023-12-14.yang rename to src/confd/yang/infix-containers@2024-02-01.yang index b268409d..bb33d5f2 100644 --- a/src/confd/yang/infix-containers@2023-12-14.yang +++ b/src/confd/yang/infix-containers@2024-02-01.yang @@ -22,7 +22,7 @@ module infix-containers { prefix infix-if; } - revision 2023-12-14 { + revision 2024-02-01 { description "Initial revision"; reference "internal"; } @@ -52,45 +52,25 @@ module infix-containers { * Data Nodes */ - container container { + container containers { list container { key "name"; - leaf name { - description "Name of the container"; - type string; - } - leaf enabled { description "Enable or disable a container configuration."; type boolean; default true; } - list env { - description "Set environment variables, key=\"value\" pairs."; - key key; - - leaf key { - description "Single word, [A-Za-z_]"; - type string; - } - - leaf value { - description "Argument to key can be a single word or quoted multiple words."; - mandatory true; - type string; - } - } - - leaf entrypoint { - description "Override the default ENTRYPOINT from the image."; + leaf name { + description "Name of the container"; type string; } - leaf hostname { - description "Sets the container host name that is available inside the container."; - type inet:domain-name; + leaf id { + description "Container ID, unique hash."; + config false; + type string; } leaf image { @@ -110,6 +90,38 @@ module infix-containers { type string; } + leaf image-id { + description "Docker image ID, exact hash used."; + config false; + type string; + } + + list env { + description "Set environment variables, key=\"value\" pairs."; + key key; + + leaf key { + description "Single word, [A-Za-z_]"; + type string; + } + + leaf value { + description "Argument to key can be a single word or quoted multiple words."; + mandatory true; + type string; + } + } + + leaf command { + description "Override ENTRYPOINT from image and run command + args."; + type string; + } + + leaf hostname { + description "Sets the container host name that is available inside the container."; + type inet:domain-name; + } + leaf restart-policy { description "Restart policy to when containers exit/crash."; type restart-policy; @@ -121,56 +133,56 @@ module infix-containers { type boolean; } - choice network { + container network { description "Select network mode: none, host, or container network interfaces."; - case host-network { - leaf host-network { - description "Run in same network namespace as host."; - type boolean; - } - } + leaf host { + description "Run in same network namespace as host, share DNS and publish all ports."; + type boolean; + } - case network { - list network { - description "Container network interface to connect to the container."; - key name; + list interface { + description "Container network interface(s) to connect to the container."; + key name; - leaf name { - description "CNI network to connect to the container."; - type if:interface-ref; - must "/if:interfaces/if:interface[if:name = current()]/infix-if:container-network" { - error-message "Container networks must be interfaces classified as container-network."; - } - } + leaf name { + description "CNI network (interface name) to connect to the container."; + type if:interface-ref; + must "/if:interfaces/if:interface[if:name = current()]/infix-if:container-network" { + error-message "Container networks must be interfaces classified as container-network."; + } + } - leaf-list option { - description "Options for CNI bridges. + leaf-list option { + description "Options for CNI bridges. Example: ip=1.2.3.4 to request a specific IP, both IPv4 and IPv6. interface_name=foo0 name to set interface name inside container."; - type string; - } - } + type string; + } + } - leaf-list publish { - description "Publish container port, or a range of ports, to the host. + leaf-list publish { + description "Publish container port, or a range of ports, to the host. Syntax: [[ip:][hostPort]:]containerPort[/protocol] Sample: 8080:80 -- forward tcp port 8080 to container port 80 - 69:69/udp -- forward udp port 69 to container port 69 - 127.0.0.1:8080:80 -- forward only from loopback interface"; - type string; - } + 69:69/udp -- forward udp port 69 to container port 69 + 127.0.0.1:8080:80 -- forward only from loopback interface"; + type string; + } - leaf-list dns { - description "Set custom DNS servers, or 'none' to use /etc/resolv.conf in image."; - type string; - } + leaf-list dns { + description "Set custom DNS servers, or 'none' to use /etc/resolv.conf in image."; + type string; + } - leaf-list search { - description "Set custom DNS search domains, or '.' to not set search domain."; - type string; - } - } + leaf-list search { + description "Set custom DNS search domains, or '.' to not set search domain."; + type string; + } + + must "(host and not(interface)) or (not(host) and interface) or (not(host) and not(interface))" { + error-message "Host and interfaces are mutually exclusive"; + } } leaf read-only { @@ -231,72 +243,29 @@ module infix-containers { } } - action start { - description "Start a stopped container."; - } - action stop { - description "Stop a running container."; - } - action restart { - description "Restart a running, or start, a stopped container."; - } - } - } - - /* - * Operational state data nodes - */ - - container container-state { - config false; - - list container { - key "name"; - - leaf name { - description "Name of the container."; - type string; - } - - leaf id { - description "Container ID, unique hash."; - type string; - } - - leaf comand { - description "Command being run by container."; - type string; - } - - leaf image { - description "Docker image used."; - type string; - } - - leaf image_id { - description "Docker image ID, exact hash used."; - type string; - } - - leaf-list networks { - description "CNI networks attached to container."; - type string; - } - - leaf-list ports { - description "Exposed ports: [[ip:][hostPort]:]containerPort[/protocol]"; - type string; - } - leaf running { description "Status of container, running or not."; + config false; type boolean; } leaf status { description "Status of container, human friendly."; + config false; type string; } + + action start { + description "Start a stopped container."; + } + + action stop { + description "Stop a running container."; + } + + action restart { + description "Restart a running, or start, a stopped container."; + } } } } diff --git a/src/statd/statd.c b/src/statd/statd.c index f4bb494f..b310f2d8 100644 --- a/src/statd/statd.c +++ b/src/statd/statd.c @@ -40,7 +40,7 @@ #define XPATH_ROUTING_TABLE "/ietf-routing:routing/ribs" #define XPATH_HARDWARE_BASE "/ietf-hardware:hardware" #define XPATH_ROUTING_OSPF XPATH_ROUTING_BASE "/ospf" -#define XPATH_CONTAIN_BASE "/infix-containers:container-state" +#define XPATH_CONTAIN_BASE "/infix-containers:containers" TAILQ_HEAD(sub_head, sub);