diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 4757721..0000000 --- a/.dockerignore +++ /dev/null @@ -1,4 +0,0 @@ -venv/ -.pytest_cache/ -.idea/ -Dockerfile diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 79f04cc..0000000 --- a/.flake8 +++ /dev/null @@ -1,3 +0,0 @@ -[flake8] -ignore = E501,W503 -exclude = migrations,venv diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml deleted file mode 100644 index 0171721..0000000 --- a/.github/workflows/build.yaml +++ /dev/null @@ -1,116 +0,0 @@ -name: Build -on: - push: - branches-ignore: - - main -jobs: - restvirt: - name: Restvirt - runs-on: self-hosted - steps: - - name: Set up Python 3.10.6 - id: python - uses: actions/setup-python@v4 - with: - python-version: 3.10.6 - - name: Check out code - uses: actions/checkout@v3 - - name: Install dependencies - run: | - sudo apt update - sudo apt install -y libvirt-dev - pip install -r requirements.txt -r requirements-dev.txt - - name: Lint - run: | - black --check --diff --color ./ - isort --check --diff --color ./ - - name: Unit Tests - run: | - tmp_dir=$(mktemp -d) - chmod -R 0711 "${tmp_dir}" - sudo iptables -P FORWARD ACCEPT - sudo ${{ steps.python.outputs.python-path }} -m pytest tests/unit - - name: Build Snap - run: | - sudo snap install --classic snapcraft - sudo usermod --append --groups lxd $(whoami) - sudo lxd init --auto - snapcraft --use-lxd - - name: Integration Tests - run: | - sudo snap install --dangerous minivirt*.snap - sudo snap connect minivirt:network-control - sudo snap connect minivirt:firewall-control - sudo snap connect minivirt:libvirt - cat << EOF | sudo tee -a /var/snap/minivirt/common/ca.crt - -----BEGIN CERTIFICATE----- - MIICCjCCAY+gAwIBAgIUSl7KWjtgvG9rNMz7hhYRKy5LsB8wCgYIKoZIzj0EAwIw - RDELMAkGA1UEBhMCREUxDzANBgNVBAgMBkJlcmxpbjERMA8GA1UECgwIbWluaXZp - cnQxETAPBgNVBAMMCG1pbml2aXJ0MB4XDTIzMDcwODIzMjUyMFoXDTMzMDcwNTIz - MjUyMFowRDELMAkGA1UEBhMCREUxDzANBgNVBAgMBkJlcmxpbjERMA8GA1UECgwI - bWluaXZpcnQxETAPBgNVBAMMCG1pbml2aXJ0MHYwEAYHKoZIzj0CAQYFK4EEACID - YgAEI3nOFzsWO3w8qGLSjDSiX3OWCH7qBRcTjt/luPjXLqe3DVcFQPLYN31PaggR - o0jCjrKklxqtzHmmMdMRnyoRPbQOQPRa9N177a2s97M5ZJQVkeFL8WRUf7x1P0Cd - SZvco0IwQDAdBgNVHQ4EFgQUde9ormRTZys4Nt81qAPcSm1qQWMwDwYDVR0TAQH/ - BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwIDaQAwZgIxAJTpUOUz - RJwMLNUEa4qIgdamMuqyl5h6ghT9zX5BLsX3cFs+MqJ/J0HcKDJd81lVlQIxAPCG - hy/zgj6wy28S8GWe8KdbZ73BJtC5MyOu6hHD9EpZ1hT8K3q0VIwMEyUMmbv3Xw== - -----END CERTIFICATE----- - EOF - cat << EOF | sudo tee -a /var/snap/minivirt/common/server.crt - -----BEGIN CERTIFICATE----- - MIICeTCCAf+gAwIBAgIUVG1gWhFwRQ5kzxwRzF6V5h6D8ZYwCgYIKoZIzj0EAwIw - RDELMAkGA1UEBhMCREUxDzANBgNVBAgMBkJlcmxpbjERMA8GA1UECgwIbWluaXZp - cnQxETAPBgNVBAMMCG1pbml2aXJ0MB4XDTIzMDcwODIzMzA0MloXDTI1MDcwNzIz - MzA0MlowRDELMAkGA1UEBhMCREUxDzANBgNVBAgMBkJlcmxpbjERMA8GA1UECgwI - bWluaXZpcnQxETAPBgNVBAMMCG1pbml2aXJ0MHYwEAYHKoZIzj0CAQYFK4EEACID - YgAEHWV9eL3/egpqcgTaMDPWga2xpfTZCc66yNxkVGPsw5BWE/EXvWtuUCjDmHWo - HOdrbt7iI9lA9VnSwlC9PeIvX4lK2dXNOpn3GJlZ8JkjpZZBg0mxaUt6vQMyGSco - cOOAo4GxMIGuMB8GA1UdIwQYMBaAFHXvaK5kU2crODbfNagD3EptakFjMAwGA1Ud - EwEB/wQCMAAwDgYDVR0PAQH/BAQDAgWgMCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMB - BggrBgEFBQcDAjAsBgNVHREEJTAjgglsb2NhbGhvc3SHBH8AAAGHEAAAAAAAAAAA - AAAAAAAAAAEwHQYDVR0OBBYEFDtlzMapZ4eV0/m8ina1GM3biELeMAoGCCqGSM49 - BAMCA2gAMGUCMECXLmHsWMTaFRK+qWaBRZMLuhFNixMsSmmHHIqGlvIrWFa5MiN6 - RaZ7aTGa/HMKZAIxAPRJZ11Vp1BNBszSiswk32hsck4JP9h1hn00IMu33iK0+q22 - Jv73oZy3l4gQmLlCDg== - -----END CERTIFICATE----- - EOF - cat << EOF | sudo tee -a /var/snap/minivirt/common/server.key - -----BEGIN PRIVATE KEY----- - MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDCIJQuTNENeEnhjCtdV - GXoFgh69LQ+Ms5B/i0jWMSqMALL26uG6NNM4nvHgIMbChKGhZANiAAQdZX14vf96 - CmpyBNowM9aBrbGl9NkJzrrI3GRUY+zDkFYT8Re9a25QKMOYdagc52tu3uIj2UD1 - WdLCUL094i9fiUrZ1c06mfcYmVnwmSOllkGDSbFpS3q9AzIZJyhw44A= - -----END PRIVATE KEY----- - EOF - sudo snap set minivirt controller.args='--debug -b :8093 -c $SNAP_COMMON --server-cert $SNAP_COMMON/server.crt --server-key $SNAP_COMMON/server.key --client-ca-cert $SNAP_COMMON/ca.crt' - sudo snap start --enable minivirt.unbound - sudo snap start --enable minivirt.controller - - sudo minivirt.register -a 127.0.0.1:8093 --name default -b 127.0.0.1:8099 --client-cert /var/snap/minivirt/common/server.crt --client-key /var/snap/minivirt/common/server.key --server-ca-cert /var/snap/minivirt/common/ca.crt - sudo snap set minivirt daemon.args='--debug -a 127.0.0.1:8093 -b 127.0.0.1:8099 -c $SNAP_COMMON --client-cert $SNAP_COMMON/server.crt --client-key $SNAP_COMMON/server.key --server-ca-cert $SNAP_COMMON/ca.crt' - sudo snap start --enable minivirt.daemon - - sleep 10 - if ! sudo ${{ steps.python.outputs.python-path }} -m pytest tests/integration ; then - sudo journalctl - exit 1 - fi -# sleep 5 -# snap services minivirt -# sudo journalctl -e -# sudo apt install -y net-tools -# sudo netstat -tulpen -# sudo ${{ steps.python.outputs.python-path }} -m pytest tests/integration -# sudo journalctl -e -# sudo netstat -tulpen -# - name: Build -# run: | -# docker build --target test -t restvirt-tests . -# docker build -t restvirt . -# - name: Test -# run: | -# tmp_dir=$(mktemp -d) -# chmod -R 0711 "${tmp_dir}" -# iptables -P FORWARD ACCEPT -# docker run --rm --cap-add=NET_ADMIN --network host -t -v /var/run/libvirt:/var/run/libvirt -v "${tmp_dir}":/data/restvirt/images restvirt-tests --pool-dir "${tmp_dir}" diff --git a/.github/workflows/virtuerl.yaml b/.github/workflows/virtuerl.yaml new file mode 100644 index 0000000..086c2a2 --- /dev/null +++ b/.github/workflows/virtuerl.yaml @@ -0,0 +1,32 @@ +name: Build +on: + push: + branches-ignore: + - main +jobs: + virtuerl: + name: virtuerl + runs-on: self-hosted + steps: + - name: Check out code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install prerequisites + run: | + sudo apt update + sudo apt install -y rebar3 g++ tree nftables ovmf swtpm + - name: Enable IP forwarding + run: | + sudo tee /etc/sysctl.d/99-local.conf << EOF + net.ipv4.ip_forward=1 + net.ipv6.conf.all.forwarding=1 + EOF + sudo service procps restart + - name: Test + run: | + sudo rebar3 ct + - name: Create tar + run: | + sudo rebar3 tar + sudo find _build/ -name '*.tar.gz' -exec hack/repack {} \; diff --git a/.gitignore b/.gitignore index 6478b2e..78647f9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,25 @@ -.idea/ -venv/ -.venv/ -__pycache__/ -*.snap -*.egg-info -*.sqlite3 +*.iml +.DS_Store +.vagrant/ +khepri*/ + +.rebar3 +_build +_checkouts +_vendor +.eunit +*.o +*.beam +*.plt +*.swp +*.swo +.erlang.cookie +ebin +log +erl_crash.dump +.rebar +logs +#.idea +#*.iml +rebar3.crashdump +*~ diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 0c1ae78..0000000 --- a/Dockerfile +++ /dev/null @@ -1,42 +0,0 @@ -FROM python:3.9.7-slim-bullseye AS base -ENV PATH="/root/.local/bin:$PATH" -WORKDIR /app - - -FROM base AS base_build -RUN apt-get update && apt-get install -y --no-install-recommends \ - gcc \ - libc-dev \ - pkg-config \ - libvirt-dev \ - git \ - && rm -rf /var/lib/apt/lists/* -COPY requirements.txt . -RUN pip install --user -r requirements.txt - - -FROM base_build as test_build -COPY requirements-dev.txt . -RUN pip install --user -r requirements-dev.txt - - -FROM base as base_run -RUN apt-get update && apt-get install -y --no-install-recommends \ - libvirt0 \ - iptables \ - libnftables1 \ - && rm -rf /var/lib/apt/lists/* - - -FROM base_run as test -COPY --from=test_build /root/.local /root/.local -COPY . . -ENTRYPOINT ["/usr/bin/env", "python3", "-m", "pytest"] -CMD ["tests"] - - -FROM base_run as default -LABEL org.opencontainers.image.source=https://github.com/verbit/restvirt -COPY --from=base_build /root/.local /root/.local -COPY . . -ENTRYPOINT ["/usr/bin/env", "python3", "main.py"] diff --git a/README.md b/README.md index c9f4625..8f86b0e 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,57 @@ -# RESTvirt +virtuerl +===== -Manage libvirt through a REST API. +An OTP application -# Installation - -```shell -apt install --no-install-recommends dnsmasq-base libvirt-daemon-system qemu-kvm qemu-utils -snap install minivirt -sudo snap connect minivirt:network-control -sudo snap connect minivirt:firewall-control -sudo snap connect minivirt:libvirt +# Required packages +```sh +sudo apt install ovmf swtpm ``` -## Getting Started - -```shell -git clone git@github.com:verbit/restvirt.git -cd restvirt - -apt install python3-dev libvirt-dev -pip install -r requirements.txt +# Windows Guests + +## Disable boot prompt +```sh +sudo mount -o loop Win11_23H2_EnglishInternational_x64v2.iso /mnt/win + +genisoimage \ + --allow-limited-size \ + -no-emul-boot \ + -b "boot/etfsboot.com" \ + -boot-load-seg 0 \ + -boot-load-size 8 \ + -eltorito-alt-boot \ + -no-emul-boot \ + -e "efi/microsoft/boot/efisys_noprompt.bin" \ + -boot-load-size 1 \ + -iso-level 4 \ + -udf \ + -o "win.iso" \ + /mnt/win/ +``` -mkdir /etc/restvirt +# Running -# start controller -python main.py controller +On the server -#start daemon -apt install libvirt-daemon-system -python main.py daemon +Make sure IP forwarding is enabled (`/etc/sysctl.conf`) ``` - -## Code Generation -```shell -python -m grpc_tools.protoc --python_out=. --grpc_python_out=. -Iprotos/ protos/minivirt/*.proto +# Uncomment the next line to enable packet forwarding for IPv4 +net.ipv4.ip_forward=1 +# Uncomment the next line to enable packet forwarding for IPv6 +net.ipv6.conf.all.forwarding=1 ``` +Run `sysctl -w` to commit changes. -## Known Issues +```sh +sudo -s ./erts-13.1.5/bin/erl -mode embedded -boot releases/0.7.0+build.61.ref8fc0b7e/start -config releases/0.7.0+build.61.ref8fc0b7e/sys.config -proto_dist inet6_tcp -name verbit@verbit.in-berlin.de -setcookie abcdef +``` -* AppArmor - * https://ubuntu.com/server/docs/virtualization-libvirt - * https://bugs.launchpad.net/ubuntu/+source/libvirt/+bug/1677398 +Locally +```sh +rebar3 compile +erl -name moi -proto_dist inet6_tcp -setcookie abcdef -pa _build/default/lib/*/ebin -hidden +(moi@t460s.lan)1> net_adm:ping('virtuerl@a.in6.dev'). +pong +(moi@t460s.lan)2> virtuerl_ui:start('virtuerl@a.in6.dev'). +``` diff --git a/config/sys.config b/config/sys.config new file mode 100644 index 0000000..d445da2 --- /dev/null +++ b/config/sys.config @@ -0,0 +1,9 @@ +[{kernel, + [{logger_level, all}, + {logger, + [{handler, default, logger_std_h, + #{ level => debug, + formatter => {logger_formatter, #{single_line => false}}}} + ]}]}, + {erlexec, [{root, true}, {user, "root"}]} +]. diff --git a/examples/docker-compose.yml b/examples/docker-compose.yml deleted file mode 100644 index 22a1621..0000000 --- a/examples/docker-compose.yml +++ /dev/null @@ -1,21 +0,0 @@ -version: "3.3" -services: - web: - image: ghcr.io/verbit/restvirt:latest - command: - - -b - - :8093 - - --server-cert - - /etc/restvirt/pki/server.crt - - --server-key - - /etc/restvirt/pki/server.key - - --client-ca-cert - - /etc/restvirt/pki/ca.crt - cap_add: - - NET_ADMIN - - NET_RAW - network_mode: host - volumes: - - "/etc/restvirt:/etc/restvirt" - - "/data/restvirt:/data/restvirt" - - "/var/run/libvirt:/var/run/libvirt" diff --git a/hack/repack b/hack/repack new file mode 100755 index 0000000..c32c5a0 --- /dev/null +++ b/hack/repack @@ -0,0 +1,47 @@ +#!/bin/bash + +scratch=$(mktemp -d -t tmp.XXXXXXXXXX) +function finish { + rm -rf "$scratch" +} +trap finish EXIT + +pwd=$PWD +archive=$1 + +#list=( virtuerl/virtuerl*.tar.gz ) +#archive="${list[-1]}" + +tar xzf "$archive" --one-top-level="$scratch" +# list=( virtuerl/releases/*/ ) +# release="${list[-1]}" +# version="$(basename "$release")" +relvsndir=$(find $scratch/releases/* -type d) +version=$(basename $relvsndir) + +relnamepath=$(find $scratch/releases/ -maxdepth 1 -type f -name '*.rel') +relname=$(basename $relnamepath .rel) + +# cp -R virtuerl/{lib,erts*} "$scratch" +# mkdir -p "$scratch/releases" +# cp -R "virtuerl/releases/$version/" "$scratch/releases/$version" +# cp virtuerl/releases/{RELEASES,start_erl.data} "$scratch/releases/" +mv "$scratch/releases/$version/$relname.rel" "$scratch/releases/$version/$relname-$version.rel" +mv "$scratch/releases/$relname.rel" "$scratch/releases/$relname-$version.rel" + +# cp "$scratch/releases/$version/$relname-$version.rel" "$scratch/releases/$relname-$version.rel" + +# cp -R virtuerl/* "$scratch" +cd "$scratch" +# tree -I lib +# exit + + +mkdir -p bin/ +rm bin/* +find erts* ! -name 'start_erl' ! -name 'erlexec' ! -name 'beam.smp' ! -name 'epmd' ! -name 'inet_gethost' ! -name 'erl_child_setup' ! -name 'heart' -type f -exec rm {} + +find releases -type f \( -name 'vm.args' -o -name 'start_clean.boot' -o -name 'no_dot_erlang.boot' \) -exec rm {} + +cp erts*/bin/start_erl bin/start_erl +tree -I lib + +tar czf "$pwd/$relname-$version.tar.gz" * diff --git a/minivirt/__init__.py b/minivirt/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/minivirt/controller.py b/minivirt/controller.py deleted file mode 100644 index 1aacd5a..0000000 --- a/minivirt/controller.py +++ /dev/null @@ -1,193 +0,0 @@ -import grpc -from google.protobuf import empty_pb2 -from grpc import StatusCode - -from minivirt import ( - controller_pb2_grpc, - daemon_pb2, - daemon_pb2_grpc, - dns_pb2, - dns_pb2_grpc, - route_pb2, - route_pb2_grpc, -) -from minivirt.host import HostController -from minivirt.models import DNSRecord -from minivirt.route import GenericRouteController, GenericRouteTableController, SyncEventHandler - - -class ControllerSyncHandler(SyncEventHandler): - def __init__(self, host_controller): - self.host_controller = host_controller - - def handle_sync(self, session): - channels = self.host_controller.channels() - for channel in channels: - client = daemon_pb2_grpc.DaemonServiceStub(channel) - client.SyncRoutes(daemon_pb2.SyncRoutesRequest()) - - -class Controller( - controller_pb2_grpc.ControllerServiceServicer, - dns_pb2_grpc.DNSServicer, - route_pb2_grpc.RouteServiceServicer, -): - def __init__(self, session_factory, host_controller: HostController, dns_controller): - self.session_factory = session_factory - self.host_controller = host_controller - self.dns_controller = dns_controller - sync_handler = ControllerSyncHandler(host_controller) - self.route_table_controller = GenericRouteTableController(session_factory, sync_handler) - self.route_controller = GenericRouteController(session_factory, sync_handler) - self.channel_cache = {} - - def _get_daemon_client(self, hostname=None): - if not hostname: - hostname = "default" - return daemon_pb2_grpc.DaemonServiceStub(self.host_controller.channel(hostname)) - - def GetDNSRecord(self, request, context): - record = self.dns_controller.record(request.name, request.type) - if record is None: - context.set_code(StatusCode.NOT_FOUND) - return empty_pb2.Empty() - return dns_pb2.DNSRecord( - name=record.name, - type=record.type, - ttl=record.ttl, - records=record.records, - ) - - def ListDNSRecords(self, request, context): - return dns_pb2.ListDNSRecordsResponse( - dns_records=[ - dns_pb2.DNSRecord( - name=record.name, - type=record.type, - ttl=record.ttl, - records=record.records, - ) - for record in self.dns_controller.records() - ] - ) - - def PutDNSRecord(self, request, context): - record = request.dns_record - self.dns_controller.set( - DNSRecord( - name=record.name, - type=record.type, - ttl=record.ttl, - records=record.records, - ) - ) - return record - - def DeleteDNSRecord(self, request, context): - self.dns_controller.remove(request.name, request.type) - return empty_pb2.Empty() - - def GetRouteTable(self, request, context): - table = self.route_table_controller.route_table(request.id) - if table is None: - context.set_code(StatusCode.NOT_FOUND) - return empty_pb2.Empty() - - return route_pb2.RouteTable( - id=table.id, - name=table.name, - ) - - def ListRouteTables(self, request, context): - tables = self.route_table_controller.route_tables() - - return route_pb2.ListRouteTablesResponse( - route_tables=[ - route_pb2.RouteTable( - id=table.id, - name=table.name, - ) - for table in tables - ] - ) - - def CreateRouteTable(self, request, context): - table = self.route_table_controller.create_route_table(request.route_table) - return table - - def DeleteRouteTable(self, request, context): - self.route_table_controller.remove_route_table(request.id) - return empty_pb2.Empty() - - def GetRoute(self, request, context): - route = self.route_controller.route(request.route_table_id, request.destination) - return route_pb2.Route( - route_table_id=route.route_table_id, - destination=route.destination, - gateways=route.gateways, - ) - - def ListRoutes(self, request, context): - routes = self.route_controller.routes(request.route_table_id) - - return route_pb2.ListRoutesResponse( - routes=[ - route_pb2.Route( - route_table_id=route.route_table_id, - destination=route.destination, - gateways=route.gateways, - ) - for route in routes - ] - ) - - def PutRoute(self, request, context): - route = self.route_controller.put_route(request.route) - return route - - def DeleteRoute(self, request, context): - self.route_controller.remove_route(request.route_table_id, request.destination) - return empty_pb2.Empty() - - def SyncRoutes(self, request, context): - self.route_table_controller.sync() - self.route_controller.sync() - return empty_pb2.Empty() - - def Sync(self, request, context): - # TODO: implement or remove - return super().Sync(request, context) - - -def monkeypatch_controller(): - cls = Controller - super_methods = { - getattr(base, a) for base in cls.__bases__ for a in dir(base) if not a.startswith("__") - } - sub_methods = {getattr(cls, a) for a in dir(cls) if not a.startswith("__")} - - for method in super_methods & sub_methods: - method_name = method.__name__ - - def create_target_method(method_name): - def target(self, request, context): - client = self._get_daemon_client(request.host) - try: - to_call = getattr(client, method_name) - if isinstance(to_call, grpc.UnaryUnaryMultiCallable): - return to_call(request) - elif isinstance(to_call, grpc.UnaryStreamMultiCallable): - return (chunk for chunk in to_call(request)) - else: - assert False # TODO - except grpc.RpcError as e: - context.set_code(e.code()) - context.set_details(e.details()) - return empty_pb2.Empty() - - return target - - setattr(cls, method_name, create_target_method(method_name)) - - -monkeypatch_controller() diff --git a/minivirt/controller_pb2.py b/minivirt/controller_pb2.py deleted file mode 100644 index 83d0cfa..0000000 --- a/minivirt/controller_pb2.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: minivirt/controller.proto -"""Generated protocol buffer code.""" -from google.protobuf.internal import builder as _builder -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 -from minivirt import daemon_pb2 as minivirt_dot_daemon__pb2 -from minivirt import domain_pb2 as minivirt_dot_domain__pb2 -from minivirt import volume_pb2 as minivirt_dot_volume__pb2 -from minivirt import port_forwarding_pb2 as minivirt_dot_port__forwarding__pb2 -from minivirt import dns_pb2 as minivirt_dot_dns__pb2 -from minivirt import route_pb2 as minivirt_dot_route__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x19minivirt/controller.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x15minivirt/daemon.proto\x1a\x15minivirt/domain.proto\x1a\x15minivirt/volume.proto\x1a\x1eminivirt/port_forwarding.proto\x1a\x12minivirt/dns.proto\x1a\x14minivirt/route.proto2\xce\x11\n\x11\x43ontrollerService\x12\x32\n\x0cGetDNSRecord\x12\x14.DNSRecordIdentifier\x1a\n.DNSRecord\"\x00\x12\x43\n\x0eListDNSRecords\x12\x16.ListDNSRecordsRequest\x1a\x17.ListDNSRecordsResponse\"\x00\x12\x32\n\x0cPutDNSRecord\x12\x14.PutDNSRecordRequest\x1a\n.DNSRecord\"\x00\x12\x41\n\x0f\x44\x65leteDNSRecord\x12\x14.DNSRecordIdentifier\x1a\x16.google.protobuf.Empty\"\x00\x12,\n\nGetNetwork\x12\x12.GetNetworkRequest\x1a\x08.Network\"\x00\x12=\n\x0cListNetworks\x12\x14.ListNetworksRequest\x1a\x15.ListNetworksResponse\"\x00\x12\x32\n\rCreateNetwork\x12\x15.CreateNetworkRequest\x1a\x08.Network\"\x00\x12@\n\rDeleteNetwork\x12\x15.DeleteNetworkRequest\x1a\x16.google.protobuf.Empty\"\x00\x12<\n\x0bStartDomain\x12\x13.StartDomainRequest\x1a\x16.google.protobuf.Empty\"\x00\x12:\n\nStopDomain\x12\x12.StopDomainRequest\x1a\x16.google.protobuf.Empty\"\x00\x12)\n\tGetDomain\x12\x11.GetDomainRequest\x1a\x07.Domain\"\x00\x12:\n\x0bListDomains\x12\x13.ListDomainsRequest\x1a\x14.ListDomainsResponse\"\x00\x12/\n\x0c\x43reateDomain\x12\x14.CreateDomainRequest\x1a\x07.Domain\"\x00\x12>\n\x0c\x44\x65leteDomain\x12\x14.DeleteDomainRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x37\n\rDownloadImage\x12\x15.DownloadImageRequest\x1a\x0b.ImageChunk\"\x00\x30\x01\x12)\n\tGetVolume\x12\x11.GetVolumeRequest\x1a\x07.Volume\"\x00\x12:\n\x0bListVolumes\x12\x13.ListVolumesRequest\x1a\x14.ListVolumesResponse\"\x00\x12/\n\x0c\x43reateVolume\x12\x14.CreateVolumeRequest\x1a\x07.Volume\"\x00\x12/\n\x0cUpdateVolume\x12\x14.UpdateVolumeRequest\x1a\x07.Volume\"\x00\x12>\n\x0c\x44\x65leteVolume\x12\x14.DeleteVolumeRequest\x1a\x16.google.protobuf.Empty\"\x00\x12X\n\x15ListVolumeAttachments\x12\x1d.ListVolumeAttachmentsRequest\x1a\x1e.ListVolumeAttachmentsResponse\"\x00\x12G\n\x13GetVolumeAttachment\x12\x1b.VolumeAttachmentIdentifier\x1a\x11.VolumeAttachment\"\x00\x12@\n\x0c\x41ttachVolume\x12\x1b.VolumeAttachmentIdentifier\x1a\x11.VolumeAttachment\"\x00\x12\x45\n\x0c\x44\x65tachVolume\x12\x1b.VolumeAttachmentIdentifier\x1a\x16.google.protobuf.Empty\"\x00\x12\x41\n\x11GetPortForwarding\x12\x19.PortForwardingIdentifier\x1a\x0f.PortForwarding\"\x00\x12R\n\x13ListPortForwardings\x12\x1b.ListPortForwardingsRequest\x1a\x1c.ListPortForwardingsResponse\"\x00\x12\x41\n\x11PutPortForwarding\x12\x19.PutPortForwardingRequest\x1a\x0f.PortForwarding\"\x00\x12K\n\x14\x44\x65letePortForwarding\x12\x19.PortForwardingIdentifier\x1a\x16.google.protobuf.Empty\"\x00\x12\x35\n\rGetRouteTable\x12\x15.RouteTableIdentifier\x1a\x0b.RouteTable\"\x00\x12\x46\n\x0fListRouteTables\x12\x17.ListRouteTablesRequest\x1a\x18.ListRouteTablesResponse\"\x00\x12;\n\x10\x43reateRouteTable\x12\x18.CreateRouteTableRequest\x1a\x0b.RouteTable\"\x00\x12\x43\n\x10\x44\x65leteRouteTable\x12\x15.RouteTableIdentifier\x1a\x16.google.protobuf.Empty\"\x00\x12&\n\x08GetRoute\x12\x10.RouteIdentifier\x1a\x06.Route\"\x00\x12\x37\n\nListRoutes\x12\x12.ListRoutesRequest\x1a\x13.ListRoutesResponse\"\x00\x12&\n\x08PutRoute\x12\x10.PutRouteRequest\x1a\x06.Route\"\x00\x12\x39\n\x0b\x44\x65leteRoute\x12\x10.RouteIdentifier\x1a\x16.google.protobuf.Empty\"\x00\x12:\n\nSyncRoutes\x12\x12.SyncRoutesRequest\x1a\x16.google.protobuf.Empty\"\x00\x42\x06Z\x04.;pbb\x06proto3') - -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'minivirt.controller_pb2', globals()) -if _descriptor._USE_C_DESCRIPTORS == False: - - DESCRIPTOR._options = None - DESCRIPTOR._serialized_options = b'Z\004.;pb' - _CONTROLLERSERVICE._serialized_start=202 - _CONTROLLERSERVICE._serialized_end=2456 -# @@protoc_insertion_point(module_scope) diff --git a/minivirt/controller_pb2_grpc.py b/minivirt/controller_pb2_grpc.py deleted file mode 100644 index a4fab8c..0000000 --- a/minivirt/controller_pb2_grpc.py +++ /dev/null @@ -1,1260 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 -from minivirt import daemon_pb2 as minivirt_dot_daemon__pb2 -from minivirt import dns_pb2 as minivirt_dot_dns__pb2 -from minivirt import domain_pb2 as minivirt_dot_domain__pb2 -from minivirt import port_forwarding_pb2 as minivirt_dot_port__forwarding__pb2 -from minivirt import route_pb2 as minivirt_dot_route__pb2 -from minivirt import volume_pb2 as minivirt_dot_volume__pb2 - - -class ControllerServiceStub(object): - """Missing associated documentation comment in .proto file.""" - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.GetDNSRecord = channel.unary_unary( - '/ControllerService/GetDNSRecord', - request_serializer=minivirt_dot_dns__pb2.DNSRecordIdentifier.SerializeToString, - response_deserializer=minivirt_dot_dns__pb2.DNSRecord.FromString, - ) - self.ListDNSRecords = channel.unary_unary( - '/ControllerService/ListDNSRecords', - request_serializer=minivirt_dot_dns__pb2.ListDNSRecordsRequest.SerializeToString, - response_deserializer=minivirt_dot_dns__pb2.ListDNSRecordsResponse.FromString, - ) - self.PutDNSRecord = channel.unary_unary( - '/ControllerService/PutDNSRecord', - request_serializer=minivirt_dot_dns__pb2.PutDNSRecordRequest.SerializeToString, - response_deserializer=minivirt_dot_dns__pb2.DNSRecord.FromString, - ) - self.DeleteDNSRecord = channel.unary_unary( - '/ControllerService/DeleteDNSRecord', - request_serializer=minivirt_dot_dns__pb2.DNSRecordIdentifier.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.GetNetwork = channel.unary_unary( - '/ControllerService/GetNetwork', - request_serializer=minivirt_dot_domain__pb2.GetNetworkRequest.SerializeToString, - response_deserializer=minivirt_dot_domain__pb2.Network.FromString, - ) - self.ListNetworks = channel.unary_unary( - '/ControllerService/ListNetworks', - request_serializer=minivirt_dot_domain__pb2.ListNetworksRequest.SerializeToString, - response_deserializer=minivirt_dot_domain__pb2.ListNetworksResponse.FromString, - ) - self.CreateNetwork = channel.unary_unary( - '/ControllerService/CreateNetwork', - request_serializer=minivirt_dot_domain__pb2.CreateNetworkRequest.SerializeToString, - response_deserializer=minivirt_dot_domain__pb2.Network.FromString, - ) - self.DeleteNetwork = channel.unary_unary( - '/ControllerService/DeleteNetwork', - request_serializer=minivirt_dot_domain__pb2.DeleteNetworkRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.StartDomain = channel.unary_unary( - '/ControllerService/StartDomain', - request_serializer=minivirt_dot_domain__pb2.StartDomainRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.StopDomain = channel.unary_unary( - '/ControllerService/StopDomain', - request_serializer=minivirt_dot_domain__pb2.StopDomainRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.GetDomain = channel.unary_unary( - '/ControllerService/GetDomain', - request_serializer=minivirt_dot_domain__pb2.GetDomainRequest.SerializeToString, - response_deserializer=minivirt_dot_domain__pb2.Domain.FromString, - ) - self.ListDomains = channel.unary_unary( - '/ControllerService/ListDomains', - request_serializer=minivirt_dot_domain__pb2.ListDomainsRequest.SerializeToString, - response_deserializer=minivirt_dot_domain__pb2.ListDomainsResponse.FromString, - ) - self.CreateDomain = channel.unary_unary( - '/ControllerService/CreateDomain', - request_serializer=minivirt_dot_domain__pb2.CreateDomainRequest.SerializeToString, - response_deserializer=minivirt_dot_domain__pb2.Domain.FromString, - ) - self.DeleteDomain = channel.unary_unary( - '/ControllerService/DeleteDomain', - request_serializer=minivirt_dot_domain__pb2.DeleteDomainRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.DownloadImage = channel.unary_stream( - '/ControllerService/DownloadImage', - request_serializer=minivirt_dot_domain__pb2.DownloadImageRequest.SerializeToString, - response_deserializer=minivirt_dot_domain__pb2.ImageChunk.FromString, - ) - self.GetVolume = channel.unary_unary( - '/ControllerService/GetVolume', - request_serializer=minivirt_dot_volume__pb2.GetVolumeRequest.SerializeToString, - response_deserializer=minivirt_dot_volume__pb2.Volume.FromString, - ) - self.ListVolumes = channel.unary_unary( - '/ControllerService/ListVolumes', - request_serializer=minivirt_dot_volume__pb2.ListVolumesRequest.SerializeToString, - response_deserializer=minivirt_dot_volume__pb2.ListVolumesResponse.FromString, - ) - self.CreateVolume = channel.unary_unary( - '/ControllerService/CreateVolume', - request_serializer=minivirt_dot_volume__pb2.CreateVolumeRequest.SerializeToString, - response_deserializer=minivirt_dot_volume__pb2.Volume.FromString, - ) - self.UpdateVolume = channel.unary_unary( - '/ControllerService/UpdateVolume', - request_serializer=minivirt_dot_volume__pb2.UpdateVolumeRequest.SerializeToString, - response_deserializer=minivirt_dot_volume__pb2.Volume.FromString, - ) - self.DeleteVolume = channel.unary_unary( - '/ControllerService/DeleteVolume', - request_serializer=minivirt_dot_volume__pb2.DeleteVolumeRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.ListVolumeAttachments = channel.unary_unary( - '/ControllerService/ListVolumeAttachments', - request_serializer=minivirt_dot_volume__pb2.ListVolumeAttachmentsRequest.SerializeToString, - response_deserializer=minivirt_dot_volume__pb2.ListVolumeAttachmentsResponse.FromString, - ) - self.GetVolumeAttachment = channel.unary_unary( - '/ControllerService/GetVolumeAttachment', - request_serializer=minivirt_dot_volume__pb2.VolumeAttachmentIdentifier.SerializeToString, - response_deserializer=minivirt_dot_volume__pb2.VolumeAttachment.FromString, - ) - self.AttachVolume = channel.unary_unary( - '/ControllerService/AttachVolume', - request_serializer=minivirt_dot_volume__pb2.VolumeAttachmentIdentifier.SerializeToString, - response_deserializer=minivirt_dot_volume__pb2.VolumeAttachment.FromString, - ) - self.DetachVolume = channel.unary_unary( - '/ControllerService/DetachVolume', - request_serializer=minivirt_dot_volume__pb2.VolumeAttachmentIdentifier.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.GetPortForwarding = channel.unary_unary( - '/ControllerService/GetPortForwarding', - request_serializer=minivirt_dot_port__forwarding__pb2.PortForwardingIdentifier.SerializeToString, - response_deserializer=minivirt_dot_port__forwarding__pb2.PortForwarding.FromString, - ) - self.ListPortForwardings = channel.unary_unary( - '/ControllerService/ListPortForwardings', - request_serializer=minivirt_dot_port__forwarding__pb2.ListPortForwardingsRequest.SerializeToString, - response_deserializer=minivirt_dot_port__forwarding__pb2.ListPortForwardingsResponse.FromString, - ) - self.PutPortForwarding = channel.unary_unary( - '/ControllerService/PutPortForwarding', - request_serializer=minivirt_dot_port__forwarding__pb2.PutPortForwardingRequest.SerializeToString, - response_deserializer=minivirt_dot_port__forwarding__pb2.PortForwarding.FromString, - ) - self.DeletePortForwarding = channel.unary_unary( - '/ControllerService/DeletePortForwarding', - request_serializer=minivirt_dot_port__forwarding__pb2.PortForwardingIdentifier.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.GetRouteTable = channel.unary_unary( - '/ControllerService/GetRouteTable', - request_serializer=minivirt_dot_route__pb2.RouteTableIdentifier.SerializeToString, - response_deserializer=minivirt_dot_route__pb2.RouteTable.FromString, - ) - self.ListRouteTables = channel.unary_unary( - '/ControllerService/ListRouteTables', - request_serializer=minivirt_dot_route__pb2.ListRouteTablesRequest.SerializeToString, - response_deserializer=minivirt_dot_route__pb2.ListRouteTablesResponse.FromString, - ) - self.CreateRouteTable = channel.unary_unary( - '/ControllerService/CreateRouteTable', - request_serializer=minivirt_dot_route__pb2.CreateRouteTableRequest.SerializeToString, - response_deserializer=minivirt_dot_route__pb2.RouteTable.FromString, - ) - self.DeleteRouteTable = channel.unary_unary( - '/ControllerService/DeleteRouteTable', - request_serializer=minivirt_dot_route__pb2.RouteTableIdentifier.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.GetRoute = channel.unary_unary( - '/ControllerService/GetRoute', - request_serializer=minivirt_dot_route__pb2.RouteIdentifier.SerializeToString, - response_deserializer=minivirt_dot_route__pb2.Route.FromString, - ) - self.ListRoutes = channel.unary_unary( - '/ControllerService/ListRoutes', - request_serializer=minivirt_dot_route__pb2.ListRoutesRequest.SerializeToString, - response_deserializer=minivirt_dot_route__pb2.ListRoutesResponse.FromString, - ) - self.PutRoute = channel.unary_unary( - '/ControllerService/PutRoute', - request_serializer=minivirt_dot_route__pb2.PutRouteRequest.SerializeToString, - response_deserializer=minivirt_dot_route__pb2.Route.FromString, - ) - self.DeleteRoute = channel.unary_unary( - '/ControllerService/DeleteRoute', - request_serializer=minivirt_dot_route__pb2.RouteIdentifier.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.SyncRoutes = channel.unary_unary( - '/ControllerService/SyncRoutes', - request_serializer=minivirt_dot_daemon__pb2.SyncRoutesRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - - -class ControllerServiceServicer(object): - """Missing associated documentation comment in .proto file.""" - - def GetDNSRecord(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListDNSRecords(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def PutDNSRecord(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeleteDNSRecord(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetNetwork(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListNetworks(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def CreateNetwork(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeleteNetwork(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def StartDomain(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def StopDomain(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetDomain(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListDomains(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def CreateDomain(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeleteDomain(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DownloadImage(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetVolume(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListVolumes(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def CreateVolume(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def UpdateVolume(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeleteVolume(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListVolumeAttachments(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetVolumeAttachment(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def AttachVolume(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DetachVolume(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetPortForwarding(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListPortForwardings(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def PutPortForwarding(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeletePortForwarding(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetRouteTable(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListRouteTables(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def CreateRouteTable(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeleteRouteTable(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetRoute(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListRoutes(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def PutRoute(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeleteRoute(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def SyncRoutes(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_ControllerServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'GetDNSRecord': grpc.unary_unary_rpc_method_handler( - servicer.GetDNSRecord, - request_deserializer=minivirt_dot_dns__pb2.DNSRecordIdentifier.FromString, - response_serializer=minivirt_dot_dns__pb2.DNSRecord.SerializeToString, - ), - 'ListDNSRecords': grpc.unary_unary_rpc_method_handler( - servicer.ListDNSRecords, - request_deserializer=minivirt_dot_dns__pb2.ListDNSRecordsRequest.FromString, - response_serializer=minivirt_dot_dns__pb2.ListDNSRecordsResponse.SerializeToString, - ), - 'PutDNSRecord': grpc.unary_unary_rpc_method_handler( - servicer.PutDNSRecord, - request_deserializer=minivirt_dot_dns__pb2.PutDNSRecordRequest.FromString, - response_serializer=minivirt_dot_dns__pb2.DNSRecord.SerializeToString, - ), - 'DeleteDNSRecord': grpc.unary_unary_rpc_method_handler( - servicer.DeleteDNSRecord, - request_deserializer=minivirt_dot_dns__pb2.DNSRecordIdentifier.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'GetNetwork': grpc.unary_unary_rpc_method_handler( - servicer.GetNetwork, - request_deserializer=minivirt_dot_domain__pb2.GetNetworkRequest.FromString, - response_serializer=minivirt_dot_domain__pb2.Network.SerializeToString, - ), - 'ListNetworks': grpc.unary_unary_rpc_method_handler( - servicer.ListNetworks, - request_deserializer=minivirt_dot_domain__pb2.ListNetworksRequest.FromString, - response_serializer=minivirt_dot_domain__pb2.ListNetworksResponse.SerializeToString, - ), - 'CreateNetwork': grpc.unary_unary_rpc_method_handler( - servicer.CreateNetwork, - request_deserializer=minivirt_dot_domain__pb2.CreateNetworkRequest.FromString, - response_serializer=minivirt_dot_domain__pb2.Network.SerializeToString, - ), - 'DeleteNetwork': grpc.unary_unary_rpc_method_handler( - servicer.DeleteNetwork, - request_deserializer=minivirt_dot_domain__pb2.DeleteNetworkRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'StartDomain': grpc.unary_unary_rpc_method_handler( - servicer.StartDomain, - request_deserializer=minivirt_dot_domain__pb2.StartDomainRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'StopDomain': grpc.unary_unary_rpc_method_handler( - servicer.StopDomain, - request_deserializer=minivirt_dot_domain__pb2.StopDomainRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'GetDomain': grpc.unary_unary_rpc_method_handler( - servicer.GetDomain, - request_deserializer=minivirt_dot_domain__pb2.GetDomainRequest.FromString, - response_serializer=minivirt_dot_domain__pb2.Domain.SerializeToString, - ), - 'ListDomains': grpc.unary_unary_rpc_method_handler( - servicer.ListDomains, - request_deserializer=minivirt_dot_domain__pb2.ListDomainsRequest.FromString, - response_serializer=minivirt_dot_domain__pb2.ListDomainsResponse.SerializeToString, - ), - 'CreateDomain': grpc.unary_unary_rpc_method_handler( - servicer.CreateDomain, - request_deserializer=minivirt_dot_domain__pb2.CreateDomainRequest.FromString, - response_serializer=minivirt_dot_domain__pb2.Domain.SerializeToString, - ), - 'DeleteDomain': grpc.unary_unary_rpc_method_handler( - servicer.DeleteDomain, - request_deserializer=minivirt_dot_domain__pb2.DeleteDomainRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'DownloadImage': grpc.unary_stream_rpc_method_handler( - servicer.DownloadImage, - request_deserializer=minivirt_dot_domain__pb2.DownloadImageRequest.FromString, - response_serializer=minivirt_dot_domain__pb2.ImageChunk.SerializeToString, - ), - 'GetVolume': grpc.unary_unary_rpc_method_handler( - servicer.GetVolume, - request_deserializer=minivirt_dot_volume__pb2.GetVolumeRequest.FromString, - response_serializer=minivirt_dot_volume__pb2.Volume.SerializeToString, - ), - 'ListVolumes': grpc.unary_unary_rpc_method_handler( - servicer.ListVolumes, - request_deserializer=minivirt_dot_volume__pb2.ListVolumesRequest.FromString, - response_serializer=minivirt_dot_volume__pb2.ListVolumesResponse.SerializeToString, - ), - 'CreateVolume': grpc.unary_unary_rpc_method_handler( - servicer.CreateVolume, - request_deserializer=minivirt_dot_volume__pb2.CreateVolumeRequest.FromString, - response_serializer=minivirt_dot_volume__pb2.Volume.SerializeToString, - ), - 'UpdateVolume': grpc.unary_unary_rpc_method_handler( - servicer.UpdateVolume, - request_deserializer=minivirt_dot_volume__pb2.UpdateVolumeRequest.FromString, - response_serializer=minivirt_dot_volume__pb2.Volume.SerializeToString, - ), - 'DeleteVolume': grpc.unary_unary_rpc_method_handler( - servicer.DeleteVolume, - request_deserializer=minivirt_dot_volume__pb2.DeleteVolumeRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'ListVolumeAttachments': grpc.unary_unary_rpc_method_handler( - servicer.ListVolumeAttachments, - request_deserializer=minivirt_dot_volume__pb2.ListVolumeAttachmentsRequest.FromString, - response_serializer=minivirt_dot_volume__pb2.ListVolumeAttachmentsResponse.SerializeToString, - ), - 'GetVolumeAttachment': grpc.unary_unary_rpc_method_handler( - servicer.GetVolumeAttachment, - request_deserializer=minivirt_dot_volume__pb2.VolumeAttachmentIdentifier.FromString, - response_serializer=minivirt_dot_volume__pb2.VolumeAttachment.SerializeToString, - ), - 'AttachVolume': grpc.unary_unary_rpc_method_handler( - servicer.AttachVolume, - request_deserializer=minivirt_dot_volume__pb2.VolumeAttachmentIdentifier.FromString, - response_serializer=minivirt_dot_volume__pb2.VolumeAttachment.SerializeToString, - ), - 'DetachVolume': grpc.unary_unary_rpc_method_handler( - servicer.DetachVolume, - request_deserializer=minivirt_dot_volume__pb2.VolumeAttachmentIdentifier.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'GetPortForwarding': grpc.unary_unary_rpc_method_handler( - servicer.GetPortForwarding, - request_deserializer=minivirt_dot_port__forwarding__pb2.PortForwardingIdentifier.FromString, - response_serializer=minivirt_dot_port__forwarding__pb2.PortForwarding.SerializeToString, - ), - 'ListPortForwardings': grpc.unary_unary_rpc_method_handler( - servicer.ListPortForwardings, - request_deserializer=minivirt_dot_port__forwarding__pb2.ListPortForwardingsRequest.FromString, - response_serializer=minivirt_dot_port__forwarding__pb2.ListPortForwardingsResponse.SerializeToString, - ), - 'PutPortForwarding': grpc.unary_unary_rpc_method_handler( - servicer.PutPortForwarding, - request_deserializer=minivirt_dot_port__forwarding__pb2.PutPortForwardingRequest.FromString, - response_serializer=minivirt_dot_port__forwarding__pb2.PortForwarding.SerializeToString, - ), - 'DeletePortForwarding': grpc.unary_unary_rpc_method_handler( - servicer.DeletePortForwarding, - request_deserializer=minivirt_dot_port__forwarding__pb2.PortForwardingIdentifier.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'GetRouteTable': grpc.unary_unary_rpc_method_handler( - servicer.GetRouteTable, - request_deserializer=minivirt_dot_route__pb2.RouteTableIdentifier.FromString, - response_serializer=minivirt_dot_route__pb2.RouteTable.SerializeToString, - ), - 'ListRouteTables': grpc.unary_unary_rpc_method_handler( - servicer.ListRouteTables, - request_deserializer=minivirt_dot_route__pb2.ListRouteTablesRequest.FromString, - response_serializer=minivirt_dot_route__pb2.ListRouteTablesResponse.SerializeToString, - ), - 'CreateRouteTable': grpc.unary_unary_rpc_method_handler( - servicer.CreateRouteTable, - request_deserializer=minivirt_dot_route__pb2.CreateRouteTableRequest.FromString, - response_serializer=minivirt_dot_route__pb2.RouteTable.SerializeToString, - ), - 'DeleteRouteTable': grpc.unary_unary_rpc_method_handler( - servicer.DeleteRouteTable, - request_deserializer=minivirt_dot_route__pb2.RouteTableIdentifier.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'GetRoute': grpc.unary_unary_rpc_method_handler( - servicer.GetRoute, - request_deserializer=minivirt_dot_route__pb2.RouteIdentifier.FromString, - response_serializer=minivirt_dot_route__pb2.Route.SerializeToString, - ), - 'ListRoutes': grpc.unary_unary_rpc_method_handler( - servicer.ListRoutes, - request_deserializer=minivirt_dot_route__pb2.ListRoutesRequest.FromString, - response_serializer=minivirt_dot_route__pb2.ListRoutesResponse.SerializeToString, - ), - 'PutRoute': grpc.unary_unary_rpc_method_handler( - servicer.PutRoute, - request_deserializer=minivirt_dot_route__pb2.PutRouteRequest.FromString, - response_serializer=minivirt_dot_route__pb2.Route.SerializeToString, - ), - 'DeleteRoute': grpc.unary_unary_rpc_method_handler( - servicer.DeleteRoute, - request_deserializer=minivirt_dot_route__pb2.RouteIdentifier.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'SyncRoutes': grpc.unary_unary_rpc_method_handler( - servicer.SyncRoutes, - request_deserializer=minivirt_dot_daemon__pb2.SyncRoutesRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'ControllerService', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - - - # This class is part of an EXPERIMENTAL API. -class ControllerService(object): - """Missing associated documentation comment in .proto file.""" - - @staticmethod - def GetDNSRecord(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/GetDNSRecord', - minivirt_dot_dns__pb2.DNSRecordIdentifier.SerializeToString, - minivirt_dot_dns__pb2.DNSRecord.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ListDNSRecords(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/ListDNSRecords', - minivirt_dot_dns__pb2.ListDNSRecordsRequest.SerializeToString, - minivirt_dot_dns__pb2.ListDNSRecordsResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def PutDNSRecord(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/PutDNSRecord', - minivirt_dot_dns__pb2.PutDNSRecordRequest.SerializeToString, - minivirt_dot_dns__pb2.DNSRecord.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def DeleteDNSRecord(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/DeleteDNSRecord', - minivirt_dot_dns__pb2.DNSRecordIdentifier.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def GetNetwork(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/GetNetwork', - minivirt_dot_domain__pb2.GetNetworkRequest.SerializeToString, - minivirt_dot_domain__pb2.Network.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ListNetworks(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/ListNetworks', - minivirt_dot_domain__pb2.ListNetworksRequest.SerializeToString, - minivirt_dot_domain__pb2.ListNetworksResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def CreateNetwork(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/CreateNetwork', - minivirt_dot_domain__pb2.CreateNetworkRequest.SerializeToString, - minivirt_dot_domain__pb2.Network.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def DeleteNetwork(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/DeleteNetwork', - minivirt_dot_domain__pb2.DeleteNetworkRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def StartDomain(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/StartDomain', - minivirt_dot_domain__pb2.StartDomainRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def StopDomain(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/StopDomain', - minivirt_dot_domain__pb2.StopDomainRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def GetDomain(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/GetDomain', - minivirt_dot_domain__pb2.GetDomainRequest.SerializeToString, - minivirt_dot_domain__pb2.Domain.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ListDomains(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/ListDomains', - minivirt_dot_domain__pb2.ListDomainsRequest.SerializeToString, - minivirt_dot_domain__pb2.ListDomainsResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def CreateDomain(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/CreateDomain', - minivirt_dot_domain__pb2.CreateDomainRequest.SerializeToString, - minivirt_dot_domain__pb2.Domain.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def DeleteDomain(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/DeleteDomain', - minivirt_dot_domain__pb2.DeleteDomainRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def DownloadImage(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_stream(request, target, '/ControllerService/DownloadImage', - minivirt_dot_domain__pb2.DownloadImageRequest.SerializeToString, - minivirt_dot_domain__pb2.ImageChunk.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def GetVolume(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/GetVolume', - minivirt_dot_volume__pb2.GetVolumeRequest.SerializeToString, - minivirt_dot_volume__pb2.Volume.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ListVolumes(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/ListVolumes', - minivirt_dot_volume__pb2.ListVolumesRequest.SerializeToString, - minivirt_dot_volume__pb2.ListVolumesResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def CreateVolume(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/CreateVolume', - minivirt_dot_volume__pb2.CreateVolumeRequest.SerializeToString, - minivirt_dot_volume__pb2.Volume.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def UpdateVolume(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/UpdateVolume', - minivirt_dot_volume__pb2.UpdateVolumeRequest.SerializeToString, - minivirt_dot_volume__pb2.Volume.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def DeleteVolume(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/DeleteVolume', - minivirt_dot_volume__pb2.DeleteVolumeRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ListVolumeAttachments(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/ListVolumeAttachments', - minivirt_dot_volume__pb2.ListVolumeAttachmentsRequest.SerializeToString, - minivirt_dot_volume__pb2.ListVolumeAttachmentsResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def GetVolumeAttachment(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/GetVolumeAttachment', - minivirt_dot_volume__pb2.VolumeAttachmentIdentifier.SerializeToString, - minivirt_dot_volume__pb2.VolumeAttachment.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def AttachVolume(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/AttachVolume', - minivirt_dot_volume__pb2.VolumeAttachmentIdentifier.SerializeToString, - minivirt_dot_volume__pb2.VolumeAttachment.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def DetachVolume(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/DetachVolume', - minivirt_dot_volume__pb2.VolumeAttachmentIdentifier.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def GetPortForwarding(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/GetPortForwarding', - minivirt_dot_port__forwarding__pb2.PortForwardingIdentifier.SerializeToString, - minivirt_dot_port__forwarding__pb2.PortForwarding.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ListPortForwardings(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/ListPortForwardings', - minivirt_dot_port__forwarding__pb2.ListPortForwardingsRequest.SerializeToString, - minivirt_dot_port__forwarding__pb2.ListPortForwardingsResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def PutPortForwarding(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/PutPortForwarding', - minivirt_dot_port__forwarding__pb2.PutPortForwardingRequest.SerializeToString, - minivirt_dot_port__forwarding__pb2.PortForwarding.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def DeletePortForwarding(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/DeletePortForwarding', - minivirt_dot_port__forwarding__pb2.PortForwardingIdentifier.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def GetRouteTable(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/GetRouteTable', - minivirt_dot_route__pb2.RouteTableIdentifier.SerializeToString, - minivirt_dot_route__pb2.RouteTable.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ListRouteTables(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/ListRouteTables', - minivirt_dot_route__pb2.ListRouteTablesRequest.SerializeToString, - minivirt_dot_route__pb2.ListRouteTablesResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def CreateRouteTable(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/CreateRouteTable', - minivirt_dot_route__pb2.CreateRouteTableRequest.SerializeToString, - minivirt_dot_route__pb2.RouteTable.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def DeleteRouteTable(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/DeleteRouteTable', - minivirt_dot_route__pb2.RouteTableIdentifier.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def GetRoute(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/GetRoute', - minivirt_dot_route__pb2.RouteIdentifier.SerializeToString, - minivirt_dot_route__pb2.Route.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ListRoutes(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/ListRoutes', - minivirt_dot_route__pb2.ListRoutesRequest.SerializeToString, - minivirt_dot_route__pb2.ListRoutesResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def PutRoute(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/PutRoute', - minivirt_dot_route__pb2.PutRouteRequest.SerializeToString, - minivirt_dot_route__pb2.Route.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def DeleteRoute(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/DeleteRoute', - minivirt_dot_route__pb2.RouteIdentifier.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def SyncRoutes(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ControllerService/SyncRoutes', - minivirt_dot_daemon__pb2.SyncRoutesRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/minivirt/daemon.py b/minivirt/daemon.py deleted file mode 100644 index 6ad4171..0000000 --- a/minivirt/daemon.py +++ /dev/null @@ -1,830 +0,0 @@ -import ipaddress -import os -import string -import threading -import uuid -from datetime import datetime, timezone - -import libvirt -import xmltodict -from google.protobuf import empty_pb2, timestamp_pb2 -from grpc import StatusCode -from pyroute2 import IPRoute -from pyroute2.netlink.rtnl import rtypes -from sqlalchemy import delete, select - -from minivirt import ( - controller_pb2_grpc, - daemon_pb2_grpc, - domain_pb2, - port_forwarding_pb2, - route_pb2, - volume_pb2, -) -from minivirt.image import create_cloud_config_image -from minivirt.models import Domain, PortForwarding - - -def libvirt_state_to_string(state): - if state == libvirt.VIR_DOMAIN_NOSTATE: - return "NOSTATE" - elif state == libvirt.VIR_DOMAIN_RUNNING: - return "RUNNING" - elif state == libvirt.VIR_DOMAIN_BLOCKED: - return "BLOCKED" - elif state == libvirt.VIR_DOMAIN_PAUSED: - return "PAUSED" - elif state == libvirt.VIR_DOMAIN_SHUTDOWN: - return "SHUTDOWN" - elif state == libvirt.VIR_DOMAIN_CRASHED: - return "CRASHED" - elif state == libvirt.VIR_DOMAIN_PMSUSPENDED: - return "PMSUSPENDED" - elif state == libvirt.VIR_DOMAIN_SHUTOFF: - return "SHUTOFF" - return "UNKNOWN" - - -def domain_to_dict(domain): - domain_dict = xmltodict.parse(domain.XMLDesc(libvirt.VIR_DOMAIN_XML_INACTIVE)) - d = domain_dict["domain"] - res = { - "uuid": d["uuid"], - "name": d["name"], - "vcpu": int(d["vcpu"]["#text"]), - "memory": int(d["memory"]["#text"]) // 1024, - "network": d["devices"]["interface"]["source"]["@network"], - "nested_virtualization": d["cpu"]["@mode"] == "host-model", - } - try: # FIXME: once all have create metadata, remove this try/catch - dt = datetime.fromisoformat(d["metadata"]["restvirt:metadata"]["created"]) - res["created_at"] = timestamp_pb2.Timestamp( - seconds=int(dt.timestamp()) - ) # we don't need sub-second precision - except Exception as e: - print(e) - return res - - -def _volume_to_dict(vol): - _, cap, _ = vol.info() - name = vol.name() - return { - "id": name, - "name": name, - "size": cap, - } - - -def _get_attachments(domain): - domain_dict = xmltodict.parse(domain.XMLDesc()) - disks = domain_dict["domain"]["devices"]["disk"] - volume_ids = [ - d["alias"]["@name"][3:] - for d in disks - if d["@device"] == "disk" and d["alias"]["@name"].startswith("ua-") - ] - attachments = [ - { - "volume_id": vid, - "disk_address": _disk_address(domain_dict, vid), - } - for vid in volume_ids - ] - - return attachments - - -def _get_all_attachments(domains, vol): - vol_id = vol.name() - attachments = [(domain, _get_attachments(domain)) for domain in domains] - attachments = [(d, da) for d, das in attachments for da in das] - filtered_domains = [(d, da) for (d, da) in attachments if da["volume_id"] == vol_id] - filtered_domains = [ - {"domain_id": d.UUIDString(), "disk_address": da["disk_address"]} - for d, da in filtered_domains - ] - return filtered_domains - - -def _disk_address(domain_dict, volume_id): - disks = domain_dict["domain"]["devices"]["disk"] - - da = [d["address"] for d in disks if d["alias"]["@name"] == f"ua-{volume_id}"][0] - daddr = [int(da[f"@{k}"], 16) for k in ["domain", "bus", "slot", "function"]] - return f"{da['@type']}-{daddr[0]:04x}:{daddr[1]:02x}:{daddr[2]:02x}.{daddr[3]:x}" - - -def disk_address(domain, volume_id): - domain_dict = xmltodict.parse(domain.XMLDesc()) - return _disk_address(domain_dict, volume_id) - - -class IPRouteTableSynchronizer: - id_range_min, id_range_max = 30069, 30169 - - def __init__(self): - self.lock = threading.Lock() - - def handle_sync(self, client: controller_pb2_grpc.ControllerServiceStub): - with self.lock, IPRoute() as ip: - tables = client.ListRouteTables(route_pb2.ListRouteTablesRequest()).route_tables - table_ids = {t.id for t in tables} - - def filt(r): - prio = r.get_attr("FRA_PRIORITY") - return prio is not None and self.id_range_min <= prio <= self.id_range_max - - rules = filter(filt, ip.get_rules()) - rules = {r.get_attr("FRA_PRIORITY"): r for r in rules} - - # step 1: delete tables not in conf - for prio in rules: - if prio not in table_ids: - ip.rule("del", priority=prio) - - # step 2: update tables - # TODO: this is broken - for table in tables: - if table.id not in rules or rules[table.id]["table"] != table.id: - try: - ip.rule("del", priority=table.id) - except: - pass - ip.rule( - "add", - priority=table.id, - table=table.id, - ) - - -# TODO: RouteTableController -# try: -# # FIXME: make "virbr0" configurable -# ip.rule( -# "add", table=TABLE_ID, priority=30069, iifname="virbr0" -# ) # FIXME: check if rule identical -# except NetlinkError as e: -# if e.code != 17: # 17 = rule already exists -# raise e - - -class IPRouteSynchronizer: - id_range_min, id_range_max = 30069, 30169 - - def __init__(self): - self.lock = threading.Lock() - - def get_ip_routes(self, ip): - filtered_routes = {} - for r in ip.get_routes(): - table_id = r.get_attr("RTA_TABLE") - if not self.id_range_min <= table_id < self.id_range_max: - continue - if r["family"] != 2 or r["type"] != rtypes["RTN_UNICAST"]: - continue - dst = r.get_attr("RTA_DST") - if dst is None: - continue - dstnet = f"{dst}/{r['dst_len']}" - addr = ipaddress.ip_network(dstnet) - - gateway = r.get_attr("RTA_GATEWAY") - if gateway is not None: - gateways = [gateway] - else: - routes = r.get_attr("RTA_MULTIPATH") - gateways = [r.get_attr("RTA_GATEWAY") for r in routes] - filtered_routes[addr] = (table_id, {ipaddress.ip_address(gw) for gw in gateways}) - - return filtered_routes - - def remove_ip_route(self, ip, table_id, dst): - ip.route("del", table=table_id, dst=str(dst)) - - def put_ip_route(self, ip, table_id, dst, gws): - try: - ip.route("del", table=table_id, dst=str(dst)) - except: - pass - ip.route( - "add", - table=table_id, - dst=str(dst), - multipath=[{"gateway": str(gw)} for gw in gws], - ) - - def handle_sync(self, client: controller_pb2_grpc.ControllerServiceStub): - with self.lock, IPRoute() as ip: - tables = client.ListRouteTables(route_pb2.ListRouteTablesRequest()).route_tables - routes = [ - client.ListRoutes(route_pb2.ListRoutesRequest(route_table_id=table.id)).routes - for table in tables - ] - routes = [r for rs in routes for r in rs] - routes = { - ipaddress.IPv4Network(route.destination): ( - route.route_table_id, - {ipaddress.IPv4Address(gw) for gw in route.gateways}, - ) - for route in routes - } - - filtered_routes = self.get_ip_routes(ip) - - # step 1: delete routes not in conf - for dst, (tid, gws) in filtered_routes.items(): - if dst not in routes: - self.remove_ip_route(ip, tid, dst) - - # step 2: update routes - for dst, (tid, gws) in routes.items(): - if dst not in filtered_routes or filtered_routes[dst][1] != gws: - self.put_ip_route(ip, tid, dst, gws) - - -def create_storage_pool(conn, name, pool_dir, location=None): - if location is None: - location = name - - try: - conn.storagePoolLookupByName(name) - except libvirt.libvirtError as e: - if e.get_error_code() == libvirt.VIR_ERR_NO_STORAGE_POOL: - pool = conn.storagePoolDefineXML( - f""" - {name} - - {os.path.join(pool_dir, location)} - - """ - ) - pool.build() - pool.create() - else: - raise e - - -def get_root_volume(conn, domain_name): - try: - pool = conn.storagePoolLookupByName("volumes") - vol = pool.storageVolLookupByName(f"{domain_name}-root.qcow2") - except: - # FIXME: can be removed once not in use - pool = conn.storagePoolLookupByName("restvirtimages") - vol = pool.storageVolLookupByName(f"{domain_name}-root.qcow2") - return vol - - -def _network_prefix(net_elem): - # FIXME: remove/simplify me, once all network use prefix - if "@netmask" in net_elem: - return ipaddress.ip_network("0.0.0.0/" + net_elem["@netmask"]).prefixlen - return net_elem["@prefix"] - - -def _network_to_pb(net): - net_dict = xmltodict.parse(net.XMLDesc(), force_list=("ip",))["network"] - cidr6 = "" - if len(net_dict["ip"]) > 1: - cidr6 = str( - ipaddress.ip_network( - f'{net_dict["ip"][1]["@address"]}/{_network_prefix(net_dict["ip"][1])}', - strict=False, - ) - ) - return domain_pb2.Network( - uuid=net_dict["uuid"], - name=net_dict["name"], - cidr=str( - ipaddress.ip_network( - f'{net_dict["ip"][0]["@address"]}/{_network_prefix(net_dict["ip"][0])}', - strict=False, - ) - ), - cidr6=cidr6, - ) - - -class DaemonService(daemon_pb2_grpc.DaemonServiceServicer): - def __init__( - self, session_factory, port_fwd_sync_handler, controller_channel, pool_dir="/data/restvirt" - ): - self.session_factory = session_factory - self.port_fwd_sync_handler = port_fwd_sync_handler - self.controller = controller_pb2_grpc.ControllerServiceStub(controller_channel) - - self.tables_synchronizer = IPRouteTableSynchronizer() - self.routes_synchronizer = IPRouteSynchronizer() - - self.conn = libvirt.open("qemu:///system?socket=/var/run/libvirt/libvirt-sock") - - self.lock = threading.Lock() - - create_storage_pool(self.conn, "restvirtimages", pool_dir, "images") - create_storage_pool(self.conn, "volumes", pool_dir) - - def GetNetwork(self, request, context): - net = self.conn.networkLookupByUUIDString(request.uuid) - return _network_to_pb(net) - - def ListNetworks(self, request, context): - return domain_pb2.ListNetworksResponse( - networks=[_network_to_pb(net) for net in (self.conn.listAllNetworks())] - ) - - def CreateNetwork(self, request, context): - network = request.network - - nets = [network.cidr, network.cidr6] - nets = [ipaddress.ip_network(net) for net in nets if net] - assert len(nets), "no networks supplied" - - def to_family_string(net): - if isinstance(net, ipaddress.IPv4Network): - return "ipv4" - elif isinstance(net, ipaddress.IPv6Network): - return "ipv6" - else: - assert False, "unknown ip familiy" - - netdefs = [ - f"" - for net in nets - ] - creation_timestamp = datetime.now(timezone.utc) - lvnet = self.conn.networkDefineXML( - f""" - {network.name} - - - {creation_timestamp.isoformat()} - - - - - - -{chr(10).join(netdefs)} - -""" - ) - lvnet.create() - lvnet.setAutostart(True) - return _network_to_pb(lvnet) - - def DeleteNetwork(self, request, context): - net = self.conn.networkLookupByUUIDString(request.uuid) - net.destroy() - net.undefine() - return empty_pb2.Empty() - - def StartDomain(self, request, context): - domain = self.conn.lookupByUUIDString(request.uuid) - domain.create() - return empty_pb2.Empty() - - def StopDomain(self, request, context): - domain = self.conn.lookupByUUIDString(request.uuid) - if request.force: - domain.destroy() - else: - domain.shutdown() - return empty_pb2.Empty() - - def _get_domain(self, uuid): - domain_lv = self.conn.lookupByUUIDString(uuid) - domain_dict = domain_to_dict(domain_lv) - net = self.conn.networkLookupByName(domain_dict["network"]) - domain_dict["network"] = net.UUIDString() - state, _ = domain_lv.state() - domain_dict["state"] = libvirt_state_to_string(state) - - with self.session_factory() as session: - domain = ( - session.execute( - select(Domain).filter( - Domain.id == uuid, - ) - ) - .scalars() - .one_or_none() - ) - domain_dict["os_type"] = domain.os_type - domain_dict["private_ip"] = domain.private_ip - domain_dict["ipv6_address"] = domain.ipv6_address - domain_dict["user_data"] = domain.user_data - - return domain_dict - - def GetDomain(self, request, context): - return domain_pb2.Domain(**self._get_domain(request.uuid)) - - def ListDomains(self, request, context): - domains = self.conn.listAllDomains() - ds = [domain_pb2.Domain(**domain_to_dict(d)) for d in domains] - return domain_pb2.ListDomainsResponse(domains=ds) - - def CreateDomain(self, request, context): - domreq = request.domain - - lvnet = self.conn.networkLookupByUUIDString(domreq.network) - net_dict = xmltodict.parse(lvnet.XMLDesc(), force_list=("ip",)) - net_def = net_dict["network"]["ip"][0] - gateway = ipaddress.ip_address(net_def["@address"]) - net = ipaddress.ip_network(f"{gateway}/{_network_prefix(net_def)}", strict=False) - - gateway6 = None - net6 = None - if len(net_dict["network"]["ip"]) > 1: - net6_def = net_dict["network"]["ip"][1] - gateway6 = ipaddress.ip_address(net6_def["@address"]) - net6 = ipaddress.ip_network(f'{gateway6}/{net6_def["@prefix"]}', strict=False) - - dom_uuid = uuid.uuid4() - os_type = domreq.os_type - if os_type is None: - os_type = "linux" - user_data = domreq.user_data - if user_data is None: - user_data = "" - private_ip = domreq.private_ip - ipv6_address = domreq.ipv6_address - with self.lock: - with self.session_factory() as session: - domains = session.execute(select(Domain)).scalars().all() - - ips = set([dom.private_ip for dom in domains]) - if not private_ip: - available = {str(h) for h in net.hosts()} - available -= {str(net.network_address), str(net.broadcast_address)} - available -= ips - private_ip = available.pop() - else: - assert private_ip not in ips - - domain = Domain( - id=str(dom_uuid), - os_type=os_type, - private_ip=private_ip, - ipv6_address=ipv6_address, - user_data=user_data, - ) - session.add(domain) - session.commit() - - ip = ipaddress.ip_address(private_ip) - ip6 = ipaddress.ip_address(ipv6_address) if ipv6_address else None - - img_pool = self.conn.storagePoolLookupByName("restvirtimages") - if not domreq.base_image: - base_img_name = "debian-12-generic-amd64-20240102-1614.qcow2" - try: - base_img = img_pool.storageVolLookupByName(base_img_name) - except: - from urllib.request import urlopen - - res = urlopen( - f"https://cloud.debian.org/images/cloud/bookworm/20240102-1614/debian-12-generic-amd64-20240102-1614.qcow2" - ) - size = int(res.getheader("Content-length")) - base_img = img_pool.createXML( - f""" - {base_img_name} - {size} - - - -""" - ) - stream = self.conn.newStream() - base_img.upload(stream, 0, size) - while True: - chunk = res.read(64 * 1024) - if not chunk: - break - stream.send(chunk) - stream.finish() - else: - base_img = img_pool.storageVolLookupByName(domreq.base_image) - - vol_pool = self.conn.storagePoolLookupByName("volumes") - vol = vol_pool.createXML( - f""" - {domreq.name}-root.qcow2 - {20} - - {base_img.path()} - - - - - -""" - ) - root_image_path = vol.path() - - mac = "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}".format( - 0x52, 0x54, 0x00, ip.packed[-3], ip.packed[-2], ip.packed[-1] - ) - ccfg_raw = create_cloud_config_image( - domain_id=dom_uuid, - user_data=user_data, - mac=mac, - network=net, - network6=net6, - address=ip, - address6=ip6, - gateway=net[1], - gateway6=gateway6, - name=domreq.name, - ) - stream = self.conn.newStream() - ccfg_vol = img_pool.createXML( - f""" - {domreq.name}-cloud-init.img - {len(ccfg_raw)} - - - -""" - ) - ccfg_vol.upload(stream, 0, len(ccfg_raw)) - stream.send(ccfg_raw) - stream.finish() - - creation_timestamp = datetime.now(timezone.utc) - dom = self.conn.defineXML( - f""" - {dom_uuid} - - - {creation_timestamp.isoformat()} - - - - - - {"" if domreq.nested_virtualization else ""} - {domreq.name} - {domreq.vcpu} - {domreq.memory} - - hvm - - - - - - - - - - - - - - - - - - - -""" - ) - - dom.setAutostart(True) - dom.create() - - return domain_pb2.Domain(**self._get_domain(dom.UUIDString())) - - def DeleteDomain(self, request, context): - with self.session_factory() as session: - session.execute(delete(Domain).where(Domain.id == request.uuid)) - session.commit() - - dom = self.conn.lookupByUUIDString(str(request.uuid)) - name = dom.name() - try: - dom.destroy() - except libvirt.libvirtError as e: - if "Requested operation is not valid: domain is not running" in str(e): - pass - else: - raise e - dom.undefine() - - pool = self.conn.storagePoolLookupByName("restvirtimages") - vol = get_root_volume(self.conn, name) - vol.delete() - vol = pool.storageVolLookupByName(f"{name}-cloud-init.img") - vol.delete() - - return empty_pb2.Empty() - - def DownloadImage(self, request, context): - dom = self.conn.lookupByUUIDString(request.domain_id) - name = dom.name() - vol = get_root_volume(self.conn, name) - stream = self.conn.newStream() - vol.download(stream, 0, 0) - while True: - bytes = stream.recv(64 * 1024) - if not bytes: - break - yield domain_pb2.ImageChunk(bytes=bytes) - stream.finish() - - def GetVolume(self, request, context): - pool = self.conn.storagePoolLookupByName("volumes") - vol = pool.storageVolLookupByName(request.uuid) - return volume_pb2.Volume(**_volume_to_dict(vol)) - - def ListVolumes(self, request, context): - pool = self.conn.storagePoolLookupByName("volumes") - vols = pool.listAllVolumes() - vol_dicts = [_volume_to_dict(vol) for vol in vols] - return volume_pb2.ListVolumesResponse(volumes=[volume_pb2.Volume(**d) for d in vol_dicts]) - - def CreateVolume(self, request, context): - pool = self.conn.storagePoolLookupByName("volumes") - vol = pool.createXML( - f""" - {request.volume.name} - {request.volume.size} - - - -""" - ) - return volume_pb2.Volume( - id=vol.name(), - name=request.volume.name, - size=request.volume.size, - ) - - def UpdateVolume(self, request, context): - pool = self.conn.storagePoolLookupByName("volumes") - vol = pool.storageVolLookupByName(request.volume.id) - _, current_capacity, _ = vol.info() - desired_capacity = request.volume.size - if desired_capacity < current_capacity: - raise Exception("shrinking volumes is not supported") - vol.resize(desired_capacity) - return volume_pb2.Volume(**_volume_to_dict(vol)) - - def DeleteVolume(self, request, context): - pool = self.conn.storagePoolLookupByName("volumes") - vol = pool.storageVolLookupByName(request.uuid) - if _get_all_attachments(self.conn.listAllDomains(), vol): - raise Exception("volume is attached z") - vol.delete() - return empty_pb2.Empty() - - def ListVolumeAttachments(self, request, context): - domain = self.conn.lookupByUUIDString(request.domain_id) - return volume_pb2.ListVolumeAttachmentsResponse( - attachments=[ - volume_pb2.VolumeAttachment(domain_id=request.domain_id, **a) - for a in _get_attachments(domain) - ] - ) - - def GetVolumeAttachment(self, request, context): - domain = self.conn.lookupByUUIDString(request.domain_id) - pool = self.conn.storagePoolLookupByName("volumes") - vol = pool.storageVolLookupByName(request.volume_id) - - return volume_pb2.VolumeAttachment( - domain_id=domain.UUIDString(), - volume_id=vol.name(), - disk_address=disk_address(domain, request.volume_id), - ) - - def AttachVolume(self, request, context): - domain = self.conn.lookupByUUIDString(request.domain_id) - pool = self.conn.storagePoolLookupByName("volumes") - vol = pool.storageVolLookupByName(request.volume_id) - - domain_dict = xmltodict.parse(domain.XMLDesc()) - disks = domain_dict["domain"]["devices"]["disk"] - - volumes_ids = [ - d["alias"]["@name"][3:] - for d in disks - if d["@device"] == "disk" and d["alias"]["@name"].startswith("ua-") - ] - - if request.volume_id in volumes_ids: - return volume_pb2.VolumeAttachment( - domain_id=request.domain_id, - volume_id=request.volume_id, - disk_address=disk_address(domain, request.volume_id), - ) - - disk_shortnames = [d["target"]["@dev"][-1:] for d in disks] - disk_letter = sorted(set(string.ascii_lowercase).difference(disk_shortnames))[0] - domain.attachDeviceFlags( - f""" - - - - - - """, - libvirt.VIR_DOMAIN_AFFECT_LIVE | libvirt.VIR_DOMAIN_AFFECT_CONFIG, - ) - - return volume_pb2.VolumeAttachment( - domain_id=request.domain_id, - volume_id=request.volume_id, - disk_address=disk_address(domain, request.volume_id), - ) - - def DetachVolume(self, request, context): - domain = self.conn.lookupByUUIDString(request.domain_id) - alias = f"ua-{request.volume_id}" - try: - domain.detachDeviceAlias( - alias, - libvirt.VIR_DOMAIN_AFFECT_LIVE | libvirt.VIR_DOMAIN_AFFECT_CONFIG, - ) - except libvirt.libvirtError as e: - if f"no device found with alias {alias}" not in e.get_error_message(): - raise e - return empty_pb2.Empty() - - def GetPortForwarding(self, request, context): - with self.session_factory() as session: - forwarding = ( - session.execute( - select(PortForwarding).filter( - PortForwarding.protocol == request.protocol, - PortForwarding.source_port == request.source_port, - ) - ) - .scalars() - .one_or_none() - ) - if forwarding is None: - context.set_code(StatusCode.NOT_FOUND) - return empty_pb2.Empty() - return port_forwarding_pb2.PortForwarding( - protocol=forwarding.protocol, - source_port=forwarding.source_port, - target_ip=forwarding.target_ip, - target_port=forwarding.target_port, - ) - - def ListPortForwardings(self, request, context): - with self.session_factory() as session: - fwds = session.execute(select(PortForwarding)).scalars().all() - return port_forwarding_pb2.ListPortForwardingsResponse( - port_forwardings=[ - port_forwarding_pb2.PortForwarding( - protocol=fwd.protocol, - source_port=fwd.source_port, - target_ip=fwd.target_ip, - target_port=fwd.target_port, - ) - for fwd in fwds - ] - ) - - def PutPortForwarding(self, request, context): - f = request.port_forwarding - route = PortForwarding( - protocol=f.protocol, - source_port=f.source_port, - target_ip=f.target_ip, - target_port=f.target_port, - ) - - with self.session_factory() as session: - session.merge(route) - session.commit() - self.port_fwd_sync_handler.handle_sync(session, self.conn) - - return f - - def DeletePortForwarding(self, request, context): - with self.session_factory() as session: - session.execute( - delete(PortForwarding).where( - PortForwarding.protocol == request.protocol, - PortForwarding.source_port == request.source_port, - ) - ) - session.commit() - self.port_fwd_sync_handler.handle_sync(session, self.conn) - - return empty_pb2.Empty() - - def sync(self): - with self.session_factory() as session: - self.port_fwd_sync_handler.handle_sync(session, self.conn) - - def SyncRoutes(self, request, context): - self.tables_synchronizer.handle_sync(self.controller) - self.routes_synchronizer.handle_sync(self.controller) - return empty_pb2.Empty() diff --git a/minivirt/daemon_pb2.py b/minivirt/daemon_pb2.py deleted file mode 100644 index 0ce5595..0000000 --- a/minivirt/daemon_pb2.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: minivirt/daemon.proto -"""Generated protocol buffer code.""" -from google.protobuf.internal import builder as _builder -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 -from minivirt import domain_pb2 as minivirt_dot_domain__pb2 -from minivirt import volume_pb2 as minivirt_dot_volume__pb2 -from minivirt import port_forwarding_pb2 as minivirt_dot_port__forwarding__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15minivirt/daemon.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x15minivirt/domain.proto\x1a\x15minivirt/volume.proto\x1a\x1eminivirt/port_forwarding.proto\"\x13\n\x11SyncRoutesRequest2\x95\x0c\n\rDaemonService\x12,\n\nGetNetwork\x12\x12.GetNetworkRequest\x1a\x08.Network\"\x00\x12=\n\x0cListNetworks\x12\x14.ListNetworksRequest\x1a\x15.ListNetworksResponse\"\x00\x12\x32\n\rCreateNetwork\x12\x15.CreateNetworkRequest\x1a\x08.Network\"\x00\x12@\n\rDeleteNetwork\x12\x15.DeleteNetworkRequest\x1a\x16.google.protobuf.Empty\"\x00\x12<\n\x0bStartDomain\x12\x13.StartDomainRequest\x1a\x16.google.protobuf.Empty\"\x00\x12:\n\nStopDomain\x12\x12.StopDomainRequest\x1a\x16.google.protobuf.Empty\"\x00\x12)\n\tGetDomain\x12\x11.GetDomainRequest\x1a\x07.Domain\"\x00\x12:\n\x0bListDomains\x12\x13.ListDomainsRequest\x1a\x14.ListDomainsResponse\"\x00\x12/\n\x0c\x43reateDomain\x12\x14.CreateDomainRequest\x1a\x07.Domain\"\x00\x12>\n\x0c\x44\x65leteDomain\x12\x14.DeleteDomainRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x37\n\rDownloadImage\x12\x15.DownloadImageRequest\x1a\x0b.ImageChunk\"\x00\x30\x01\x12)\n\tGetVolume\x12\x11.GetVolumeRequest\x1a\x07.Volume\"\x00\x12:\n\x0bListVolumes\x12\x13.ListVolumesRequest\x1a\x14.ListVolumesResponse\"\x00\x12/\n\x0c\x43reateVolume\x12\x14.CreateVolumeRequest\x1a\x07.Volume\"\x00\x12/\n\x0cUpdateVolume\x12\x14.UpdateVolumeRequest\x1a\x07.Volume\"\x00\x12>\n\x0c\x44\x65leteVolume\x12\x14.DeleteVolumeRequest\x1a\x16.google.protobuf.Empty\"\x00\x12X\n\x15ListVolumeAttachments\x12\x1d.ListVolumeAttachmentsRequest\x1a\x1e.ListVolumeAttachmentsResponse\"\x00\x12G\n\x13GetVolumeAttachment\x12\x1b.VolumeAttachmentIdentifier\x1a\x11.VolumeAttachment\"\x00\x12@\n\x0c\x41ttachVolume\x12\x1b.VolumeAttachmentIdentifier\x1a\x11.VolumeAttachment\"\x00\x12\x45\n\x0c\x44\x65tachVolume\x12\x1b.VolumeAttachmentIdentifier\x1a\x16.google.protobuf.Empty\"\x00\x12\x41\n\x11GetPortForwarding\x12\x19.PortForwardingIdentifier\x1a\x0f.PortForwarding\"\x00\x12R\n\x13ListPortForwardings\x12\x1b.ListPortForwardingsRequest\x1a\x1c.ListPortForwardingsResponse\"\x00\x12\x41\n\x11PutPortForwarding\x12\x19.PutPortForwardingRequest\x1a\x0f.PortForwarding\"\x00\x12K\n\x14\x44\x65letePortForwarding\x12\x19.PortForwardingIdentifier\x1a\x16.google.protobuf.Empty\"\x00\x12:\n\nSyncRoutes\x12\x12.SyncRoutesRequest\x1a\x16.google.protobuf.Empty\"\x00\x42\x06Z\x04.;pbb\x06proto3') - -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'minivirt.daemon_pb2', globals()) -if _descriptor._USE_C_DESCRIPTORS == False: - - DESCRIPTOR._options = None - DESCRIPTOR._serialized_options = b'Z\004.;pb' - _SYNCROUTESREQUEST._serialized_start=132 - _SYNCROUTESREQUEST._serialized_end=151 - _DAEMONSERVICE._serialized_start=154 - _DAEMONSERVICE._serialized_end=1711 -# @@protoc_insertion_point(module_scope) diff --git a/minivirt/daemon_pb2_grpc.py b/minivirt/daemon_pb2_grpc.py deleted file mode 100644 index d202fd5..0000000 --- a/minivirt/daemon_pb2_grpc.py +++ /dev/null @@ -1,862 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 -from minivirt import daemon_pb2 as minivirt_dot_daemon__pb2 -from minivirt import domain_pb2 as minivirt_dot_domain__pb2 -from minivirt import port_forwarding_pb2 as minivirt_dot_port__forwarding__pb2 -from minivirt import volume_pb2 as minivirt_dot_volume__pb2 - - -class DaemonServiceStub(object): - """Missing associated documentation comment in .proto file.""" - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.GetNetwork = channel.unary_unary( - '/DaemonService/GetNetwork', - request_serializer=minivirt_dot_domain__pb2.GetNetworkRequest.SerializeToString, - response_deserializer=minivirt_dot_domain__pb2.Network.FromString, - ) - self.ListNetworks = channel.unary_unary( - '/DaemonService/ListNetworks', - request_serializer=minivirt_dot_domain__pb2.ListNetworksRequest.SerializeToString, - response_deserializer=minivirt_dot_domain__pb2.ListNetworksResponse.FromString, - ) - self.CreateNetwork = channel.unary_unary( - '/DaemonService/CreateNetwork', - request_serializer=minivirt_dot_domain__pb2.CreateNetworkRequest.SerializeToString, - response_deserializer=minivirt_dot_domain__pb2.Network.FromString, - ) - self.DeleteNetwork = channel.unary_unary( - '/DaemonService/DeleteNetwork', - request_serializer=minivirt_dot_domain__pb2.DeleteNetworkRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.StartDomain = channel.unary_unary( - '/DaemonService/StartDomain', - request_serializer=minivirt_dot_domain__pb2.StartDomainRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.StopDomain = channel.unary_unary( - '/DaemonService/StopDomain', - request_serializer=minivirt_dot_domain__pb2.StopDomainRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.GetDomain = channel.unary_unary( - '/DaemonService/GetDomain', - request_serializer=minivirt_dot_domain__pb2.GetDomainRequest.SerializeToString, - response_deserializer=minivirt_dot_domain__pb2.Domain.FromString, - ) - self.ListDomains = channel.unary_unary( - '/DaemonService/ListDomains', - request_serializer=minivirt_dot_domain__pb2.ListDomainsRequest.SerializeToString, - response_deserializer=minivirt_dot_domain__pb2.ListDomainsResponse.FromString, - ) - self.CreateDomain = channel.unary_unary( - '/DaemonService/CreateDomain', - request_serializer=minivirt_dot_domain__pb2.CreateDomainRequest.SerializeToString, - response_deserializer=minivirt_dot_domain__pb2.Domain.FromString, - ) - self.DeleteDomain = channel.unary_unary( - '/DaemonService/DeleteDomain', - request_serializer=minivirt_dot_domain__pb2.DeleteDomainRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.DownloadImage = channel.unary_stream( - '/DaemonService/DownloadImage', - request_serializer=minivirt_dot_domain__pb2.DownloadImageRequest.SerializeToString, - response_deserializer=minivirt_dot_domain__pb2.ImageChunk.FromString, - ) - self.GetVolume = channel.unary_unary( - '/DaemonService/GetVolume', - request_serializer=minivirt_dot_volume__pb2.GetVolumeRequest.SerializeToString, - response_deserializer=minivirt_dot_volume__pb2.Volume.FromString, - ) - self.ListVolumes = channel.unary_unary( - '/DaemonService/ListVolumes', - request_serializer=minivirt_dot_volume__pb2.ListVolumesRequest.SerializeToString, - response_deserializer=minivirt_dot_volume__pb2.ListVolumesResponse.FromString, - ) - self.CreateVolume = channel.unary_unary( - '/DaemonService/CreateVolume', - request_serializer=minivirt_dot_volume__pb2.CreateVolumeRequest.SerializeToString, - response_deserializer=minivirt_dot_volume__pb2.Volume.FromString, - ) - self.UpdateVolume = channel.unary_unary( - '/DaemonService/UpdateVolume', - request_serializer=minivirt_dot_volume__pb2.UpdateVolumeRequest.SerializeToString, - response_deserializer=minivirt_dot_volume__pb2.Volume.FromString, - ) - self.DeleteVolume = channel.unary_unary( - '/DaemonService/DeleteVolume', - request_serializer=minivirt_dot_volume__pb2.DeleteVolumeRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.ListVolumeAttachments = channel.unary_unary( - '/DaemonService/ListVolumeAttachments', - request_serializer=minivirt_dot_volume__pb2.ListVolumeAttachmentsRequest.SerializeToString, - response_deserializer=minivirt_dot_volume__pb2.ListVolumeAttachmentsResponse.FromString, - ) - self.GetVolumeAttachment = channel.unary_unary( - '/DaemonService/GetVolumeAttachment', - request_serializer=minivirt_dot_volume__pb2.VolumeAttachmentIdentifier.SerializeToString, - response_deserializer=minivirt_dot_volume__pb2.VolumeAttachment.FromString, - ) - self.AttachVolume = channel.unary_unary( - '/DaemonService/AttachVolume', - request_serializer=minivirt_dot_volume__pb2.VolumeAttachmentIdentifier.SerializeToString, - response_deserializer=minivirt_dot_volume__pb2.VolumeAttachment.FromString, - ) - self.DetachVolume = channel.unary_unary( - '/DaemonService/DetachVolume', - request_serializer=minivirt_dot_volume__pb2.VolumeAttachmentIdentifier.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.GetPortForwarding = channel.unary_unary( - '/DaemonService/GetPortForwarding', - request_serializer=minivirt_dot_port__forwarding__pb2.PortForwardingIdentifier.SerializeToString, - response_deserializer=minivirt_dot_port__forwarding__pb2.PortForwarding.FromString, - ) - self.ListPortForwardings = channel.unary_unary( - '/DaemonService/ListPortForwardings', - request_serializer=minivirt_dot_port__forwarding__pb2.ListPortForwardingsRequest.SerializeToString, - response_deserializer=minivirt_dot_port__forwarding__pb2.ListPortForwardingsResponse.FromString, - ) - self.PutPortForwarding = channel.unary_unary( - '/DaemonService/PutPortForwarding', - request_serializer=minivirt_dot_port__forwarding__pb2.PutPortForwardingRequest.SerializeToString, - response_deserializer=minivirt_dot_port__forwarding__pb2.PortForwarding.FromString, - ) - self.DeletePortForwarding = channel.unary_unary( - '/DaemonService/DeletePortForwarding', - request_serializer=minivirt_dot_port__forwarding__pb2.PortForwardingIdentifier.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.SyncRoutes = channel.unary_unary( - '/DaemonService/SyncRoutes', - request_serializer=minivirt_dot_daemon__pb2.SyncRoutesRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - - -class DaemonServiceServicer(object): - """Missing associated documentation comment in .proto file.""" - - def GetNetwork(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListNetworks(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def CreateNetwork(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeleteNetwork(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def StartDomain(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def StopDomain(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetDomain(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListDomains(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def CreateDomain(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeleteDomain(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DownloadImage(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetVolume(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListVolumes(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def CreateVolume(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def UpdateVolume(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeleteVolume(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListVolumeAttachments(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetVolumeAttachment(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def AttachVolume(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DetachVolume(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetPortForwarding(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListPortForwardings(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def PutPortForwarding(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeletePortForwarding(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def SyncRoutes(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_DaemonServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'GetNetwork': grpc.unary_unary_rpc_method_handler( - servicer.GetNetwork, - request_deserializer=minivirt_dot_domain__pb2.GetNetworkRequest.FromString, - response_serializer=minivirt_dot_domain__pb2.Network.SerializeToString, - ), - 'ListNetworks': grpc.unary_unary_rpc_method_handler( - servicer.ListNetworks, - request_deserializer=minivirt_dot_domain__pb2.ListNetworksRequest.FromString, - response_serializer=minivirt_dot_domain__pb2.ListNetworksResponse.SerializeToString, - ), - 'CreateNetwork': grpc.unary_unary_rpc_method_handler( - servicer.CreateNetwork, - request_deserializer=minivirt_dot_domain__pb2.CreateNetworkRequest.FromString, - response_serializer=minivirt_dot_domain__pb2.Network.SerializeToString, - ), - 'DeleteNetwork': grpc.unary_unary_rpc_method_handler( - servicer.DeleteNetwork, - request_deserializer=minivirt_dot_domain__pb2.DeleteNetworkRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'StartDomain': grpc.unary_unary_rpc_method_handler( - servicer.StartDomain, - request_deserializer=minivirt_dot_domain__pb2.StartDomainRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'StopDomain': grpc.unary_unary_rpc_method_handler( - servicer.StopDomain, - request_deserializer=minivirt_dot_domain__pb2.StopDomainRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'GetDomain': grpc.unary_unary_rpc_method_handler( - servicer.GetDomain, - request_deserializer=minivirt_dot_domain__pb2.GetDomainRequest.FromString, - response_serializer=minivirt_dot_domain__pb2.Domain.SerializeToString, - ), - 'ListDomains': grpc.unary_unary_rpc_method_handler( - servicer.ListDomains, - request_deserializer=minivirt_dot_domain__pb2.ListDomainsRequest.FromString, - response_serializer=minivirt_dot_domain__pb2.ListDomainsResponse.SerializeToString, - ), - 'CreateDomain': grpc.unary_unary_rpc_method_handler( - servicer.CreateDomain, - request_deserializer=minivirt_dot_domain__pb2.CreateDomainRequest.FromString, - response_serializer=minivirt_dot_domain__pb2.Domain.SerializeToString, - ), - 'DeleteDomain': grpc.unary_unary_rpc_method_handler( - servicer.DeleteDomain, - request_deserializer=minivirt_dot_domain__pb2.DeleteDomainRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'DownloadImage': grpc.unary_stream_rpc_method_handler( - servicer.DownloadImage, - request_deserializer=minivirt_dot_domain__pb2.DownloadImageRequest.FromString, - response_serializer=minivirt_dot_domain__pb2.ImageChunk.SerializeToString, - ), - 'GetVolume': grpc.unary_unary_rpc_method_handler( - servicer.GetVolume, - request_deserializer=minivirt_dot_volume__pb2.GetVolumeRequest.FromString, - response_serializer=minivirt_dot_volume__pb2.Volume.SerializeToString, - ), - 'ListVolumes': grpc.unary_unary_rpc_method_handler( - servicer.ListVolumes, - request_deserializer=minivirt_dot_volume__pb2.ListVolumesRequest.FromString, - response_serializer=minivirt_dot_volume__pb2.ListVolumesResponse.SerializeToString, - ), - 'CreateVolume': grpc.unary_unary_rpc_method_handler( - servicer.CreateVolume, - request_deserializer=minivirt_dot_volume__pb2.CreateVolumeRequest.FromString, - response_serializer=minivirt_dot_volume__pb2.Volume.SerializeToString, - ), - 'UpdateVolume': grpc.unary_unary_rpc_method_handler( - servicer.UpdateVolume, - request_deserializer=minivirt_dot_volume__pb2.UpdateVolumeRequest.FromString, - response_serializer=minivirt_dot_volume__pb2.Volume.SerializeToString, - ), - 'DeleteVolume': grpc.unary_unary_rpc_method_handler( - servicer.DeleteVolume, - request_deserializer=minivirt_dot_volume__pb2.DeleteVolumeRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'ListVolumeAttachments': grpc.unary_unary_rpc_method_handler( - servicer.ListVolumeAttachments, - request_deserializer=minivirt_dot_volume__pb2.ListVolumeAttachmentsRequest.FromString, - response_serializer=minivirt_dot_volume__pb2.ListVolumeAttachmentsResponse.SerializeToString, - ), - 'GetVolumeAttachment': grpc.unary_unary_rpc_method_handler( - servicer.GetVolumeAttachment, - request_deserializer=minivirt_dot_volume__pb2.VolumeAttachmentIdentifier.FromString, - response_serializer=minivirt_dot_volume__pb2.VolumeAttachment.SerializeToString, - ), - 'AttachVolume': grpc.unary_unary_rpc_method_handler( - servicer.AttachVolume, - request_deserializer=minivirt_dot_volume__pb2.VolumeAttachmentIdentifier.FromString, - response_serializer=minivirt_dot_volume__pb2.VolumeAttachment.SerializeToString, - ), - 'DetachVolume': grpc.unary_unary_rpc_method_handler( - servicer.DetachVolume, - request_deserializer=minivirt_dot_volume__pb2.VolumeAttachmentIdentifier.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'GetPortForwarding': grpc.unary_unary_rpc_method_handler( - servicer.GetPortForwarding, - request_deserializer=minivirt_dot_port__forwarding__pb2.PortForwardingIdentifier.FromString, - response_serializer=minivirt_dot_port__forwarding__pb2.PortForwarding.SerializeToString, - ), - 'ListPortForwardings': grpc.unary_unary_rpc_method_handler( - servicer.ListPortForwardings, - request_deserializer=minivirt_dot_port__forwarding__pb2.ListPortForwardingsRequest.FromString, - response_serializer=minivirt_dot_port__forwarding__pb2.ListPortForwardingsResponse.SerializeToString, - ), - 'PutPortForwarding': grpc.unary_unary_rpc_method_handler( - servicer.PutPortForwarding, - request_deserializer=minivirt_dot_port__forwarding__pb2.PutPortForwardingRequest.FromString, - response_serializer=minivirt_dot_port__forwarding__pb2.PortForwarding.SerializeToString, - ), - 'DeletePortForwarding': grpc.unary_unary_rpc_method_handler( - servicer.DeletePortForwarding, - request_deserializer=minivirt_dot_port__forwarding__pb2.PortForwardingIdentifier.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'SyncRoutes': grpc.unary_unary_rpc_method_handler( - servicer.SyncRoutes, - request_deserializer=minivirt_dot_daemon__pb2.SyncRoutesRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'DaemonService', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - - - # This class is part of an EXPERIMENTAL API. -class DaemonService(object): - """Missing associated documentation comment in .proto file.""" - - @staticmethod - def GetNetwork(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DaemonService/GetNetwork', - minivirt_dot_domain__pb2.GetNetworkRequest.SerializeToString, - minivirt_dot_domain__pb2.Network.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ListNetworks(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DaemonService/ListNetworks', - minivirt_dot_domain__pb2.ListNetworksRequest.SerializeToString, - minivirt_dot_domain__pb2.ListNetworksResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def CreateNetwork(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DaemonService/CreateNetwork', - minivirt_dot_domain__pb2.CreateNetworkRequest.SerializeToString, - minivirt_dot_domain__pb2.Network.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def DeleteNetwork(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DaemonService/DeleteNetwork', - minivirt_dot_domain__pb2.DeleteNetworkRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def StartDomain(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DaemonService/StartDomain', - minivirt_dot_domain__pb2.StartDomainRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def StopDomain(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DaemonService/StopDomain', - minivirt_dot_domain__pb2.StopDomainRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def GetDomain(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DaemonService/GetDomain', - minivirt_dot_domain__pb2.GetDomainRequest.SerializeToString, - minivirt_dot_domain__pb2.Domain.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ListDomains(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DaemonService/ListDomains', - minivirt_dot_domain__pb2.ListDomainsRequest.SerializeToString, - minivirt_dot_domain__pb2.ListDomainsResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def CreateDomain(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DaemonService/CreateDomain', - minivirt_dot_domain__pb2.CreateDomainRequest.SerializeToString, - minivirt_dot_domain__pb2.Domain.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def DeleteDomain(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DaemonService/DeleteDomain', - minivirt_dot_domain__pb2.DeleteDomainRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def DownloadImage(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_stream(request, target, '/DaemonService/DownloadImage', - minivirt_dot_domain__pb2.DownloadImageRequest.SerializeToString, - minivirt_dot_domain__pb2.ImageChunk.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def GetVolume(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DaemonService/GetVolume', - minivirt_dot_volume__pb2.GetVolumeRequest.SerializeToString, - minivirt_dot_volume__pb2.Volume.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ListVolumes(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DaemonService/ListVolumes', - minivirt_dot_volume__pb2.ListVolumesRequest.SerializeToString, - minivirt_dot_volume__pb2.ListVolumesResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def CreateVolume(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DaemonService/CreateVolume', - minivirt_dot_volume__pb2.CreateVolumeRequest.SerializeToString, - minivirt_dot_volume__pb2.Volume.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def UpdateVolume(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DaemonService/UpdateVolume', - minivirt_dot_volume__pb2.UpdateVolumeRequest.SerializeToString, - minivirt_dot_volume__pb2.Volume.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def DeleteVolume(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DaemonService/DeleteVolume', - minivirt_dot_volume__pb2.DeleteVolumeRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ListVolumeAttachments(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DaemonService/ListVolumeAttachments', - minivirt_dot_volume__pb2.ListVolumeAttachmentsRequest.SerializeToString, - minivirt_dot_volume__pb2.ListVolumeAttachmentsResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def GetVolumeAttachment(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DaemonService/GetVolumeAttachment', - minivirt_dot_volume__pb2.VolumeAttachmentIdentifier.SerializeToString, - minivirt_dot_volume__pb2.VolumeAttachment.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def AttachVolume(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DaemonService/AttachVolume', - minivirt_dot_volume__pb2.VolumeAttachmentIdentifier.SerializeToString, - minivirt_dot_volume__pb2.VolumeAttachment.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def DetachVolume(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DaemonService/DetachVolume', - minivirt_dot_volume__pb2.VolumeAttachmentIdentifier.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def GetPortForwarding(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DaemonService/GetPortForwarding', - minivirt_dot_port__forwarding__pb2.PortForwardingIdentifier.SerializeToString, - minivirt_dot_port__forwarding__pb2.PortForwarding.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ListPortForwardings(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DaemonService/ListPortForwardings', - minivirt_dot_port__forwarding__pb2.ListPortForwardingsRequest.SerializeToString, - minivirt_dot_port__forwarding__pb2.ListPortForwardingsResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def PutPortForwarding(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DaemonService/PutPortForwarding', - minivirt_dot_port__forwarding__pb2.PutPortForwardingRequest.SerializeToString, - minivirt_dot_port__forwarding__pb2.PortForwarding.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def DeletePortForwarding(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DaemonService/DeletePortForwarding', - minivirt_dot_port__forwarding__pb2.PortForwardingIdentifier.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def SyncRoutes(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DaemonService/SyncRoutes', - minivirt_dot_daemon__pb2.SyncRoutesRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/minivirt/dns_controller.py b/minivirt/dns_controller.py deleted file mode 100644 index 119ef39..0000000 --- a/minivirt/dns_controller.py +++ /dev/null @@ -1,104 +0,0 @@ -from dns import resolver -from dnslib import QTYPE, RCODE, RR, copy -from dnslib.server import DNSServer -from sqlalchemy import delete, select - -from minivirt.models import DNSRecord - - -class DNSController: - def __init__(self, session_factory): - self.session_factory = session_factory - self.server = None - - r = resolver.Resolver() - self.upstream = r.nameservers[0] - - def records(self): - with self.session_factory() as session: - return session.execute(select(DNSRecord)).scalars().all() - - def record(self, name, type): - with self.session_factory() as session: - return ( - session.execute( - select(DNSRecord).filter( - DNSRecord.name == name, - DNSRecord.type == type, - ) - ) - .scalars() - .one_or_none() - ) - - def load_zone_list(self): - zone_file = "\n".join( - [f"{f.name} {f.ttl} {f.type} {' '.join(f.records)}" for f in self.records()] - ) - return [(rr.rname, QTYPE[rr.rtype], rr) for rr in RR.fromZone(zone_file)] - - def set(self, record: DNSRecord): - with self.session_factory() as session: - session.merge(record) - session.commit() - - def remove(self, name, type): - with self.session_factory() as session: - session.execute( - delete(DNSRecord).where( - DNSRecord.name == name, - DNSRecord.type == type, - ) - ) - session.commit() - - def resolve(self, request, handler): - zone = self.load_zone_list() - - reply = None - qname = request.q.qname - qtype = QTYPE[request.q.qtype] - for name, rtype, rr in zone: - # Check if label & type match - if qname.matchGlob(name): - reply = request.reply() - if qtype == rtype or qtype == "ANY" or rtype == "CNAME": - # Since we have a glob match fix reply label - a = copy.copy(rr) - a.rname = qname - reply.add_answer(a) - # Check for A/AAAA records associated with reply and - # add in additional section - if rtype in ["CNAME", "NS", "MX", "PTR"]: - for a_name, a_rtype, a_rr in zone: - if a_name == rr.rdata.label and a_rtype in ["A", "AAAA"]: - reply.add_answer(a_rr) - - if reply is None: - # check for zone delegation - for name, rtype, rr in zone: - if rtype == "NS" and qname.matchSuffix(name): - if reply is None: - reply = request.reply() - reply.add_auth(copy.copy(rr)) - - if reply is None: - # try: - # reply = dnslib.DNSRecord.parse(request.send(self.upstream, 53, timeout=3)) - # except socket.timeout: - # reply.header.rcode = RCODE.SERVFAIL - reply = request.reply() - reply.header.rcode = RCODE.NXDOMAIN - reply.header.ra = False # we don't support recursion - return reply - - def start(self, port=53): - self.server = DNSServer(self, port=port) - self.server.start_thread() - return self.server.server.server_address[1] - - def stop(self): - if self.server is not None: - self.server.stop() - self.server.server.server_close() - self.server = None diff --git a/minivirt/dns_pb2.py b/minivirt/dns_pb2.py deleted file mode 100644 index ed12e7f..0000000 --- a/minivirt/dns_pb2.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: minivirt/dns.proto -"""Generated protocol buffer code.""" -from google.protobuf.internal import builder as _builder -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x12minivirt/dns.proto\x1a\x1bgoogle/protobuf/empty.proto\"1\n\x13\x44NSRecordIdentifier\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\"E\n\tDNSRecord\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\x0b\n\x03ttl\x18\x03 \x01(\x04\x12\x0f\n\x07records\x18\x04 \x03(\t\"\x17\n\x15ListDNSRecordsRequest\"9\n\x16ListDNSRecordsResponse\x12\x1f\n\x0b\x64ns_records\x18\x01 \x03(\x0b\x32\n.DNSRecord\"5\n\x13PutDNSRecordRequest\x12\x1e\n\ndns_record\x18\x01 \x01(\x0b\x32\n.DNSRecord2\xf5\x01\n\x03\x44NS\x12\x32\n\x0cGetDNSRecord\x12\x14.DNSRecordIdentifier\x1a\n.DNSRecord\"\x00\x12\x43\n\x0eListDNSRecords\x12\x16.ListDNSRecordsRequest\x1a\x17.ListDNSRecordsResponse\"\x00\x12\x32\n\x0cPutDNSRecord\x12\x14.PutDNSRecordRequest\x1a\n.DNSRecord\"\x00\x12\x41\n\x0f\x44\x65leteDNSRecord\x12\x14.DNSRecordIdentifier\x1a\x16.google.protobuf.Empty\"\x00\x42\x06Z\x04.;pbb\x06proto3') - -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'minivirt.dns_pb2', globals()) -if _descriptor._USE_C_DESCRIPTORS == False: - - DESCRIPTOR._options = None - DESCRIPTOR._serialized_options = b'Z\004.;pb' - _DNSRECORDIDENTIFIER._serialized_start=51 - _DNSRECORDIDENTIFIER._serialized_end=100 - _DNSRECORD._serialized_start=102 - _DNSRECORD._serialized_end=171 - _LISTDNSRECORDSREQUEST._serialized_start=173 - _LISTDNSRECORDSREQUEST._serialized_end=196 - _LISTDNSRECORDSRESPONSE._serialized_start=198 - _LISTDNSRECORDSRESPONSE._serialized_end=255 - _PUTDNSRECORDREQUEST._serialized_start=257 - _PUTDNSRECORDREQUEST._serialized_end=310 - _DNS._serialized_start=313 - _DNS._serialized_end=558 -# @@protoc_insertion_point(module_scope) diff --git a/minivirt/dns_pb2_grpc.py b/minivirt/dns_pb2_grpc.py deleted file mode 100644 index 727efe5..0000000 --- a/minivirt/dns_pb2_grpc.py +++ /dev/null @@ -1,166 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 -from minivirt import dns_pb2 as minivirt_dot_dns__pb2 - - -class DNSStub(object): - """Missing associated documentation comment in .proto file.""" - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.GetDNSRecord = channel.unary_unary( - '/DNS/GetDNSRecord', - request_serializer=minivirt_dot_dns__pb2.DNSRecordIdentifier.SerializeToString, - response_deserializer=minivirt_dot_dns__pb2.DNSRecord.FromString, - ) - self.ListDNSRecords = channel.unary_unary( - '/DNS/ListDNSRecords', - request_serializer=minivirt_dot_dns__pb2.ListDNSRecordsRequest.SerializeToString, - response_deserializer=minivirt_dot_dns__pb2.ListDNSRecordsResponse.FromString, - ) - self.PutDNSRecord = channel.unary_unary( - '/DNS/PutDNSRecord', - request_serializer=minivirt_dot_dns__pb2.PutDNSRecordRequest.SerializeToString, - response_deserializer=minivirt_dot_dns__pb2.DNSRecord.FromString, - ) - self.DeleteDNSRecord = channel.unary_unary( - '/DNS/DeleteDNSRecord', - request_serializer=minivirt_dot_dns__pb2.DNSRecordIdentifier.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - - -class DNSServicer(object): - """Missing associated documentation comment in .proto file.""" - - def GetDNSRecord(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListDNSRecords(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def PutDNSRecord(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeleteDNSRecord(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_DNSServicer_to_server(servicer, server): - rpc_method_handlers = { - 'GetDNSRecord': grpc.unary_unary_rpc_method_handler( - servicer.GetDNSRecord, - request_deserializer=minivirt_dot_dns__pb2.DNSRecordIdentifier.FromString, - response_serializer=minivirt_dot_dns__pb2.DNSRecord.SerializeToString, - ), - 'ListDNSRecords': grpc.unary_unary_rpc_method_handler( - servicer.ListDNSRecords, - request_deserializer=minivirt_dot_dns__pb2.ListDNSRecordsRequest.FromString, - response_serializer=minivirt_dot_dns__pb2.ListDNSRecordsResponse.SerializeToString, - ), - 'PutDNSRecord': grpc.unary_unary_rpc_method_handler( - servicer.PutDNSRecord, - request_deserializer=minivirt_dot_dns__pb2.PutDNSRecordRequest.FromString, - response_serializer=minivirt_dot_dns__pb2.DNSRecord.SerializeToString, - ), - 'DeleteDNSRecord': grpc.unary_unary_rpc_method_handler( - servicer.DeleteDNSRecord, - request_deserializer=minivirt_dot_dns__pb2.DNSRecordIdentifier.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'DNS', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - - - # This class is part of an EXPERIMENTAL API. -class DNS(object): - """Missing associated documentation comment in .proto file.""" - - @staticmethod - def GetDNSRecord(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DNS/GetDNSRecord', - minivirt_dot_dns__pb2.DNSRecordIdentifier.SerializeToString, - minivirt_dot_dns__pb2.DNSRecord.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ListDNSRecords(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DNS/ListDNSRecords', - minivirt_dot_dns__pb2.ListDNSRecordsRequest.SerializeToString, - minivirt_dot_dns__pb2.ListDNSRecordsResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def PutDNSRecord(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DNS/PutDNSRecord', - minivirt_dot_dns__pb2.PutDNSRecordRequest.SerializeToString, - minivirt_dot_dns__pb2.DNSRecord.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def DeleteDNSRecord(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DNS/DeleteDNSRecord', - minivirt_dot_dns__pb2.DNSRecordIdentifier.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/minivirt/domain_pb2.py b/minivirt/domain_pb2.py deleted file mode 100644 index 8f041ec..0000000 --- a/minivirt/domain_pb2.py +++ /dev/null @@ -1,60 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: minivirt/domain.proto -"""Generated protocol buffer code.""" -from google.protobuf.internal import builder as _builder -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 -from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15minivirt/domain.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"0\n\x12StartDomainRequest\x12\x0c\n\x04host\x18\x01 \x01(\t\x12\x0c\n\x04uuid\x18\x02 \x01(\t\">\n\x11StopDomainRequest\x12\x0c\n\x04host\x18\x01 \x01(\t\x12\x0c\n\x04uuid\x18\x02 \x01(\t\x12\r\n\x05\x66orce\x18\x03 \x01(\x08\".\n\x10GetDomainRequest\x12\x0c\n\x04uuid\x18\x01 \x01(\t\x12\x0c\n\x04host\x18\x02 \x01(\t\"\xaf\x02\n\x06\x44omain\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x0c\n\x04uuid\x18\x02 \x01(\t\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x0c\n\x04vcpu\x18\x04 \x01(\r\x12\x0e\n\x06memory\x18\x05 \x01(\x04\x12\x0f\n\x07network\x18\x06 \x01(\t\x12\x0e\n\x06\x62ridge\x18\x07 \x01(\t\x12\r\n\x05state\x18\x08 \x01(\t\x12\x12\n\nprivate_ip\x18\t \x01(\t\x12\x14\n\x0cipv6_address\x18\x0f \x01(\t\x12\x11\n\tuser_data\x18\n \x01(\t\x12\x1d\n\x15nested_virtualization\x18\x0b \x01(\x08\x12\x12\n\nbase_image\x18\x0c \x01(\t\x12.\n\ncreated_at\x18\r \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x0f\n\x07os_type\x18\x0e \x01(\t\"\"\n\x12ListDomainsRequest\x12\x0c\n\x04host\x18\x01 \x01(\t\"/\n\x13ListDomainsResponse\x12\x18\n\x07\x64omains\x18\x01 \x03(\x0b\x32\x07.Domain\"<\n\x13\x43reateDomainRequest\x12\x17\n\x06\x64omain\x18\x01 \x01(\x0b\x32\x07.Domain\x12\x0c\n\x04host\x18\x02 \x01(\t\"1\n\x13\x44\x65leteDomainRequest\x12\x0c\n\x04uuid\x18\x01 \x01(\t\x12\x0c\n\x04host\x18\x02 \x01(\t\"7\n\x14\x44ownloadImageRequest\x12\x11\n\tdomain_id\x18\x01 \x01(\t\x12\x0c\n\x04host\x18\x02 \x01(\t\"\x1b\n\nImageChunk\x12\r\n\x05\x62ytes\x18\x01 \x01(\x0c\"/\n\x11GetNetworkRequest\x12\x0c\n\x04uuid\x18\x01 \x01(\t\x12\x0c\n\x04host\x18\x02 \x01(\t\"B\n\x07Network\x12\x0c\n\x04uuid\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0c\n\x04\x63idr\x18\x03 \x01(\t\x12\r\n\x05\x63idr6\x18\x04 \x01(\t\"#\n\x13ListNetworksRequest\x12\x0c\n\x04host\x18\x01 \x01(\t\"2\n\x14ListNetworksResponse\x12\x1a\n\x08networks\x18\x01 \x03(\x0b\x32\x08.Network\"?\n\x14\x43reateNetworkRequest\x12\x19\n\x07network\x18\x01 \x01(\x0b\x32\x08.Network\x12\x0c\n\x04host\x18\x02 \x01(\t\"2\n\x14\x44\x65leteNetworkRequest\x12\x0c\n\x04uuid\x18\x01 \x01(\t\x12\x0c\n\x04host\x18\x02 \x01(\t2\x83\x04\n\rDomainService\x12)\n\tGetDomain\x12\x11.GetDomainRequest\x1a\x07.Domain\"\x00\x12:\n\x0bListDomains\x12\x13.ListDomainsRequest\x1a\x14.ListDomainsResponse\"\x00\x12/\n\x0c\x43reateDomain\x12\x14.CreateDomainRequest\x1a\x07.Domain\"\x00\x12>\n\x0c\x44\x65leteDomain\x12\x14.DeleteDomainRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x37\n\rDownloadImage\x12\x15.DownloadImageRequest\x1a\x0b.ImageChunk\"\x00\x30\x01\x12,\n\nGetNetwork\x12\x12.GetNetworkRequest\x1a\x08.Network\"\x00\x12=\n\x0cListNetworks\x12\x14.ListNetworksRequest\x1a\x15.ListNetworksResponse\"\x00\x12\x32\n\rCreateNetwork\x12\x15.CreateNetworkRequest\x1a\x08.Network\"\x00\x12@\n\rDeleteNetwork\x12\x15.DeleteNetworkRequest\x1a\x16.google.protobuf.Empty\"\x00\x42\x06Z\x04.;pbb\x06proto3') - -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'minivirt.domain_pb2', globals()) -if _descriptor._USE_C_DESCRIPTORS == False: - - DESCRIPTOR._options = None - DESCRIPTOR._serialized_options = b'Z\004.;pb' - _STARTDOMAINREQUEST._serialized_start=87 - _STARTDOMAINREQUEST._serialized_end=135 - _STOPDOMAINREQUEST._serialized_start=137 - _STOPDOMAINREQUEST._serialized_end=199 - _GETDOMAINREQUEST._serialized_start=201 - _GETDOMAINREQUEST._serialized_end=247 - _DOMAIN._serialized_start=250 - _DOMAIN._serialized_end=553 - _LISTDOMAINSREQUEST._serialized_start=555 - _LISTDOMAINSREQUEST._serialized_end=589 - _LISTDOMAINSRESPONSE._serialized_start=591 - _LISTDOMAINSRESPONSE._serialized_end=638 - _CREATEDOMAINREQUEST._serialized_start=640 - _CREATEDOMAINREQUEST._serialized_end=700 - _DELETEDOMAINREQUEST._serialized_start=702 - _DELETEDOMAINREQUEST._serialized_end=751 - _DOWNLOADIMAGEREQUEST._serialized_start=753 - _DOWNLOADIMAGEREQUEST._serialized_end=808 - _IMAGECHUNK._serialized_start=810 - _IMAGECHUNK._serialized_end=837 - _GETNETWORKREQUEST._serialized_start=839 - _GETNETWORKREQUEST._serialized_end=886 - _NETWORK._serialized_start=888 - _NETWORK._serialized_end=954 - _LISTNETWORKSREQUEST._serialized_start=956 - _LISTNETWORKSREQUEST._serialized_end=991 - _LISTNETWORKSRESPONSE._serialized_start=993 - _LISTNETWORKSRESPONSE._serialized_end=1043 - _CREATENETWORKREQUEST._serialized_start=1045 - _CREATENETWORKREQUEST._serialized_end=1108 - _DELETENETWORKREQUEST._serialized_start=1110 - _DELETENETWORKREQUEST._serialized_end=1160 - _DOMAINSERVICE._serialized_start=1163 - _DOMAINSERVICE._serialized_end=1678 -# @@protoc_insertion_point(module_scope) diff --git a/minivirt/domain_pb2_grpc.py b/minivirt/domain_pb2_grpc.py deleted file mode 100644 index 04e622a..0000000 --- a/minivirt/domain_pb2_grpc.py +++ /dev/null @@ -1,331 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 -from minivirt import domain_pb2 as minivirt_dot_domain__pb2 - - -class DomainServiceStub(object): - """Missing associated documentation comment in .proto file.""" - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.GetDomain = channel.unary_unary( - '/DomainService/GetDomain', - request_serializer=minivirt_dot_domain__pb2.GetDomainRequest.SerializeToString, - response_deserializer=minivirt_dot_domain__pb2.Domain.FromString, - ) - self.ListDomains = channel.unary_unary( - '/DomainService/ListDomains', - request_serializer=minivirt_dot_domain__pb2.ListDomainsRequest.SerializeToString, - response_deserializer=minivirt_dot_domain__pb2.ListDomainsResponse.FromString, - ) - self.CreateDomain = channel.unary_unary( - '/DomainService/CreateDomain', - request_serializer=minivirt_dot_domain__pb2.CreateDomainRequest.SerializeToString, - response_deserializer=minivirt_dot_domain__pb2.Domain.FromString, - ) - self.DeleteDomain = channel.unary_unary( - '/DomainService/DeleteDomain', - request_serializer=minivirt_dot_domain__pb2.DeleteDomainRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.DownloadImage = channel.unary_stream( - '/DomainService/DownloadImage', - request_serializer=minivirt_dot_domain__pb2.DownloadImageRequest.SerializeToString, - response_deserializer=minivirt_dot_domain__pb2.ImageChunk.FromString, - ) - self.GetNetwork = channel.unary_unary( - '/DomainService/GetNetwork', - request_serializer=minivirt_dot_domain__pb2.GetNetworkRequest.SerializeToString, - response_deserializer=minivirt_dot_domain__pb2.Network.FromString, - ) - self.ListNetworks = channel.unary_unary( - '/DomainService/ListNetworks', - request_serializer=minivirt_dot_domain__pb2.ListNetworksRequest.SerializeToString, - response_deserializer=minivirt_dot_domain__pb2.ListNetworksResponse.FromString, - ) - self.CreateNetwork = channel.unary_unary( - '/DomainService/CreateNetwork', - request_serializer=minivirt_dot_domain__pb2.CreateNetworkRequest.SerializeToString, - response_deserializer=minivirt_dot_domain__pb2.Network.FromString, - ) - self.DeleteNetwork = channel.unary_unary( - '/DomainService/DeleteNetwork', - request_serializer=minivirt_dot_domain__pb2.DeleteNetworkRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - - -class DomainServiceServicer(object): - """Missing associated documentation comment in .proto file.""" - - def GetDomain(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListDomains(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def CreateDomain(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeleteDomain(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DownloadImage(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetNetwork(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListNetworks(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def CreateNetwork(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeleteNetwork(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_DomainServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'GetDomain': grpc.unary_unary_rpc_method_handler( - servicer.GetDomain, - request_deserializer=minivirt_dot_domain__pb2.GetDomainRequest.FromString, - response_serializer=minivirt_dot_domain__pb2.Domain.SerializeToString, - ), - 'ListDomains': grpc.unary_unary_rpc_method_handler( - servicer.ListDomains, - request_deserializer=minivirt_dot_domain__pb2.ListDomainsRequest.FromString, - response_serializer=minivirt_dot_domain__pb2.ListDomainsResponse.SerializeToString, - ), - 'CreateDomain': grpc.unary_unary_rpc_method_handler( - servicer.CreateDomain, - request_deserializer=minivirt_dot_domain__pb2.CreateDomainRequest.FromString, - response_serializer=minivirt_dot_domain__pb2.Domain.SerializeToString, - ), - 'DeleteDomain': grpc.unary_unary_rpc_method_handler( - servicer.DeleteDomain, - request_deserializer=minivirt_dot_domain__pb2.DeleteDomainRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'DownloadImage': grpc.unary_stream_rpc_method_handler( - servicer.DownloadImage, - request_deserializer=minivirt_dot_domain__pb2.DownloadImageRequest.FromString, - response_serializer=minivirt_dot_domain__pb2.ImageChunk.SerializeToString, - ), - 'GetNetwork': grpc.unary_unary_rpc_method_handler( - servicer.GetNetwork, - request_deserializer=minivirt_dot_domain__pb2.GetNetworkRequest.FromString, - response_serializer=minivirt_dot_domain__pb2.Network.SerializeToString, - ), - 'ListNetworks': grpc.unary_unary_rpc_method_handler( - servicer.ListNetworks, - request_deserializer=minivirt_dot_domain__pb2.ListNetworksRequest.FromString, - response_serializer=minivirt_dot_domain__pb2.ListNetworksResponse.SerializeToString, - ), - 'CreateNetwork': grpc.unary_unary_rpc_method_handler( - servicer.CreateNetwork, - request_deserializer=minivirt_dot_domain__pb2.CreateNetworkRequest.FromString, - response_serializer=minivirt_dot_domain__pb2.Network.SerializeToString, - ), - 'DeleteNetwork': grpc.unary_unary_rpc_method_handler( - servicer.DeleteNetwork, - request_deserializer=minivirt_dot_domain__pb2.DeleteNetworkRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'DomainService', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - - - # This class is part of an EXPERIMENTAL API. -class DomainService(object): - """Missing associated documentation comment in .proto file.""" - - @staticmethod - def GetDomain(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DomainService/GetDomain', - minivirt_dot_domain__pb2.GetDomainRequest.SerializeToString, - minivirt_dot_domain__pb2.Domain.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ListDomains(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DomainService/ListDomains', - minivirt_dot_domain__pb2.ListDomainsRequest.SerializeToString, - minivirt_dot_domain__pb2.ListDomainsResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def CreateDomain(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DomainService/CreateDomain', - minivirt_dot_domain__pb2.CreateDomainRequest.SerializeToString, - minivirt_dot_domain__pb2.Domain.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def DeleteDomain(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DomainService/DeleteDomain', - minivirt_dot_domain__pb2.DeleteDomainRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def DownloadImage(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_stream(request, target, '/DomainService/DownloadImage', - minivirt_dot_domain__pb2.DownloadImageRequest.SerializeToString, - minivirt_dot_domain__pb2.ImageChunk.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def GetNetwork(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DomainService/GetNetwork', - minivirt_dot_domain__pb2.GetNetworkRequest.SerializeToString, - minivirt_dot_domain__pb2.Network.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ListNetworks(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DomainService/ListNetworks', - minivirt_dot_domain__pb2.ListNetworksRequest.SerializeToString, - minivirt_dot_domain__pb2.ListNetworksResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def CreateNetwork(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DomainService/CreateNetwork', - minivirt_dot_domain__pb2.CreateNetworkRequest.SerializeToString, - minivirt_dot_domain__pb2.Network.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def DeleteNetwork(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DomainService/DeleteNetwork', - minivirt_dot_domain__pb2.DeleteNetworkRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/minivirt/host.py b/minivirt/host.py deleted file mode 100644 index c5a317c..0000000 --- a/minivirt/host.py +++ /dev/null @@ -1,126 +0,0 @@ -import threading -import uuid -from datetime import datetime, timedelta - -import grpc -from google.protobuf import empty_pb2 -from grpc import StatusCode -from sqlalchemy import select - -from minivirt import host_pb2, host_pb2_grpc -from minivirt.models import BootstrapToken, Host - - -class HostController: - def __init__(self, session_factory, creds=None): - self.session_factory = session_factory - self.creds = creds - self.lock = threading.Lock() - self.cache = {} - - for host in self.hosts(): - self.cache[host.name] = self._create_channel(host.address) - - def _create_channel(self, address): - if self.creds is not None: - return grpc.secure_channel(address, self.creds) - return grpc.insecure_channel(address) - - def hosts(self): - with self.session_factory() as session: - return session.execute(select(Host)).scalars().all() - - def host(self, name): - with self.session_factory.begin() as session: - return session.get(Host, name) - - def channel(self, hostname): - with self.lock: - return self.cache.get(hostname) - - def channels(self): - with self.lock: - return list(self.cache.values()) - - def register(self, token, hostname, address): - with self.session_factory.begin() as session: - token = session.get(BootstrapToken, token) - if token is None or token.expires_at <= datetime.utcnow(): - raise Exception("BootstrapToken invalid") - session.add(Host(name=hostname, address=address)) - with self.lock: - self.cache[hostname] = self._create_channel(address) - return empty_pb2.Empty() - - def deregister(self, hostname): - with self.session_factory.begin() as session: - host = session.get(Host, hostname) - if host is None: - raise Exception("Host not found") - session.delete(host) - with self.lock: - del self.cache[hostname] - - -class HostService(host_pb2_grpc.HostServiceServicer): - def __init__(self, host_controller: HostController, session_factory): - self.session_factory = session_factory - self.host_controller = host_controller - - def CreateBootstrapToken(self, request, context): - with self.session_factory.begin() as session: - if not request.expires_at: - expires_at = datetime.utcnow() + timedelta(minutes=10) - else: - expires_at = datetime.fromisoformat(request.expires_at) - token = BootstrapToken( - token=str(uuid.uuid4()), - expires_at=expires_at, - ) - session.add(token) - return host_pb2.CreateBootstrapTokenResponse(token=token.token) - - def GetHost(self, request, context): - host = self.host_controller.host(request.name) - if host is None: - context.set_code(StatusCode.NOT_FOUND) - context.set_details("Host not found") - return - return host_pb2.Host(name=host.name, address=host.address) - - def ListHosts(self, request, context): - hosts = self.host_controller.hosts() - hosts = [host_pb2.Host(name=h.name, address=h.address) for h in hosts] - return host_pb2.ListHostsResponse(hosts=hosts) - - def Register(self, request, context): - try: - self.host_controller.register(request.token, request.host.name, request.host.address) - return empty_pb2.Empty() - except Exception as e: - if "BootstrapToken invalid" in str(e): - context.set_code(StatusCode.INVALID_ARGUMENT) - context.set_details("BootstrapToken invalid") - return - raise e - - def Deregister(self, request, context): - try: - self.host_controller.deregister(request.name) - return empty_pb2.Empty() - except Exception as e: - if "Host not found" in str(e): - context.set_code(StatusCode.NOT_FOUND) - context.set_details("Host not found") - return - raise e - - def Heartbeat(self, request, context): - with self.session_factory.begin() as session: - host = session.get(Host, request.host.name) - if host is None: - context.set_code(StatusCode.NOT_FOUND) - context.set_details("Host not found") - return - host.last_heartbeat = datetime.utcnow() - return host_pb2.HeartbeatResponse() diff --git a/minivirt/host_pb2.py b/minivirt/host_pb2.py deleted file mode 100644 index 22c7fd5..0000000 --- a/minivirt/host_pb2.py +++ /dev/null @@ -1,43 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: minivirt/host.proto -"""Generated protocol buffer code.""" -from google.protobuf.internal import builder as _builder -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x13minivirt/host.proto\x1a\x1bgoogle/protobuf/empty.proto\"1\n\x1b\x43reateBootstrapTokenRequest\x12\x12\n\nexpires_at\x18\x01 \x01(\t\"-\n\x1c\x43reateBootstrapTokenResponse\x12\r\n\x05token\x18\x01 \x01(\t\"%\n\x04Host\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0f\n\x07\x61\x64\x64ress\x18\x02 \x01(\t\"\x12\n\x10ListHostsRequest\")\n\x11ListHostsResponse\x12\x14\n\x05hosts\x18\x01 \x03(\x0b\x32\x05.Host\"9\n\x13RegisterHostRequest\x12\x13\n\x04host\x18\x01 \x01(\x0b\x32\x05.Host\x12\r\n\x05token\x18\x02 \x01(\t\"\'\n\x10HeartbeatRequest\x12\x13\n\x04host\x18\x01 \x01(\x0b\x32\x05.Host\"\x13\n\x11HeartbeatResponse2\xc5\x02\n\x0bHostService\x12U\n\x14\x43reateBootstrapToken\x12\x1c.CreateBootstrapTokenRequest\x1a\x1d.CreateBootstrapTokenResponse\"\x00\x12\x19\n\x07GetHost\x12\x05.Host\x1a\x05.Host\"\x00\x12\x34\n\tListHosts\x12\x11.ListHostsRequest\x1a\x12.ListHostsResponse\"\x00\x12)\n\x08Register\x12\x14.RegisterHostRequest\x1a\x05.Host\"\x00\x12-\n\nDeregister\x12\x05.Host\x1a\x16.google.protobuf.Empty\"\x00\x12\x34\n\tHeartbeat\x12\x11.HeartbeatRequest\x1a\x12.HeartbeatResponse\"\x00\x42\x06Z\x04.;pbb\x06proto3') - -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'minivirt.host_pb2', globals()) -if _descriptor._USE_C_DESCRIPTORS == False: - - DESCRIPTOR._options = None - DESCRIPTOR._serialized_options = b'Z\004.;pb' - _CREATEBOOTSTRAPTOKENREQUEST._serialized_start=52 - _CREATEBOOTSTRAPTOKENREQUEST._serialized_end=101 - _CREATEBOOTSTRAPTOKENRESPONSE._serialized_start=103 - _CREATEBOOTSTRAPTOKENRESPONSE._serialized_end=148 - _HOST._serialized_start=150 - _HOST._serialized_end=187 - _LISTHOSTSREQUEST._serialized_start=189 - _LISTHOSTSREQUEST._serialized_end=207 - _LISTHOSTSRESPONSE._serialized_start=209 - _LISTHOSTSRESPONSE._serialized_end=250 - _REGISTERHOSTREQUEST._serialized_start=252 - _REGISTERHOSTREQUEST._serialized_end=309 - _HEARTBEATREQUEST._serialized_start=311 - _HEARTBEATREQUEST._serialized_end=350 - _HEARTBEATRESPONSE._serialized_start=352 - _HEARTBEATRESPONSE._serialized_end=371 - _HOSTSERVICE._serialized_start=374 - _HOSTSERVICE._serialized_end=699 -# @@protoc_insertion_point(module_scope) diff --git a/minivirt/host_pb2_grpc.py b/minivirt/host_pb2_grpc.py deleted file mode 100644 index ec74a36..0000000 --- a/minivirt/host_pb2_grpc.py +++ /dev/null @@ -1,232 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 -from minivirt import host_pb2 as minivirt_dot_host__pb2 - - -class HostServiceStub(object): - """Missing associated documentation comment in .proto file.""" - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.CreateBootstrapToken = channel.unary_unary( - '/HostService/CreateBootstrapToken', - request_serializer=minivirt_dot_host__pb2.CreateBootstrapTokenRequest.SerializeToString, - response_deserializer=minivirt_dot_host__pb2.CreateBootstrapTokenResponse.FromString, - ) - self.GetHost = channel.unary_unary( - '/HostService/GetHost', - request_serializer=minivirt_dot_host__pb2.Host.SerializeToString, - response_deserializer=minivirt_dot_host__pb2.Host.FromString, - ) - self.ListHosts = channel.unary_unary( - '/HostService/ListHosts', - request_serializer=minivirt_dot_host__pb2.ListHostsRequest.SerializeToString, - response_deserializer=minivirt_dot_host__pb2.ListHostsResponse.FromString, - ) - self.Register = channel.unary_unary( - '/HostService/Register', - request_serializer=minivirt_dot_host__pb2.RegisterHostRequest.SerializeToString, - response_deserializer=minivirt_dot_host__pb2.Host.FromString, - ) - self.Deregister = channel.unary_unary( - '/HostService/Deregister', - request_serializer=minivirt_dot_host__pb2.Host.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.Heartbeat = channel.unary_unary( - '/HostService/Heartbeat', - request_serializer=minivirt_dot_host__pb2.HeartbeatRequest.SerializeToString, - response_deserializer=minivirt_dot_host__pb2.HeartbeatResponse.FromString, - ) - - -class HostServiceServicer(object): - """Missing associated documentation comment in .proto file.""" - - def CreateBootstrapToken(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetHost(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListHosts(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def Register(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def Deregister(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def Heartbeat(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_HostServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'CreateBootstrapToken': grpc.unary_unary_rpc_method_handler( - servicer.CreateBootstrapToken, - request_deserializer=minivirt_dot_host__pb2.CreateBootstrapTokenRequest.FromString, - response_serializer=minivirt_dot_host__pb2.CreateBootstrapTokenResponse.SerializeToString, - ), - 'GetHost': grpc.unary_unary_rpc_method_handler( - servicer.GetHost, - request_deserializer=minivirt_dot_host__pb2.Host.FromString, - response_serializer=minivirt_dot_host__pb2.Host.SerializeToString, - ), - 'ListHosts': grpc.unary_unary_rpc_method_handler( - servicer.ListHosts, - request_deserializer=minivirt_dot_host__pb2.ListHostsRequest.FromString, - response_serializer=minivirt_dot_host__pb2.ListHostsResponse.SerializeToString, - ), - 'Register': grpc.unary_unary_rpc_method_handler( - servicer.Register, - request_deserializer=minivirt_dot_host__pb2.RegisterHostRequest.FromString, - response_serializer=minivirt_dot_host__pb2.Host.SerializeToString, - ), - 'Deregister': grpc.unary_unary_rpc_method_handler( - servicer.Deregister, - request_deserializer=minivirt_dot_host__pb2.Host.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'Heartbeat': grpc.unary_unary_rpc_method_handler( - servicer.Heartbeat, - request_deserializer=minivirt_dot_host__pb2.HeartbeatRequest.FromString, - response_serializer=minivirt_dot_host__pb2.HeartbeatResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'HostService', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - - - # This class is part of an EXPERIMENTAL API. -class HostService(object): - """Missing associated documentation comment in .proto file.""" - - @staticmethod - def CreateBootstrapToken(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/HostService/CreateBootstrapToken', - minivirt_dot_host__pb2.CreateBootstrapTokenRequest.SerializeToString, - minivirt_dot_host__pb2.CreateBootstrapTokenResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def GetHost(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/HostService/GetHost', - minivirt_dot_host__pb2.Host.SerializeToString, - minivirt_dot_host__pb2.Host.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ListHosts(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/HostService/ListHosts', - minivirt_dot_host__pb2.ListHostsRequest.SerializeToString, - minivirt_dot_host__pb2.ListHostsResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def Register(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/HostService/Register', - minivirt_dot_host__pb2.RegisterHostRequest.SerializeToString, - minivirt_dot_host__pb2.Host.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def Deregister(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/HostService/Deregister', - minivirt_dot_host__pb2.Host.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def Heartbeat(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/HostService/Heartbeat', - minivirt_dot_host__pb2.HeartbeatRequest.SerializeToString, - minivirt_dot_host__pb2.HeartbeatResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/minivirt/image.py b/minivirt/image.py deleted file mode 100644 index 436512a..0000000 --- a/minivirt/image.py +++ /dev/null @@ -1,91 +0,0 @@ -from io import BytesIO - -import pycdlib -import yaml - - -def _read_from_cloud_config_file(data, section): - section_data = BytesIO() - iso = pycdlib.PyCdlib() - iso.open_fp(BytesIO(data)) - iso.get_file_from_iso_fp(section_data, joliet_path=section) - iso.close() - return section_data.getvalue().decode() - - -def read_user_data_from_cloud_config_image(data): - return _read_from_cloud_config_file(data, "/user-data") - - -def read_ip_from_cloud_config_image(data): - network_config = _read_from_cloud_config_file(data, "/network-config") - network_config_dict = yaml.safe_load(network_config) - cidr = network_config_dict["ethernets"]["primary"]["addresses"][0] - ip, prefix = cidr.split("/") - return ip - - -def create_cloud_config_image( - domain_id, user_data, mac, network, network6, address, address6, gateway, gateway6, name -): - assert gateway in network - assert address in network - - if network6 is not None: - assert gateway6 in network6 - assert address6 in network6 - - network_config = f"""version: 2 -ethernets: - primary: - match: - macaddress: "{mac}" - set-name: "ens2" - dhcp4: false - dhcp6: false - # default libvirt network - addresses: - - {address}/{network.prefixlen} - {f"- {address6}/{network6.prefixlen}" if address6 is not None else ""} - gateway4: {gateway} - {f"gateway6: {gateway6}" if gateway6 is not None else ""} - nameservers: - addresses: - - {gateway} - {f"- {gateway6}" if gateway6 is not None else ""} -""" - - meta_config = f"""instance-id: {domain_id} -local-hostname: {name} -""" - - iso = pycdlib.PyCdlib() - iso.new( - interchange_level=3, - joliet=3, - rock_ridge="1.09", - vol_ident="cidata", - sys_ident="LINUX", - ) - - iso_config = { - "network-config": network_config, - "meta-data": meta_config, - "user-data": user_data, - } - - for section_name, data_string in iso_config.items(): - data = data_string.encode() - iso.add_fp( - BytesIO(data), - len(data), - iso_path=f'/{section_name.replace("-", "").upper()}.;1', - rr_name=section_name, - joliet_path=f"/{section_name}", - ) - - result = BytesIO() - iso.write_fp(result) - iso.close() - - return result.getvalue() diff --git a/minivirt/main.py b/minivirt/main.py deleted file mode 100644 index f4e69a8..0000000 --- a/minivirt/main.py +++ /dev/null @@ -1,297 +0,0 @@ -import argparse -import logging -import os -import re -from concurrent import futures - -import grpc -import libvirt -from grpc_reflection.v1alpha import reflection -from sqlalchemy import create_engine, event -from sqlalchemy.engine import Engine -from sqlalchemy.orm import sessionmaker -from sqlalchemy.pool import StaticPool - -from minivirt import ( - controller_pb2_grpc, - daemon_pb2_grpc, - dns_pb2_grpc, - domain_pb2, - domain_pb2_grpc, - host_pb2, - host_pb2_grpc, - port_forwarding_pb2_grpc, - route_pb2_grpc, - volume_pb2_grpc, -) -from minivirt.controller import Controller -from minivirt.daemon import DaemonService -from minivirt.dns_controller import DNSController -from minivirt.host import HostController, HostService -from minivirt.migrations import run_migrations -from minivirt.port_forwarding import IPTablesPortForwardingSynchronizer -from minivirt.utils import UnaryUnaryInterceptor -from minivirt.version import __version__ - -libvirt.registerErrorHandler(lambda u, e: None, None) - - -@event.listens_for(Engine, "connect") -def set_sqlite_pragma(dbapi_connection, connection_record): - cursor = dbapi_connection.cursor() - cursor.execute("PRAGMA foreign_keys=ON") - cursor.close() - - -def start_controller(args): - host, port = args.bind - host = host or "0.0.0.0" - - if args.debug: - logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO) - - engine = create_engine( - f"sqlite:///{os.path.join(args.config, 'controller.sqlite3')}", - connect_args={"check_same_thread": False}, - poolclass=StaticPool, - future=True, - ) - run_migrations(engine) - session_factory = sessionmaker(engine, future=True) - - dns_controller = DNSController(session_factory) - dns_controller.start(port=5353) - - server = grpc.server( - futures.ThreadPoolExecutor(max_workers=10), interceptors=[UnaryUnaryInterceptor()] - ) - - creds = read_grpc_creds(args.client_ca_cert, args.server_cert, args.server_key) - - host_controller = HostController(session_factory, to_channel_creds(creds)) - controller = Controller( - session_factory=session_factory, - host_controller=host_controller, - dns_controller=dns_controller, - ) - controller_pb2_grpc.add_ControllerServiceServicer_to_server(controller, server) - dns_pb2_grpc.add_DNSServicer_to_server(controller, server) - domain_pb2_grpc.add_DomainServiceServicer_to_server(controller, server) - port_forwarding_pb2_grpc.add_PortForwardingServiceServicer_to_server(controller, server) - route_pb2_grpc.add_RouteServiceServicer_to_server(controller, server) - volume_pb2_grpc.add_VolumeServiceServicer_to_server(controller, server) - host_pb2_grpc.add_HostServiceServicer_to_server( - HostService(host_controller, session_factory), server - ) - - server.add_secure_port(f"{host}:{port}", to_server_creds(creds)) - reflection.enable_server_reflection( - [ - service_descriptor.full_name - for service_descriptor in domain_pb2.DESCRIPTOR.services_by_name.values() - ] - + [reflection.SERVICE_NAME], - server, - ) - - logging.info(f"Starting Controller ({__version__})") - server.start() - server.wait_for_termination() - - -def read_grpc_creds(ca_cert_path, cert_path, key_path): - with ( - open(ca_cert_path, "rb") as ca_cert, - open(cert_path, "rb") as cert, - open(key_path, "rb") as key, - ): - root_certificate = ca_cert.read() - certificate_chain = cert.read() - private_key = key.read() - - return (root_certificate, certificate_chain, private_key) - - -def to_channel_creds(triple): - (root_certificate, certificate_chain, private_key) = triple - return grpc.ssl_channel_credentials( - root_certificates=root_certificate, - certificate_chain=certificate_chain, - private_key=private_key, - ) - - -def to_server_creds(triple): - (root_certificate, certificate_chain, private_key) = triple - return grpc.ssl_server_credentials( - [(private_key, certificate_chain)], - root_certificates=root_certificate, - require_client_auth=True, - ) - - -def get_controller_channel(args): - controller_host, controller_port = args.controller - client_key_pair_provided = args.client_cert is not None and args.client_key is not None - - assert args.server_ca_cert is not None - assert client_key_pair_provided - - return grpc.secure_channel( - f"{controller_host}:{controller_port}", - to_channel_creds(read_grpc_creds(args.server_ca_cert, args.client_cert, args.client_key)), - ) - - -def start_daemon(args): - addr, port = args.bind - port = port or 0 - addr = addr or "0.0.0.0" - - if args.debug: - logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO) - - engine = create_engine( - f"sqlite:///{os.path.join(args.config, 'daemon.sqlite3')}", - connect_args={"check_same_thread": False}, - poolclass=StaticPool, - future=True, - ) - run_migrations(engine) - session_factory = sessionmaker(engine, future=True) - - daemon = grpc.server( - futures.ThreadPoolExecutor(max_workers=10), interceptors=[UnaryUnaryInterceptor()] - ) - daemon.add_secure_port( - f"{addr}:{port}", - to_server_creds(read_grpc_creds(args.server_ca_cert, args.client_cert, args.client_key)), - ) - - controller_channel = get_controller_channel(args) - - daemon_service = DaemonService( - session_factory, - IPTablesPortForwardingSynchronizer( - controller_pb2_grpc.ControllerServiceStub(controller_channel), - args.dns_server, - ), - controller_channel, - ) - daemon_pb2_grpc.add_DaemonServiceServicer_to_server(daemon_service, daemon) - daemon_service.sync() - - logging.info(f"Starting Daemon ({__version__})") - daemon.start() - daemon.wait_for_termination() - - -def register_host(args): - addr, port = args.bind - port = port or 0 - addr = addr or "0.0.0.0" - - controller_channel = get_controller_channel(args) - - host_client = host_pb2_grpc.HostServiceStub(controller_channel) - token = host_client.CreateBootstrapToken(host_pb2.CreateBootstrapTokenRequest()).token - host_client.Register( - host_pb2.RegisterHostRequest( - token=token, - host=host_pb2.Host( - name=args.name, - address=f"{addr}:{port}", - ), - ) - ) - - logging.info(f"Successfully registered host ({__version__})") - - -def deregister_host(args): - controller_channel = get_controller_channel(args) - - host_client = host_pb2_grpc.HostServiceStub(controller_channel) - host_client.Deregister(host_pb2.Host(name=args.name)) - - logging.info(f"Successfully deregistered host ({__version__})") - - -def main(): - logging.basicConfig( - level=logging.DEBUG, format="%(asctime)s %(levelname)s:%(name)s:%(message)s" - ) - - p = re.compile(r"^(\S*):(\d+)$") - - def bind_address(s): - match = p.match(s) - if not match: - raise argparse.ArgumentTypeError("invalid bind address: " + s) - res = match.group(1), int(match.group(2)) - return res - - parser = argparse.ArgumentParser( - description="restvirt", formatter_class=argparse.ArgumentDefaultsHelpFormatter - ) - subparsers = parser.add_subparsers() - - controller_parser = subparsers.add_parser("controller") - controller_parser.add_argument("--debug", action="store_true", help="run in debug mode") - controller_parser.add_argument( - "-b", "--bind", type=bind_address, default=":8090", help="controller bind address" - ) - controller_parser.add_argument( - "-c", "--config", default="/etc/restvirt", help="configuration folder" - ) - controller_parser.add_argument("--server-cert") - controller_parser.add_argument("--server-key") - controller_parser.add_argument("--client-ca-cert") - controller_parser.set_defaults(func=start_controller) - - daemon_parser = subparsers.add_parser("daemon") - daemon_parser.add_argument("--name", default="default", help="host's name") - daemon_parser.add_argument("--debug", action="store_true", help="run in debug mode") - daemon_parser.add_argument("--dns-server", help="custom DNS server") - daemon_parser.add_argument( - "-b", "--bind", type=bind_address, default="127.0.0.1:8099", help="daemon bind address" - ) - daemon_parser.add_argument( - "-a", "--controller", type=bind_address, default="127.0.0.1:8094", help="controller address" - ) - daemon_parser.add_argument( - "-c", "--config", default="/etc/restvirt", help="configuration folder" - ) - daemon_parser.add_argument("--client-cert") - daemon_parser.add_argument("--client-key") - daemon_parser.add_argument("--server-ca-cert") - daemon_parser.set_defaults(func=start_daemon) - - register_parser = subparsers.add_parser("register") - register_parser.add_argument("--name", help="host's name") - register_parser.add_argument("-b", "--bind", type=bind_address, help="daemon bind address") - register_parser.add_argument( - "-a", "--controller", type=bind_address, default="127.0.0.1:8094", help="controller address" - ) - register_parser.add_argument("--client-cert") - register_parser.add_argument("--client-key") - register_parser.add_argument("--server-ca-cert") - register_parser.set_defaults(func=register_host) - - deregister_parser = subparsers.add_parser("deregister") - deregister_parser.add_argument("--name", help="host's name") - deregister_parser.add_argument( - "-a", "--controller", type=bind_address, default="127.0.0.1:8094", help="controller address" - ) - deregister_parser.add_argument("--client-cert") - deregister_parser.add_argument("--client-key") - deregister_parser.add_argument("--server-ca-cert") - deregister_parser.set_defaults(func=deregister_host) - - args = parser.parse_args() - logging.debug(args) - args.func(args) - - -if __name__ == "__main__": - main() diff --git a/minivirt/migrations.py b/minivirt/migrations.py deleted file mode 100644 index c281f13..0000000 --- a/minivirt/migrations.py +++ /dev/null @@ -1,116 +0,0 @@ -import itertools -import sqlite3 - -import sqlalchemy as sa - - -def _migration_0(engine): - with engine.connect() as conn: - conn.connection.executescript( - """ -BEGIN; - -ALTER TABLE domains ADD ipv6_address VARCHAR; -ALTER TABLE networks ADD cidr6 VARCHAR; - -DELETE FROM versions; -INSERT INTO versions VALUES('1'); - -COMMIT; -""" - ) - - -migrations = { - "0": _migration_0, -} - - -def run_migrations(engine: sa.engine.Engine): - create_initial(engine) - - with engine.connect() as conn: - connection = conn.connection - connection.row_factory = sqlite3.Row - cur = connection.cursor() - cur.row_factory = sqlite3.Row - try: - cur.execute("SELECT * FROM versions") - res = cur.fetchall() - assert len(res) == 1 - version = res[0]["version"] - finally: - cur.close() - - for key in itertools.dropwhile(lambda k: k != version, migrations): - print(f'Executing migration from version "{key}"') - migration_func = migrations[key] - migration_func(engine) - - -def create_initial(engine): - with engine.connect() as conn: - conn.connection.executescript( - """ -CREATE TABLE IF NOT EXISTS bootstrap_tokens ( - token VARCHAR NOT NULL, - expires_at DATETIME, - PRIMARY KEY (token) -); - -CREATE TABLE IF NOT EXISTS hosts ( - name VARCHAR NOT NULL, - address VARCHAR, - last_heartbeat DATETIME, - PRIMARY KEY (name) -); - -CREATE TABLE IF NOT EXISTS networks ( - id VARCHAR NOT NULL, - cidr VARCHAR, - PRIMARY KEY (id) -); - -CREATE TABLE IF NOT EXISTS route_tables ( - id INTEGER NOT NULL, - name VARCHAR, - PRIMARY KEY (id) -); - -CREATE TABLE IF NOT EXISTS port_forwardings ( - protocol VARCHAR NOT NULL, - source_port INTEGER NOT NULL, - target_ip VARCHAR, - target_port INTEGER, - PRIMARY KEY (protocol, source_port) -); - -CREATE TABLE IF NOT EXISTS dns_records ( - name VARCHAR NOT NULL, - type VARCHAR NOT NULL, - ttl INTEGER, - records VARCHAR, - PRIMARY KEY (name, type) -); - -CREATE TABLE IF NOT EXISTS domains ( - id VARCHAR NOT NULL, - private_ip VARCHAR, - os_type VARCHAR, - user_data TEXT, - PRIMARY KEY (id), - UNIQUE (private_ip) -); - -CREATE TABLE IF NOT EXISTS routes ( - destination VARCHAR NOT NULL, - gateways VARCHAR, - route_table_id INTEGER, - PRIMARY KEY (destination), - FOREIGN KEY(route_table_id) REFERENCES route_tables (id) ON DELETE CASCADE -); - -CREATE TABLE IF NOT EXISTS versions (version VARCHAR); -INSERT INTO versions SELECT '0' WHERE NOT EXISTS (SELECT * FROM versions); -""" - ) diff --git a/minivirt/models.py b/minivirt/models.py deleted file mode 100644 index 79cd2a9..0000000 --- a/minivirt/models.py +++ /dev/null @@ -1,132 +0,0 @@ -from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, Text, TypeDecorator, Unicode -from sqlalchemy.orm import backref, registry, relationship - - -class StringList(TypeDecorator): - impl = Unicode - cache_ok = True - - def __init__(self, delimiter=","): - self.delimiter = delimiter - super().__init__() - - def process_bind_param(self, value, dialect): - assert all([self.delimiter not in e for e in value]) - return self.delimiter.join(value) - - def process_result_value(self, value, dialect): - return value.split(self.delimiter) - - -class StringSet(TypeDecorator): - impl = Unicode - cache_ok = True - - def __init__(self, delimiter=","): - self.delimiter = delimiter - super().__init__() - - def process_bind_param(self, value, dialect): - assert isinstance(value, set) - assert all([self.delimiter not in e for e in value]) - return self.delimiter.join(sorted(value)) - - def process_result_value(self, value, dialect): - return set(value.split(self.delimiter)) - - -mapper_registry = registry() -Base = mapper_registry.generate_base() - - -class BootstrapToken(Base): - __tablename__ = "bootstrap_tokens" - - token = Column(String, primary_key=True) - expires_at = Column(DateTime) - - def __repr__(self): - return f"BootstrapToken(token={self.token!r}, expires_at={self.expires_at!r})" - - -class Host(Base): - __tablename__ = "hosts" - - name = Column(String, primary_key=True) - address = Column(String) - last_heartbeat = Column(DateTime) - - def __repr__(self): - return f"Host(name={self.name!r}, address={self.address!r})" - - -class Network(Base): - __tablename__ = "networks" - - id = Column(String, primary_key=True) - cidr = Column(String) - cidr6 = Column(String) - - def __repr__(self): - return f"Network({self.id!r}: {self.cidr!r} {self.cidr6!r})" - - -class RouteTable(Base): - __tablename__ = "route_tables" - - id = Column(Integer, primary_key=True) - name = Column(String) - - def __repr__(self): - return f"RouteTable(id={self.id!r}, name={self.name!r})" - - -class Route(Base): - __tablename__ = "routes" - - destination = Column(String, primary_key=True) - gateways = Column(StringSet) - route_table_id = Column(Integer, ForeignKey("route_tables.id", ondelete="CASCADE")) - route_table = relationship( - "RouteTable", backref=backref("routes", cascade="all, delete-orphan", passive_deletes=True) - ) - - def __repr__(self): - return f"Route(dest={self.destination!r}, gateways={self.gateways!r}, table={self.route_table!r})" - - -class PortForwarding(Base): - __tablename__ = "port_forwardings" - - protocol = Column(String, primary_key=True) - source_port = Column(Integer, primary_key=True) - target_ip = Column(String) - target_port = Column(Integer) - - def __repr__(self): - return f"{self.protocol} :{self.source_port} -> {self.target_ip}:{self.target_port}" - - -class DNSRecord(Base): - __tablename__ = "dns_records" - - name = Column(String, primary_key=True) - type = Column(String, primary_key=True) - ttl = Column(Integer) - records = Column(StringList) - - def __repr__(self): - return f"DNSEntry({self.name} {self.type} {self.ttl} {' '.join(self.records)}" - - -class Domain(Base): - __tablename__ = "domains" - - id = Column(String, primary_key=True) - private_ip = Column(String, unique=True) - ipv6_address = Column(String, unique=True) - os_type = Column(String) - user_data = Column(Text) - - def __repr__(self): - return f"Domain(id={self.id} private_ip={self.private_ip} ipv6_address={self.ipv6_address})" diff --git a/minivirt/port_forwarding.py b/minivirt/port_forwarding.py deleted file mode 100644 index f9095c5..0000000 --- a/minivirt/port_forwarding.py +++ /dev/null @@ -1,301 +0,0 @@ -import ipaddress -import threading - -import libvirt -import nftables -from sqlalchemy import select - -from minivirt import domain_pb2 -from minivirt.daemon import _network_to_pb -from minivirt.models import PortForwarding - - -class IPTablesPortForwardingSynchronizer: - def __init__(self, controller, dns_addr=None): - self.controller = controller - self.lock = threading.Lock() - self.dns_addr = dns_addr - - def handle_sync(self, session, libvirt_conn: libvirt.virConnect): - with self.lock: - forwardings = session.execute(select(PortForwarding).filter()).scalars().all() - - table_name = "restvirt" - - nft = nftables.Nftables() - nft.set_json_output(True) - - rfc1918_nets = [ - { - "prefix": { - "addr": str(net.network_address), - "len": net.prefixlen, - } - } - for net in [ - ipaddress.ip_network(n) - for n in ["192.168.0.0/16", "172.16.0.0/12", "10.0.0.0/8"] - ] - ] - commands = [ - {"add": {"table": {"name": table_name, "family": "inet"}}}, - {"delete": {"table": {"name": table_name, "family": "inet"}}}, - {"add": {"table": {"name": table_name, "family": "inet"}}}, - { - "add": { - "chain": { - "table": table_name, - "family": "inet", - "name": "output", - "type": "nat", - "hook": "output", - "prio": -105, - "policy": "accept", - } - } - }, - { - "add": { - "chain": { - "table": table_name, - "family": "inet", - "name": "prerouting", - "type": "nat", - "hook": "prerouting", - "prio": -105, - "policy": "accept", - } - } - }, - { - "add": { - "chain": { - "table": table_name, - "family": "inet", - "name": "postrouting", - "type": "nat", - "hook": "postrouting", - "prio": 95, - "policy": "accept", - } - } - }, - { - "add": { - "chain": { - "table": table_name, - "family": "inet", - "name": "forward", - "type": "filter", - "hook": "forward", - "prio": -5, - "policy": "accept", - } - } - }, - { - "add": { # ensure rfc1918 rules - "rule": { - "table": table_name, - "family": "inet", - "chain": "postrouting", - "expr": [ - { - "match": { - "op": "in", - "left": {"payload": {"protocol": "ip", "field": "saddr"}}, - "right": {"set": rfc1918_nets}, - } - }, - { - "match": { - "op": "!=", - "left": {"payload": {"protocol": "ip", "field": "daddr"}}, - "right": {"set": rfc1918_nets}, - } - }, - { - "counter": None, - }, - {"masquerade": None}, - ], - } - } - }, - ] - - # FIXME: limit to eno2 src port - for f in forwardings: - commands.extend( - [ - { - "add": { - "rule": { - "table": table_name, - "family": "inet", - "chain": "output", - "expr": [ - { - "match": { - "op": "==", - "left": { - "payload": { - "protocol": f.protocol, - "field": "dport", - } - }, - "right": f.source_port, - } - }, - { - "counter": None, - }, - { - "dnat": { - "family": "ip", - "addr": f.target_ip, - "port": f.target_port, - } - }, - ], - } - } - }, - { - "add": { - "rule": { - "table": table_name, - "family": "inet", - "chain": "prerouting", - "expr": [ - { - "match": { - "op": "==", - "left": { - "payload": { - "protocol": f.protocol, - "field": "dport", - } - }, - "right": f.source_port, - } - }, - { - "counter": None, - }, - { - "dnat": { - "family": "ip", - "addr": f.target_ip, - "port": f.target_port, - } - }, - ], - } - } - }, - { - "add": { - "rule": { - "table": table_name, - "family": "inet", - "chain": "forward", - "expr": [ - { - "match": { - "op": "==", - "left": { - "payload": { - "protocol": f.protocol, - "field": "dport", - } - }, - "right": f.target_port, - } - }, - { - "match": { - "op": "==", - "left": { - "payload": {"protocol": "ip", "field": "daddr"} - }, - "right": f.target_ip, - } - }, - { - "counter": None, - }, - {"accept": None}, - ], - } - } - }, - ] - ) - - networks = [_network_to_pb(net) for net in libvirt_conn.listAllNetworks()] - for network in networks: - net = ipaddress.IPv4Network(network.cidr) - - dns_addr = self.dns_addr - if self.dns_addr is None: - dns_addr = str(net[1]) - - for chain in ["output", "prerouting"]: - for protocol in ["udp", "tcp"]: - commands.extend( - [ - { - "add": { # FIXME: use configurable target IP:port - "rule": { - "table": table_name, - "family": "inet", - "chain": chain, - "expr": [ - { - "match": { - "op": "==", - "left": { - "payload": { - "protocol": protocol, - "field": "dport", - } - }, - "right": 53, - } - }, - { - "match": { - "op": "==", - "left": { - "payload": { - "protocol": "ip", - "field": "daddr", - } - }, - "right": str(net[1]), - } - }, - { - "counter": None, - }, - { - "dnat": { - "family": "ip", - "addr": dns_addr, - "port": 5354, - } - }, - ], - } - } - }, - ] - ) - - rc, output, error = nft.json_cmd({"nftables": commands}) - # FIXME: replace with logging - print(rc) - print(output) - print(error) - assert rc == 0 diff --git a/minivirt/port_forwarding_pb2.py b/minivirt/port_forwarding_pb2.py deleted file mode 100644 index 868b34d..0000000 --- a/minivirt/port_forwarding_pb2.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: minivirt/port_forwarding.proto -"""Generated protocol buffer code.""" -from google.protobuf.internal import builder as _builder -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1eminivirt/port_forwarding.proto\x1a\x1bgoogle/protobuf/empty.proto\"O\n\x18PortForwardingIdentifier\x12\x0c\n\x04host\x18\x01 \x01(\t\x12\x10\n\x08protocol\x18\x02 \x01(\t\x12\x13\n\x0bsource_port\x18\x03 \x01(\r\"_\n\x0ePortForwarding\x12\x10\n\x08protocol\x18\x02 \x01(\t\x12\x13\n\x0bsource_port\x18\x03 \x01(\r\x12\x11\n\ttarget_ip\x18\x04 \x01(\t\x12\x13\n\x0btarget_port\x18\x05 \x01(\r\"*\n\x1aListPortForwardingsRequest\x12\x0c\n\x04host\x18\x01 \x01(\t\"H\n\x1bListPortForwardingsResponse\x12)\n\x10port_forwardings\x18\x01 \x03(\x0b\x32\x0f.PortForwarding\"R\n\x18PutPortForwardingRequest\x12(\n\x0fport_forwarding\x18\x01 \x01(\x0b\x32\x0f.PortForwarding\x12\x0c\n\x04host\x18\x02 \x01(\t2\xbe\x02\n\x15PortForwardingService\x12\x41\n\x11GetPortForwarding\x12\x19.PortForwardingIdentifier\x1a\x0f.PortForwarding\"\x00\x12R\n\x13ListPortForwardings\x12\x1b.ListPortForwardingsRequest\x1a\x1c.ListPortForwardingsResponse\"\x00\x12\x41\n\x11PutPortForwarding\x12\x19.PutPortForwardingRequest\x1a\x0f.PortForwarding\"\x00\x12K\n\x14\x44\x65letePortForwarding\x12\x19.PortForwardingIdentifier\x1a\x16.google.protobuf.Empty\"\x00\x42\x06Z\x04.;pbb\x06proto3') - -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'minivirt.port_forwarding_pb2', globals()) -if _descriptor._USE_C_DESCRIPTORS == False: - - DESCRIPTOR._options = None - DESCRIPTOR._serialized_options = b'Z\004.;pb' - _PORTFORWARDINGIDENTIFIER._serialized_start=63 - _PORTFORWARDINGIDENTIFIER._serialized_end=142 - _PORTFORWARDING._serialized_start=144 - _PORTFORWARDING._serialized_end=239 - _LISTPORTFORWARDINGSREQUEST._serialized_start=241 - _LISTPORTFORWARDINGSREQUEST._serialized_end=283 - _LISTPORTFORWARDINGSRESPONSE._serialized_start=285 - _LISTPORTFORWARDINGSRESPONSE._serialized_end=357 - _PUTPORTFORWARDINGREQUEST._serialized_start=359 - _PUTPORTFORWARDINGREQUEST._serialized_end=441 - _PORTFORWARDINGSERVICE._serialized_start=444 - _PORTFORWARDINGSERVICE._serialized_end=762 -# @@protoc_insertion_point(module_scope) diff --git a/minivirt/port_forwarding_pb2_grpc.py b/minivirt/port_forwarding_pb2_grpc.py deleted file mode 100644 index 0441b5d..0000000 --- a/minivirt/port_forwarding_pb2_grpc.py +++ /dev/null @@ -1,166 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 -from minivirt import port_forwarding_pb2 as minivirt_dot_port__forwarding__pb2 - - -class PortForwardingServiceStub(object): - """Missing associated documentation comment in .proto file.""" - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.GetPortForwarding = channel.unary_unary( - '/PortForwardingService/GetPortForwarding', - request_serializer=minivirt_dot_port__forwarding__pb2.PortForwardingIdentifier.SerializeToString, - response_deserializer=minivirt_dot_port__forwarding__pb2.PortForwarding.FromString, - ) - self.ListPortForwardings = channel.unary_unary( - '/PortForwardingService/ListPortForwardings', - request_serializer=minivirt_dot_port__forwarding__pb2.ListPortForwardingsRequest.SerializeToString, - response_deserializer=minivirt_dot_port__forwarding__pb2.ListPortForwardingsResponse.FromString, - ) - self.PutPortForwarding = channel.unary_unary( - '/PortForwardingService/PutPortForwarding', - request_serializer=minivirt_dot_port__forwarding__pb2.PutPortForwardingRequest.SerializeToString, - response_deserializer=minivirt_dot_port__forwarding__pb2.PortForwarding.FromString, - ) - self.DeletePortForwarding = channel.unary_unary( - '/PortForwardingService/DeletePortForwarding', - request_serializer=minivirt_dot_port__forwarding__pb2.PortForwardingIdentifier.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - - -class PortForwardingServiceServicer(object): - """Missing associated documentation comment in .proto file.""" - - def GetPortForwarding(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListPortForwardings(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def PutPortForwarding(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeletePortForwarding(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_PortForwardingServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'GetPortForwarding': grpc.unary_unary_rpc_method_handler( - servicer.GetPortForwarding, - request_deserializer=minivirt_dot_port__forwarding__pb2.PortForwardingIdentifier.FromString, - response_serializer=minivirt_dot_port__forwarding__pb2.PortForwarding.SerializeToString, - ), - 'ListPortForwardings': grpc.unary_unary_rpc_method_handler( - servicer.ListPortForwardings, - request_deserializer=minivirt_dot_port__forwarding__pb2.ListPortForwardingsRequest.FromString, - response_serializer=minivirt_dot_port__forwarding__pb2.ListPortForwardingsResponse.SerializeToString, - ), - 'PutPortForwarding': grpc.unary_unary_rpc_method_handler( - servicer.PutPortForwarding, - request_deserializer=minivirt_dot_port__forwarding__pb2.PutPortForwardingRequest.FromString, - response_serializer=minivirt_dot_port__forwarding__pb2.PortForwarding.SerializeToString, - ), - 'DeletePortForwarding': grpc.unary_unary_rpc_method_handler( - servicer.DeletePortForwarding, - request_deserializer=minivirt_dot_port__forwarding__pb2.PortForwardingIdentifier.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'PortForwardingService', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - - - # This class is part of an EXPERIMENTAL API. -class PortForwardingService(object): - """Missing associated documentation comment in .proto file.""" - - @staticmethod - def GetPortForwarding(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/PortForwardingService/GetPortForwarding', - minivirt_dot_port__forwarding__pb2.PortForwardingIdentifier.SerializeToString, - minivirt_dot_port__forwarding__pb2.PortForwarding.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ListPortForwardings(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/PortForwardingService/ListPortForwardings', - minivirt_dot_port__forwarding__pb2.ListPortForwardingsRequest.SerializeToString, - minivirt_dot_port__forwarding__pb2.ListPortForwardingsResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def PutPortForwarding(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/PortForwardingService/PutPortForwarding', - minivirt_dot_port__forwarding__pb2.PutPortForwardingRequest.SerializeToString, - minivirt_dot_port__forwarding__pb2.PortForwarding.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def DeletePortForwarding(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/PortForwardingService/DeletePortForwarding', - minivirt_dot_port__forwarding__pb2.PortForwardingIdentifier.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/minivirt/route.py b/minivirt/route.py deleted file mode 100644 index a336eee..0000000 --- a/minivirt/route.py +++ /dev/null @@ -1,133 +0,0 @@ -import ipaddress -from typing import Dict, Set - -from sqlalchemy import delete, select - -from minivirt import route_pb2 -from minivirt.models import Route, RouteTable - - -class SyncEventHandler: - def handle_sync(self, session): - pass - - -class GenericRouteTableController: - id_range_min, id_range_max = 30069, 30169 - - def __init__(self, session_factory, sync_handler=SyncEventHandler()): - self.session_factory = session_factory - self.sync_handler = sync_handler - - def route_tables(self): - with self.session_factory() as session: - return session.execute(select(RouteTable)).scalars().all() - - def route_table(self, id): - with self.session_factory() as session: - return ( - session.execute(select(RouteTable).filter(RouteTable.id == id)) - .scalars() - .one_or_none() - ) - - def _get_available_id(self, session): - ids = session.execute(select(RouteTable.id).order_by(RouteTable.id)).scalars().all() - if not ids or ids[0] > self.id_range_min: - return self.id_range_min - for i in range(len(ids) - 1): - if ids[i + 1] - ids[i] > 1: - return ids[i] + 1 - assert ids[-1] < self.id_range_max - return ids[-1] + 1 - - def create_route_table(self, r: route_pb2.RouteTable): - with self.session_factory() as session: - route = RouteTable( - id=self._get_available_id(session), - name=r.name, - ) - session.add(route) - session.commit() - self.sync_handler.handle_sync(session) - - return route_pb2.RouteTable( - id=route.id, - name=route.name, - ) - - def remove_route_table(self, id): - with self.session_factory() as session: - res = session.execute(delete(RouteTable).where(RouteTable.id == id)) - if res.rowcount == 0: - return False - - assert res.rowcount == 1 - session.commit() - self.sync_handler.handle_sync(session) - return True - - def sync(self): - with self.session_factory() as session: - self.sync_handler.handle_sync(session) - - -AliasIPConf = Dict[ipaddress.IPv4Network, Set[ipaddress.IPv4Address]] - -TABLE_ID = 69 - - -class GenericRouteController: - def __init__(self, session_factory, sync_handler=SyncEventHandler()): - self.session_factory = session_factory - self.sync_handler = sync_handler - - def routes(self, route_table_id=None): - with self.session_factory() as session: - return ( - session.execute(select(Route).filter(Route.route_table_id == route_table_id)) - .scalars() - .all() - ) - - def route(self, route_table_id, destination): - with self.session_factory() as session: - return ( - session.execute( - select(Route).filter( - Route.route_table_id == route_table_id, - Route.destination == destination, - ) - ) - .scalars() - .one_or_none() - ) - - def put_route(self, r: route_pb2.Route): - route = Route( - destination=r.destination, - gateways=set(r.gateways), - route_table_id=r.route_table_id, - ) - with self.session_factory() as session: - merged = session.merge(route) - if session.is_modified(merged): - session.commit() - self.sync_handler.handle_sync(session) - - return r - - def remove_route(self, route_table_id, destination): - with self.session_factory() as session: - session.execute( - delete(Route).where( - Route.destination == destination, - Route.route_table_id == route_table_id, - ) - ) - session.commit() - self.sync_handler.handle_sync(session) - - def sync(self): - with self.session_factory() as session: - self.sync_handler.handle_sync(session) diff --git a/minivirt/route_pb2.py b/minivirt/route_pb2.py deleted file mode 100644 index 6133ea9..0000000 --- a/minivirt/route_pb2.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: minivirt/route.proto -"""Generated protocol buffer code.""" -from google.protobuf.internal import builder as _builder -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14minivirt/route.proto\x1a\x1bgoogle/protobuf/empty.proto\"<\n\nRouteTable\x12\x14\n\x0cnetwork_name\x18\x01 \x01(\t\x12\n\n\x02id\x18\x02 \x01(\r\x12\x0c\n\x04name\x18\x03 \x01(\t\"\"\n\x14RouteTableIdentifier\x12\n\n\x02id\x18\x01 \x01(\r\".\n\x16ListRouteTablesRequest\x12\x14\n\x0cnetwork_name\x18\x01 \x01(\t\"<\n\x17ListRouteTablesResponse\x12!\n\x0croute_tables\x18\x01 \x03(\x0b\x32\x0b.RouteTable\";\n\x17\x43reateRouteTableRequest\x12 \n\x0broute_table\x18\x01 \x01(\x0b\x32\x0b.RouteTable\">\n\x0fRouteIdentifier\x12\x16\n\x0eroute_table_id\x18\x01 \x01(\r\x12\x13\n\x0b\x64\x65stination\x18\x02 \x01(\t\"F\n\x05Route\x12\x16\n\x0eroute_table_id\x18\x01 \x01(\r\x12\x13\n\x0b\x64\x65stination\x18\x02 \x01(\t\x12\x10\n\x08gateways\x18\x03 \x03(\t\"+\n\x11ListRoutesRequest\x12\x16\n\x0eroute_table_id\x18\x01 \x01(\r\",\n\x12ListRoutesResponse\x12\x16\n\x06routes\x18\x01 \x03(\x0b\x32\x06.Route\"(\n\x0fPutRouteRequest\x12\x15\n\x05route\x18\x01 \x01(\x0b\x32\x06.Route\"\r\n\x0bSyncRequest2\x83\x04\n\x0cRouteService\x12\x35\n\rGetRouteTable\x12\x15.RouteTableIdentifier\x1a\x0b.RouteTable\"\x00\x12\x46\n\x0fListRouteTables\x12\x17.ListRouteTablesRequest\x1a\x18.ListRouteTablesResponse\"\x00\x12;\n\x10\x43reateRouteTable\x12\x18.CreateRouteTableRequest\x1a\x0b.RouteTable\"\x00\x12\x43\n\x10\x44\x65leteRouteTable\x12\x15.RouteTableIdentifier\x1a\x16.google.protobuf.Empty\"\x00\x12&\n\x08GetRoute\x12\x10.RouteIdentifier\x1a\x06.Route\"\x00\x12\x37\n\nListRoutes\x12\x12.ListRoutesRequest\x1a\x13.ListRoutesResponse\"\x00\x12&\n\x08PutRoute\x12\x10.PutRouteRequest\x1a\x06.Route\"\x00\x12\x39\n\x0b\x44\x65leteRoute\x12\x10.RouteIdentifier\x1a\x16.google.protobuf.Empty\"\x00\x12.\n\x04Sync\x12\x0c.SyncRequest\x1a\x16.google.protobuf.Empty\"\x00\x42\x06Z\x04.;pbb\x06proto3') - -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'minivirt.route_pb2', globals()) -if _descriptor._USE_C_DESCRIPTORS == False: - - DESCRIPTOR._options = None - DESCRIPTOR._serialized_options = b'Z\004.;pb' - _ROUTETABLE._serialized_start=53 - _ROUTETABLE._serialized_end=113 - _ROUTETABLEIDENTIFIER._serialized_start=115 - _ROUTETABLEIDENTIFIER._serialized_end=149 - _LISTROUTETABLESREQUEST._serialized_start=151 - _LISTROUTETABLESREQUEST._serialized_end=197 - _LISTROUTETABLESRESPONSE._serialized_start=199 - _LISTROUTETABLESRESPONSE._serialized_end=259 - _CREATEROUTETABLEREQUEST._serialized_start=261 - _CREATEROUTETABLEREQUEST._serialized_end=320 - _ROUTEIDENTIFIER._serialized_start=322 - _ROUTEIDENTIFIER._serialized_end=384 - _ROUTE._serialized_start=386 - _ROUTE._serialized_end=456 - _LISTROUTESREQUEST._serialized_start=458 - _LISTROUTESREQUEST._serialized_end=501 - _LISTROUTESRESPONSE._serialized_start=503 - _LISTROUTESRESPONSE._serialized_end=547 - _PUTROUTEREQUEST._serialized_start=549 - _PUTROUTEREQUEST._serialized_end=589 - _SYNCREQUEST._serialized_start=591 - _SYNCREQUEST._serialized_end=604 - _ROUTESERVICE._serialized_start=607 - _ROUTESERVICE._serialized_end=1122 -# @@protoc_insertion_point(module_scope) diff --git a/minivirt/route_pb2_grpc.py b/minivirt/route_pb2_grpc.py deleted file mode 100644 index f6211f2..0000000 --- a/minivirt/route_pb2_grpc.py +++ /dev/null @@ -1,331 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 -from minivirt import route_pb2 as minivirt_dot_route__pb2 - - -class RouteServiceStub(object): - """Missing associated documentation comment in .proto file.""" - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.GetRouteTable = channel.unary_unary( - '/RouteService/GetRouteTable', - request_serializer=minivirt_dot_route__pb2.RouteTableIdentifier.SerializeToString, - response_deserializer=minivirt_dot_route__pb2.RouteTable.FromString, - ) - self.ListRouteTables = channel.unary_unary( - '/RouteService/ListRouteTables', - request_serializer=minivirt_dot_route__pb2.ListRouteTablesRequest.SerializeToString, - response_deserializer=minivirt_dot_route__pb2.ListRouteTablesResponse.FromString, - ) - self.CreateRouteTable = channel.unary_unary( - '/RouteService/CreateRouteTable', - request_serializer=minivirt_dot_route__pb2.CreateRouteTableRequest.SerializeToString, - response_deserializer=minivirt_dot_route__pb2.RouteTable.FromString, - ) - self.DeleteRouteTable = channel.unary_unary( - '/RouteService/DeleteRouteTable', - request_serializer=minivirt_dot_route__pb2.RouteTableIdentifier.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.GetRoute = channel.unary_unary( - '/RouteService/GetRoute', - request_serializer=minivirt_dot_route__pb2.RouteIdentifier.SerializeToString, - response_deserializer=minivirt_dot_route__pb2.Route.FromString, - ) - self.ListRoutes = channel.unary_unary( - '/RouteService/ListRoutes', - request_serializer=minivirt_dot_route__pb2.ListRoutesRequest.SerializeToString, - response_deserializer=minivirt_dot_route__pb2.ListRoutesResponse.FromString, - ) - self.PutRoute = channel.unary_unary( - '/RouteService/PutRoute', - request_serializer=minivirt_dot_route__pb2.PutRouteRequest.SerializeToString, - response_deserializer=minivirt_dot_route__pb2.Route.FromString, - ) - self.DeleteRoute = channel.unary_unary( - '/RouteService/DeleteRoute', - request_serializer=minivirt_dot_route__pb2.RouteIdentifier.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.Sync = channel.unary_unary( - '/RouteService/Sync', - request_serializer=minivirt_dot_route__pb2.SyncRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - - -class RouteServiceServicer(object): - """Missing associated documentation comment in .proto file.""" - - def GetRouteTable(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListRouteTables(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def CreateRouteTable(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeleteRouteTable(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetRoute(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListRoutes(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def PutRoute(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeleteRoute(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def Sync(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_RouteServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'GetRouteTable': grpc.unary_unary_rpc_method_handler( - servicer.GetRouteTable, - request_deserializer=minivirt_dot_route__pb2.RouteTableIdentifier.FromString, - response_serializer=minivirt_dot_route__pb2.RouteTable.SerializeToString, - ), - 'ListRouteTables': grpc.unary_unary_rpc_method_handler( - servicer.ListRouteTables, - request_deserializer=minivirt_dot_route__pb2.ListRouteTablesRequest.FromString, - response_serializer=minivirt_dot_route__pb2.ListRouteTablesResponse.SerializeToString, - ), - 'CreateRouteTable': grpc.unary_unary_rpc_method_handler( - servicer.CreateRouteTable, - request_deserializer=minivirt_dot_route__pb2.CreateRouteTableRequest.FromString, - response_serializer=minivirt_dot_route__pb2.RouteTable.SerializeToString, - ), - 'DeleteRouteTable': grpc.unary_unary_rpc_method_handler( - servicer.DeleteRouteTable, - request_deserializer=minivirt_dot_route__pb2.RouteTableIdentifier.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'GetRoute': grpc.unary_unary_rpc_method_handler( - servicer.GetRoute, - request_deserializer=minivirt_dot_route__pb2.RouteIdentifier.FromString, - response_serializer=minivirt_dot_route__pb2.Route.SerializeToString, - ), - 'ListRoutes': grpc.unary_unary_rpc_method_handler( - servicer.ListRoutes, - request_deserializer=minivirt_dot_route__pb2.ListRoutesRequest.FromString, - response_serializer=minivirt_dot_route__pb2.ListRoutesResponse.SerializeToString, - ), - 'PutRoute': grpc.unary_unary_rpc_method_handler( - servicer.PutRoute, - request_deserializer=minivirt_dot_route__pb2.PutRouteRequest.FromString, - response_serializer=minivirt_dot_route__pb2.Route.SerializeToString, - ), - 'DeleteRoute': grpc.unary_unary_rpc_method_handler( - servicer.DeleteRoute, - request_deserializer=minivirt_dot_route__pb2.RouteIdentifier.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'Sync': grpc.unary_unary_rpc_method_handler( - servicer.Sync, - request_deserializer=minivirt_dot_route__pb2.SyncRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'RouteService', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - - - # This class is part of an EXPERIMENTAL API. -class RouteService(object): - """Missing associated documentation comment in .proto file.""" - - @staticmethod - def GetRouteTable(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/RouteService/GetRouteTable', - minivirt_dot_route__pb2.RouteTableIdentifier.SerializeToString, - minivirt_dot_route__pb2.RouteTable.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ListRouteTables(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/RouteService/ListRouteTables', - minivirt_dot_route__pb2.ListRouteTablesRequest.SerializeToString, - minivirt_dot_route__pb2.ListRouteTablesResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def CreateRouteTable(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/RouteService/CreateRouteTable', - minivirt_dot_route__pb2.CreateRouteTableRequest.SerializeToString, - minivirt_dot_route__pb2.RouteTable.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def DeleteRouteTable(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/RouteService/DeleteRouteTable', - minivirt_dot_route__pb2.RouteTableIdentifier.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def GetRoute(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/RouteService/GetRoute', - minivirt_dot_route__pb2.RouteIdentifier.SerializeToString, - minivirt_dot_route__pb2.Route.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ListRoutes(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/RouteService/ListRoutes', - minivirt_dot_route__pb2.ListRoutesRequest.SerializeToString, - minivirt_dot_route__pb2.ListRoutesResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def PutRoute(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/RouteService/PutRoute', - minivirt_dot_route__pb2.PutRouteRequest.SerializeToString, - minivirt_dot_route__pb2.Route.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def DeleteRoute(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/RouteService/DeleteRoute', - minivirt_dot_route__pb2.RouteIdentifier.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def Sync(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/RouteService/Sync', - minivirt_dot_route__pb2.SyncRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/minivirt/utils.py b/minivirt/utils.py deleted file mode 100644 index 7e0cd91..0000000 --- a/minivirt/utils.py +++ /dev/null @@ -1,39 +0,0 @@ -import logging -from timeit import default_timer as timer - -import grpc -import libvirt -from google.protobuf import empty_pb2 - - -class UnaryUnaryInterceptor(grpc.ServerInterceptor): - def intercept_service(self, continuation, handler_call_details): - next = continuation(handler_call_details) - if next is None: - return None - if next.unary_unary is None: - return next - - def letsgo(request, context): - start = timer() - try: - response = next.unary_unary(request, context) - except libvirt.libvirtError as e: - status_code = grpc.StatusCode.INTERNAL - if e.get_error_code() in [ - libvirt.VIR_ERR_NO_DOMAIN, - libvirt.VIR_ERR_NO_STORAGE_VOL, - ]: - status_code = grpc.StatusCode.NOT_FOUND - context.set_code(status_code) - context.set_details(f"{e} ({e.get_error_code()})") - response = empty_pb2.Empty() - - logging.debug(f"{handler_call_details.method} [{(timer() - start)*1000:.3f} ms]") - return response - - return grpc.unary_unary_rpc_method_handler( - letsgo, - request_deserializer=next.request_deserializer, - response_serializer=next.response_serializer, - ) diff --git a/minivirt/version.py b/minivirt/version.py deleted file mode 100644 index d5cfca6..0000000 --- a/minivirt/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "unknown" diff --git a/minivirt/volume_pb2.py b/minivirt/volume_pb2.py deleted file mode 100644 index 84b8819..0000000 --- a/minivirt/volume_pb2.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: minivirt/volume.proto -"""Generated protocol buffer code.""" -from google.protobuf.internal import builder as _builder -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15minivirt/volume.proto\x1a\x1bgoogle/protobuf/empty.proto\".\n\x10GetVolumeRequest\x12\x0c\n\x04uuid\x18\x01 \x01(\t\x12\x0c\n\x04host\x18\x02 \x01(\t\"0\n\x06Volume\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0c\n\x04size\x18\x03 \x01(\x04\"\"\n\x12ListVolumesRequest\x12\x0c\n\x04host\x18\x01 \x01(\t\"/\n\x13ListVolumesResponse\x12\x18\n\x07volumes\x18\x01 \x03(\x0b\x32\x07.Volume\"<\n\x13\x43reateVolumeRequest\x12\x17\n\x06volume\x18\x01 \x01(\x0b\x32\x07.Volume\x12\x0c\n\x04host\x18\x02 \x01(\t\"<\n\x13UpdateVolumeRequest\x12\x0c\n\x04host\x18\x01 \x01(\t\x12\x17\n\x06volume\x18\x02 \x01(\x0b\x32\x07.Volume\"1\n\x13\x44\x65leteVolumeRequest\x12\x0c\n\x04uuid\x18\x01 \x01(\t\x12\x0c\n\x04host\x18\x02 \x01(\t\"P\n\x1aVolumeAttachmentIdentifier\x12\x11\n\tdomain_id\x18\x01 \x01(\t\x12\x11\n\tvolume_id\x18\x02 \x01(\t\x12\x0c\n\x04host\x18\x03 \x01(\t\"N\n\x10VolumeAttachment\x12\x11\n\tdomain_id\x18\x01 \x01(\t\x12\x11\n\tvolume_id\x18\x02 \x01(\t\x12\x14\n\x0c\x64isk_address\x18\x03 \x01(\t\"?\n\x1cListVolumeAttachmentsRequest\x12\x11\n\tdomain_id\x18\x01 \x01(\t\x12\x0c\n\x04host\x18\x02 \x01(\t\"G\n\x1dListVolumeAttachmentsResponse\x12&\n\x0b\x61ttachments\x18\x01 \x03(\x0b\x32\x11.VolumeAttachment2\xc4\x04\n\rVolumeService\x12)\n\tGetVolume\x12\x11.GetVolumeRequest\x1a\x07.Volume\"\x00\x12:\n\x0bListVolumes\x12\x13.ListVolumesRequest\x1a\x14.ListVolumesResponse\"\x00\x12/\n\x0c\x43reateVolume\x12\x14.CreateVolumeRequest\x1a\x07.Volume\"\x00\x12/\n\x0cUpdateVolume\x12\x14.UpdateVolumeRequest\x1a\x07.Volume\"\x00\x12>\n\x0c\x44\x65leteVolume\x12\x14.DeleteVolumeRequest\x1a\x16.google.protobuf.Empty\"\x00\x12X\n\x15ListVolumeAttachments\x12\x1d.ListVolumeAttachmentsRequest\x1a\x1e.ListVolumeAttachmentsResponse\"\x00\x12G\n\x13GetVolumeAttachment\x12\x1b.VolumeAttachmentIdentifier\x1a\x11.VolumeAttachment\"\x00\x12@\n\x0c\x41ttachVolume\x12\x1b.VolumeAttachmentIdentifier\x1a\x11.VolumeAttachment\"\x00\x12\x45\n\x0c\x44\x65tachVolume\x12\x1b.VolumeAttachmentIdentifier\x1a\x16.google.protobuf.Empty\"\x00\x42\x06Z\x04.;pbb\x06proto3') - -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'minivirt.volume_pb2', globals()) -if _descriptor._USE_C_DESCRIPTORS == False: - - DESCRIPTOR._options = None - DESCRIPTOR._serialized_options = b'Z\004.;pb' - _GETVOLUMEREQUEST._serialized_start=54 - _GETVOLUMEREQUEST._serialized_end=100 - _VOLUME._serialized_start=102 - _VOLUME._serialized_end=150 - _LISTVOLUMESREQUEST._serialized_start=152 - _LISTVOLUMESREQUEST._serialized_end=186 - _LISTVOLUMESRESPONSE._serialized_start=188 - _LISTVOLUMESRESPONSE._serialized_end=235 - _CREATEVOLUMEREQUEST._serialized_start=237 - _CREATEVOLUMEREQUEST._serialized_end=297 - _UPDATEVOLUMEREQUEST._serialized_start=299 - _UPDATEVOLUMEREQUEST._serialized_end=359 - _DELETEVOLUMEREQUEST._serialized_start=361 - _DELETEVOLUMEREQUEST._serialized_end=410 - _VOLUMEATTACHMENTIDENTIFIER._serialized_start=412 - _VOLUMEATTACHMENTIDENTIFIER._serialized_end=492 - _VOLUMEATTACHMENT._serialized_start=494 - _VOLUMEATTACHMENT._serialized_end=572 - _LISTVOLUMEATTACHMENTSREQUEST._serialized_start=574 - _LISTVOLUMEATTACHMENTSREQUEST._serialized_end=637 - _LISTVOLUMEATTACHMENTSRESPONSE._serialized_start=639 - _LISTVOLUMEATTACHMENTSRESPONSE._serialized_end=710 - _VOLUMESERVICE._serialized_start=713 - _VOLUMESERVICE._serialized_end=1293 -# @@protoc_insertion_point(module_scope) diff --git a/minivirt/volume_pb2_grpc.py b/minivirt/volume_pb2_grpc.py deleted file mode 100644 index 3c41da1..0000000 --- a/minivirt/volume_pb2_grpc.py +++ /dev/null @@ -1,331 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 -from minivirt import volume_pb2 as minivirt_dot_volume__pb2 - - -class VolumeServiceStub(object): - """Missing associated documentation comment in .proto file.""" - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.GetVolume = channel.unary_unary( - '/VolumeService/GetVolume', - request_serializer=minivirt_dot_volume__pb2.GetVolumeRequest.SerializeToString, - response_deserializer=minivirt_dot_volume__pb2.Volume.FromString, - ) - self.ListVolumes = channel.unary_unary( - '/VolumeService/ListVolumes', - request_serializer=minivirt_dot_volume__pb2.ListVolumesRequest.SerializeToString, - response_deserializer=minivirt_dot_volume__pb2.ListVolumesResponse.FromString, - ) - self.CreateVolume = channel.unary_unary( - '/VolumeService/CreateVolume', - request_serializer=minivirt_dot_volume__pb2.CreateVolumeRequest.SerializeToString, - response_deserializer=minivirt_dot_volume__pb2.Volume.FromString, - ) - self.UpdateVolume = channel.unary_unary( - '/VolumeService/UpdateVolume', - request_serializer=minivirt_dot_volume__pb2.UpdateVolumeRequest.SerializeToString, - response_deserializer=minivirt_dot_volume__pb2.Volume.FromString, - ) - self.DeleteVolume = channel.unary_unary( - '/VolumeService/DeleteVolume', - request_serializer=minivirt_dot_volume__pb2.DeleteVolumeRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.ListVolumeAttachments = channel.unary_unary( - '/VolumeService/ListVolumeAttachments', - request_serializer=minivirt_dot_volume__pb2.ListVolumeAttachmentsRequest.SerializeToString, - response_deserializer=minivirt_dot_volume__pb2.ListVolumeAttachmentsResponse.FromString, - ) - self.GetVolumeAttachment = channel.unary_unary( - '/VolumeService/GetVolumeAttachment', - request_serializer=minivirt_dot_volume__pb2.VolumeAttachmentIdentifier.SerializeToString, - response_deserializer=minivirt_dot_volume__pb2.VolumeAttachment.FromString, - ) - self.AttachVolume = channel.unary_unary( - '/VolumeService/AttachVolume', - request_serializer=minivirt_dot_volume__pb2.VolumeAttachmentIdentifier.SerializeToString, - response_deserializer=minivirt_dot_volume__pb2.VolumeAttachment.FromString, - ) - self.DetachVolume = channel.unary_unary( - '/VolumeService/DetachVolume', - request_serializer=minivirt_dot_volume__pb2.VolumeAttachmentIdentifier.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - - -class VolumeServiceServicer(object): - """Missing associated documentation comment in .proto file.""" - - def GetVolume(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListVolumes(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def CreateVolume(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def UpdateVolume(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeleteVolume(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListVolumeAttachments(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetVolumeAttachment(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def AttachVolume(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DetachVolume(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_VolumeServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'GetVolume': grpc.unary_unary_rpc_method_handler( - servicer.GetVolume, - request_deserializer=minivirt_dot_volume__pb2.GetVolumeRequest.FromString, - response_serializer=minivirt_dot_volume__pb2.Volume.SerializeToString, - ), - 'ListVolumes': grpc.unary_unary_rpc_method_handler( - servicer.ListVolumes, - request_deserializer=minivirt_dot_volume__pb2.ListVolumesRequest.FromString, - response_serializer=minivirt_dot_volume__pb2.ListVolumesResponse.SerializeToString, - ), - 'CreateVolume': grpc.unary_unary_rpc_method_handler( - servicer.CreateVolume, - request_deserializer=minivirt_dot_volume__pb2.CreateVolumeRequest.FromString, - response_serializer=minivirt_dot_volume__pb2.Volume.SerializeToString, - ), - 'UpdateVolume': grpc.unary_unary_rpc_method_handler( - servicer.UpdateVolume, - request_deserializer=minivirt_dot_volume__pb2.UpdateVolumeRequest.FromString, - response_serializer=minivirt_dot_volume__pb2.Volume.SerializeToString, - ), - 'DeleteVolume': grpc.unary_unary_rpc_method_handler( - servicer.DeleteVolume, - request_deserializer=minivirt_dot_volume__pb2.DeleteVolumeRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'ListVolumeAttachments': grpc.unary_unary_rpc_method_handler( - servicer.ListVolumeAttachments, - request_deserializer=minivirt_dot_volume__pb2.ListVolumeAttachmentsRequest.FromString, - response_serializer=minivirt_dot_volume__pb2.ListVolumeAttachmentsResponse.SerializeToString, - ), - 'GetVolumeAttachment': grpc.unary_unary_rpc_method_handler( - servicer.GetVolumeAttachment, - request_deserializer=minivirt_dot_volume__pb2.VolumeAttachmentIdentifier.FromString, - response_serializer=minivirt_dot_volume__pb2.VolumeAttachment.SerializeToString, - ), - 'AttachVolume': grpc.unary_unary_rpc_method_handler( - servicer.AttachVolume, - request_deserializer=minivirt_dot_volume__pb2.VolumeAttachmentIdentifier.FromString, - response_serializer=minivirt_dot_volume__pb2.VolumeAttachment.SerializeToString, - ), - 'DetachVolume': grpc.unary_unary_rpc_method_handler( - servicer.DetachVolume, - request_deserializer=minivirt_dot_volume__pb2.VolumeAttachmentIdentifier.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'VolumeService', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - - - # This class is part of an EXPERIMENTAL API. -class VolumeService(object): - """Missing associated documentation comment in .proto file.""" - - @staticmethod - def GetVolume(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/VolumeService/GetVolume', - minivirt_dot_volume__pb2.GetVolumeRequest.SerializeToString, - minivirt_dot_volume__pb2.Volume.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ListVolumes(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/VolumeService/ListVolumes', - minivirt_dot_volume__pb2.ListVolumesRequest.SerializeToString, - minivirt_dot_volume__pb2.ListVolumesResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def CreateVolume(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/VolumeService/CreateVolume', - minivirt_dot_volume__pb2.CreateVolumeRequest.SerializeToString, - minivirt_dot_volume__pb2.Volume.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def UpdateVolume(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/VolumeService/UpdateVolume', - minivirt_dot_volume__pb2.UpdateVolumeRequest.SerializeToString, - minivirt_dot_volume__pb2.Volume.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def DeleteVolume(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/VolumeService/DeleteVolume', - minivirt_dot_volume__pb2.DeleteVolumeRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ListVolumeAttachments(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/VolumeService/ListVolumeAttachments', - minivirt_dot_volume__pb2.ListVolumeAttachmentsRequest.SerializeToString, - minivirt_dot_volume__pb2.ListVolumeAttachmentsResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def GetVolumeAttachment(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/VolumeService/GetVolumeAttachment', - minivirt_dot_volume__pb2.VolumeAttachmentIdentifier.SerializeToString, - minivirt_dot_volume__pb2.VolumeAttachment.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def AttachVolume(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/VolumeService/AttachVolume', - minivirt_dot_volume__pb2.VolumeAttachmentIdentifier.SerializeToString, - minivirt_dot_volume__pb2.VolumeAttachment.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def DetachVolume(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/VolumeService/DetachVolume', - minivirt_dot_volume__pb2.VolumeAttachmentIdentifier.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/protos/minivirt/controller.proto b/protos/minivirt/controller.proto deleted file mode 100644 index fe4120d..0000000 --- a/protos/minivirt/controller.proto +++ /dev/null @@ -1,60 +0,0 @@ -syntax = "proto3"; - -option go_package = ".;pb"; - -import "google/protobuf/empty.proto"; -import "minivirt/daemon.proto"; -import "minivirt/domain.proto"; -import "minivirt/volume.proto"; -import "minivirt/port_forwarding.proto"; -import "minivirt/dns.proto"; -import "minivirt/route.proto"; - -service ControllerService { - rpc GetDNSRecord(DNSRecordIdentifier) returns (DNSRecord) {} - rpc ListDNSRecords(ListDNSRecordsRequest) returns (ListDNSRecordsResponse) {} - rpc PutDNSRecord(PutDNSRecordRequest) returns (DNSRecord) {} - rpc DeleteDNSRecord(DNSRecordIdentifier) returns (google.protobuf.Empty) {} - - rpc GetNetwork(GetNetworkRequest) returns (Network) {} - rpc ListNetworks(ListNetworksRequest) returns (ListNetworksResponse) {} - rpc CreateNetwork(CreateNetworkRequest) returns (Network) {} - rpc DeleteNetwork(DeleteNetworkRequest) returns (google.protobuf.Empty) {} - - rpc StartDomain(StartDomainRequest) returns (google.protobuf.Empty) {} - rpc StopDomain(StopDomainRequest) returns (google.protobuf.Empty) {} - rpc GetDomain(GetDomainRequest) returns (Domain) {} - rpc ListDomains(ListDomainsRequest) returns (ListDomainsResponse) {} - rpc CreateDomain(CreateDomainRequest) returns (Domain) {} - rpc DeleteDomain(DeleteDomainRequest) returns (google.protobuf.Empty) {} - - rpc DownloadImage(DownloadImageRequest) returns (stream ImageChunk) {} - - rpc GetVolume(GetVolumeRequest) returns (Volume) {} - rpc ListVolumes(ListVolumesRequest) returns (ListVolumesResponse) {} - rpc CreateVolume(CreateVolumeRequest) returns (Volume) {} - rpc UpdateVolume(UpdateVolumeRequest) returns (Volume) {} - rpc DeleteVolume(DeleteVolumeRequest) returns (google.protobuf.Empty) {} - - rpc ListVolumeAttachments(ListVolumeAttachmentsRequest) returns (ListVolumeAttachmentsResponse) {} - rpc GetVolumeAttachment(VolumeAttachmentIdentifier) returns (VolumeAttachment) {} - rpc AttachVolume(VolumeAttachmentIdentifier) returns (VolumeAttachment) {} - rpc DetachVolume(VolumeAttachmentIdentifier) returns (google.protobuf.Empty) {} - - rpc GetPortForwarding(PortForwardingIdentifier) returns (PortForwarding) {} - rpc ListPortForwardings(ListPortForwardingsRequest) returns (ListPortForwardingsResponse) {} - rpc PutPortForwarding(PutPortForwardingRequest) returns (PortForwarding) {} - rpc DeletePortForwarding(PortForwardingIdentifier) returns (google.protobuf.Empty) {} - - rpc GetRouteTable(RouteTableIdentifier) returns (RouteTable) {} - rpc ListRouteTables(ListRouteTablesRequest) returns (ListRouteTablesResponse) {} - rpc CreateRouteTable(CreateRouteTableRequest) returns (RouteTable) {} - rpc DeleteRouteTable(RouteTableIdentifier) returns (google.protobuf.Empty) {} - - rpc GetRoute(RouteIdentifier) returns (Route) {} - rpc ListRoutes(ListRoutesRequest) returns (ListRoutesResponse) {} - rpc PutRoute(PutRouteRequest) returns (Route) {} - rpc DeleteRoute(RouteIdentifier) returns (google.protobuf.Empty) {} - - rpc SyncRoutes(SyncRoutesRequest) returns (google.protobuf.Empty) {} -} diff --git a/protos/minivirt/daemon.proto b/protos/minivirt/daemon.proto deleted file mode 100644 index 93dc9b9..0000000 --- a/protos/minivirt/daemon.proto +++ /dev/null @@ -1,44 +0,0 @@ -syntax = "proto3"; - -option go_package = ".;pb"; - -import "google/protobuf/empty.proto"; -import "minivirt/domain.proto"; -import "minivirt/volume.proto"; -import "minivirt/port_forwarding.proto"; - -message SyncRoutesRequest {} - -service DaemonService { - rpc GetNetwork(GetNetworkRequest) returns (Network) {} - rpc ListNetworks(ListNetworksRequest) returns (ListNetworksResponse) {} - rpc CreateNetwork(CreateNetworkRequest) returns (Network) {} - rpc DeleteNetwork(DeleteNetworkRequest) returns (google.protobuf.Empty) {} - - rpc StartDomain(StartDomainRequest) returns (google.protobuf.Empty) {} - rpc StopDomain(StopDomainRequest) returns (google.protobuf.Empty) {} - rpc GetDomain(GetDomainRequest) returns (Domain) {} - rpc ListDomains(ListDomainsRequest) returns (ListDomainsResponse) {} - rpc CreateDomain(CreateDomainRequest) returns (Domain) {} - rpc DeleteDomain(DeleteDomainRequest) returns (google.protobuf.Empty) {} - - rpc DownloadImage(DownloadImageRequest) returns (stream ImageChunk) {} - - rpc GetVolume(GetVolumeRequest) returns (Volume) {} - rpc ListVolumes(ListVolumesRequest) returns (ListVolumesResponse) {} - rpc CreateVolume(CreateVolumeRequest) returns (Volume) {} - rpc UpdateVolume(UpdateVolumeRequest) returns (Volume) {} - rpc DeleteVolume(DeleteVolumeRequest) returns (google.protobuf.Empty) {} - - rpc ListVolumeAttachments(ListVolumeAttachmentsRequest) returns (ListVolumeAttachmentsResponse) {} - rpc GetVolumeAttachment(VolumeAttachmentIdentifier) returns (VolumeAttachment) {} - rpc AttachVolume(VolumeAttachmentIdentifier) returns (VolumeAttachment) {} - rpc DetachVolume(VolumeAttachmentIdentifier) returns (google.protobuf.Empty) {} - - rpc GetPortForwarding(PortForwardingIdentifier) returns (PortForwarding) {} - rpc ListPortForwardings(ListPortForwardingsRequest) returns (ListPortForwardingsResponse) {} - rpc PutPortForwarding(PutPortForwardingRequest) returns (PortForwarding) {} - rpc DeletePortForwarding(PortForwardingIdentifier) returns (google.protobuf.Empty) {} - - rpc SyncRoutes(SyncRoutesRequest) returns (google.protobuf.Empty) {} -} diff --git a/protos/minivirt/dns.proto b/protos/minivirt/dns.proto deleted file mode 100644 index 53b6d5a..0000000 --- a/protos/minivirt/dns.proto +++ /dev/null @@ -1,34 +0,0 @@ -syntax = "proto3"; - -option go_package = ".;pb"; - -import "google/protobuf/empty.proto"; - -message DNSRecordIdentifier { - string name = 1; - string type = 2; -} - -message DNSRecord { - string name = 1; - string type = 2; - uint64 ttl = 3; - repeated string records = 4; -} - -message ListDNSRecordsRequest {} - -message ListDNSRecordsResponse { - repeated DNSRecord dns_records = 1; -} - -message PutDNSRecordRequest { - DNSRecord dns_record = 1; -} - -service DNS { - rpc GetDNSRecord(DNSRecordIdentifier) returns (DNSRecord) {} - rpc ListDNSRecords(ListDNSRecordsRequest) returns (ListDNSRecordsResponse) {} - rpc PutDNSRecord(PutDNSRecordRequest) returns (DNSRecord) {} - rpc DeleteDNSRecord(DNSRecordIdentifier) returns (google.protobuf.Empty) {} -} diff --git a/protos/minivirt/domain.proto b/protos/minivirt/domain.proto deleted file mode 100644 index aa7e76b..0000000 --- a/protos/minivirt/domain.proto +++ /dev/null @@ -1,113 +0,0 @@ -syntax = "proto3"; - -option go_package = ".;pb"; - -import "google/protobuf/empty.proto"; -import "google/protobuf/timestamp.proto"; - -message StartDomainRequest { - string host = 1; - string uuid = 2; -} - -message StopDomainRequest { - string host = 1; - string uuid = 2; - bool force = 3; -} - -message GetDomainRequest { - string uuid = 1; - string host = 2; -} - -message Domain { - uint64 id = 1; - string uuid = 2; - string name = 3; - uint32 vcpu = 4; - uint64 memory = 5; - string network = 6; - string bridge = 7; - string state = 8; - string private_ip = 9; - string ipv6_address = 15; - string user_data = 10; - bool nested_virtualization = 11; - string base_image = 12; - google.protobuf.Timestamp created_at = 13; - string os_type = 14; -} - -message ListDomainsRequest { - string host = 1; -} - -message ListDomainsResponse { - repeated Domain domains = 1; -} - -message CreateDomainRequest { - Domain domain = 1; - string host = 2; -} - -message DeleteDomainRequest { - string uuid = 1; - string host = 2; -} - -message DownloadImageRequest { - string domain_id = 1; - string host = 2; -} - -message ImageChunk { - bytes bytes = 1; -} - - -message GetNetworkRequest { - string uuid = 1; - string host = 2; -} - -message Network { - string uuid = 1; - string name = 2; - string cidr = 3; - string cidr6 = 4; -} - -message ListNetworksRequest { - string host = 1; -} - -message ListNetworksResponse { - repeated Network networks = 1; -} - -message CreateNetworkRequest { - Network network = 1; - string host = 2; -} - -message DeleteNetworkRequest { - string uuid = 1; - string host = 2; -} - - -service DomainService { - rpc GetDomain(GetDomainRequest) returns (Domain) {} - rpc ListDomains(ListDomainsRequest) returns (ListDomainsResponse) {} - rpc CreateDomain(CreateDomainRequest) returns (Domain) {} - rpc DeleteDomain(DeleteDomainRequest) returns (google.protobuf.Empty) {} - - rpc DownloadImage(DownloadImageRequest) returns (stream ImageChunk) {} - - rpc GetNetwork(GetNetworkRequest) returns (Network) {} - rpc ListNetworks(ListNetworksRequest) returns (ListNetworksResponse) {} - rpc CreateNetwork(CreateNetworkRequest) returns (Network) {} - rpc DeleteNetwork(DeleteNetworkRequest) returns (google.protobuf.Empty) {} -} diff --git a/protos/minivirt/host.proto b/protos/minivirt/host.proto deleted file mode 100644 index c3ab844..0000000 --- a/protos/minivirt/host.proto +++ /dev/null @@ -1,46 +0,0 @@ -syntax = "proto3"; - -option go_package = ".;pb"; - -import "google/protobuf/empty.proto"; - -message CreateBootstrapTokenRequest { - string expires_at = 1; -} - -message CreateBootstrapTokenResponse { - string token = 1; -} - -message Host { - string name = 1; - string address = 2; -} - -message ListHostsRequest {} - -message ListHostsResponse { - repeated Host hosts = 1; -} - -message RegisterHostRequest { - Host host = 1; - string token = 2; -} - -message HeartbeatRequest { - Host host = 1; -} - -message HeartbeatResponse { -} - -service HostService { - rpc CreateBootstrapToken(CreateBootstrapTokenRequest) returns (CreateBootstrapTokenResponse) {} - rpc GetHost(Host) returns (Host) {} - rpc ListHosts(ListHostsRequest) returns (ListHostsResponse) {} - rpc Register(RegisterHostRequest) returns (Host) {} - rpc Deregister(Host) returns (google.protobuf.Empty) {} - - rpc Heartbeat(HeartbeatRequest) returns (HeartbeatResponse) {} -} diff --git a/protos/minivirt/port_forwarding.proto b/protos/minivirt/port_forwarding.proto deleted file mode 100644 index 1151264..0000000 --- a/protos/minivirt/port_forwarding.proto +++ /dev/null @@ -1,38 +0,0 @@ -syntax = "proto3"; - -option go_package = ".;pb"; - -import "google/protobuf/empty.proto"; - -message PortForwardingIdentifier { - string host = 1; - string protocol = 2; - uint32 source_port = 3; -} - -message PortForwarding { - string protocol = 2; - uint32 source_port = 3; - string target_ip = 4; - uint32 target_port = 5; -} - -message ListPortForwardingsRequest { - string host = 1; -} - -message ListPortForwardingsResponse { - repeated PortForwarding port_forwardings = 1; -} - -message PutPortForwardingRequest { - PortForwarding port_forwarding = 1; - string host = 2; -} - -service PortForwardingService { - rpc GetPortForwarding(PortForwardingIdentifier) returns (PortForwarding) {} - rpc ListPortForwardings(ListPortForwardingsRequest) returns (ListPortForwardingsResponse) {} - rpc PutPortForwarding(PutPortForwardingRequest) returns (PortForwarding) {} - rpc DeletePortForwarding(PortForwardingIdentifier) returns (google.protobuf.Empty) {} -} diff --git a/protos/minivirt/route.proto b/protos/minivirt/route.proto deleted file mode 100644 index a375d89..0000000 --- a/protos/minivirt/route.proto +++ /dev/null @@ -1,67 +0,0 @@ -syntax = "proto3"; - -option go_package = ".;pb"; - -import "google/protobuf/empty.proto"; - -message RouteTable { - string network_name = 1; - uint32 id = 2; - string name = 3; -} - -message RouteTableIdentifier { - uint32 id = 1; -} - -message ListRouteTablesRequest { - string network_name = 1; -} - -message ListRouteTablesResponse { - repeated RouteTable route_tables = 1; -} - -message CreateRouteTableRequest { - RouteTable route_table = 1; -} - -message RouteIdentifier { - uint32 route_table_id = 1; - string destination = 2; -} - -message Route { - uint32 route_table_id = 1; - string destination = 2; - repeated string gateways = 3; -} - -message ListRoutesRequest { - uint32 route_table_id = 1; -} - -message ListRoutesResponse { - repeated Route routes = 1; -} - -message PutRouteRequest { - Route route = 1; -} - -message SyncRequest { -} - -service RouteService { - rpc GetRouteTable(RouteTableIdentifier) returns (RouteTable) {} - rpc ListRouteTables(ListRouteTablesRequest) returns (ListRouteTablesResponse) {} - rpc CreateRouteTable(CreateRouteTableRequest) returns (RouteTable) {} - rpc DeleteRouteTable(RouteTableIdentifier) returns (google.protobuf.Empty) {} - - rpc GetRoute(RouteIdentifier) returns (Route) {} - rpc ListRoutes(ListRoutesRequest) returns (ListRoutesResponse) {} - rpc PutRoute(PutRouteRequest) returns (Route) {} - rpc DeleteRoute(RouteIdentifier) returns (google.protobuf.Empty) {} - - rpc Sync(SyncRequest) returns (google.protobuf.Empty) {} -} diff --git a/protos/minivirt/volume.proto b/protos/minivirt/volume.proto deleted file mode 100644 index ec1cd07..0000000 --- a/protos/minivirt/volume.proto +++ /dev/null @@ -1,73 +0,0 @@ -syntax = "proto3"; - -option go_package = ".;pb"; - -import "google/protobuf/empty.proto"; - -message GetVolumeRequest { - string uuid = 1; - string host = 2; -} - -message Volume { - string id = 1; - string name = 2; - uint64 size = 3; -} - -message ListVolumesRequest { - string host = 1; -} - -message ListVolumesResponse { - repeated Volume volumes = 1; -} - -message CreateVolumeRequest { - Volume volume = 1; - string host = 2; -} - -message UpdateVolumeRequest { - string host = 1; - Volume volume = 2; -} - -message DeleteVolumeRequest { - string uuid = 1; - string host = 2; -} - -message VolumeAttachmentIdentifier { - string domain_id = 1; - string volume_id = 2; - string host = 3; -} - -message VolumeAttachment { - string domain_id = 1; - string volume_id = 2; - string disk_address = 3; -} - -message ListVolumeAttachmentsRequest { - string domain_id = 1; - string host = 2; -} - -message ListVolumeAttachmentsResponse { - repeated VolumeAttachment attachments = 1; -} - -service VolumeService { - rpc GetVolume(GetVolumeRequest) returns (Volume) {} - rpc ListVolumes(ListVolumesRequest) returns (ListVolumesResponse) {} - rpc CreateVolume(CreateVolumeRequest) returns (Volume) {} - rpc UpdateVolume(UpdateVolumeRequest) returns (Volume) {} - rpc DeleteVolume(DeleteVolumeRequest) returns (google.protobuf.Empty) {} - - rpc ListVolumeAttachments(ListVolumeAttachmentsRequest) returns (ListVolumeAttachmentsResponse) {} - rpc GetVolumeAttachment(VolumeAttachmentIdentifier) returns (VolumeAttachment) {} - rpc AttachVolume(VolumeAttachmentIdentifier) returns (VolumeAttachment) {} - rpc DetachVolume(VolumeAttachmentIdentifier) returns (google.protobuf.Empty) {} -} diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 2a9d90d..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,18 +0,0 @@ -#[project.scripts] -#restvirt="main:main" - -[tool.black] -line-length = 100 -exclude = ''' -( - migrations/ - | venv/ - | .*_pb2(_grpc)?.py -) -''' - -[tool.isort] -profile="black" -combine_as_imports="true" -skip_glob=["*_pb2.py","*_pb2_grpc.py"] -line_length=100 diff --git a/rebar.config b/rebar.config new file mode 100644 index 0000000..d87276d --- /dev/null +++ b/rebar.config @@ -0,0 +1,30 @@ +{erl_opts, [debug_info]}. +{deps, [ + {khepri, "0.7.0"}, + {thoas, "1.0.0"}, + {mochiweb, "3.1.2"}, + {erlexec, "~> 2.0"} +]}. + +{relx, [ + {release, {virtuerl, git}, [virtuerl, {khepri, load}, {mnesia, load}, erts]}, + {mode, prod}, + {extended_start_script, false} +]}. + +{shell, [ + {config, "config/sys.config"}, + {apps, [virtuerl]} +]}. + +{ct_opts, [ + {sys_config, "config/sys.config"} +]}. + +{project_plugins, [ + {eqwalizer_rebar3, + {git_subdir, + "https://github.com/whatsapp/eqwalizer.git", + {branch, "main"}, + "eqwalizer_rebar3"}} +]}. diff --git a/rebar.lock b/rebar.lock new file mode 100644 index 0000000..f90d145 --- /dev/null +++ b/rebar.lock @@ -0,0 +1,32 @@ +{"1.2.0", +[{<<"aten">>,{pkg,<<"aten">>,<<"0.5.8">>},2}, + {<<"erlexec">>,{pkg,<<"erlexec">>,<<"2.0.2">>},0}, + {<<"gen_batch_server">>,{pkg,<<"gen_batch_server">>,<<"0.8.8">>},2}, + {<<"horus">>,{pkg,<<"horus">>,<<"0.2.3">>},1}, + {<<"khepri">>,{pkg,<<"khepri">>,<<"0.7.0">>},0}, + {<<"mochiweb">>,{pkg,<<"mochiweb">>,<<"3.1.2">>},0}, + {<<"ra">>,{pkg,<<"ra">>,<<"2.5.1">>},1}, + {<<"seshat">>,{pkg,<<"seshat">>,<<"0.4.0">>},2}, + {<<"thoas">>,{pkg,<<"thoas">>,<<"1.0.0">>},0}]}. +[ +{pkg_hash,[ + {<<"aten">>, <<"B5C97F48517C4F37F26A519AA57A00A31FF1B8EA4324EC1CAE27F818ED5C0DB2">>}, + {<<"erlexec">>, <<"995E40477DE94C37EC1264CC3E52EB6273938E80C9BCC4F94110A3F1C0D9ABA3">>}, + {<<"gen_batch_server">>, <<"7840A1FA63EE1EFFC83E8A91D22664847A2BA1192D30EAFFFD914ACB51578068">>}, + {<<"horus">>, <<"A8AC0E7B335B83860ECECFA12EE4CB50289A15A515175BEF804A9C0D06C619B5">>}, + {<<"khepri">>, <<"5CB9B1D35051DAEAF6308BFC64446A4BC2DEAAAE39C619DFB890B6CCFF8F45AF">>}, + {<<"mochiweb">>, <<"D872D470DBBA367171A38C41592B19DE370854E74C8621BF0C6FE39B17CDD1CF">>}, + {<<"ra">>, <<"9EE208B7CD229B34F67C56B6BEFAD0E906C5B37C6175DB9C76C00B1BC0CF82D8">>}, + {<<"seshat">>, <<"1D5DC4294E36B8745245AB2649E24E39D7B6B1209D7A6484F2B8D706C35C9814">>}, + {<<"thoas">>, <<"567C03902920827A18A89F05B79A37B5BF93553154B883E0131801600CF02CE0">>}]}, +{pkg_hash_ext,[ + {<<"aten">>, <<"64D40A8CF0DDFEA4E13AF00B7327F0925147F83612D0627D9506CBFFE90C13EF">>}, + {<<"erlexec">>, <<"CC829A7C6C23D399832DA2E998EA5EBC552232A6FE3EB1EDB400178EC8287DCB">>}, + {<<"gen_batch_server">>, <<"C3E6A1A2A0FB62AEE631A98CFA0FD8903E9562422CBF72043953E2FB1D203017">>}, + {<<"horus">>, <<"0CA6AA70A348F73EF0B78F498C82F9C29F49B45D5915B890945A1C326A216C10">>}, + {<<"khepri">>, <<"784D90A578340E137BF7E2C8D1F1F45E59D6E13262804A841BF9E709E7505954">>}, + {<<"mochiweb">>, <<"A8609035711F9264E8ABE54312D1B1645374B9E08986AE06E979BB0AC8B99F9F">>}, + {<<"ra">>, <<"13B03F02CF6C1837C527EDD4A953F0C09DA0ABAD0AF6985B64BFD66943C4C5C3">>}, + {<<"seshat">>, <<"2C3DEEC7FF86E0D0C05EDEBD3455C8363123C227BE292FFFFC1A05EEC08BFF63">>}, + {<<"thoas">>, <<"FC763185B932ECB32A554FB735EE03C3B6B1B31366077A2427D2A97F3BD26735">>}]} +]. diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index 6820cd1..0000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,5 +0,0 @@ -pytest==7.2.0 -isort[colors]==5.10.1 -black==22.6.0 -fabric==2.7.1 -grpcio-tools==1.51.1 diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index f062f4b..0000000 --- a/requirements.txt +++ /dev/null @@ -1,13 +0,0 @@ -libvirt-python==8.10.0 -grpcio==1.51.1 -grpcio-reflection==1.51.1 -protobuf==4.21.10 -pycdlib==1.12.0 -pyroute2==0.7.2 -git+https://salsa.debian.org/pkg-netfilter-team/pkg-nftables.git@upstream/1.0.4#egg=nftables&subdirectory=py -python-iptables==1.0.0 -SQLAlchemy==1.4.44 -xmltodict==0.12.0 -dnslib==0.9.16 -dnspython==2.2.1 -PyYAML==6.0 diff --git a/setup.py b/setup.py deleted file mode 100644 index 312d35e..0000000 --- a/setup.py +++ /dev/null @@ -1,30 +0,0 @@ -from setuptools import find_packages, setup - -from minivirt.version import __version__ - -setup( - name="restvirt", - version="0.1", # TODO: use __version__ instead - packages=find_packages(), - url="", - license="Apache", - author="Ilya Verbitskiy", - author_email="ilya@verbit.io", - description="", - entry_points={"console_scripts": ["restvirt=minivirt.main:main"]}, - install_requires=[ - "libvirt-python==8.10.0", - "grpcio==1.51.1", - "grpcio-reflection==1.51.1", - "protobuf==4.21.10", - "pycdlib==1.12.0", - "pyroute2==0.7.2", - "nftables @ git+https://salsa.debian.org/pkg-netfilter-team/pkg-nftables.git@upstream/1.0.4#egg=nftables&subdirectory=py", - "python-iptables==1.0.0", - "SQLAlchemy==1.4.44", - "xmltodict==0.12.0", - "dnslib==0.9.16", - "dnspython==2.2.1", - "PyYAML==6.0", - ], -) diff --git a/snap/hooks/configure b/snap/hooks/configure deleted file mode 100755 index 4622a34..0000000 --- a/snap/hooks/configure +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh -e - -mkdir -p "$SNAP_DATA/var/run/bird/" -if [ ! -f "$SNAP_DATA/etc/bird/minivirt.conf" ]; then - mkdir -p "$SNAP_DATA/etc/bird/" - cp "$SNAP/etc/bird/minivirt.conf" "$SNAP_DATA/etc/bird/minivirt.conf" -fi - -controller_args="$(snapctl get controller.args)" -if [ -z "$controller_args" ]; then - snapctl set controller.args='-c $SNAP_COMMON' -fi - -daemon_args="$(snapctl get daemon.args)" -if [ -z "$daemon_args" ]; then - snapctl set daemon.args='-c $SNAP_COMMON' -fi - -snapctl restart minivirt diff --git a/snap/hooks/install b/snap/hooks/install deleted file mode 100644 index 2140bca..0000000 --- a/snap/hooks/install +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -e - -snapctl stop --disable minivirt diff --git a/snap/local/bin/bird-wrapper b/snap/local/bin/bird-wrapper deleted file mode 100755 index e113aaa..0000000 --- a/snap/local/bin/bird-wrapper +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -e - -exec bird -f -c "$SNAP_DATA/etc/bird/minivirt.conf" -s "$SNAP_DATA/var/run/bird/bird.ctl" diff --git a/snap/local/bin/birdc-wrapper b/snap/local/bin/birdc-wrapper deleted file mode 100755 index 2dc229f..0000000 --- a/snap/local/bin/birdc-wrapper +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -e - -exec birdc -s "$SNAP_DATA/var/run/bird/bird.ctl" "$@" diff --git a/snap/local/bin/restvirt-controller b/snap/local/bin/restvirt-controller deleted file mode 100755 index 50ed1e7..0000000 --- a/snap/local/bin/restvirt-controller +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -e - -exec restvirt controller $(snapctl get controller.args | envsubst) diff --git a/snap/local/bin/restvirt-daemon b/snap/local/bin/restvirt-daemon deleted file mode 100755 index 533826b..0000000 --- a/snap/local/bin/restvirt-daemon +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -e - -exec restvirt daemon $(snapctl get daemon.args | envsubst) diff --git a/snap/local/bin/restvirt-deregister b/snap/local/bin/restvirt-deregister deleted file mode 100755 index 92cc0de..0000000 --- a/snap/local/bin/restvirt-deregister +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -e - -exec restvirt deregister "$@" diff --git a/snap/local/bin/restvirt-register b/snap/local/bin/restvirt-register deleted file mode 100755 index ed0eaa7..0000000 --- a/snap/local/bin/restvirt-register +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -e - -exec restvirt register "$@" diff --git a/snap/local/bin/unbound-wrapper b/snap/local/bin/unbound-wrapper deleted file mode 100755 index 5c544be..0000000 --- a/snap/local/bin/unbound-wrapper +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -e - -exec unbound -d -p -c "$SNAP/etc/unbound/minivirt.conf" diff --git a/snap/local/etc/bird/minivirt.conf b/snap/local/etc/bird/minivirt.conf deleted file mode 100644 index 255aaa0..0000000 --- a/snap/local/etc/bird/minivirt.conf +++ /dev/null @@ -1,229 +0,0 @@ -# This is a basic configuration file, which contains boilerplate options and -# some basic examples. It allows the BIRD daemon to start but will not cause -# anything else to happen. -# -# Please refer to the BIRD User's Guide documentation, which is also available -# online at http://bird.network.cz/ in HTML format, for more information on -# configuring BIRD and adding routing protocols. - -# Configure logging -log syslog all; -# log "/var/log/bird.log" { debug, trace, info, remote, warning, error, auth, fatal, bug }; - -# Set router ID. It is a unique identification of your router, usually one of -# IPv4 addresses of the router. It is recommended to configure it explicitly. -# router id 198.51.100.1; - -# Turn on global debugging of all protocols (all messages or just selected classes) -# debug protocols all; -# debug protocols { events, states }; - -# Turn on internal watchdog -# watchdog warning 5 s; -# watchdog timeout 30 s; - -# You can define your own constants -# define my_asn = 65000; -# define my_addr = 198.51.100.1; - -# Tables master4 and master6 are defined by default -# ipv4 table master4; -# ipv6 table master6; - -# Define more tables, e.g. for policy routing or as MRIB -# ipv4 table mrib4; -# ipv6 table mrib6; - -# The Device protocol is not a real routing protocol. It does not generate any -# routes and it only serves as a module for getting information about network -# interfaces from the kernel. It is necessary in almost any configuration. -protocol device { -} - -# The direct protocol is not a real routing protocol. It automatically generates -# direct routes to all network interfaces. Can exist in as many instances as you -# wish if you want to populate multiple routing tables with direct routes. -protocol direct { - disabled; # Disable by default - ipv4; # Connect to default IPv4 table - ipv6; # ... and to default IPv6 table -} - -# The Kernel protocol is not a real routing protocol. Instead of communicating -# with other routers in the network, it performs synchronization of BIRD -# routing tables with the OS kernel. One instance per table. -protocol kernel { - merge paths on; - ipv4 { # Connect protocol to IPv4 table by channel -# table master4; # Default IPv4 table is master4 - import all; # Import to table, default is import all - export all; # Export to protocol. default is export none - }; -# learn; # Learn alien routes from the kernel -# kernel table 30068; # Kernel table to synchronize with (default: main) -} - -# Another instance for IPv6, skipping default options -protocol kernel { - ipv6 { export all; }; -} - -# Static routes (Again, there can be multiple instances, for different address -# families and to disable/enable various groups of static routes on the fly). -protocol static { - ipv4; # Again, IPv4 channel with default options - -# route 0.0.0.0/0 via 198.51.100.10; -# route 192.0.2.0/24 blackhole; -# route 10.0.0.0/8 unreachable; -# route 10.2.0.0/24 via "eth0"; -# # Static routes can be defined with optional attributes -# route 10.1.1.0/24 via 198.51.100.3 { rip_metric = 3; }; -# route 10.1.2.0/24 via 198.51.100.3 { ospf_metric1 = 100; }; -# route 10.1.3.0/24 via 198.51.100.4 { ospf_metric2 = 100; }; -} - -# Pipe protocol connects two routing tables. Beware of loops. -# protocol pipe { -# table master4; # No ipv4/ipv6 channel definition like in other protocols -# peer table mrib4; -# import all; # Direction peer table -> table -# export all; # Direction table -> peer table -# } - -# RIP example, both RIP and RIPng are supported -# protocol rip { -# ipv4 { -# # Export direct, static routes and ones from RIP itself -# import all; -# export where source ~ [ RTS_DEVICE, RTS_STATIC, RTS_RIP ]; -# }; -# interface "eth*" { -# update time 10; # Default period is 30 -# timeout time 60; # Default timeout is 180 -# authentication cryptographic; # No authentication by default -# password "hello" { algorithm hmac sha256; }; # Default is MD5 -# }; -# } - -# OSPF example, both OSPFv2 and OSPFv3 are supported -# protocol ospf v3 { -# ipv6 { -# import all; -# export where source = RTS_STATIC; -# }; -# area 0 { -# interface "eth*" { -# type broadcast; # Detected by default -# cost 10; # Interface metric -# hello 5; # Default hello perid 10 is too long -# }; -# interface "tun*" { -# type ptp; # PtP mode, avoids DR selection -# cost 100; # Interface metric -# hello 5; # Default hello perid 10 is too long -# }; -# interface "dummy0" { -# stub; # Stub interface, just propagate it -# }; -# }; -#} - -# Define simple filter as an example for BGP import filter -# See https://gitlab.labs.nic.cz/labs/bird/wikis/BGP_filtering for more examples -# filter rt_import -# { -# if bgp_path.first != 64496 then accept; -# if bgp_path.len > 64 then accept; -# if bgp_next_hop != from then accept; -# reject; -# } - -protocol bgp k8s { - local as 64513; - neighbor range 192.168.122.0/24 port 179 as 64513; - direct; - rr client; - passive on; - ipv4 { - add paths on; - import all; - export all; - }; -} - -# protocol bgp up { -# local port 178 as 64513; -# neighbor 192.168.122.111 as 64513; -# direct; -# ipv4 { -# add paths on; -# import all; -# export none; -# }; -# } - -# BGP example, explicit name 'uplink1' is used instead of default 'bgp1' -# protocol bgp uplink1 { -# description "My BGP uplink"; -# local 198.51.100.1 as 65000; -# neighbor 198.51.100.10 as 64496; -# hold time 90; # Default is 240 -# password "secret"; # Password used for MD5 authentication -# -# ipv4 { # regular IPv4 unicast (1/1) -# import filter rt_import; -# export where source ~ [ RTS_STATIC, RTS_BGP ]; -# }; -# -# ipv6 { # regular IPv6 unicast (2/1) -# import filter rt_import; -# export filter { # The same as 'where' expression above -# if source ~ [ RTS_STATIC, RTS_BGP ] -# then accept; -# else reject; -# }; -# }; -# -# ipv4 multicast { # IPv4 multicast topology (1/2) -# table mrib4; # explicit IPv4 table -# import filter rt_import; -# export all; -# }; -# -# ipv6 multicast { # IPv6 multicast topology (2/2) -# table mrib6; # explicit IPv6 table -# import filter rt_import; -# export all; -# }; -#} - -# Template example. Using templates to define IBGP route reflector clients. -# template bgp rr_clients { -# local 10.0.0.1 as 65000; -# neighbor as 65000; -# rr client; -# rr cluster id 1.0.0.1; -# -# ipv4 { -# import all; -# export where source = RTS_BGP; -# }; -# -# ipv6 { -# import all; -# export where source = RTS_BGP; -# }; -# } -# -# protocol bgp client1 from rr_clients { -# neighbor 10.0.1.1; -# } -# -# protocol bgp client2 from rr_clients { -# neighbor 10.0.2.1; -# } -# -# protocol bgp client3 from rr_clients { -# neighbor 10.0.3.1; -# } diff --git a/snap/local/etc/unbound/minivirt.conf b/snap/local/etc/unbound/minivirt.conf deleted file mode 100644 index 383e96a..0000000 --- a/snap/local/etc/unbound/minivirt.conf +++ /dev/null @@ -1,20 +0,0 @@ -server: - #logfile: "/var/log/unbound/unbound.log" - verbosity: 1 - chroot: "" - username: "" - port: 5354 - interface: 0.0.0.0 - interface: ::0 - access-control: 192.168.0.0/16 allow - access-control: 10.0.0.0/8 allow - access-control: fc00::/7 allow - do-ip6: yes - do-not-query-localhost: no - domain-insecure: "internal" - directory: "." -remote-control: - control-enable: no -stub-zone: - name: "internal" - stub-addr: 127.0.0.1@5353 diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml deleted file mode 100644 index 8553d93..0000000 --- a/snap/snapcraft.yaml +++ /dev/null @@ -1,83 +0,0 @@ -name: minivirt # you probably want to 'snapcraft register ' -base: core22 # the base snap is the execution environment for this snap -version: 'git' # just for humans, typically '1.2+git' or '1.3.2' -summary: Single-line elevator pitch for your amazing snap # 79 char long summary -description: | - This is my-snap's description. You have a paragraph or two to tell the - most important story about your snap. Keep it under 100 words though, - we live in tweetspace and your description wants to look good in the snap - store. - -grade: stable # must be 'stable' to release into candidate/stable channels -confinement: strict # use 'strict' once you have the right plugs and slots - -apps: - controller: - command: bin/restvirt-controller - daemon: simple - plugs: - - network - - network-bind - unbound: - command: bin/unbound-wrapper - daemon: simple - after: - - controller - plugs: - - network - - network-bind - bird: - command: bin/bird-wrapper - daemon: simple - before: - - daemon - plugs: - - network - - network-bind - - network-control - birdc: - command: bin/birdc-wrapper - register: - command: bin/restvirt-register - plugs: - - network - - network-bind - deregister: - command: bin/restvirt-deregister - plugs: - - network - - network-bind - daemon: - command: bin/restvirt-daemon - daemon: simple - after: - - controller - restart-condition: always - restart-delay: 2s - plugs: - - libvirt - - network - - network-bind - - network-control - - firewall-control - -parts: - wrapper: - plugin: dump - source: snap/local/ - stage-packages: - - gettext-base - minivirt: - # See 'snapcraft plugins' - plugin: python - source: ./ - build-packages: - - git - - libvirt-dev - - pkg-config - stage-packages: - - libvirt0 - - libbrotli1 # kill me please - - libnftables1 - - unbound - - bird2 diff --git a/src/virtuerl.app.src b/src/virtuerl.app.src new file mode 100644 index 0000000..ab60c7c --- /dev/null +++ b/src/virtuerl.app.src @@ -0,0 +1,19 @@ +{application, virtuerl, + [{description, "An OTP application"}, + {vsn, git}, + {registered, []}, + {mod, {virtuerl_app, []}}, + {applications, + [kernel, + stdlib, + inets, + mochiweb, + thoas, + erlexec + ]}, + {env,[]}, + {modules, []}, + + {licenses, ["Apache-2.0"]}, + {links, []} + ]}. diff --git a/src/virtuerl_api.erl b/src/virtuerl_api.erl new file mode 100644 index 0000000..57cba3a --- /dev/null +++ b/src/virtuerl_api.erl @@ -0,0 +1,194 @@ +%%%------------------------------------------------------------------- +%%% @author ilya +%%% @copyright (C) 2023, +%%% @doc +%%% @end +%%%------------------------------------------------------------------- +-module(virtuerl_api). + +-behaviour(cowboy_handler). + +-export([start_link/0]). +-export([handle/4]). +%%-export([init/2, content_types_provided/2, to_text/2, allowed_methods/2, content_types_accepted/2, from_json/2]). + +-define(SERVER, ?MODULE). + +%%%=================================================================== +%%% Spawning and gen_server implementation +%%%=================================================================== + +start_link() -> + mochiweb_http:start_link([ + {name, ?MODULE}, + {loop, fun loop/1}, + {ip, any}, + {port, 8081} + ]). + + +%% 1> {ok,MP} = re:compile("(?A)|(?B)|(?C)"). +%% {ok,{re_pattern,3,0,0, +%% <<69,82,67,80,119,0,0,0,0,0,0,0,1,0,0,0,255,255,255,255, +%% 255,255,...>>}} +%% 2> {namelist, N} = re:inspect(MP,namelist). +%% {namelist,[<<"A">>,<<"B">>,<<"C">>]} +%% 3> {match,L} = re:run("AA",MP,[{capture,all_names,binary}]). +%% {match,[<<"A">>,<<>>,<<>>]} +%% 4> NameMap = lists:zip(N,L). +%% [{<<"A">>,<<"A">>},{<<"B">>,<<>>},{<<"C">>,<<>>}] + +urls() -> + RawURLs = [ + {"^/networks/?$", networks, ['GET', 'POST']}, + {"^/networks/(?[^/]+)/?$", network, ['GET', 'PUT', 'DELETE']}, + {"^/domains/?$", domains, ['GET', 'POST']}, + {"^/domains/(?[^/]+)/?$", domain, ['GET', 'DELETE']} + ], + CompiledUrls = lists:map(fun ({URL, Tag, Methods}) -> + {ok, Pat} = re:compile(URL), + {namelist, Namelist} = re:inspect(Pat, namelist), + {Pat, Namelist, Tag, sets:from_list(Methods)} + end, RawURLs), + CompiledUrls. + +dispatch(Req, []) -> + mochiweb_request:not_found(Req); +dispatch(Req, [{Pat, Namelist, Tag, AllowedMethods}|T]) -> + Path = mochiweb_request:get(path, Req), + case re:run(Path, Pat, [{capture, all_names, list}]) of + {match, Match} -> dispatch_matched(Match, Namelist, Tag, AllowedMethods, Req); + match -> dispatch_matched([], Namelist, Tag, AllowedMethods, Req); + nomatch -> dispatch(Req, T) + end. + +dispatch_matched(Match, Namelist, Tag, AllowedMethods, Req) -> + Method = mochiweb_request:get(method, Req), + case sets:is_element(Method, AllowedMethods) of + false -> mochiweb_request:respond({405, [{"Content-Type", "text/plain"}], "Method Not Allowed"}, Req); + _ -> + PathMap = maps:from_list(lists:zip(lists:map(fun binary_to_atom/1, Namelist), Match)), + io:format("PathMap: ~p~n", [PathMap]), + try handle(Tag, Method, PathMap, Req) + catch + error:function_clause -> mochiweb_request:respond({500, [{"Content-Type", "text/plain"}], "Error"}, Req) +%% ; +%% error:bad_argument -> mochiweb_request:respond({500, [{"Content-Type", "text/plain"}], "Error"}, Req) + end + end. + +loop(Req) -> + dispatch(Req, urls()). + +%% handler +%%init(Req0, State) -> +%% Req = cowboy_req:reply(200, #{<<"content-type">> => <<"text/plain">>}, <<"Hello elloh">>, Req0), +%% {ok, Req, State}. + +%%init(Req, State) -> +%% {cowboy_rest, Req, State}. +%% +%%allowed_methods(Req, State) -> +%% {[<<"GET">>, <<"HEAD">>, <<"OPTIONS">>, <<"POST">>], Req, State}. +%% +%%content_types_accepted(Req, State) -> +%% {[{{<<"application">>, <<"json">>, '*'}, from_json}], +%% Req, State +%% }. +%% +%%from_json(Req, State) -> +%% {ok, RawJSON, Req1} = cowboy_req:read_body(Req), +%% {ok, JSON} = thoas:decode(RawJSON), +%% #{<<"networkID">> := NetworkID} = JSON, +%% Conf = #{network_id => NetworkID}, +%%%% virtuerl_mgt:create_vm(Conf), +%% {true, Req1, State}. +%% +%%content_types_provided(Req, State) -> +%% {[{{<<"text">>, <<"plain">>, '*'}, to_text}], +%% Req, State +%% }. +%% +%%to_text(Req, State) -> +%% {<<"OK">>, Req, State}. + +parse_json(Req) -> + Body = mochiweb_request:recv_body(Req), + {ok, JSON} = thoas:decode(Body), + JSON. + +handle(networks, 'GET', _, Req) -> + {ok, Nets} = virtuerl_ipam:ipam_list_nets(), + io:format("~p~n", [Nets]), + mochiweb_request:respond({200, [{"Content-Type", "application/json"}], thoas:encode(Nets)}, Req); +handle(networks, 'POST', _, Req) -> + JSON = parse_json(Req), + #{<<"network">> := NetJson} = JSON, + {ok, NetDefs} = case NetJson of + #{<<"cidr4">> := Cidr4, <<"cidr6">> := Cidr6} -> + {Addr4, Prefixlen4} = virtuerl_net:parse_cidr(Cidr4), + {Addr6, Prefixlen6} = virtuerl_net:parse_cidr(Cidr6), + {ok, [{Addr4, Prefixlen4}, {Addr6, Prefixlen6}]}; + #{<<"cidr4">> := Cidr4} -> + {Addr4, Prefixlen4} = virtuerl_net:parse_cidr(Cidr4), + {ok, [{Addr4, Prefixlen4}]}; + #{<<"cidr6">> := Cidr6} -> + {Addr6, Prefixlen6} = virtuerl_net:parse_cidr(Cidr6), + {ok, [{Addr6, Prefixlen6}]}; + _ -> + {error, no_cidrs_provided} + end, + + io:format("POST~n"), + io:format("NetworkDefs ~p~n", [NetDefs]), + {ok, ID} = virtuerl_ipam:ipam_create_net(NetDefs), + RespJSON = thoas:encode(#{id => ID}), + mochiweb_request:respond({201, [{"Content-Type", "application/json"}, {"Location", "/networks/" ++ binary_to_list(ID)}], RespJSON}, Req); +handle(network, 'PUT', #{id := ID}, Req) -> + JSON = parse_json(Req), + #{<<"network">> := #{<<"cidr">> := CIDR}} = JSON, + {Addr, Prefixlen} = virtuerl_net:parse_cidr(CIDR), + io:format("PUT: ~p~n", [ID]), + io:format("NetworkDef ~p/~p~n", [Addr, Prefixlen]), + virtuerl_ipam:ipam_put_net({list_to_binary(ID), Addr, Prefixlen}), + mochiweb_request:ok({[], "HELLO\n"}, Req); +handle(network, 'GET', #{id := ID}, Req) -> + io:format("GET: ~p~n", [ID]), + {ok, Net} = virtuerl_ipam:ipam_get_net(list_to_binary(ID)), + RespJson = thoas:encode(Net), + mochiweb_request:respond({200, [{"Content-Type", "application/json"}], RespJson}, Req); +handle(network, 'DELETE', #{id := ID}, Req) -> + io:format("DELETE: ~p~n", [ID]), + ok = virtuerl_ipam:ipam_delete_net(list_to_binary(ID)), + mochiweb_request:respond({204, [{"Content-Type", "application/json"}], <<"">>}, Req); + +handle(domains, 'POST', _, Req) -> + JSON = parse_json(Req), + #{<<"domain">> := #{<<"network_id">> := NetworkID}} = JSON, + #{<<"domain">> := SubJSON} = JSON, + Addresses0 = maps:with([<<"ipv4_addr">>, <<"ipv6_addr">>], SubJSON), + DomainMap0 = maps:from_list([{binary_to_atom(K), V} || {K, V} <- maps:to_list(Addresses0)]), + DomainMap1 = DomainMap0#{network_id => NetworkID}, + {ok, Resp} = virtuerl_mgt:domain_create(DomainMap1), + #{id := DomainID} = Resp, + RespJSON = thoas:encode(Resp), + mochiweb_request:respond({201, [{"Content-Type", "application/json"}, {"Location", "/domains/" ++ binary_to_list(DomainID)}], RespJSON}, Req); +handle(domain, 'GET', #{id := ID}, Req) -> + io:format("DOMAIN GET: ~p~n", [ID]), + DomResp = virtuerl_mgt:domain_get(#{id => list_to_binary(ID)}), + io:format("~p~n", [DomResp]), + case DomResp of + {ok, Dom} -> + mochiweb_request:ok({[], thoas:encode(Dom)}, Req); + notfound -> + mochiweb_request:not_found(Req) + end; +handle(domain, 'DELETE', #{id := ID}, Req) -> + io:format("DOMAIN DELETE: ~p~n", [ID]), + Resp = virtuerl_mgt:domain_delete(#{id => list_to_binary(ID)}), + case Resp of + ok -> + mochiweb_request:respond({204, [{"Content-Type", "application/json"}], <<"">>}, Req); + _ -> + mochiweb_request:respond({500, [{"Content-Type", "text/plain"}], "Error"}, Req) + end. diff --git a/src/virtuerl_app.erl b/src/virtuerl_app.erl new file mode 100644 index 0000000..507cc22 --- /dev/null +++ b/src/virtuerl_app.erl @@ -0,0 +1,40 @@ +%%%------------------------------------------------------------------- +%% @doc virtuerl public API +%% @end +%%%------------------------------------------------------------------- + +-module(virtuerl_app). + +-behaviour(application). + +-export([start/2, stop/1, start/0]). + +%% app start/stop +start(_StartType, _StartArgs) -> +%% Dispatch = cowboy_router:compile([ +%% {'_', [ +%% {"/domains", virtuerl_api, []} +%% ]} +%% ]), +%% Res = cowboy:start_clear(my_listener, [{port, 8080}], #{env => #{dispatch => Dispatch}}), +%% io:format("RESULT: ~p~n", [Res]), +%% {ok, _} = Res, +%% exec:debug(4), + httpc:set_options([{ipfamily, inet6fb4}]), + + case filelib:is_file("virtuerl.config") of + true -> + {ok, [Conf]} = file:consult("virtuerl.config"), + application:set_env(Conf); + false -> + ok + end, + + virtuerl_sup:start_link(). + +start() -> + application:ensure_all_started(virtuerl). +%% exec:debug(4). + +stop(_State) -> + ok. diff --git a/src/virtuerl_ghac.erl b/src/virtuerl_ghac.erl new file mode 100644 index 0000000..d641fdc --- /dev/null +++ b/src/virtuerl_ghac.erl @@ -0,0 +1,220 @@ +-module(virtuerl_ghac). + +-export([handle_continue/2]). + +-export([list_domains/0]). + +-export([list_runners/0]). + +-behaviour(gen_server). + +-export([pending_jobs/0]). + +-export([start/0, start_link/0]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, + code_change/3]). + +-define(SERVER, ?MODULE). + +base_url() -> + {ok, Org} = application:get_env(gh_org), + {ok, Repo} = application:get_env(gh_repo), + ["https://api.github.com/repos/", Org, "/", Repo]. + +headers() -> + {ok, Token} = application:get_env(gh_pat), + [{"User-Agent", "terstegen"}, {"Authorization", ["Bearer ", Token]}]. + +pending_jobs() -> + {ok, {{_, 200, _}, _Headers, RunsRaw}} = httpc:request(get, {[base_url(), "/actions/runs?status=queued"], headers()}, [], []), + {ok, RunsJson} = thoas:decode(RunsRaw), + #{<<"workflow_runs">> := WorkflowRuns} = RunsJson, + RunIds = [RunId || #{<<"id">> := RunId} = Run <- WorkflowRuns], + Jobs = lists:map(fun list_jobs/1, RunIds), + NumJobs = lists:foldl(fun (#{<<"total_count">> := Count}, Acc) -> Acc + Count end, 0, Jobs), + NumJobs. + +list_jobs(RunId) -> + {ok, {{_, 200, _}, _Headers, JobsRaw}} = httpc:request(get, {[base_url(), "/actions/runs/", integer_to_list(RunId), "/jobs"], headers()}, [], []), + {ok, JobsJson} = thoas:decode(JobsRaw), + JobsJson. + +list_runners() -> + {ok, {{_, 200, _}, _Headers, RunnersRaw}} = httpc:request(get, {[base_url(), "/actions/runners"], headers()}, [], []), + {ok, RunnersJson} = thoas:decode(RunnersRaw), + #{<<"runners">> := Runners} = RunnersJson, + [Runner || #{<<"status">> := Status} = Runner <- Runners, Status == <<"online">>]. + +list_domains() -> + Domains = virtuerl_mgt:domains_list(), + [Domain || #{name := Name} = Domain <- Domains, + string:prefix(Name, "actions-") /= nomatch, + string:prefix(Name, "actions-base") == nomatch]. + +create_runner(NetId) -> + {ok, {{_, 201, _}, _Headers, TokenRaw}} = httpc:request(post, {[base_url(), "/actions/runners/registration-token"], headers(), "application/json", ""}, [], []), + {ok, #{<<"token">> := Token}} = thoas:decode(TokenRaw), + + {ok, _} = virtuerl_mgt:domain_create(#{ + network_id => NetId, + name => io_lib:format("actions-~s", [virtuerl_util:uuid4()]), + vcpu => 2, + memory => 8192, + base_image => "actions-base.qcow2", + user_data => [ +"#cloud-config\n", +"\n", +"users:\n", +" - default\n", +" - name: ilya\n", +" shell: /bin/bash\n", +" sudo: ALL=(ALL) NOPASSWD:ALL\n", +" ssh_authorized_keys:\n", +" - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDCKxHNpVg1whPegPv0KcRQTOfyVIqLwvMfVLyT9OpBPXHDudsFz9soOgMUEyWm8ZJ+pJ9fRCg66B+D5/ZRTwJCBpyNncfXCwu8xEJgEeoIubObh6t6dHWqqxX/yhHAS5GIRUSypm78qg6V+SQ6SeJXSjOCLAbZmhyWgJrlDm9M6GTPQhPAztrgsCUrzxIpZ5el5BwJXrm3I+LOmofAUqgbLQz9HuGJzPpnfABDa9WoVfI0L7oTr0qGpWwx8l71b2s8AYl7GMD/bEkZKyi9SSwEVCHA88F7dYYrZ3+fMXE/mJf+v0ece2lIDT7Te1gtqiLu/izJNmqD+b6mtnnXxVxNOtynhv3t6uLE9kBX22SBCCRqPJzETGNXvYH6fATEe88dhLh8kTppLRB5UGUd/zztxuNBSpMwFXaq8SlTKURxvF8BuFIPCz0FW8fq+TA/xZfBYsiVt59jXgl6BQyEGY4bMuMtT2nD8QXwZ5vsj52mzKGJwBwduiaX302brHYUyQkuyLII5iqmCNZ5YLlMY76a61Yg9pWMeRwQscSO2k4a18GOo+sIrQVTyUQiT3KhRRaDNrZuCPicQRgkJuiS1fKt1cWjnOlyweLxSYbpKnoS0H7vt+NrtbU1u9FPknXQPQ0pxixPpV3zgUdfOLmisFH7WGVjwNVvZAlNc5uyqm0fbw== ilya@verbit.io\n", +"\n", +"runcmd:\n", +" - cd /opt/actions-runner && RUNNER_ALLOW_RUNASROOT=1 ./config.sh --unattended --url https://github.com/verbit/restvirt --token ", Token, " --ephemeral\n", +" - cd /opt/actions-runner && ./svc.sh install\n", +" - cd /opt/actions-runner && ./svc.sh start\n" + ] + }). + +sync_runners(NetId, DomainList, RunnerList) -> + io:format("Syncing...~nDomains: ~p~nRunners: ~p~n", [DomainList, RunnerList]), + + Domains = maps:from_list([{DomId, Domain} || #{id := DomId} = Domain <- DomainList]), + Runners = maps:from_list([{Name, Runner} || #{<<"name">> := Name} = Runner <- RunnerList]), + + Now = erlang:system_time(millisecond), + + ToDeleteIds = [DomId || #{id := DomId, name := Name, created_at := CreatedAt} = _Domain <- DomainList, + Now - CreatedAt > 5*60*1000, + not maps:is_key(iolist_to_binary(Name), Runners)], + [virtuerl_mgt:domain_delete(#{id => DomId}) || DomId <- ToDeleteIds], + + RemDomains = maps:without(ToDeleteIds, Domains), + + RequiredRunners = max(0, pending_jobs() + 2 - maps:size(RemDomains)), + io:format("Required new runners: ~p~n", [RequiredRunners]), + [create_runner(NetId) || _ <- lists:seq(1, RequiredRunners)], + + ok. + +start() -> + gen_server:start({local, ?SERVER}, ?MODULE, [], []). + +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + +init([]) -> + {ok, Nets} = virtuerl_ipam:ipam_list_nets(), + Filtered = maps:filter(fun (NetId, NetConf) -> + Cidrs = [lists:flatten(io_lib:format("~s/~B", [Addr, Prefixlen])) || #{address := Addr, prefixlen := Prefixlen} <- maps:values(maps:with([cidr4, cidr6], NetConf))], + Res = lists:sort(Cidrs) == lists:sort(["192.168.24.0/24", "fdbe:3c6a:3aef::/48"]), + Res + end, Nets), + + {ok, NetId} = case maps:keys(Filtered) of + [Res] -> {ok, Res}; + [] -> + io:format("ghac: creating network for actions worker~n"), + {ok, Id} = virtuerl_ipam:ipam_create_net([{<<192:8,168:8,24:8,0:8>>, 24}, {<<16#fdbe:16, 16#3c6a:16, 16#3aef:16, 0:80>>, 48}]), + {ok, Id}; + _ -> error + end, + + self() ! sync, + {ok, {NetId}, {continue, ensure_base}}. + +wait_for_domain_shutdown(DomId, Timeout) -> + timer:sleep(2000), + Deadline = erlang:system_time(millisecond) + Timeout, + do_wait_for_domain_shutdown(DomId, Deadline). + +do_wait_for_domain_shutdown(DomId, Deadline) -> + case virtuerl_mgt:domain_get(#{id => DomId}) of + {ok, #{state := stopped}} -> ok; + {error, Error} -> {error, Error}; + _ -> + case erlang:system_time(millisecond) > Deadline of + true -> + {error, timeout}; + false -> + timer:sleep(2000), + do_wait_for_domain_shutdown(DomId, Deadline) + end + end. + +handle_continue(ensure_base, {NetId} = State) -> + ok = case lists:filter(fun (ImgName) -> string:equal(ImgName, "actions-base.qcow2") end, virtuerl_img:list_images()) of + [_ImgFound] -> ok; + [] -> + io:format("ghac: creating base image domain~n"), + % create image + {ok, #{id := DomId}} = virtuerl_mgt:domain_create(#{ + network_id => NetId, + name => "actions-base", + vcpu => 2, + memory => 8192, + base_image => "debian-12-genericcloud-amd64-20240507-1740.qcow2", + user_data => [ +"#cloud-config\n", +"\n", +% "# apt_get_command: ["apt-get", "--option=Dpkg::Options::=--force-confold", "--option=Dpkg::options::=--force-unsafe-io", "--assume-yes", "--quiet", "--no-install-recommends"]\n", +"package_upgrade: true\n", +"# packages:\n", +"# - libvirt-daemon-system\n", +"# - libvirt-dev\n", +"# - qemu-kvm\n", +"# - qemu-utils\n", +"# - dnsmasq-base\n", + +"runcmd:\n", +" - apt install -y gcc g++ pkg-config\n", +" - apt install -y --no-install-recommends rebar3 qemu-kvm qemu-utils dnsmasq-base gcc\n", +" - mkdir -p /opt/actions-runner\n", +" - curl -O -L https://github.com/actions/runner/releases/download/v2.316.1/actions-runner-linux-x64-2.316.1.tar.gz\n", +" - tar xzf ./actions-runner-linux-x64-2.316.1.tar.gz -C /opt/actions-runner\n", +" - rm ./actions-runner-linux-x64-2.316.1.tar.gz\n", + +"apt:\n", +" primary:\n", +" - arches: [default]\n", +" search:\n", +" - http://mirror.ipb.de/debian/\n", +" - http://security.debian.org/debian-security\n", + +"power_state:\n", +" mode: poweroff\n", +" condition: test -d /opt/actions-runner\n" + ] + }), + + ok = wait_for_domain_shutdown(DomId, 10*60*1000), % 10 minutes + virtuerl_mgt:image_from_domain(DomId, "actions-base"), + virtuerl_mgt:domain_delete(#{id => DomId}), + + ok; + _ -> error + end, + + {noreply, State}. + +handle_call(_Req, _From, State) -> + {reply, ok, State}. + +handle_cast(_Request, State) -> + {noreply, State}. + +handle_info(sync, {NetId} = State) -> + DomainList = list_domains(), + RunnerList = list_runners(), + sync_runners(NetId, DomainList, RunnerList), + erlang:send_after(30*1000, self(), sync), + {noreply, State}. + +terminate(_Reason, State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. diff --git a/src/virtuerl_img.erl b/src/virtuerl_img.erl new file mode 100644 index 0000000..9c5719d --- /dev/null +++ b/src/virtuerl_img.erl @@ -0,0 +1,82 @@ +%%%------------------------------------------------------------------- +%%% @author ilya +%%% @copyright (C) 2023, +%%% @doc +%%% @end +%%%------------------------------------------------------------------- +-module(virtuerl_img). + +-export([list_images/0]). + +-behaviour(gen_server). + +-export([start_link/0]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, + code_change/3]). +-export([ensure_image/1]). + +-define(SERVER, ?MODULE). +-define(APPLICATION, virtuerl). + +%%%=================================================================== +%%% Spawning and gen_server implementation +%%%=================================================================== + +list_images() -> + gen_server:call(?SERVER, list_images). + +ensure_image(ImageName) -> + gen_server:call(?SERVER, {ensure_image, ImageName}, infinity). + +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + +init([]) -> + {ok, []}. + +handle_call(list_images, _Sender, State) -> + {ok, Filenames} = file:list_dir(virtuerl_mgt:home_path()), + Images = [Filename || Filename <- Filenames, virtuerl_util:ends_with(Filename, ".qcow2")], + Result = lists:uniq(["debian-12-genericcloud-amd64-20240507-1740.qcow2" | Images]), + {reply, Result, State}; + +handle_call({ensure_image, ImageName}, _Sender, State) -> + io:format("DOWNLOADING..."), + Path = filename:join(virtuerl_mgt:home_path(), ImageName), + Res = case filelib:is_regular(Path) of + true -> {ok, Path}; + false -> + {ok, Pat} = re:compile("^debian-12-genericcloud-amd64-(\\d+-\\d+).qcow2$"), + case re:run(ImageName, Pat, [{capture, all_but_first, list}]) of + {match, [Build]} -> + CacheImagePath = filename:join(["/tmp/virtuerl/cache", ImageName]), + case filelib:is_regular(CacheImagePath) of + true -> + file:copy(CacheImagePath, Path), + {ok, Path}; + false -> + TempImagePath = filename:join(["/tmp/virtuerl/", ImageName]), + ok = filelib:ensure_dir(CacheImagePath), + {ok, _} = httpc:request(get, {["https://cloud.debian.org/images/cloud/bookworm/", Build, "/", ImageName], []}, [], + [{stream, TempImagePath}]), + file:rename(TempImagePath, CacheImagePath), + file:copy(CacheImagePath, Path), + {ok, Path} + end; + nomatch -> + {error, not_supported} + end + end, + {reply, Res, State}. + +handle_cast(_Req, State) -> + {noreply, State}. + +handle_info(_Req, State) -> + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. diff --git a/src/virtuerl_ipam.erl b/src/virtuerl_ipam.erl new file mode 100644 index 0000000..b31cec0 --- /dev/null +++ b/src/virtuerl_ipam.erl @@ -0,0 +1,270 @@ +%%%------------------------------------------------------------------- +%% @doc virtuerl public API +%% @end +%%%------------------------------------------------------------------- + +-module(virtuerl_ipam). + +-behavior(gen_server). + +-export([req/1, init/1, handle_call/3, subnet/1, get_range/1, get_next/6, terminate/2, ipam_next_ip/1, start_server/1, stop_server/1, ipam_put_net/1, start_link/0, handle_cast/2, ipam_delete_net/1, ipam_create_net/1, ipam_list_nets/0, ipam_get_net/1, ipam_put_ip/3, assign_next/3]). +-export([unassign/1]). + +-include_lib("khepri/include/khepri.hrl"). +-include_lib("khepri/src/khepri_error.hrl"). + +req(Msg) -> + %kh ! {self(), Msg}, + %receive + % Res -> + % io:format("Received ~p~n", [Res]) + %end. + %gen_server:call(virtuerl, {ip_put, default, <<"192.168.122.0/24">>, <<"192.168.122.10">>}). + %gen_server:call(virtuerl, {ip_next, default, <<"192.168.122.0/24">>}). + case Msg of + net_put -> + gen_server:call(ipam, {net_put, default, {<<"abcdef">>, <<192:8,168:8,122:8,0:8>>, 24}}); + ip_put -> + gen_server:call(ipam, {ip_put, default, <<"192.168.122.0/24">>, <<"192.168.122.0/28">>, <<"192.168.122.12">>}); + ip_next -> + gen_server:call(ipam, {ip_next, <<"abcdef">>}); + ip_clear -> + gen_server:call(ipam, {ip_clear}), + io:format("something~p~n", ['t']) + end. + + +-record(network, {last_insert, from, to, address, prefixlen}). + +-spec ipam_create_net([{binary(), integer()}]) -> {ok, binary()}. +ipam_create_net(NetworkDef) -> + ID = virtuerl_util:uuid4(), + ipam_put_net({ID, NetworkDef}). + +ipam_put_net(NetworkDef) -> + case gen_server:call(ipam, {net_put, NetworkDef}) of + ok -> + {ok, NetworkDef}; + Other -> + Other + end. + +-spec ipam_list_nets() -> {ok, #{binary() => #{cidr4 | cidr6 := #{address := nonempty_binary(), prefixlen := integer()}}}}. +ipam_list_nets() -> + gen_server:call(ipam, net_list). + +ipam_delete_net(ID) when is_list(ID) -> + ipam_delete_net(list_to_binary(ID)); +ipam_delete_net(ID) -> + case gen_server:call(ipam, {net_delete, ID}) of + {ok, Res} -> + Res; + Other -> + Other + end. + +ipam_get_net(Id) -> + case gen_server:call(ipam, {net_get, Id}) of + {ok, Pools} -> + io:format("POOLS: ~p~n", [Pools]), + Cidrs = [iolist_to_binary([Address, "/", integer_to_binary(Prefixlen)]) || #{address := Address, prefixlen := Prefixlen} <- maps:values(Pools)], + Res = #{id => Id, cidrs => Cidrs}, + {ok, Res}; + Other -> + io:format("Error ~p~n", [Other]), + Other + end. + +ipam_put_ip(NetworkName, IP, DomainId) -> + gen_server:call(ipam, {ip_put, NetworkName, IP, DomainId}). + +assign_next(NetworkID, Tag, VMID) -> + case gen_server:call(ipam, {ip_next, NetworkID, Tag, VMID}) of + {ok, Res} -> + Res; + Other -> + Other + end. + +unassign(DomainId) -> + gen_server:call(ipam, {unassign, DomainId}). + +ipam_next_ip(NetworkName) -> + case gen_server:call(ipam, {ip_next, NetworkName}) of + {ok, Res} -> + Res; + Other -> + Other + end. + +%% entry point by child spec +start_link() -> + io:format("IPAM: start_link~n"), + gen_server:start_link({local, ipam}, ?MODULE, [], []). + +init([]) -> + io:format("starting IPAM service~n"), + {ok, StoreId} = khepri:start(filename:join(virtuerl_mgt:home_path(), "khepri")), + init([StoreId]); +init([StoreId]) -> + {ok, StoreId}. + +terminate(_Reason, StoreId) -> + khepri:stop(StoreId). + +handle_call(net_list, _From, StoreId) -> + case khepri:get_many(StoreId, [network, ?KHEPRI_WILDCARD_STAR, ?KHEPRI_WILDCARD_STAR]) of + {ok, Map} -> + ToMapKey = fun(Tag) -> case Tag of ipv4 -> cidr4; ipv6 -> cidr6 end end, + Res0 = [{NetworkId, ToMapKey(Tag), #{address => virtuerl_net:format_ip_bitstring(Address), prefixlen => PrefixLen}} || {[network, NetworkId, Tag], #network{address = Address, prefixlen = PrefixLen}} <- maps:to_list(Map)], + Res1 = lists:foldl(fun({NetworkId, Tag, Def}, MapAcc) -> maps:update_with(NetworkId, fun(L) -> [{Tag, Def}|L] end, [{Tag, Def}], MapAcc) end, #{}, Res0), +%% Res1 = maps:groups_from_list(fun({NetworkId, _, _}) -> NetworkId end, fun({_, Tag, Def}) -> {Tag, Def} end, Res0), + Res = maps:map(fun(_, V) -> maps:from_list(V) end, Res1), + {reply, {ok, Res}, StoreId}; + Res -> {reply, Res, StoreId} + end; +handle_call({net_get, NetworkId}, _From, StoreId) -> + case khepri:get_many(StoreId, [network, NetworkId, ?KHEPRI_WILDCARD_STAR]) of + {ok, Map} -> + ToMapKey = fun(Tag) -> case Tag of ipv4 -> cidr4; ipv6 -> cidr6 end end, + Res0 = [{NetworkId, ToMapKey(Tag), #{address => virtuerl_net:format_ip_bitstring(Address), prefixlen => PrefixLen}} || {[network, NetworkId, Tag], #network{address = Address, prefixlen = PrefixLen}} <- maps:to_list(Map)], + Res1 = lists:foldl(fun({NetworkId, Tag, Def}, MapAcc) -> maps:update_with(NetworkId, fun(L) -> [{Tag, Def}|L] end, [{Tag, Def}], MapAcc) end, #{}, Res0), +%% Res1 = maps:groups_from_list(fun({NetworkId, _, _}) -> NetworkId end, fun({_, Tag, Def}) -> {Tag, Def} end, Res0), + #{NetworkId := Res} = maps:map(fun(_, V) -> maps:from_list(V) end, Res1), + {reply, {ok, Res}, StoreId}; + Res -> {reply, Res, StoreId} + end; +handle_call({net_put, Network}, _From, StoreId) -> + {ID, Defs} = Network, + Ranges = [get_range({Address, PrefixLen}) || {Address, PrefixLen} <- Defs], + TooSmallNets = [too_small || {From, To} <- Ranges, To - From =< 8], + case TooSmallNets of + [] -> + InsertNet = fun({Address, Prefixlen}) -> + {From, To} = get_range({Address, Prefixlen}), + BitLength = bit_size(Address), + Tag = case BitLength of + 32 -> ipv4; + 128 -> ipv6 + end, + ok = khepri:put(StoreId, [network, ID, Tag], #network{address = Address, prefixlen = Prefixlen, from = <<(From + 8):BitLength>>, to = <>, last_insert = <<(From + 8):BitLength>>}) + end, + lists:foreach(InsertNet, Defs), + {reply, {ok, ID}, StoreId}; + _ -> + {reply, {error, network_too_small}, StoreId} + end; +handle_call({net_delete, ID}, _From, StoreId) -> + Ret = khepri:delete(StoreId, [network, ID]), + {reply, Ret, StoreId}; + +handle_call({ip_next, NetworkID, Tag, DomainID}, _From, StoreId) -> + R = khepri:transaction(StoreId, fun() -> + case khepri_tx:get([network, NetworkID, Tag]) of + {ok, Network} -> + #network{address = Address, prefixlen = PrefixLen, last_insert= LastInsertBin, from= FromBin, to= ToBin} = Network, + BitLength = bit_size(LastInsertBin), + <> = LastInsertBin, + <> = FromBin, + <> = ToBin, + {ok, Map} = khepri_tx:get_many([network, NetworkID, Tag, #if_name_matches{regex = any}]), + NextIP = case LastInsert of + undefined -> + get_next(Map, [network, NetworkID, Tag], BitLength, From, To, From); + Payload -> + get_next(Map, [network, NetworkID, Tag], BitLength, From, To, Payload) + end, + case NextIP of + {ok, IP} -> + khepri_tx:put([network, NetworkID, Tag, <>], DomainID), + {ok, {Address, PrefixLen}, <>}; + Other -> + Other + end; + {error, ?khepri_error(node_not_found, _)} -> + {error, network_not_found} + end + end), + {reply, R, StoreId}; + +handle_call({ip_put, NetworkId, IpAddr, DomainId}, _From, StoreId) -> + Tag = case bit_size(IpAddr) of + 32 -> ipv4; + 128 -> ipv6 + end, + R = case khepri:get([network, NetworkId, Tag]) of + {ok, Network} -> + #network{address = Address, prefixlen = PrefixLen} = Network, + case khepri:create(StoreId, [network, NetworkId, Tag, IpAddr], DomainId) of + ok -> + {ok, {Address, PrefixLen}}; + Other -> Other + end; + {error, ?khepri_error(node_not_found, _)} -> + {error, network_not_found} + end, + {reply, R, StoreId}; + +handle_call({ip_delete, NetworkId, IpAddr}, _From, StoreId) -> + R = khepri:delete(StoreId, [network, NetworkId, ?KHEPRI_WILDCARD_STAR, IpAddr]), + {reply, R, StoreId}; + +handle_call({unassign, DomainId}, _From, StoreId) -> + ok = khepri:delete_many(StoreId, [network, ?KHEPRI_WILDCARD_STAR, ?KHEPRI_WILDCARD_STAR, #if_data_matches{pattern = DomainId}]), + {reply, ok, StoreId}; + +handle_call({ip_clear}, _From, StoreId) -> + R = khepri:delete(StoreId, [network]), + {reply, R, StoreId}. + +handle_cast(Request, State) -> + erlang:error(not_implemented). + +start_server(StoreId) -> + gen_server:start_link({local, ipam}, ?MODULE, [StoreId], []). + +stop_server(Pid) -> + exit(Pid, normal). + +%% internal functions +get_next(Map, Path, BitLength, From, To, Start) -> + get_next(Map, Path, BitLength, From, To, Start, Start). + +get_next(Map, Path, BitLength, From, To, OriginalStart, Start) -> + case maps:is_key(Path ++ [<>], Map) of + true -> + Next = case Start of + To -> From; % wrap around + _ -> Start + 1 + end, + case Next of + OriginalStart -> + {error, no_ip_available}; + _ -> + get_next(Map, Path, BitLength, From, To, OriginalStart, Next) + end; + false -> + {ok, Start} + end. + + +get_range({_Address, PrefixLen}) -> + case _Address of + <> -> + io:format("IPv6~n", []), + get_range({Address, 128, PrefixLen}); + <> -> + io:format("IPv4~n", []), + get_range({Address, 32, PrefixLen}) + end; + +get_range({Address, AddressLength, PrefixLen}) -> + ShiftAmount = AddressLength - PrefixLen, + From = (Address bsr ShiftAmount) bsl ShiftAmount, + To = From + (1 bsl ShiftAmount) - 1, + io:format("From ~p to ~p~n", [From, To]), + io:format("From ~p to ~p~n", [<>, <>]), + {From, To}. + +subnet({_Address, PrefixLen}) -> + get_range({_Address, PrefixLen}). diff --git a/src/virtuerl_mgt.erl b/src/virtuerl_mgt.erl new file mode 100644 index 0000000..db4b6b0 --- /dev/null +++ b/src/virtuerl_mgt.erl @@ -0,0 +1,294 @@ +%%%------------------------------------------------------------------- +%%% @author ilya +%%% @copyright (C) 2023, +%%% @doc +%%% @end +%%%------------------------------------------------------------------- +-module(virtuerl_mgt). + +-include_lib("kernel/include/logger.hrl"). + +-export([image_from_domain/2]). + +-export([domain_update/1]). + +-behaviour(gen_server). + +-export([start_link/0]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, + code_change/3, handle_continue/2]). +-export([create_vm/0, domain_create/1, domain_get/1, domain_delete/1, domain_stop/1, domain_start/1, domains_list/0]). +-export([home_path/0]). + +-define(SERVER, ?MODULE). +-define(APPLICATION, virtuerl). + +create_vm() -> + gen_server:call(?SERVER, {domain_create, {default}}). + +%%create_vm(#{cpus := NumCPUs, memory := Memory}) -> +domain_create(Conf) -> + gen_server:call(?SERVER, {domain_create, Conf}, infinity). + +domain_delete(Conf) -> + gen_server:call(?SERVER, {domain_delete, Conf}, infinity). + +domain_get(Conf) -> + gen_server:call(?SERVER, {domain_get, Conf}). + +-spec domains_list() -> #{}. +domains_list() -> + gen_server:call(?SERVER, domains_list). + +domain_update(Conf) -> + gen_server:call(?SERVER, {domain_update, Conf}). + +domain_stop(Id) -> + gen_server:call(?SERVER, {domain_update, #{id => Id, state => stopped}}). + +domain_start(Id) -> + gen_server:call(?SERVER, {domain_update, #{id => Id, state => running}}). + +image_from_domain(DomainId, ImageName) -> + gen_server:call(?SERVER, {image_from_domain, #{id => DomainId, image_name => ImageName}}, infinity). + + +%%%=================================================================== +%%% Spawning and gen_server implementation +%%%=================================================================== + +home_path() -> + application:get_env(?APPLICATION, home, "var"). + +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + +init([]) -> + {ok, Table} = dets:open_file(domains, [{file, filename:join(home_path(), "domains.dets")}]), +%% virtuerl_ipam:ipam_put_net({default, <<192:8, 168:8, 10:8, 0:8>>, 28}), +%% application:ensure_all_started(grpcbox), + {ok, {Table}, {continue, setup_base}}. +%% {ok, {Table}}. + +handle_continue(setup_base, State) -> + ok = filelib:ensure_path(filename:join(home_path(), "domains")), + {noreply, State, {continue, sync_domains}}; + + +handle_continue(sync_domains, {Table} = State) -> + TargetDomains = sets:from_list([Id || {Id, Domain} <- dets:match_object(Table, '_'), + case Domain of + #{state := stopped} -> false; + _ -> true + end]), + RunningDomains = sets:from_list([Id || {Id, _, _, _} <- supervisor:which_children(virtuerl_sup), is_binary(Id)]), + ToDelete = sets:subtract(RunningDomains, TargetDomains), + ToAdd = sets:subtract(TargetDomains, RunningDomains), + [supervisor:terminate_child(virtuerl_sup, Id) || Id <- sets:to_list(ToDelete)], + [supervisor:delete_child(virtuerl_sup, Id) || Id <- sets:to_list(ToDelete)], + ok = gen_server:call(virtuerl_net, {net_update}, infinity), + [ supervisor:start_child(virtuerl_sup, { + Id, + {virtuerl_qemu, start_link, [Id]}, + transient, + infinity, + worker, + [] +}) || Id <- sets:to_list(ToAdd)], + {noreply, State}. + + +generate_unique_tap_name(TapNames) -> + TapName = io_lib:format("verltap~s", [binary:encode_hex(<<(rand:uniform(16#ffffff)):24>>)]), + case sets:is_element(TapName, TapNames) of + false -> + TapName; + true -> + generate_unique_tap_name(TapNames) + end. + +handle_call({domain_create, Conf}, _From, State) -> + {Table} = State, + DomainID = virtuerl_util:uuid4(), + Domain0 = maps:merge(#{id => DomainID, name => DomainID, vcpu => 4, memory => 4096, + os_type => "linux", created_at => erlang:system_time(millisecond)}, Conf), % TODO: save ipv4/6 addr as well + Domain = case Domain0 of + #{os_type := "linux"} -> + maps:merge(#{base_image => "debian-12-genericcloud-amd64-20240211-1654.qcow2"}, Domain0); + #{os_type := "windows"} -> + maps:merge(#{setup_iso => "Win11_23H2_EnglishInternational_x64v2_noprompt.iso"}, Domain0) + end, + dets:insert_new(Table, {DomainID, Domain}), + dets:sync(Table), + ?LOG_NOTICE(#{event => domain_requested, domain => Domain}), + + #{network_id := NetworkID} = Domain, + {ok, #{cidrs := Cidrs}} = virtuerl_ipam:ipam_get_net(NetworkID), + Keys = lists:map(fun(Cidr) -> + {Ip, _} = virtuerl_net:parse_cidr(Cidr), + case bit_size(Ip) of + 32 -> ipv4_addr; + 128 -> ipv6_addr + end + end, Cidrs), + + Default = maps:from_keys(Keys, undefined), + Conf0 = maps:merge(Default, Conf), + Conf1 = maps:intersect(Default, Conf0), + Conf2 = maps:to_list(Conf1), + + KeyToTag = fun(Key) -> case Key of ipv4_addr -> ipv4; ipv6_addr -> ipv6 end end, + + Addresses = [ + case Addr of + undefined -> + Tag = KeyToTag(Key), + {ok, {NetAddr, Prefixlen}, Ip} = virtuerl_ipam:assign_next(NetworkID, Tag, DomainID), + {Key, NetAddr, Ip, Prefixlen}; + Addr -> + Addr1 = virtuerl_net:parse_ip(Addr), + {ok, {NetAddr, Prefixlen}} = virtuerl_ipam:ipam_put_ip(NetworkID, Addr1, DomainID), + {Key, NetAddr, Addr1, Prefixlen} + end + || {Key, Addr} <- Conf2 + ], + IpCidrs = [{Ip, Prefixlen} || {_, _, Ip, Prefixlen} <- Addresses], + AddressesMap = maps:from_list([{K, A} || {K, _, A, _} <- Addresses]), + Ipv4Addr = maps:get(ipv4_addr, AddressesMap, undefined), + Ipv6Addr = maps:get(ipv6_addr, AddressesMap, undefined), + + Domains = dets:match_object(Table, '_'), + TapNames = sets:from_list([Tap || #{tap_name := Tap} <- Domains]), + TapName = generate_unique_tap_name(TapNames), % TODO: TapName should be generated on a per-deployment basis + <> = <<(rand:uniform(16#ffffffffffff)):48>>, + MacAddr = <>, + + DomainWithIps = Domain#{ + network_addrs => Cidrs, + mac_addr=>MacAddr, + ipv4_addr=>Ipv4Addr, + ipv6_addr => Ipv6Addr, + cidrs => IpCidrs, + tap_name => TapName}, + dets:insert(Table, {DomainID, DomainWithIps}), + dets:sync(Table), + ?LOG_NOTICE(#{event => domain_ready, domain => DomainWithIps}), + virtuerl_pubsub:send({domain_created, DomainID}), + + ok = gen_server:call(virtuerl_net, {net_update}), + + supervisor:start_child(virtuerl_sup, { + DomainID, + {virtuerl_qemu, start_link, [DomainID]}, + transient, + infinity, + worker, + [] + }), + {reply, + {ok, maps:merge(#{id => DomainID, tap_name => iolist_to_binary(TapName), mac_addr => binary:encode_hex(MacAddr)}, + maps:map(fun(_, V) -> iolist_to_binary(virtuerl_net:format_ip(V)) end, AddressesMap))}, + State}; +handle_call({domain_update, #{id := DomainID} = DomainUpdate0}, _From, {Table} = State) -> + DomainUpdate = maps:remove(id, DomainUpdate0), + Reply = case dets:lookup(Table, DomainID) of + [{_, Domain}] -> + ok = dets:insert(Table, {DomainID, maps:merge(Domain, DomainUpdate)}), + ok = dets:sync(Table), + virtuerl_pubsub:send({domain_updated, DomainID}), + ok; + [] -> {error, notfound} + end, + {reply, Reply, State, {continue, sync_domains}}; +handle_call(domains_list, _From, State) -> + {Table} = State, + Domains = dets:match_object(Table, '_'), + {reply, [maps:merge(#{state => running, name => Id, vcpu => 1, memory => 512}, Domain) || {Id, Domain} <- Domains], State}; +handle_call({domain_get, #{id := DomainID}}, _From, State) -> + {Table} = State, + Reply = case dets:lookup(Table, DomainID) of + [{_, #{mac_addr := MacAddr, ipv4_addr:=IP, tap_name := TapName} = Domain}] -> + DomRet = Domain#{ + mac_addr := binary:encode_hex(MacAddr), + ipv4_addr := virtuerl_net:format_ip_bitstring(IP), + tap_name := iolist_to_binary(TapName)}, + {ok, maps:merge(#{state => running, name => DomainID, vcpu => 1, memory => 512}, DomRet)}; + [] -> notfound + end, + {reply, Reply, State}; +handle_call({domain_delete, #{id := DomainID}}, _From, State) -> + {Table} = State, + Res = case dets:lookup(Table, DomainID) of + [] -> {error, notfound}; + [{_, Domain}] -> + dets:insert(Table, {DomainID, Domain#{state => deleting}}), + dets:sync(Table), + spawn_link(fun () -> + io:format("terminating ~p~n", [DomainID]), + supervisor:terminate_child(virtuerl_sup, DomainID), + io:format("done terminating ~p~n", [DomainID]), + supervisor:delete_child(virtuerl_sup, DomainID), + DomainHomePath = filename:join([virtuerl_mgt:home_path(), "domains", DomainID]), + file:del_dir_r(DomainHomePath), + ok = virtuerl_ipam:unassign(DomainID), + ok = gen_server:call(virtuerl_net, {net_update}), + dets:delete(Table, DomainID), + virtuerl_pubsub:send({domain_deleted, DomainID}) + end), + ok + end, + {reply, Res, State}; +handle_call({image_from_domain, #{id := DomainID, image_name := ImageName}}, _From, {Table} = State) -> + case dets:lookup(Table, DomainID) of + [] -> gen_server:reply(_From, {error, notfound}); + [{_, Domain}] -> + spawn(fun() -> + #{state := DomState} = Domain, + case DomState of + running -> domain_stop(DomainID); + _ -> ok + end, + + ImgPath = filename:join([virtuerl_mgt:home_path(), "domains", DomainID, "root.qcow2"]), + ImgName = string:concat(ImageName, ".qcow2"), + DestPath = filename:join(["/tmp/virtuerl", ImgName]), + ok = filelib:ensure_dir(DestPath), + {ok, _} = file:copy(ImgPath, DestPath), + ok = file:rename(DestPath, filename:join([virtuerl_mgt:home_path(), ImgName])), + + case DomState of + running -> domain_start(DomainID); + _ -> ok + end, + + gen_server:reply(_From, ok) + end) + end, + {noreply, State}. + +handle_cast(_Request, State) -> + {noreply, State}. + +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, {Table}) -> + dets:close(Table), + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== + +update_bird_conf(Table) -> + % 1. write config + %% for VM in VMs: + %% append VM.IP to static routes: VM.IP via $VM.network.bridge + % 2. birdc configure + % 3. profit? +%% VMs = dets:match_object(Table, '_'), +%% [io:format("~p~n", [VM]) || VM <- VMs]. + ok. diff --git a/src/virtuerl_net.erl b/src/virtuerl_net.erl new file mode 100644 index 0000000..f3be7e9 --- /dev/null +++ b/src/virtuerl_net.erl @@ -0,0 +1,368 @@ +%%%------------------------------------------------------------------- +%%% @author ilya +%%% @copyright (C) 2023, +%%% @doc +%%% @end +%%%------------------------------------------------------------------- +-module(virtuerl_net). + +-export([update_net/0]). + +-export([format_cidr/1]). + +-export([normalize_net/1]). + +-behaviour(gen_server). + +-include_lib("kernel/include/logger.hrl"). + +-export([start_link/0]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, + code_change/3]). +-export([parse_cidr/1, format_ip/1, format_ip_bitstring/1, parse_ip/1, bridge_addr/1, bridge_addr/2]). + +-define(SERVER, ?MODULE). + +%%%=================================================================== +%%% Spawning and gen_server implementation +%%%=================================================================== + +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + +update_net() -> + gen_server:call(?SERVER, {net_update}). + +init([]) -> + ?LOG_INFO(#{what => "Started", who => virtuerl_net}), + {ok, Table} = dets:open_file(domains, [{file, filename:join(virtuerl_mgt:home_path(), "domains.dets")}]), + update_net(Table), + % TODO: erlexec: spawn bird -f + {ok, {Table}}. + +terminate(_Reason, {Table}) -> + dets:close(Table). + +handle_call({vm_create, Conf}, _From, State) -> + {reply, ok, State}; + +handle_call({net_update}, _From, State) -> + {Table} = State, + update_net(Table), + {reply, ok, State}. + +handle_cast({net_update}, State) -> + {Table} = State, + update_net(Table), + {noreply, State}. + +handle_info(_Info, State) -> + {noreply, State}. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== + +startswith(Str, Pre) -> + case string:prefix(Str, Pre) of + nomatch -> false; + _ -> true + end. + +update_net(Table) -> + % 1. write config + %% for VM in VMs: + %% append VM.IP to static routes: VM.IP via $VM.network.bridge + % 2. birdc configure + % 3. profit? + reload_net(Table). + +handle_interface(If, Table) -> + %% 1. Delete all devices without an address set + Addrs = maps:get(<<"addr_info">>, If, []), + case Addrs of + [] -> os:cmd(io_lib:format("ip addr del ~p", [maps:get(<<"ifname">>, If)])) + end. + +get_cidrs(If) -> + Addrs = maps:get(<<"addr_info">>, If, []), + Ifname = maps:get(<<"ifname">>, If), + case Addrs of + [] -> {unset, Ifname}; + AddrInfos when is_list(AddrInfos) -> + Cidrs = [ iolist_to_binary([Ip, "/", integer_to_binary(Prefixlen)]) + || #{<<"local">> := Ip, <<"prefixlen">> := Prefixlen, <<"scope">> := <<"global">>} <- AddrInfos], + {lists:sort(Cidrs), Ifname} + end. + +reload_net(Table) -> + Output = os:cmd("ip -j addr"), + {ok, JSON} = thoas:decode(Output), + % io:format("~p~n", [JSON]), + Matched = maps:from_list([get_cidrs(L) || L <- JSON, startswith(maps:get(<<"ifname">>, L), <<"verlbr">>)]), +%% lists:foreach(fun(L) -> handle_interface(L, Table) end, Matched), + % io:format("Actual: ~p~n", [Matched]), + Domains = dets:match_object(Table, '_'), + + TargetAddrs = sets:from_list([lists:sort(network_cidrs_to_bride_cidrs(Cidrs)) || {_, #{network_addrs := Cidrs}} <- Domains]), + % io:format("Target: ~p~n", [sets:to_list(TargetAddrs)]), + update_nftables(Domains), + sync_networks(Matched, TargetAddrs), + sync_taps(Domains), + + update_bird_conf(Domains), + ok. + +update_nftables(Domains) -> + BridgeAddrs = lists:uniq(lists:flatten([network_cidrs_to_bride_addrs(Cidrs) || {_, #{network_addrs := Cidrs}} <- Domains])), + BridgeAddrsTyped = lists:map(fun (Addr) -> + case binary:match(Addr, <<":">>) of + nomatch -> {ipv4, Addr}; + _ -> {ipv6, Addr} + end + end, BridgeAddrs), + + DnsRules = [ + case Family of + ipv4 -> + [" ip daddr ", Addr, " ", Prot, " dport 53 dnat to ", Addr, ":5354\n"]; + ipv6 -> + [" ip6 daddr ", Addr, " ", Prot, " dport 53 dnat to [", Addr, "]:5354\n"] + end + || {Family, Addr} <- BridgeAddrsTyped, Prot <- ["tcp", "udp"]], + + + ToRule = fun (#{protocols := Protos, target_ports := Ports}, Cidrs) -> + ProtosStr = string:join(Protos, ","), + Ports0 = [ + case Port of + Num when is_integer(Num) -> + integer_to_list(Num); + Str when is_list(Str) orelse is_binary(Str) -> + Str + end + || Port <- Ports], % FIXME: validate ports? + PortsStr = ["{", string:join(Ports0, ","), "}"], + + TagIp = fun ({Ip, _}) -> + Tag = case bit_size(Ip) of + 32 -> + "ip"; + 128 -> + "ip6" + end, + {Tag, Ip} + end, + TaggedIps = lists:map(TagIp, Cidrs), + + [[" meta l4proto {", ProtosStr, "} ", Tag, " daddr ", virtuerl_net:format_ip(Ip), " th dport ", PortsStr, " accept\n"] + || {Tag, Ip} <- TaggedIps] + end, + ForwardRules = [ToRule(InboundRule, Cidrs) || {_Id, #{cidrs := Cidrs, inbound_rules := InboundRules0}} <- Domains, InboundRule <- InboundRules0], + % lists:flatten(List), + + IoList = [ + "table inet virtuerl\ndelete table inet virtuerl\n\n", + "table inet virtuerl {\n", + " chain input {\n", + " type filter hook input priority filter; policy accept;\n", + " ct state established,related accept\n", + ForwardRules, + " oifname \"verlbr*\" reject\n", + " }\n", + "\n", + " chain forward {\n", + " type filter hook forward priority filter; policy accept;\n", + " iifname \"verlbr*\" accept\n", + " ct state established,related accept\n", + ForwardRules, + " oifname \"verlbr*\" reject\n", + " }\n", + "\n", + "\n", + "\n", + "\n", + " chain output {\n", + " type nat hook output priority -105; policy accept;\n", + DnsRules, + " }\n", + + " chain prerouting {\n", + " type nat hook prerouting priority dstnat - 5; policy accept;\n", + DnsRules, + " }\n", + + " chain postrouting {\n", + " type nat hook postrouting priority -5; policy accept;\n", + " iifname \"verlbr*\" ip saddr { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 } ip daddr != { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 } masquerade\n", + " iifname \"verlbr*\" ip6 saddr fc00::/7 ip6 daddr != fc00::/7 masquerade\n", + " }\n", + "}\n" + ], + % io:format("~s~n", [IoList]), + + Path = iolist_to_binary(["/tmp/virtuerl/", "nftables_", virtuerl_util:uuid4(), ".conf"]), + ok = filelib:ensure_dir(Path), + ok = file:write_file(Path, IoList), + + NftOut = os:cmd(io_lib:format("nft -f ~s", [Path])), + case NftOut of + "" -> ok; + _ -> error({nft_error, NftOut}) + end, + file:delete(Path). + +network_cidrs_to_bride_cidrs(Cidrs) -> + lists:map(fun(Cidr) -> + {Addr, Prefixlen} = parse_cidr(Cidr), + iolist_to_binary(io_lib:format("~s/~B", [format_ip(bridge_addr(Addr)), Prefixlen])) + end, Cidrs). + +network_cidrs_to_bride_addrs(Cidrs) -> + lists:map(fun(Cidr) -> + {Addr, _} = parse_cidr(Cidr), + iolist_to_binary(io_lib:format("~s", [format_ip(bridge_addr(Addr))])) + end, Cidrs). + +bridge_addr(<>) -> + BitSize = bit_size(Addr), + <> = Addr, + <<(AddrInt+1):BitSize>>. +bridge_addr(<>, Prefixlen) -> + <> = Addr, + <>. + +normalize_net({<>, Prefixlen}) -> + <> = Addr, + {<>, Prefixlen}. + +parse_cidr(<>) -> parse_cidr(binary_to_list(CIDR)); +parse_cidr(CIDR) -> + [IP, Prefixlen] = string:split(CIDR, "/", trailing), + {I, _} = string:to_integer(Prefixlen), + {parse_ip(IP), I}. + +parse_ip(<>) -> parse_ip(binary_to_list(IP)); +parse_ip(IP) -> + {ok, Res} = inet:parse_address(IP), + case Res of + {A, B, C, D} -> <>; + {A, B, C, D, E, F, G, H} -> <> + end. + +format_ip(<>) -> + inet:ntoa({A, B, C, D}); +format_ip(<>) -> + inet:ntoa({A, B, C, D, E, F, G, H}). +format_ip_bitstring(IP) -> + list_to_binary(format_ip(IP)). + +format_cidr({<>, Prefixlen}) -> + io_lib:format("~s/~B", [format_ip(Addr), Prefixlen]). + +format_bird_route(<>) -> + io_lib:format("~s/~B", [format_ip(IP), bit_size(IP)]). + +build_routes(M) when is_map(M) -> build_routes(maps:to_list(M)); +build_routes([]) -> []; +build_routes([{Addr, Bridge}|L]) -> + [io_lib:format(" route ~s via \"~s\";", [format_bird_route(Addr), Bridge]) | build_routes(L)]. + +update_bird_conf(Domains) -> + Output = os:cmd("ip -j addr"), + {ok, JSON} = thoas:decode(Output), + Bridges = maps:from_list([get_cidrs(L) || L <- JSON, startswith(maps:get(<<"ifname">>, L), <<"verlbr">>)]), + AddrMap = maps:from_list([{Addr, {bridge_addr(NetAddr), Prefixlen}} + || {_, #{network_addrs := {NetAddr, Prefixlen}, ipv4_addr := Addr}} <- Domains]), + AddrToBridgeMap = maps:map(fun (_, Net) -> maps:get(Net, Bridges) end, AddrMap), + + % io:format("DOMAINS: ~p~n", [Domains]), + % io:format("AddrToBridgeMap: ~p~n", [AddrToBridgeMap]), + file:write_file("birderl.conf", lists:join("\n", + ["protocol static {", " ipv4;"] ++ build_routes(AddrToBridgeMap) ++ ["}\n"] )), + reload_bird(Domains). + +reload_bird(Domains) -> + ok. + +sync_networks(ActualAddrs, TargetAddrs) -> + Ifnames = sets:from_list([Name || {_, Name} <- maps:to_list(ActualAddrs)]), + ToDelete = maps:without(sets:to_list(TargetAddrs), ActualAddrs), + ToAdd = sets:subtract(TargetAddrs, sets:from_list(maps:keys(ActualAddrs))), + io:format("TO DELETE: ~p~n", [ToDelete]), + io:format("TO ADD: ~p~n", [sets:to_list(ToAdd)]), + maps:foreach(fun (_, V) -> + Cmd = io_lib:format("ip link del ~s~n", [V]), + io:format(Cmd), + os:cmd(Cmd) + end, ToDelete), + add_bridges(sets:to_list(ToAdd), Ifnames). + +add_bridges([], _) -> + ok; +add_bridges([Cidrs|T], Ifnames) -> + Ifname = generate_unique_bridge_name(Ifnames), + AddrAddCmd = [io_lib:format("ip addr add ~s dev ~s~n", [Cidr, Ifname]) || Cidr <- Cidrs], + Cmd = lists:flatten([io_lib:format("ip link add name ~s type bridge~nip link set ~s up~n", [Ifname, Ifname]), AddrAddCmd]), + io:format(Cmd), + os:cmd(Cmd), + add_bridges(T, Ifnames), + ok. + + +generate_unique_bridge_name(Ifnames) -> + Ifname = io_lib:format("verlbr~s", [binary:encode_hex(<<(rand:uniform(16#ffffff)):24>>)]), + case sets:is_element(Ifname, Ifnames) of + false -> + Ifname; + true -> + generate_unique_bridge_name(Ifnames) + end. + +to_vtap_mac(MacAddr) -> + <> = MacAddr, + <>. + +sync_taps(Domains) -> + Output = os:cmd("ip -j addr"), + {ok, JSON} = thoas:decode(Output), + Bridges = maps:from_list([get_cidrs(L) || L <- JSON, startswith(maps:get(<<"ifname">>, L), <<"verlbr">>)]), + + OutputTaps = os:cmd("ip -j link"), + {ok, JSONTaps} = thoas:decode(OutputTaps), + % io:format("TAPS: ~p~n", [JSONTaps]), + TapsActual = sets:from_list([maps:get(<<"ifname">>, L) || L <- JSONTaps, startswith(maps:get(<<"ifname">>, L), <<"verltap">>)]), + TapsTarget = sets:from_list([iolist_to_binary(TapName) || {_, #{tap_name := TapName}} <- Domains]), % TODO: persist tap_name as binary + % io:format("Taps Target: ~p~n", [sets:to_list(TapsTarget)]), + % io:format("Taps Actual: ~p~n", [sets:to_list(TapsActual)]), + TapsMap = maps:from_list([{iolist_to_binary(Tap), {to_vtap_mac(MacAddr), network_cidrs_to_bride_cidrs(Cidrs)}} + || {_, #{network_addrs := Cidrs, tap_name := Tap, mac_addr := MacAddr}} <- Domains]), + % io:format("TapsMap: ~p~n", [TapsMap]), + + TapsToDelete = sets:subtract(TapsActual, TapsTarget), + lists:foreach(fun (E) -> + Cmd = io_lib:format("ip link del ~s~n", [E]), + io:format(Cmd), + os:cmd(Cmd) +end, sets:to_list(TapsToDelete)), + + TapsToAdd = sets:subtract(TapsTarget, TapsActual), + + TapsMapsToAdd = maps:map(fun (_, {MacAddr, Net}) -> {MacAddr, maps:get(Net, Bridges)} end, maps:with(sets:to_list(TapsToAdd), TapsMap)), + add_taps(TapsMapsToAdd), + + ok. + + +add_taps(M) when is_map(M) -> add_taps(maps:to_list(M)); +add_taps([]) -> ok; +add_taps([{Tap, {Mac, Bridge}}|T]) -> + MacAddrString = virtuerl_util:mac_to_str(Mac), + Cmd = io_lib:format("ip tuntap add dev ~s mode tap~nip link set dev ~s address ~s master ~s~nip link set ~s up~n", + [Tap, Tap, MacAddrString, Bridge, Tap]), + io:format(Cmd), + os:cmd(Cmd), + add_taps(T). diff --git a/src/virtuerl_pubsub.erl b/src/virtuerl_pubsub.erl new file mode 100644 index 0000000..1eaf844 --- /dev/null +++ b/src/virtuerl_pubsub.erl @@ -0,0 +1,52 @@ +%%%------------------------------------------------------------------- +%%% @author ilya +%%% @copyright (C) 2023, +%%% @doc +%%% @end +%%%------------------------------------------------------------------- +-module(virtuerl_pubsub). + +-behaviour(gen_server). + +-export([start_link/0]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, + code_change/3]). +-export([subscribe/0, send/1]). + +-define(SERVER, ?MODULE). +-define(APPLICATION, virtuerl). + +%%%=================================================================== +%%% Spawning and gen_server implementation +%%%=================================================================== + +subscribe() -> + gen_server:call(?SERVER, subscribe). + +send(Message) -> + gen_server:cast(?SERVER, {send, Message}). + +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + +init([]) -> + {ok, []}. + +handle_call(subscribe, {Pid, _Tag}, State) -> + _Ref = monitor(process, Pid), + {reply, ok, [Pid | State]}. + +handle_cast({send, Message}, State) -> + [Pid ! Message || Pid <- State], + {noreply, State}. + +handle_info({'DOWN', Ref, process, Pid, Reason}, State) -> + io:format("~p died because of ~p~n", [Pid, Reason]), + NewState = lists:delete(Pid, State), + {noreply, NewState}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. diff --git a/src/virtuerl_qemu.erl b/src/virtuerl_qemu.erl new file mode 100644 index 0000000..865e5c0 --- /dev/null +++ b/src/virtuerl_qemu.erl @@ -0,0 +1,729 @@ +%%%------------------------------------------------------------------- +%%% @author ilya +%%% @copyright (C) 2023, +%%% @doc +%%% @end +%%%------------------------------------------------------------------- +-module(virtuerl_qemu). + +-include_lib("kernel/include/logger.hrl"). + +-behaviour(gen_server). + +-export([start_link/1, callback_mode/0]). +-export([init/1, terminate/2]). +-export([handle_continue/2, handle_info/2]). +-export([handle_call/3, handle_cast/2]). + +-define(SERVER, ?MODULE). + +%%%=================================================================== +%%% Spawning and gen_server implementation +%%%=================================================================== + +start_link(ID) -> +%% gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). +%% Pid = spawn_link(fun() -> +%% io:format("QEMU: Starting VM with ID ~p~n", [ID]), +%% timer:sleep(20000), +%% io:format("QEMU: Exiting ~p~n", [ID]), +%% timer:sleep(500), +%% exit(failure) +%% +%% end), +%% {ok, Pid}. + + gen_server:start_link(?MODULE, [ID], []). + +callback_mode() -> + handle_event_function. + +init([ID]) -> + {ok, Table} = dets:open_file(domains, [{file, filename:join(virtuerl_mgt:home_path(), "domains.dets")}]), + [{DomainId, #{mac_addr:=MacAddr, tap_name := TapName} = DomainRaw}] = dets:lookup(Table, ID), + Domain = maps:merge(#{user_data => "", vcpu => 2, memory => 4096}, DomainRaw), + State = #{table => Table, id => ID, domain => Domain, qemu_pid => undefined, qmp_pid => undefined}, + {ok, State, {continue, setup_swtpm}}. + +handle_continue(setup_swtpm, #{id := DomainId, domain := Domain} = State) -> + DomainHomePath = filename:join([virtuerl_mgt:home_path(), "domains", DomainId]), + ok = filelib:ensure_path(DomainHomePath), + + process_flag(trap_exit, true), + file:delete(filename:join(DomainHomePath, "qmp.sock")), + file:delete(filename:join(DomainHomePath, "serial.sock")), + file:delete(filename:join(DomainHomePath, "vnc.sock")), + file:delete(filename:join(DomainHomePath, "swtpm.sock")), + + {ok, _Pid, _OsPid} = exec:run_link("swtpm socket --tpmstate dir=./ --ctrl type=unixio,path=swtpm.sock --tpm2", [{cd, DomainHomePath}]), + case wait_for_socket(filename:join(DomainHomePath, "swtpm.sock")) of + ok -> + {noreply, State, {continue, setup_base}}; + timeout -> + {stop, failure, State} + end; +handle_continue(setup_base, #{id := DomainId, domain := Domain} = State) -> + DomainHomePath = filename:join([virtuerl_mgt:home_path(), "domains", DomainId]), + + OvmfVarsPath = filename:join(DomainHomePath, "OVMF_VARS_4M.ms.fd"), + case filelib:is_regular(OvmfVarsPath) of + false -> + file:copy("/usr/share/OVMF/OVMF_VARS_4M.ms.fd", OvmfVarsPath); + _ -> ok + end, + + RootVolumePath = filename:join(DomainHomePath, "root.qcow2"), + case filelib:is_regular(RootVolumePath) of + false -> + BaseImageOpts = case Domain of + #{base_image := BaseImage} -> + {ok, BaseImagePath} = virtuerl_img:ensure_image(BaseImage), + ["-b ", filename:absname(BaseImagePath), " -F qcow2 "]; + _ -> [] + end, + {ok, _} = exec:run(iolist_to_binary(["qemu-img create -f qcow2 ", BaseImageOpts, RootVolumePath, " 70G"]), [sync, stderr, stdout]); + _ -> noop + end, + ensure_cloud_config(Domain), + #{mac_addr:=MacAddr, tap_name := TapName, vcpu := Vcpu, memory := Memory} = Domain, + CdromOpts = case Domain of + #{setup_iso := SetupIso} -> + ensure_autounattend(Domain), + [" -drive file=", filename:join("/tmp/virtuerl/cache", SetupIso), ",if=ide,index=0,media=cdrom ", + " -drive file=/tmp/virtuerl/cache/virtio-win-0.1.248.iso,if=ide,index=1,media=cdrom ", + " -drive file=autounattend.iso,if=ide,index=2,media=cdrom " + ]; + _ -> [] + end, + Cmd = iolist_to_binary(["kvm -machine type=q35 -no-shutdown -S -nic tap,ifname=",TapName,",script=no,downscript=no,model=virtio-net-pci,mac=",virtuerl_util:mac_to_str(MacAddr), " -vnc unix:vnc.sock -display none -cpu host -smp ",integer_to_binary(Vcpu)," -m ",integer_to_binary(Memory), + " -chardev socket,id=chrtpm,path=swtpm.sock -tpmdev emulator,id=tpm0,chardev=chrtpm -device tpm-tis,tpmdev=tpm0", + " -drive if=pflash,format=raw,file=/usr/share/OVMF/OVMF_CODE_4M.ms.fd,readonly=on", + " -drive if=pflash,format=raw,file=OVMF_VARS_4M.ms.fd", + " -drive file=root.qcow2,if=virtio -drive driver=raw,file=cloud_config.iso,if=virtio -qmp unix:qmp.sock,server=on,wait=off -serial unix:serial.sock,server=on,wait=off", CdromOpts]), % -serial unix:serial.sock,server=on,wait=off + ?LOG_DEBUG(#{domain => DomainId, qemu_cmd => Cmd}), + {ok, Pid, OsPid} = exec:run_link(Cmd, [{cd, DomainHomePath}]), + {noreply, State#{qemu_pid => {Pid, OsPid}}, {continue, setup_qmp}}; +handle_continue(setup_qmp, #{id := ID} = State) -> + QmpSocketPath = filename:join([virtuerl_mgt:home_path(), "domains", ID, "qmp.sock"]), + io:format("waiting for qmp.sock ~p~n", [erlang:timestamp()]), +%% {ok, _} = exec:run(iolist_to_binary(["inotifywait -e create --include 'qmp\\.sock' ", ID]), [sync]), + case wait_for_socket(QmpSocketPath) of + ok -> + {ok, QmpPid} = virtuerl_qmp:start_link(QmpSocketPath, self()), + virtuerl_qmp:exec(QmpPid, cont), + {noreply, State#{qmp_pid => QmpPid}, {continue, setup_serial}}; + timeout -> + {stop, failure, State} + end; +handle_continue(setup_serial, #{id := ID} = State) -> + {noreply, State}. +%% SerialSocketPath = filename:join([virtuerl_mgt:home_path(), "domains", ID, "serial.sock"]), +%% io:format("waiting for serial.sock ~p~n", [erlang:timestamp()]), +%%%% {ok, _} = exec:run(iolist_to_binary(["inotifywait -e create --include 'qmp\\.sock' ", ID]), [sync]), +%% case wait_for_socket(SerialSocketPath) of +%% ok -> +%% % TODO: shall this be its own process instead? +%% {ok, SerialSocket} = gen_tcp:connect({local, SerialSocketPath}, 0, [local, {active, true}, {packet, line}, binary]), +%% {noreply, State#{serial_socket => SerialSocket}}; +%% timeout -> +%% {stop, failure, State} +%% end. + +handle_info({qmp, Event}, #{table := Table, id := ID, domain := Domain} = State) -> + ?LOG_INFO(#{domain => ID, qmp => Event}), + case Event of + #{<<"event">> := <<"STOP">>} -> + [{DomainId, Domain}] = dets:lookup(Table, ID), + DomainUpdated = Domain#{state => stopped}, + ok = dets:insert(Table, {DomainId, DomainUpdated}), + ok = dets:sync(Table), + {stop, normal, State#{domain => DomainUpdated}}; + _ -> {noreply, State} + end; +handle_info({tcp, SerialSocket, Data}, #{table := Table, id := ID, domain := Domain, serial_socket := SerialSocket} = State) -> + virtuerl_pubsub:send({domain_out, ID, Data}), + {noreply, State}; + +handle_info({'EXIT', Port, normal}, State) when is_port(Port) -> + % We need this so we don't crash when open_port(...) finishes + % TODO: replace the above virtuerl_util:cmd with erlexec + {noreply, State}. + +shutdown_events() -> + receive + {qmp, Event} -> + io:format("shutdown QMP: ~p~n", [Event]), + shutdown_events() + after 5000 -> ok + end. + +exit_events() -> + receive + {'EXIT', _Pid, _Reason} -> + io:format("EXIT ~p ~p!~n", [_Pid, _Reason]), + exit_events() + after 10000 -> ok + end. + +terminate(_Reason, #{table := Table, id := ID, domain := Domain, qemu_pid := {Pid, OsPid}, qmp_pid := QmpPid}) -> + ?LOG_DEBUG(#{domain => ID, event => graceful_shutdown, reason => _Reason}), + case _Reason of + normal -> ok; + _ -> % supervisor sends "shutdown" + virtuerl_qmp:exec(QmpPid, system_powerdown), + ?LOG_DEBUG(#{domain => ID, message => "waiting for guest to shutdown"}), +%% shutdown_events(), + receive + {qmp, #{<<"event">> := <<"STOP">>}} -> ok + after 5000 -> + ?LOG_WARNING(#{domain => ID, message => "timed-out waiting for guest to shutdown"}), + {error, timeout} + end + end, +%% {ok, #{<<"return">> := #{}}} = thoas:decode(PowerdownRes), + ok = virtuerl_qmp:stop(QmpPid), + ok = exec:stop(Pid), + ?LOG_DEBUG(#{domain => ID, message => "waiting for QEMU process to stop"}), + receive + {'EXIT', Pid, _} -> + ?LOG_DEBUG("QEMU OS process stopped! (~p)~n", [Pid]), + ok; + {'EXIT', OsPid, _} -> + ?LOG_DEBUG("QEMU OS process stopped (OsPid)!~n"), + ok + after 5000 -> + ?LOG_WARNING(#{domain => ID, message => "timed-out waiting for QEMU process to stop"}), + {error, timeout} + end, +%% exit_events(), + dets:close(Table). + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== + +wait_for_socket(SocketPath) -> + Self = self(), + WaiterPid = spawn(fun () -> + do_wait_for_socket(SocketPath, Self) + end), + receive + {virtuerl, socket_available} -> + ?LOG_DEBUG("done waiting for ~s ~p~n", [SocketPath, erlang:timestamp()]), + ok + after 2000 -> + ?LOG_DEBUG("failed waiting"), + exit(WaiterPid, kill), + timeout + end. + +do_wait_for_socket(SocketPath, Requester) -> + ?LOG_DEBUG("checking...~n"), + case filelib:last_modified(SocketPath) of + 0 -> + timer:sleep(20), + do_wait_for_socket(SocketPath, Requester); + _ -> + Requester ! {virtuerl, socket_available} + end. + +ensure_cloud_config(#{id := DomainID} = Domain) -> + case filelib:is_regular(filename:join([virtuerl_mgt:home_path(), "domains", DomainID, "cloud_config.iso"])) of + true -> ok; + false -> create_cloud_config(Domain) + end. + +create_cloud_config(#{id := DomainID, name := DomainName, mac_addr := MacAddr, cidrs := Cidrs, user_data := UserData}) -> + NetConf = [ + "version: 2\n", + "ethernets:\n", + " primary:\n", + " match:\n", + " macaddress: \"", virtuerl_util:mac_to_str(MacAddr), "\"\n", + " set-name: ens2\n", + " dhcp4: false\n", + " dhcp6: false\n", + " nameservers:\n", + " addresses: [8.8.8.8, 8.8.4.4, 2001:4860:4860::8888, 2001:4860:4860::8844]\n", + " addresses:\n", [[ + " - ", virtuerl_net:format_ip(IpAddr), "/", integer_to_binary(Prefixlen), "\n"] || {IpAddr, Prefixlen} <- Cidrs], + " routes:\n", [[ + " - to: default\n", + " via: ", virtuerl_net:format_ip(virtuerl_net:bridge_addr(IpAddr, Prefixlen)), "\n"] || {IpAddr, Prefixlen} <- Cidrs], + "" + ], + MetaData = [ + "instance-id: ", DomainID, "\n", + "local-hostname: ", DomainName, "\n" + ], + DomainBasePath = filename:join([virtuerl_mgt:home_path(), "domains", DomainID]), + IsoBasePath = filename:join(DomainBasePath, "iso"), + ok = filelib:ensure_path(IsoBasePath), + NetConfPath = filename:join(IsoBasePath, "network-config"), + ok = file:write_file(NetConfPath, NetConf), + MetaDataPath = filename:join(IsoBasePath, "meta-data"), + ok = file:write_file(MetaDataPath, MetaData), + UserDataPath = filename:join(IsoBasePath, "user-data"), + ok = file:write_file(UserDataPath, UserData), + IsoCmd = ["genisoimage -output ", filename:join(DomainBasePath, "cloud_config.iso"), " -volid cidata -joliet -rock ", UserDataPath, " ", MetaDataPath, " ", NetConfPath], + ok = virtuerl_util:cmd(binary_to_list(iolist_to_binary(IsoCmd))), + ok = file:del_dir_r(IsoBasePath), + ok. + +ensure_autounattend(#{id := DomainID} = Domain) -> + case filelib:is_regular(filename:join([virtuerl_mgt:home_path(), "domains", DomainID, "autounattend.iso"])) of + true -> ok; + false -> create_unattend(Domain) + end. + +create_unattend(#{id:=DomainID} = Domain) -> + DomainBasePath = filename:join([virtuerl_mgt:home_path(), "domains", DomainID]), + AutounattendPath = filename:join(DomainBasePath, "Autounattend.xml"), + UnattendPath = filename:join(DomainBasePath, "Unattend.xml"), + ok = write_unattend(Domain, AutounattendPath), + ok = write_unattend(Domain, UnattendPath), + IsoCmd = ["genisoimage -R -iso-level 4 -o ", filename:join(DomainBasePath, "autounattend.iso"), " ", AutounattendPath, " ", UnattendPath], + ok = virtuerl_util:cmd(binary_to_list(iolist_to_binary(IsoCmd))), + ok. + +write_unattend(Domain, Filename) -> + Xml = gen_unattend(Domain), + Export = xmerl:export_simple([Xml], xmerl_xml, [{prolog, ""}]), + file:write_file(Filename, iolist_to_binary(Export)). + +gen_unattend(#{id := DomainID, name := DomainName, mac_addr := MacAddr, cidrs := Cidrs}) -> + MacIdentifier = string:uppercase(string:replace(virtuerl_util:mac_to_str(MacAddr), ":", "-", all)), + GatewaysXml = [ + {'Route', [{'wcm:action', "add"}], [ + {'Identifier', [integer_to_list(Idx)]}, + {'NextHopAddress', [virtuerl_net:format_ip(virtuerl_net:bridge_addr(IpAddr, Prefixlen))]}, + {'Prefix', [virtuerl_net:format_cidr(virtuerl_net:normalize_net({IpAddr, 0}))]} + ]} + || {Idx, {IpAddr, Prefixlen}} <- lists:enumerate(Cidrs) + ], + AddressesXml = [ + {'IpAddress', [{'wcm:action', "add"}, {'wcm:keyValue', integer_to_list(Idx)}], [io_lib:format("~s/~B", [virtuerl_net:format_ip(IpAddr), Prefixlen])]} + || {Idx, {IpAddr, Prefixlen}} <- lists:enumerate(Cidrs) + ], + + DriverPaths = [ + "E:\\amd64\\w11", + "E:\\viostor\\w11\\amd64", + "E:\\NetKVM\\w11\\amd64", + "E:\\Balloon\\w11\\amd64", + "E:\\pvpanic\\w11\\amd64", + "E:\\qemupciserial\\w11\\amd64", + "E:\\qxldod\\w10\\amd64", + "E:\\vioinput\\w11\\amd64", + "E:\\viorng\\w11\\amd64", + "E:\\vioscsi\\w11\\amd64", + "E:\\vioserial\\w11\\amd64", + "E:\\vioserial\\w11\\amd64" + ], + DriverPathsXml = [ + {'PathAndCredentials', [{'wcm:action',"add"},{'wcm:keyValue',integer_to_list(Index)}], + [{'Path',[Path]}]} + || {Index, Path} <- lists:enumerate(DriverPaths)], + + Commands = [ + io_lib:format("reg.exe add \"HKLM\\SOFTWARE\\Microsoft\\Cryptography\" /v \"MachineGuid\" /t REG_SZ /d \"~s\" /f", [DomainID]), + + "reg.exe add \"HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\OOBE\" /v BypassNRO /t REG_DWORD /d 1 /f", + + "reg.exe load \"HKU\\mount\" \"C:\\Users\\Default\\NTUSER.DAT\"", + "reg.exe add \"HKU\\mount\\Software\\Microsoft\\Windows\\CurrentVersion\\Runonce\" /v \"UninstallCopilot\" /t REG_SZ /d \"powershell.exe -NoProfile -Command \\\"Get-AppxPackage -Name 'Microsoft.Windows.Ai.Copilot.Provider' | Remove-AppxPackage;\\\"\" /f", + "reg.exe add \"HKU\\mount\\Software\\Policies\\Microsoft\\Windows\\WindowsCopilot\" /v TurnOffWindowsCopilot /t REG_DWORD /d 1 /f", + "reg.exe unload \"HKU\\mount\"", + + "reg.exe delete \"HKLM\\SOFTWARE\\Microsoft\\WindowsUpdate\\Orchestrator\\UScheduler_Oobe\\DevHomeUpdate\" /f", + "cmd.exe /c \"del \"C:\\Users\\Default\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\OneDrive.lnk\"\"", + "cmd.exe /c \"del \"C:\\Windows\\System32\\OneDriveSetup.exe\"\"", + "cmd.exe /c \"del \"C:\\Windows\\SysWOW64\\OneDriveSetup.exe\"\"", + + "reg.exe load \"HKU\\mount\" \"C:\\Users\\Default\\NTUSER.DAT\"", + "reg.exe delete \"HKU\\mount\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\" /v OneDriveSetup /f", + "reg.exe unload \"HKU\\mount\"", + + "reg.exe delete \"HKLM\\SOFTWARE\\Microsoft\\WindowsUpdate\\Orchestrator\\UScheduler_Oobe\\OutlookUpdate\" /f", + "reg.exe add \"HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Communications\" /v ConfigureChatAutoInstall /t REG_DWORD /d 0 /f", + "powershell.exe -NoProfile -Command \"$xml = [xml]::new(); $xml.Load('C:\\Windows\\Panther\\unattend.xml'); $sb = [scriptblock]::Create( $xml.unattend.Extensions.ExtractScript ); Invoke-Command -ScriptBlock $sb -ArgumentList $xml;\"", + "powershell.exe -NoProfile -Command \"Get-Content -LiteralPath '%TEMP%\\remove-packages.ps1' -Raw | Invoke-Expression;\"", + "powershell.exe -NoProfile -Command \"Get-Content -LiteralPath '%TEMP%\\remove-caps.ps1' -Raw | Invoke-Expression;\"", + "powershell.exe -NoProfile -Command \"Get-Content -LiteralPath '%TEMP%\\remove-features.ps1' -Raw | Invoke-Expression;\"", + "reg.exe add \"HKLM\\SOFTWARE\\Microsoft\\PolicyManager\\current\\device\\Start\" /v ConfigureStartPins /t REG_SZ /d \"{ \\\"pinnedList\\\": [] }\" /f", + "reg.exe add \"HKLM\\SOFTWARE\\Microsoft\\PolicyManager\\current\\device\\Start\" /v ConfigureStartPins_ProviderSet /t REG_DWORD /d 1 /f", + "reg.exe add \"HKLM\\SOFTWARE\\Microsoft\\PolicyManager\\current\\device\\Start\" /v ConfigureStartPins_WinningProvider /t REG_SZ /d B5292708-1619-419B-9923-E5D9F3925E71 /f", + "reg.exe add \"HKLM\\SOFTWARE\\Microsoft\\PolicyManager\\providers\\B5292708-1619-419B-9923-E5D9F3925E71\\default\\Device\\Start\" /v ConfigureStartPins /t REG_SZ /d \"{ \\\"pinnedList\\\": [] }\" /f", + "reg.exe add \"HKLM\\SOFTWARE\\Microsoft\\PolicyManager\\providers\\B5292708-1619-419B-9923-E5D9F3925E71\\default\\Device\\Start\" /v ConfigureStartPins_LastWrite /t REG_DWORD /d 1 /f", + + "reg.exe load \"HKU\\mount\" \"C:\\Users\\Default\\NTUSER.DAT\"", + "reg.exe add \"HKU\\mount\\Software\\Microsoft\\Windows\\CurrentVersion\\Runonce\" /v \"GeoLocation\" /t REG_SZ /d \"powershell.exe -NoProfile -Command \\\"Set-WinHomeLocation -GeoId 94;\\\"\" /f", + "reg.exe unload \"HKU\\mount\"", + + "net.exe accounts /maxpwage:UNLIMITED", + "regini.exe \"%TEMP%\\disable-defender.ini\"", + "netsh.exe advfirewall firewall set rule group=\"Remote Desktop\" new enable=Yes", + "reg.exe add \"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Terminal Server\" /v fDenyTSConnections /t REG_DWORD /d 0 /f", + "powershell.exe -NoProfile -Command \"Set-ExecutionPolicy -Scope 'LocalMachine' -ExecutionPolicy 'RemoteSigned' -Force;\"", + "reg.exe add \"HKLM\\SOFTWARE\\Policies\\Microsoft\\Dsh\" /v AllowNewsAndInterests /t REG_DWORD /d 0 /f", + + "reg.exe load \"HKU\\mount\" \"C:\\Users\\Default\\NTUSER.DAT\"", + "reg.exe add \"HKU\\mount\\Software\\Microsoft\\Windows\\CurrentVersion\\ContentDeliveryManager\" /v \"ContentDeliveryAllowed\" /t REG_DWORD /d 0 /f", + "reg.exe add \"HKU\\mount\\Software\\Microsoft\\Windows\\CurrentVersion\\ContentDeliveryManager\" /v \"FeatureManagementEnabled\" /t REG_DWORD /d 0 /f", + "reg.exe add \"HKU\\mount\\Software\\Microsoft\\Windows\\CurrentVersion\\ContentDeliveryManager\" /v \"OEMPreInstalledAppsEnabled\" /t REG_DWORD /d 0 /f", + "reg.exe add \"HKU\\mount\\Software\\Microsoft\\Windows\\CurrentVersion\\ContentDeliveryManager\" /v \"PreInstalledAppsEnabled\" /t REG_DWORD /d 0 /f", + "reg.exe add \"HKU\\mount\\Software\\Microsoft\\Windows\\CurrentVersion\\ContentDeliveryManager\" /v \"PreInstalledAppsEverEnabled\" /t REG_DWORD /d 0 /f", + "reg.exe add \"HKU\\mount\\Software\\Microsoft\\Windows\\CurrentVersion\\ContentDeliveryManager\" /v \"SilentInstalledAppsEnabled\" /t REG_DWORD /d 0 /f", + "reg.exe add \"HKU\\mount\\Software\\Microsoft\\Windows\\CurrentVersion\\ContentDeliveryManager\" /v \"SoftLandingEnabled\" /t REG_DWORD /d 0 /f", + "reg.exe add \"HKU\\mount\\Software\\Microsoft\\Windows\\CurrentVersion\\ContentDeliveryManager\" /v \"SubscribedContentEnabled\" /t REG_DWORD /d 0 /f", + "reg.exe add \"HKU\\mount\\Software\\Microsoft\\Windows\\CurrentVersion\\ContentDeliveryManager\" /v \"SubscribedContent-310093Enabled\" /t REG_DWORD /d 0 /f", + "reg.exe add \"HKU\\mount\\Software\\Microsoft\\Windows\\CurrentVersion\\ContentDeliveryManager\" /v \"SubscribedContent-338387Enabled\" /t REG_DWORD /d 0 /f", + "reg.exe add \"HKU\\mount\\Software\\Microsoft\\Windows\\CurrentVersion\\ContentDeliveryManager\" /v \"SubscribedContent-338388Enabled\" /t REG_DWORD /d 0 /f", + "reg.exe add \"HKU\\mount\\Software\\Microsoft\\Windows\\CurrentVersion\\ContentDeliveryManager\" /v \"SubscribedContent-338389Enabled\" /t REG_DWORD /d 0 /f", + "reg.exe add \"HKU\\mount\\Software\\Microsoft\\Windows\\CurrentVersion\\ContentDeliveryManager\" /v \"SubscribedContent-338393Enabled\" /t REG_DWORD /d 0 /f", + "reg.exe add \"HKU\\mount\\Software\\Microsoft\\Windows\\CurrentVersion\\ContentDeliveryManager\" /v \"SubscribedContent-353698Enabled\" /t REG_DWORD /d 0 /f", + "reg.exe add \"HKU\\mount\\Software\\Microsoft\\Windows\\CurrentVersion\\ContentDeliveryManager\" /v \"SystemPaneSuggestionsEnabled\" /t REG_DWORD /d 0 /f", + "reg.exe unload \"HKU\\mount\"", + + "reg.exe add \"HKLM\\Software\\Policies\\Microsoft\\Windows\\CloudContent\" /v \"DisableWindowsConsumerFeatures\" /t REG_DWORD /d 0 /f", + "C:\\Windows\\Setup\\Scripts\\unattend-01.cmd" + ], + ToCommands = fun (Cmds) -> + [{'RunSynchronousCommand', [{'wcm:action',"add"}], + [{'Order',[integer_to_list(Idx)]}, {'Path',[Cmd]}]} + || {Idx, Cmd} <- lists:enumerate(Cmds)] + end, + + CommandsXml = ToCommands(Commands), + +% based on: https://schneegans.de/windows/unattend-generator/?LanguageMode=Unattended&UILanguage=en-GB&UserLocale=en-GB&KeyboardLayout=0409%3A00000409&GeoLocation=94&ProcessorArchitecture=amd64&BypassNetworkCheck=true&ComputerNameMode=Custom&ComputerName=XXZZCCVVasdf&TimeZoneMode=Implicit&PartitionMode=Unattended&PartitionLayout=GPT&EspSize=300&RecoveryMode=Partition&RecoverySize=1000&WindowsEditionMode=Unattended&WindowsEdition=pro_workstations_n&UserAccountMode=Unattended&AccountName0=Admin&AccountPassword0=password&AccountGroup0=Administrators&AccountName1=User&AccountPassword1=password&AccountGroup1=Users&AccountName2=&AccountName3=&AccountName4=&AutoLogonMode=Own&PasswordExpirationMode=Unlimited&LockoutMode=Default&DisableDefender=true&DisableSystemRestore=true&EnableRemoteDesktop=true&AllowPowerShellScripts=true&DisableAppSuggestions=true&DisableWidgets=true&WifiMode=Skip&ExpressSettings=DisableAll&Remove3DViewer=true&RemoveCamera=true&RemoveClipchamp=true&RemoveClock=true&RemoveCopilot=true&RemoveCortana=true&RemoveDevHome=true&RemoveFamily=true&RemoveFeedbackHub=true&RemoveGetHelp=true&RemoveInternetExplorer=true&RemoveMailCalendar=true&RemoveMaps=true&RemoveMathInputPanel=true&RemoveZuneVideo=true&RemoveNews=true&RemoveNotepadClassic=true&RemoveOffice365=true&RemoveOneDrive=true&RemoveOneNote=true&RemoveOutlook=true&RemovePaint=true&RemovePaint3D=true&RemovePeople=true&RemovePhotos=true&RemovePowerAutomate=true&RemoveQuickAssist=true&RemoveSkype=true&RemoveSnippingTool=true&RemoveSolitaire=true&RemoveStepsRecorder=true&RemoveStickyNotes=true&RemoveTeams=true&RemoveGetStarted=true&RemoveToDo=true&RemoveVoiceRecorder=true&RemoveWeather=true&RemoveWindowsMediaPlayer=true&RemoveZuneMusic=true&RemoveWordPad=true&RemoveXboxApps=true&RemoveYourPhone=true&SystemScript0=powercfg.exe+%2FHIBERNATE+OFF&SystemScriptType0=Cmd&SystemScript1=&SystemScriptType1=Ps1&SystemScript2=&SystemScriptType2=Reg&SystemScript3=&SystemScriptType3=Vbs&DefaultUserScript0=&DefaultUserScriptType0=Reg&FirstLogonScript0=&FirstLogonScriptType0=Cmd&FirstLogonScript1=&FirstLogonScriptType1=Ps1&FirstLogonScript2=&FirstLogonScriptType2=Reg&FirstLogonScript3=&FirstLogonScriptType3=Vbs&UserOnceScript0=&UserOnceScriptType0=Cmd&UserOnceScript1=&UserOnceScriptType1=Ps1&UserOnceScript2=&UserOnceScriptType2=Reg&UserOnceScript3=&UserOnceScriptType3=Vbs&WdacMode=Skip&Microsoft-Windows-PnpCustomizationsWinPE=windowsPE&Microsoft-Windows-TCPIP=windowsPE&Microsoft-Windows-TCPIP=specialize +{unattend, + [{xmlns,"urn:schemas-microsoft-com:unattend"}, + {'xmlns:wcm',"http://schemas.microsoft.com/WMIConfig/2002/State"}], + [{settings,[{pass,"offlineServicing"}],[]}, + {settings,[{pass,"windowsPE"}], + [{component, + [{name,"Microsoft-Windows-International-Core-WinPE"}, + {processorArchitecture,"amd64"}, + {publicKeyToken,"31bf3856ad364e35"}, + {language,"neutral"}, + {versionScope,"nonSxS"}], + [{'SetupUILanguage',[{'UILanguage',["en-GB"]}]}, + {'InputLocale',["0409:00000409"]}, + {'SystemLocale',["en-GB"]}, + {'UILanguage',["en-GB"]}, + {'UserLocale',["en-GB"]}]}, + {component, + [{name,"Microsoft-Windows-Setup"}, + {processorArchitecture,"amd64"}, + {publicKeyToken,"31bf3856ad364e35"}, + {language,"neutral"}, + {versionScope,"nonSxS"}], + [{'ImageInstall', + [{'OSImage', + [{'InstallTo', + [{'DiskID',["0"]}, + {'PartitionID',["3"]}]}]}]}, + {'UserData', + [{'ProductKey', + [{'Key',["WYPNQ-8C467-V2W6J-TX4WX-WT2RQ"]}]}, + {'AcceptEula',["true"]}]}, + {'DiskConfiguration', + [{'DisableEncryptedDiskProvisioning',["true"]}, + {'Disk',[{'wcm:action',"add"}], + [{'CreatePartitions', + [{'CreatePartition',[{'wcm:action',"add"}], + [{'Order',["1"]}, + {'Type',["EFI"]}, + {'Size',["400"]}]}, + {'CreatePartition',[{'wcm:action',"add"}], + [{'Order',["2"]}, + {'Type',["MSR"]}, + {'Size',["100"]}]}, + {'CreatePartition',[{'wcm:action',"add"}], + [{'Order',["3"]}, + {'Type',["Primary"]}, + {'Extend',["true"]}]}]}, + {'ModifyPartitions', + [{'ModifyPartition',[{'wcm:action',"add"}], + [{'Format',["NTFS"]}, + {'Letter',["C"]}, + {'Order',["1"]}, + {'PartitionID',["3"]}, + {'Label',["Windows 11"]}]}]}, + {'DiskID',["0"]}, + {'WillWipeDisk',["true"]}]}, + {'WillShowUI',["OnError"]}]}]}, + {component, + [{name,"Microsoft-Windows-PnpCustomizationsWinPE"}, + {processorArchitecture,"amd64"}, + {publicKeyToken,"31bf3856ad364e35"}, + {language,"neutral"}, + {versionScope,"nonSxS"}], + [{'DriverPaths', DriverPathsXml}]}]}, + {settings,[{pass,"generalize"}],[]}, + {settings,[{pass,"specialize"}], + [{component, + [{name,"Microsoft-Windows-Deployment"}, + {processorArchitecture,"amd64"}, + {publicKeyToken,"31bf3856ad364e35"}, + {language,"neutral"}, + {versionScope,"nonSxS"}], + [{'RunSynchronous', CommandsXml}]}, + {component, + [{name,"Microsoft-Windows-TCPIP"}, + {processorArchitecture,"amd64"}, + {publicKeyToken,"31bf3856ad364e35"}, + {language,"neutral"}, + {versionScope,"nonSxS"}], + [{'Interfaces',[ + {'Interface', [{'wcm:action', "add"}], [ + {'Identifier', [MacIdentifier]}, + {'Ipv4Settings', [ + {'DhcpEnabled', ["false"]} + ]}, + {'Ipv6Settings', [ + {'DhcpEnabled', ["false"]} + ]}, + {'UnicastIpAddresses', AddressesXml}, + {'Routes', GatewaysXml} + ]} + ]}]}, + {component, + [{name,"Microsoft-Windows-DNS-Client"}, + {processorArchitecture,"amd64"}, + {publicKeyToken,"31bf3856ad364e35"}, + {language,"neutral"}, + {versionScope,"nonSxS"}], + [{'Interfaces',[ + {'Interface', [{'wcm:action', "add"}], [ + {'Identifier', [MacIdentifier]}, + {'DNSServerSearchOrder', [ + {'IpAddress', [{'wcm:action', "add"}, {'wcm:keyValue', "1"}], ["8.8.8.8"]}, + {'IpAddress', [{'wcm:action', "add"}, {'wcm:keyValue', "2"}], ["8.8.4.4"]}, + {'IpAddress', [{'wcm:action', "add"}, {'wcm:keyValue', "3"}], ["2001:4860:4860::8888"]}, + {'IpAddress', [{'wcm:action', "add"}, {'wcm:keyValue', "4"}], ["2001:4860:4860::8844"]} + ]} + ]} + ]}]}, + {component, + [{name,"Microsoft-Windows-Shell-Setup"}, + {processorArchitecture,"amd64"}, + {publicKeyToken,"31bf3856ad364e35"}, + {language,"neutral"}, + {versionScope,"nonSxS"}], + [{'ComputerName',[DomainName]}]}]}, + {settings,[{pass,"auditSystem"}],[]}, + {settings,[{pass,"auditUser"}],[ + % {component, + % [{name,"Microsoft-Windows-Deployment"}, + % {processorArchitecture,"amd64"}, + % {publicKeyToken,"31bf3856ad364e35"}, + % {language,"neutral"}, + % {versionScope,"nonSxS"}], + % [{'Generalize',[ + % {'Mode', ["OOBE"]}, + % {'ForceShutdownNow', ["true"]} + % ]}]} + ]}, + {settings,[{pass,"oobeSystem"}], + [{component, + [{name,"Microsoft-Windows-International-Core"}, + {processorArchitecture,"amd64"}, + {publicKeyToken,"31bf3856ad364e35"}, + {language,"neutral"}, + {versionScope,"nonSxS"}], + [{'InputLocale',["0409:00000409"]}, + {'SystemLocale',["en-GB"]}, + {'UILanguage',["en-GB"]}, + {'UserLocale',["en-GB"]}]}, + % {component, + % [{name,"Microsoft-Windows-Deployment"}, + % {processorArchitecture,"amd64"}, + % {publicKeyToken,"31bf3856ad364e35"}, + % {language,"neutral"}, + % {versionScope,"nonSxS"}], + % [{'ExtendOSPartition',[{'Extend', ["true"]}]}, + % {'Reseal',[ + % {'Mode', ["Audit"]}, + % {'ForceShutdownNow', ["false"]} + % ]}]}, + {component, + [{name,"Microsoft-Windows-Shell-Setup"}, + {processorArchitecture,"amd64"}, + {publicKeyToken,"31bf3856ad364e35"}, + {language,"neutral"}, + {versionScope,"nonSxS"}], + [{'UserAccounts', + [{'LocalAccounts', + [{'LocalAccount', + [{'wcm:action',"add"}], + [{'Name',["Admin"]}, + {'Group',["Administrators"]}, + {'Password', + [{'Value',["SoftwarePatchenIstFamos"]}, + {'PlainText',["true"]}]}]}, + {'LocalAccount', + [{'wcm:action',"add"}], + [{'Name',["User"]}, + {'Group',["Users"]}, + {'Password', + [{'Value',["SoftwarePatchenIstFamos"]}, + {'PlainText',["true"]}]}]}]}]}, + {'AutoLogon', + [{'Username',["Admin"]}, + {'Enabled',["true"]}, + {'LogonCount',["1"]}, + {'Password', + [{'Value',["SoftwarePatchenIstFamos"]}, + {'PlainText',["true"]}]}]}, + {'OOBE', + [{'ProtectYourPC',["3"]}, + {'HideEULAPage',["true"]}, + {'HideWirelessSetupInOOBE',["true"]}]}, + {'FirstLogonCommands', + [ + % {'SynchronousCommand', + % [{'wcm:action',"add"}], + % [{'Order',["1"]}, + % {'CommandLine', + % ["del C:\\Windows\\Panther\\unattend.xml"]}]}, + % {'SynchronousCommand', + % [{'wcm:action',"add"}], + % [{'Order',["2"]}, + % {'CommandLine', + % ["C:\\Windows\\System32\\Sysprep\\Sysprep.exe /generalize /oobe /shutdown"]}]}, + {'SynchronousCommand', + [{'wcm:action',"add"}], + [{'Order',["1"]}, + {'CommandLine', + ["reg.exe add \"HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\" /v AutoLogonCount /t REG_DWORD /d 0 /f"]}]}, + {'SynchronousCommand', + [{'wcm:action',"add"}], + [{'Order',["2"]}, + {'CommandLine', + ["powershell.exe -NoProfile -Command \"Disable-ComputerRestore -Drive 'C:\\';\""]}]}]}]}]}, + {'Extensions', + [{xmlns,"https://schneegans.de/windows/unattend-generator/"}], + [{'ExtractScript', + ["param( + [xml] $Document +); + +$scriptsDir = 'C:\\Windows\\Setup\\Scripts\\'; +foreach( $file in $Document.unattend.Extensions.File ) { + $path = [System.Environment]::ExpandEnvironmentVariables( + $file.GetAttribute( 'path' ) + ); + if( $path.StartsWith( $scriptsDir ) ) { + mkdir -Path $scriptsDir -ErrorAction 'SilentlyContinue'; + } + $encoding = switch( [System.IO.Path]::GetExtension( $path ) ) { + { $_ -in '.ps1', '.xml' } { [System.Text.Encoding]::UTF8; } + { $_ -in '.reg', '.vbs', '.js' } { [System.Text.UnicodeEncoding]::new( $false, $true ); } + default { [System.Text.Encoding]::Default; } + }; + [System.IO.File]::WriteAllBytes( $path, ( $encoding.GetPreamble() + $encoding.GetBytes( $file.InnerText ) ) ); +}"]}, + {'File', + [{path,"%TEMP%\\remove-packages.ps1"}], + ["Get-AppxProvisionedPackage -Online | +Where-Object -Property 'DisplayName' -In -Value @( + 'Microsoft.Microsoft3DViewer'; + 'Microsoft.WindowsCamera'; + 'Clipchamp.Clipchamp'; + 'Microsoft.WindowsAlarms'; + 'Microsoft.549981C3F5F10'; + 'MicrosoftCorporationII.MicrosoftFamily'; + 'Microsoft.WindowsFeedbackHub'; + 'Microsoft.GetHelp'; + 'microsoft.windowscommunicationsapps'; + 'Microsoft.WindowsMaps'; + 'Microsoft.ZuneVideo'; + 'Microsoft.BingNews'; + 'Microsoft.MicrosoftOfficeHub'; + 'Microsoft.Office.OneNote'; + 'Microsoft.Paint'; + 'Microsoft.MSPaint'; + 'Microsoft.People'; + 'Microsoft.Windows.Photos'; + 'Microsoft.PowerAutomateDesktop'; + 'MicrosoftCorporationII.QuickAssist'; + 'Microsoft.SkypeApp'; + 'Microsoft.ScreenSketch'; + 'Microsoft.MicrosoftSolitaireCollection'; + 'Microsoft.MicrosoftStickyNotes'; + 'Microsoft.Getstarted'; + 'Microsoft.Todos'; + 'Microsoft.WindowsSoundRecorder'; + 'Microsoft.BingWeather'; + 'Microsoft.ZuneMusic'; + 'Microsoft.Xbox.TCUI'; + 'Microsoft.XboxApp'; + 'Microsoft.XboxGameOverlay'; + 'Microsoft.XboxGamingOverlay'; + 'Microsoft.XboxIdentityProvider'; + 'Microsoft.XboxSpeechToTextOverlay'; + 'Microsoft.GamingApp'; + 'Microsoft.YourPhone'; +) | Remove-AppxProvisionedPackage -AllUsers -Online *>&1 >> \"$env:TEMP\\remove-packages.log\"; +"]}, + {'File', + [{path,"%TEMP%\\remove-caps.ps1"}], + ["Get-WindowsCapability -Online | +Where-Object -FilterScript { + ($_.Name -split '~')[0] -in @( + 'Browser.InternetExplorer'; + 'MathRecognizer'; + 'Microsoft.Windows.Notepad'; + 'Microsoft.Windows.MSPaint'; + 'App.Support.QuickAssist'; + 'App.StepsRecorder'; + 'Media.WindowsMediaPlayer'; + 'Microsoft.Windows.WordPad'; + ); +} | Remove-WindowsCapability -Online *>&1 >> \"$env:TEMP\\remove-caps.log\"; +"]}, + {'File', + [{path,"%TEMP%\\remove-features.ps1"}], + ["Get-WindowsOptionalFeature -Online | +Where-Object -Property 'FeatureName' -In -Value @( + 'Microsoft-SnippingTool'; +) | Disable-WindowsOptionalFeature -Online -Remove -NoRestart *>&1 >> \"$env:TEMP\\remove-features.log\"; +"]}, + {'File', + [{path,"C:\\Users\\Default\\AppData\\Local\\Microsoft\\Windows\\Shell\\LayoutModification.xml"}], + [" + + + + + + + +"]}, + {'File', + [{path,"%TEMP%\\disable-defender.ini"}], + ["HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Sense + \"Start\" = REG_DWORD 4 +HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\WdBoot + \"Start\" = REG_DWORD 4 +HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\WdFilter + \"Start\" = REG_DWORD 4 +HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\WdNisDrv + \"Start\" = REG_DWORD 4 +HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\WdNisSvc + \"Start\" = REG_DWORD 4 +HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\WinDefend + \"Start\" = REG_DWORD 4 +"]}, + {'File', + [{path,"C:\\Windows\\Setup\\Scripts\\unattend-01.cmd"}], + ["powercfg.exe /HIBERNATE OFF"]}]}]} +. + +handle_call(Request, From, State) -> + erlang:error(not_implemented). + +handle_cast(Request, State) -> + erlang:error(not_implemented). diff --git a/src/virtuerl_qmp.erl b/src/virtuerl_qmp.erl new file mode 100644 index 0000000..4edfa7d --- /dev/null +++ b/src/virtuerl_qmp.erl @@ -0,0 +1,98 @@ +%%%------------------------------------------------------------------- +%%% @author ilya +%%% @copyright (C) 2023, +%%% @doc +%%% @end +%%%------------------------------------------------------------------- +-module(virtuerl_qmp). + +-include_lib("kernel/include/logger.hrl"). + +-behaviour(gen_server). + +-export([start_link/2, handle_cast/2]). +-export([init/1, terminate/2]). +-export([handle_call/3, handle_info/2]). +-export([exec/2, stop/1]). + +-define(SERVER, ?MODULE). + +exec(Pid, Command) -> + gen_server:call(Pid, Command). + +stop(Pid) -> + gen_server:stop(Pid). + +%%%=================================================================== +%%% Spawning and gen_server implementation +%%%=================================================================== + +start_link(QmpSocketPath, Receiver) -> + gen_server:start_link(?MODULE, {QmpSocketPath, Receiver}, []). + +init({QmpSocketPath, Receiver}) -> + Self = self(), + Pid = spawn_link(fun () -> qmp_translator(QmpSocketPath, Self) end), + io:format("virtuerl_qmp: init~n"), + receive + {qmp, #{<<"QMP">> := #{}}} -> ok + after 1000 -> exit(qmp_not_responding) + end, + io:format("virtuerl_qmp: init after~n"), + execute(Pid, qmp_capabilities), + io:format("virtuerl_qmp: qmp caps~n"), + {ok, {Pid, Receiver}}. + +terminate(_Reason, {Pid, _}) -> + Ref = monitor(process, Pid), + exit(Pid, normal), + receive + {'DOWN', Ref, process, Pid, normal} -> ok + end, + true = demonitor(Ref), + io:format("exiting QMP server~n"). + +handle_call(Command, _From, {Pid, _} = State) -> + execute(Pid, Command), + {reply, ok, State}. + +handle_cast(Request, State) -> + erlang:error(not_implemented). + +handle_info({qmp, #{<<"event">> := _} = Event}, {_, Receiver} = State) -> + Receiver ! {qmp, Event}, + {noreply, State}. + +execute(Pid, Command) when is_pid(Pid) -> + Pid ! {qmp, Command}, + receive + {qmp, #{<<"return">> := #{}}} -> ok + end. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== + +qmp_translator(QmpSocketPath, Receiver) -> + process_flag(trap_exit, true), + io:format("starting QMP translator (~s)!~n", [QmpSocketPath]), + {ok, QmpSocket} = gen_tcp:connect({local, QmpSocketPath}, 0, [local, {active, true}]), + qmp_loop(QmpSocket, Receiver). + +qmp_loop(QmpSocket, Receiver) -> + receive + {tcp, _Socket, RawData} -> + Lines = re:split(RawData, "\r?\n", [trim]), + Jsons = lists:map(fun (Line) -> {ok, Json} = thoas:decode(Line), Json end, Lines), + ?LOG_DEBUG(#{qmp_raw => RawData, qmp_parsed => Jsons}), + [Receiver ! {qmp, Json} || Json <- Jsons], + qmp_loop(QmpSocket, Receiver); + {qmp, Command} when is_atom(Command) -> + io:format("qmp_loop/qmp: ~p~n", [Command]), + ok = gen_tcp:send(QmpSocket, thoas:encode(#{execute => Command})), + qmp_loop(QmpSocket, Receiver); + {'EXIT', _SenderID, Reason} -> + io:format("closing QMP socket (~p)!~n", [Reason]), + ok = gen_tcp:close(QmpSocket), + exit(Reason) + end. diff --git a/src/virtuerl_sup.erl b/src/virtuerl_sup.erl new file mode 100644 index 0000000..f2757c4 --- /dev/null +++ b/src/virtuerl_sup.erl @@ -0,0 +1,84 @@ +%%%------------------------------------------------------------------- +%% @doc virtuerl top level supervisor. +%% @end +%%%------------------------------------------------------------------- + +-module(virtuerl_sup). + +-behaviour(supervisor). + +-export([start_link/0]). + +-export([init/1]). + +-define(SERVER, ?MODULE). + +start_link() -> + supervisor:start_link({local, ?SERVER}, ?MODULE, []). + +%% sup_flags() = #{strategy => strategy(), % optional +%% intensity => non_neg_integer(), % optional +%% period => pos_integer()} % optional +%% child_spec() = #{id => child_id(), % mandatory +%% start => mfargs(), % mandatory +%% restart => restart(), % optional +%% shutdown => shutdown(), % optional +%% type => worker(), % optional +%% modules => modules()} % optional +init([]) -> + SupFlags = #{strategy => one_for_one, + intensity => 300, + period => 5}, + ChildSpecs = [ + { + virtuerl_pubsub, + {virtuerl_pubsub, start_link, []}, + permanent, + infinity, + worker, + [] + }, { + virtuerl_ipam, + {virtuerl_ipam, start_link, []}, + permanent, + infinity, + worker, + [] + }, { + virtuerl_img, + {virtuerl_img, start_link, []}, + permanent, + infinity, + worker, + [] + }, { + virtuerl_mgt, + {virtuerl_mgt, start_link, []}, + permanent, + infinity, + worker, + [] + }, { + virtuerl_net, + {virtuerl_net, start_link, []}, + permanent, + infinity, + worker, + [] + } + , { + virtuerl_api, + {virtuerl_api, start_link, []}, + permanent, + infinity, + worker, + [] + } + ], + OptionalChildSpecs = case application:get_env(gh_pat) of + undefined -> []; + _ -> [{virtuerl_ghac, {virtuerl_ghac, start_link, []}, permanent, infinity, worker, []}] + end, + {ok, {SupFlags, ChildSpecs ++ OptionalChildSpecs}}. + +%% internal functions diff --git a/src/virtuerl_ui.erl b/src/virtuerl_ui.erl new file mode 100644 index 0000000..7132c56 --- /dev/null +++ b/src/virtuerl_ui.erl @@ -0,0 +1,532 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009-2021. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%%%------------------------------------------------------------------- +%%% File : hello.erl +%%% Author : Matthew Harrison +%%% Description : _really_ minimal example of a wxerlang app +%%% implemented with wx_object behaviour +%%% +%%% Created : 18 Sep 2008 by Matthew Harrison +%%% Dan rewrote it to show wx_object behaviour +%%%------------------------------------------------------------------- +-module(virtuerl_ui). +-include_lib("kernel/include/logger.hrl"). +-include_lib("wx/include/wx.hrl"). + +-export([start/0, start/1, + init/1, handle_info/2, handle_event/2, handle_call/3, + code_change/3, terminate/2]). + +-behaviour(wx_object). + +-record(state, {win, info_panel, info, domain_panel, domain_info, toolbar, domain_list_box, domains, page, net_list_box, node}). + +start() -> + start(node()). +start(Node) -> + wx_object:start_link(?MODULE, [Node], []). + +%% Init is called in the new process. +init([Node]) -> +%% virtuerl_pubsub:subscribe(), + + wx:new(), + Frame = wxFrame:new(wx:null(), + -1, % window id + "Hello World", % window title + []), + + MenuBar = wxMenuBar:new(), + Menu = wxMenu:new(), + wxMenu:append(Menu, ?wxID_EXIT, "Quit"), + wxMenuBar:append(MenuBar, Menu, "File"), + wxFrame:setMenuBar(Frame, MenuBar), + wxFrame:connect(Frame, command_menu_selected), + + {Mx, My, _, _} = wxMiniFrame:getTextExtent(Frame, "M"), + wxFrame:setClientSize(Frame, {60*Mx, 20*My}), + + Toolbar = create_toolbar(Frame, 100), + + wxFrame:createStatusBar(Frame,[]), + + %% if we don't handle this ourselves, wxwidgets will close the window + %% when the user clicks the frame's close button, but the event loop still runs + wxFrame:connect(Frame, close_window), + + RootPanel = wxPanel:new(Frame, [{size, wxFrame:getSize(Frame)}]), + Notebook = wxNotebook:new(RootPanel, ?wxID_ANY), + + Sizer = wxBoxSizer:new(?wxHORIZONTAL), + wxSizer:add(Sizer, Notebook, [{flag, ?wxEXPAND}, {proportion, 1}]), + wxPanel:setSizer(RootPanel, Sizer), + + + NetworkPanel = wxPanel:new(Notebook, []), + NetworksSizer = wxBoxSizer:new(?wxHORIZONTAL), + NetworkSplitter = wxSplitterWindow:new(NetworkPanel), + wxSizer:add(NetworksSizer, NetworkSplitter, [{flag, ?wxEXPAND}, {proportion, 1}]), + wxPanel:setSizer(NetworkPanel, NetworksSizer), + + wxNotebook:addPage(Notebook, NetworkPanel, "Networks"), + + {ok, Nets} = erpc:call(Node, virtuerl_ipam, ipam_list_nets, []), + Choices = maps:keys(Nets), + ListBox = wxListBox:new(NetworkSplitter, 42, [{choices, Choices}]), + wxListBox:connect(ListBox, command_listbox_selected), % command_listbox_doubleclicked + + Info = wxPanel:new(NetworkSplitter), + InfoSizer = wxBoxSizer:new(?wxHORIZONTAL), + InfoGrid = wxFlexGridSizer:new(3, 2, 0, 0), + wxSizer:add(InfoSizer, InfoGrid, [{flag, ?wxEXPAND}, {proportion, 1}]), + wxPanel:setSizer(Info, InfoSizer), + + wxSplitterWindow:splitHorizontally(NetworkSplitter, ListBox, Info, [{sashPosition, 25 * Mx}]), + + % BEGIN Domains + DomainPanel = wxPanel:new(Notebook, []), + DomainsSizer = wxBoxSizer:new(?wxHORIZONTAL), + DomainSplitter = wxSplitterWindow:new(DomainPanel), + wxSizer:add(DomainsSizer, DomainSplitter, [{flag, ?wxEXPAND}, {proportion, 1}]), + wxPanel:setSizer(DomainPanel, DomainsSizer), + + wxNotebook:addPage(Notebook, DomainPanel, "Domains"), + wxNotebook:connect(Notebook, command_notebook_page_changed), + wxNotebook:setSelection(Notebook, 1), + + Domains = erpc:call(Node, virtuerl_mgt, domains_list, []), + ColumnNames = ["ID", "Name", "IPs", "CPU", "RAM"], + ColumnAlignment = [?wxLIST_FORMAT_LEFT, ?wxLIST_FORMAT_LEFT, ?wxLIST_FORMAT_LEFT, ?wxLIST_FORMAT_RIGHT, ?wxLIST_FORMAT_RIGHT], + DomainsTuples = [{Id, Name, lists:join($,, [virtuerl_net:format_ip(Ip) || {Ip, _Prefixlen} <- Cidrs]), integer_to_binary(Vcpu), [integer_to_binary(Memory), $M]} || #{id := Id, name := Name, cidrs := Cidrs, vcpu := Vcpu, memory := Memory} <- Domains], + DomainsIds = [Id || {Id, _Name, _, _, _} <- DomainsTuples], + DomainListBox = wxListCtrl:new(DomainSplitter, [{style, ?wxLC_REPORT}]), + lists:foreach(fun ({Idx, Name}) -> wxListCtrl:insertColumn(DomainListBox, Idx, Name, [{format, lists:nth(Idx + 1, ColumnAlignment)}]) end, lists:enumerate(0, ColumnNames)), + lists:foreach(fun (Idx) -> + Item = wxListItem:new(), + wxListItem:setId(Item, Idx), + wxListCtrl:insertItem(DomainListBox, Item) + end, lists:seq(1, length(DomainsTuples))), + lists:foreach(fun (ColIdx) -> + lists:foreach(fun ({RowIdx, Dom}) -> + wxListCtrl:setItem(DomainListBox, RowIdx, ColIdx - 1, element(ColIdx, Dom)) + end, lists:enumerate(0, DomainsTuples)) + end, lists:seq(1, length(ColumnNames))), + wxListCtrl:connect(DomainListBox, command_list_item_selected), % command_listbox_doubleclicked + + DomainInfo = wxPanel:new(DomainSplitter), + DomainInfoSizer = wxBoxSizer:new(?wxVERTICAL), +%% DomainInfoGrid = wxFlexGridSizer:new(3, 2, 0, 0), + DomainInfoGrid = wxGridBagSizer:new(), + wxSizer:add(DomainInfoSizer, DomainInfoGrid, [{flag, ?wxEXPAND}, {proportion, 1}]), + wxPanel:setSizer(DomainInfo, DomainInfoSizer), + + DomainButtonsSizer = wxBoxSizer:new(?wxHORIZONTAL), + DomainDupBtn = wxButton:new(DomainInfo, 4044, [{label, "Duplicate"}]), + wxButton:connect(DomainDupBtn, command_button_clicked), + DomainDelBtn = wxButton:new(DomainInfo, ?wxID_ANY, [{label, "Delete"}]), + DomainEditBtn = wxButton:new(DomainInfo, 4046, [{label, "Edit"}]), + wxButton:connect(DomainEditBtn, command_button_clicked), + wxSizer:add(DomainButtonsSizer, DomainDupBtn), + wxSizer:add(DomainButtonsSizer, DomainDelBtn), + wxSizer:add(DomainButtonsSizer, DomainEditBtn), + wxSizer:add(DomainInfoSizer, DomainButtonsSizer, [{flag, ?wxALIGN_RIGHT}]), + + wxSplitterWindow:splitHorizontally(DomainSplitter, DomainListBox, DomainInfo, [{sashPosition, 25 * Mx}]), + % END Domains + + ok = wxFrame:setStatusText(Frame, "Hello World!",[]), + wxWindow:fit(Frame), + wxWindow:show(Frame), + wxWindow:raise(Frame), + wxWindow:setFocus(Frame), + wxWindow:layout(Frame), + {Frame, #state{node = Node, toolbar = Toolbar, page = 1, win=Frame, net_list_box = ListBox, info_panel = Info, info=InfoGrid, domain_panel = DomainInfo, domain_info=DomainInfoGrid, domain_list_box=DomainListBox, domains = DomainsIds}}. + +create_toolbar(Frame, BaseNum) -> + PlayIconDC = wxMemoryDC:new(), + PlayIcon = wxBitmap:new(30, 30, [{depth, 32}]), + wxBufferedDC:selectObject(PlayIconDC, PlayIcon), + wxMemoryDC:setBrush(PlayIconDC, ?wxGREEN_BRUSH), + wxMemoryDC:setPen(PlayIconDC, ?wxGREEN_PEN), + wxMemoryDC:drawPolygon(PlayIconDC, [{0,0},{30,15},{0,30}]), + wxMemoryDC:destroy(PlayIconDC), + PlayIconDisabled = wxBitmap:new(wxImage:convertToGreyscale(wxBitmap:convertToImage(PlayIcon))), + + StopIconDC = wxMemoryDC:new(), + StopIcon = wxBitmap:new(30, 30, [{depth, 32}]), + wxBufferedDC:selectObject(StopIconDC, StopIcon), + wxMemoryDC:setBrush(StopIconDC, ?wxRED_BRUSH), + wxMemoryDC:setPen(StopIconDC, ?wxRED_PEN), + wxMemoryDC:drawRectangle(StopIconDC, {0,0},{30,30}), + wxMemoryDC:destroy(StopIconDC), + StopIconDisabled = wxBitmap:new(wxImage:convertToGreyscale(wxBitmap:convertToImage(StopIcon))), + + DelIconDC = wxMemoryDC:new(), + DelIcon = wxBitmap:new(30, 30, [{depth, 32}]), + wxBufferedDC:selectObject(DelIconDC, DelIcon), + wxMemoryDC:setBrush(DelIconDC, ?wxBLUE_BRUSH), + wxMemoryDC:setPen(DelIconDC, ?wxBLACK_PEN), + wxMemoryDC:drawRectangle(DelIconDC, {3,10},{24,20}), + wxMemoryDC:drawRectangle(DelIconDC, {0,5},{30,5}), + wxMemoryDC:drawRectangle(DelIconDC, {10,0},{10,5}), + wxMemoryDC:setBrush(DelIconDC, ?wxWHITE_BRUSH), + wxMemoryDC:setPen(DelIconDC, ?wxWHITE_PEN), + wxMemoryDC:drawLine(DelIconDC, {9,14}, {9,26}), + wxMemoryDC:drawLine(DelIconDC, {15,14}, {15,26}), + wxMemoryDC:drawLine(DelIconDC, {21,14}, {21,26}), + wxMemoryDC:destroy(DelIconDC), + DelIconDisabled = wxBitmap:new(wxImage:convertToGreyscale(wxBitmap:convertToImage(DelIcon))), + + AddIconDC = wxMemoryDC:new(), + AddIcon = wxBitmap:new(30, 30, [{depth, 32}]), + wxBufferedDC:selectObject(AddIconDC, AddIcon), + wxMemoryDC:setBrush(AddIconDC, ?wxGREEN_BRUSH), + wxMemoryDC:setPen(AddIconDC, ?wxGREEN_PEN), + wxMemoryDC:drawRectangle(AddIconDC, {0,12},{30,6}), + wxMemoryDC:drawRectangle(AddIconDC, {12,0},{6,30}), + wxMemoryDC:destroy(AddIconDC), + AddIconDisabled = wxBitmap:new(wxImage:convertToGreyscale(wxBitmap:convertToImage(AddIcon))), + + Toolbar = wxFrame:createToolBar(Frame), + wxToolBar:addTool(Toolbar, BaseNum + 0, "test123", PlayIcon, PlayIconDisabled), + wxToolBar:enableTool(Toolbar, BaseNum + 0, false), + wxToolBar:addTool(Toolbar, BaseNum + 1, "test123", StopIcon, StopIconDisabled), + wxToolBar:enableTool(Toolbar, BaseNum + 1, false), + wxToolBar:addTool(Toolbar, BaseNum + 2, "test123", DelIcon, DelIconDisabled), + wxToolBar:enableTool(Toolbar, BaseNum + 2, false), + wxToolBar:addTool(Toolbar, BaseNum + 3, "test123", AddIcon, AddIconDisabled), + wxToolBar:enableTool(Toolbar, BaseNum + 3, true), + wxToolBar:realize(Toolbar), + wxToolBar:connect(Toolbar, command_menu_selected), + Toolbar. + +%% Handled as in normal gen_server callbacks +handle_info({domain_out, _Id, Text}, #state{domain_panel = DomainPanel} = State) -> + SerialOut = wx:typeCast(wxPanel:findWindow(DomainPanel, 69), wxStyledTextCtrl), + io:put_chars(Text), + wxStyledTextCtrl:appendText(SerialOut, Text), + wxStyledTextCtrl:scrollToLine(SerialOut, wxStyledTextCtrl:getLineCount(SerialOut)), + {noreply,State}; +handle_info(Msg, State) -> + ?LOG_INFO(#{info => Msg}), + {noreply, State}. + +handle_call(Msg, _From, State) -> + io:format("Got Call ~p~n",[Msg]), + {reply,ok,State}. + +%% Async Events are handled in handle_event as in handle_info +handle_event(#wx{event = #wxBookCtrl{nSel = Index}}, State) -> + io:format("TAB: ~p~n", [Index]), + {noreply, State#state{page = Index}}; +handle_event(#wx{id = 42, event = #wxCommand{type = command_listbox_selected, + cmdString = Choice}}, + State = #state{info_panel = Panel, info=Info, domains = DomainIds, node = Node}) -> + {ok, Nets} = erpc:call(Node, virtuerl_ipam, ipam_list_nets, []), + Net = maps:get(list_to_binary(Choice), Nets), + Cidrs = case Net of + #{cidr4 := Cidr4, cidr6 := Cidr6} -> + [Cidr4, Cidr6]; + #{cidr4 := Cidr4} -> + [Cidr4]; + #{cidr6 := Cidr6} -> + [Cidr6] + end, + CidrsStr = [binary_to_list(iolist_to_binary([IpAddr, "/", integer_to_binary(Prefixlen)])) || #{address:=IpAddr,prefixlen:=Prefixlen} <- Cidrs], + wxFlexGridSizer:clear(Info, [{delete_windows, true}]), + wxSizer:add(Info, wxStaticText:new(Panel, -1, "CIDR")), + wxSizer:add(Info, wxStaticText:new(Panel, -1, string:join(CidrsStr, ","))), + io:format("dblclick ~p (~p)~n", [Choice, Net]), + {noreply, State}; +handle_event(#wx{event = #wxList{type = command_list_item_selected, + itemIndex = ItemIndex}}, + State = #state{domain_panel = DomainPanel, domain_info = DomainInfo, toolbar=Toolbar, domains = DomainIds, node = Node}) -> + Domains = maps:from_list([{Id, Domain} || Domain = #{id := Id} <- erpc:call(Node, virtuerl_mgt, domains_list, [])]), + wxGridBagSizer:clear(DomainInfo, [{delete_windows, true}]), + + DomainId = lists:nth(ItemIndex + 1, DomainIds), + Domain = maps:get(DomainId, Domains), + #{state := DomainState, network_id := NetworkId} = Domain, + case DomainState of + running -> + wxToolBar:enableTool(Toolbar, 100, false), + wxToolBar:enableTool(Toolbar, 101, true); + stopped -> + wxToolBar:enableTool(Toolbar, 100, true), + wxToolBar:enableTool(Toolbar, 101, false) + end, + wxToolBar:enableTool(Toolbar, 102, true), + wxGridBagSizer:add(DomainInfo, wxStaticText:new(DomainPanel, -1, "ID"), {0, 0}), + wxGridBagSizer:add(DomainInfo, wxStaticText:new(DomainPanel, -1, DomainId), {0, 1}), + wxGridBagSizer:add(DomainInfo, wxStaticText:new(DomainPanel, -1, "Network ID"), {1, 0}), + wxGridBagSizer:add(DomainInfo, wxStaticText:new(DomainPanel, -1, NetworkId), {1, 1}), + + DomainWithoutUserData = maps:remove(user_data, Domain), + wxGridBagSizer:add(DomainInfo, wxStaticText:new(DomainPanel, -1, io_lib:format("~p", [DomainWithoutUserData])), {2, 0}, [{span, {1, 2}}]), + +%% SerialOut = wxStyledTextCtrl:new(DomainPanel, [{id, 69}]), +%% wxStyledTextCtrl:setLexer(SerialOut, ?wxSTC_LEX_ERRORLIST), +%% wxStyledTextCtrl:styleSetVisible(SerialOut, 23, false), +%% wxStyledTextCtrl:styleSetVisible(SerialOut, 24, false), +%% wxStyledTextCtrl:setProperty(SerialOut, "lexer.errorlist.value.separate", "0"), +%% wxStyledTextCtrl:setProperty(SerialOut, "lexer.errorlist.escape.sequences", "1"), +%% wxGridBagSizer:add(DomainInfo, SerialOut, {3, 0}, [{span, {1, 2}}, {flag, ?wxEXPAND}]), +%% WebView = wxWebView:new(DomainPanel, 999, [{url, "http://0.0.0.0:9000/noVNC-1.4.0/vnc.html?port=5700&?path=&resize=scale&autoconnect=true"}]), +%% wxGridBagSizer:add(DomainInfo, WebView, {3, 0}, [{span, {1, 2}}, {flag, ?wxEXPAND}]), + VncWindow = virtuerl_vnc:start(DomainPanel, DomainId, Node), + wxGridBagSizer:add(DomainInfo, VncWindow, {0, 2}, [{span, {3, 1}}, {flag, ?wxEXPAND}]), + + wxGridBagSizer:addGrowableRow(DomainInfo, 2), + + UserData = maps:get(user_data, Domain, ""), + UserDataCtrl = wxStyledTextCtrl:new(DomainPanel), + wxStyledTextCtrl:setScrollWidth(UserDataCtrl, wxStyledTextCtrl:textWidth(UserDataCtrl, wxStyledTextCtrl:getStyleAt(UserDataCtrl, 0), UserData)), + wxStyledTextCtrl:setText(UserDataCtrl, list_to_binary(UserData)), + wxStyledTextCtrl:setReadOnly(UserDataCtrl, true), + LastRowIndex = 3, +%% Msg = wxNotificationMessage:new(lists:flatten(io_lib:format("LastRowIndex: ~p~n", [LastRowIndex]))), +%% wxNotificationMessage:show(Msg), + wxGridBagSizer:add(DomainInfo, UserDataCtrl, {LastRowIndex, 0}, [{span, {1, 3}}, {flag, ?wxEXPAND}]), + wxGridBagSizer:addGrowableRow(DomainInfo, LastRowIndex), + wxGridBagSizer:addGrowableCol(DomainInfo, 2), + wxPanel:layout(DomainPanel), + io:format("dblclick ~p (~p)~n", [DomainId, Domain]), + {noreply, State}; +handle_event(#wx{id = 4044, event = #wxCommand{type = command_button_clicked}}, #state{domain_list_box = DomainListBox, domains = DomainIds, node = Node} = State) -> + SelectedItem = wxListCtrl:getNextItem(DomainListBox, -1, [{state, ?wxLIST_STATE_SELECTED}]), + DomainId = lists:nth(SelectedItem + 1, DomainIds), + {ok, Domain} = erpc:call(Node, virtuerl_mgt, domain_get, [#{id => DomainId}]), + create_domain_dialog(Node, Domain), + {noreply, State}; +handle_event(#wx{id = 4046, event = #wxCommand{type = command_button_clicked}}, #state{domain_list_box = DomainListBox, domains = DomainIds, node = Node} = State) -> + SelectedItem = wxListCtrl:getNextItem(DomainListBox, -1, [{state, ?wxLIST_STATE_SELECTED}]), + DomainId = lists:nth(SelectedItem + 1, DomainIds), + {ok, Domain} = erpc:call(Node, virtuerl_mgt, domain_get, [#{id => DomainId}]), + update_domain_dialog(Node, Domain), + {noreply, State}; +handle_event(#wx{id = 100, obj = Toolbar, event = #wxCommand{type = command_menu_selected}} = Event, #state{page = 1, domain_list_box = DomainListBox, domains = DomainIds, node = Node} = State) -> + io:format("~p~n", [Event]), + SelectedItem = wxListCtrl:getNextItem(DomainListBox, -1, [{state, ?wxLIST_STATE_SELECTED}]), + DomainId = lists:nth(SelectedItem + 1, DomainIds), + Choice = DomainId, + io:format("~p~n", [Choice]), + wxToolBar:enableTool(Toolbar, 100, false), + wxToolBar:enableTool(Toolbar, 101, true), + ok = erpc:call(Node, virtuerl_mgt, domain_start, [Choice]), + {noreply, State}; +handle_event(#wx{id = 101, obj = Toolbar, event = #wxCommand{type = command_menu_selected}} = Event, #state{page = 1, domain_list_box = DomainListBox, domains = DomainIds, node = Node} = State) -> + io:format("~p~n", [Event]), + SelectedItem = wxListCtrl:getNextItem(DomainListBox, -1, [{state, ?wxLIST_STATE_SELECTED}]), + DomainId = lists:nth(SelectedItem + 1, DomainIds), + Choice = DomainId, + io:format("~p~n", [Choice]), + wxToolBar:enableTool(Toolbar, 100, true), + wxToolBar:enableTool(Toolbar, 101, false), + ok = erpc:call(Node, virtuerl_mgt, domain_stop, [Choice]), + {noreply, State}; +handle_event(#wx{id = 102, obj = Toolbar, event = #wxCommand{type = command_menu_selected}} = Event, #state{page = 1, domain_list_box = DomainListBox, domains = DomainIds, node = Node} = State) -> + io:format("~p~n", [Event]), + SelectedItem = wxListCtrl:getNextItem(DomainListBox, -1, [{state, ?wxLIST_STATE_SELECTED}]), + DomainId = lists:nth(SelectedItem + 1, DomainIds), + Choice = DomainId, + io:format("~p~n", [Choice]), +%% wxToolBar:enableTool(Toolbar, 100, true), +%% wxToolBar:enableTool(Toolbar, 101, false), +%% ok = erpc:call(Node, virtuerl_mgt, domain_stop, (Choice), + erpc:call(Node, virtuerl_mgt, domain_delete, [#{id => DomainId}]), + {noreply, State}; +handle_event(#wx{id = 103, obj = Toolbar, event = #wxCommand{type = command_menu_selected}} = Event, #state{page = 1, domain_list_box = DomainListBox, domains = DomainIds, node = Node} = State) -> + create_domain_dialog(Node, #{network_id => "", name=> "", user_data => "", vcpu => 2, memory => 1024, inbound_rules => [], os_type => "linux"}), + {noreply, State}; +%% BEGIN: Network Toolbar +handle_event(#wx{id = 102, event = #wxCommand{type = command_menu_selected}}, #state{page = 0, net_list_box = ListBox, node = Node} = State) -> + NetId = wxListBox:getStringSelection(ListBox), + erpc:call(Node, virtuerl_ipam, ipam_delete_net, [NetId]), + {noreply, State}; +handle_event(#wx{id = 103, event = #wxCommand{type = command_menu_selected}}, #state{page = 0, node = Node} = State) -> + create_network_dialog(Node), + {noreply, State}; +%% END: Network Toolbar +handle_event(#wx{event=#wxClose{}}, State = #state{win=Frame}) -> + io:format("~p Closing window ~n",[self()]), + ok = wxFrame:setStatusText(Frame, "Closing...",[]), + wxWindow:destroy(Frame), + {stop, normal, State}; +handle_event(#wx{id = ?wxID_EXIT, event = #wxCommand{type = command_menu_selected}}, State = #state{win=Frame}) -> + io:format("~p Quitting window ~n",[self()]), + ok = wxFrame:setStatusText(Frame, "Closing...",[]), + wxWindow:destroy(Frame), + {stop, normal, State}; +handle_event(Event, State) -> + io:format("Unknown Event: ~p~n", [Event]), + {noreply, State}. + +code_change(_, _, State) -> + {stop, not_yet_implemented, State}. + +terminate(_Reason, _State) -> + ok. + +create_network_dialog(Node) -> + Dialog = wxDialog:new(wx:null(), ?wxID_ANY, "Create Network", [{size, {1000, 500}}]), + DialogSizer = wxBoxSizer:new(?wxVERTICAL), + % DialogGridSizer = wxFlexGridSizer:new(1, 2, 0, 0), + DialogGridSizer = wxBoxSizer:new(?wxHORIZONTAL), + wxSizer:add(DialogGridSizer, wxStaticText:new(Dialog, ?wxID_ANY, "CIDR")), + CidrCtrl = wxTextCtrl:new(Dialog, ?wxID_ANY, [{style, ?wxTE_MULTILINE}]), + wxSizer:add(DialogGridSizer, CidrCtrl, [{flag, ?wxEXPAND}, {proportion, 1}]), + wxSizer:add(DialogSizer, DialogGridSizer, [{flag, ?wxEXPAND}, {proportion, 1}]), + ButtonSizer = wxDialog:createStdDialogButtonSizer(Dialog, ?wxOK bor ?wxCANCEL), + wxSizer:add(DialogSizer, ButtonSizer), + wxPanel:setSizer(Dialog, DialogSizer), + + case wxDialog:showModal(Dialog) of + ?wxID_OK -> + NetDef = [ + virtuerl_net:parse_cidr(string:trim(IpStr, both)) + || IpStr <- string:split(wxTextCtrl:getValue(CidrCtrl), ",", all)], + {ok, NetId} = erpc:call(Node, virtuerl_ipam, ipam_create_net, [NetDef]); + _ -> ok + end, + wxDialog:destroy(Dialog). + +create_domain_dialog(Node, #{network_id := NetworkId, name:= DomainName, user_data := UserData, vcpu := Vcpu, memory := Memory, inbound_rules := InboundRules} = Domain) -> + {ok, Nets} = erpc:call(Node, virtuerl_ipam, ipam_list_nets, []), + NetworkChoices = maps:keys(Nets), + ImageChoices = erpc:call(Node, virtuerl_img, list_images, []), + Dialog = wxDialog:new(wx:null(), ?wxID_ANY, "Create Domain", [{size, {1000, 500}}]), + DialogSizer = wxBoxSizer:new(?wxVERTICAL), + DialogGridSizer = wxFlexGridSizer:new(1, 2, 0, 0), + wxSizer:add(DialogGridSizer, wxStaticText:new(Dialog, ?wxID_ANY, "Name")), + NameCtrl = wxTextCtrl:new(Dialog, ?wxID_ANY, [{value, DomainName}]), + wxSizer:add(DialogGridSizer, NameCtrl, [{flag, ?wxEXPAND}, {proportion, 1}]), + wxSizer:add(DialogGridSizer, wxStaticText:new(Dialog, ?wxID_ANY, "Network")), + NetworkChoice = wxChoice:new(Dialog, ?wxID_ANY, [{choices, NetworkChoices}]), + wxChoice:setStringSelection(NetworkChoice, NetworkId), + wxSizer:add(DialogGridSizer, NetworkChoice), + wxSizer:add(DialogGridSizer, wxStaticText:new(Dialog, ?wxID_ANY, "CPU")), + VcpuCtrl = wxSpinCtrl:new(Dialog, [{min, 1}, {max, 256}, {initial, Vcpu}]), + wxSizer:add(DialogGridSizer, VcpuCtrl), + wxSizer:add(DialogGridSizer, wxStaticText:new(Dialog, ?wxID_ANY, "Memory")), + MemoryCtrl = wxSpinCtrl:new(Dialog, [{min, 128}, {max, 131072}, {initial, Memory}]), + wxSizer:add(DialogGridSizer, MemoryCtrl), + wxSizer:add(DialogGridSizer, wxStaticText:new(Dialog, ?wxID_ANY, "OS")), + OsChoice = wxChoice:new(Dialog, ?wxID_ANY, [{choices, ["linux", "windows"]}]), + wxChoice:setStringSelection(OsChoice, "linux"), + wxSizer:add(DialogGridSizer, OsChoice), + wxSizer:add(DialogGridSizer, wxStaticText:new(Dialog, ?wxID_ANY, "Image")), + ImageChoice = wxChoice:new(Dialog, ?wxID_ANY, [{choices, ImageChoices}]), + [DefaultImage|_] = ImageChoices, + wxChoice:setStringSelection(ImageChoice, DefaultImage), + wxSizer:add(DialogGridSizer, ImageChoice), + + wxSizer:add(DialogSizer, DialogGridSizer), + + % Inbound Rules + % wxBoxSizer:new(?wxHORIZONTAL), + InboundRulesInitial = string:join([ string:join([string:join(Protos, ","), string:join([case Port of + Num when is_integer(Num) -> + integer_to_list(Num); + Str when is_list(Str) orelse is_binary(Str) -> + Str + end || Port <- Ports], ","), string:join(Sources, ",")], ";") || #{protocols := Protos, target_ports := Ports, sources := Sources} <- InboundRules], "\n"), + InboundRulesCtrl = wxTextCtrl:new(Dialog, ?wxID_ANY, [{style, ?wxTE_MULTILINE}, {value, InboundRulesInitial}]), + wxSizer:add(DialogSizer, InboundRulesCtrl, [{flag, ?wxEXPAND}, {proportion, 1}]), + + UserDataCtrl = wxStyledTextCtrl:new(Dialog), + wxStyledTextCtrl:setLexer(UserDataCtrl, ?wxSTC_LEX_YAML), + wxStyledTextCtrl:setText(UserDataCtrl, UserData), + wxSizer:add(DialogSizer, UserDataCtrl, [{flag, ?wxEXPAND}, {proportion, 1}]), + ButtonSizer = wxDialog:createStdDialogButtonSizer(Dialog, ?wxOK bor ?wxCANCEL), + wxSizer:add(DialogSizer, ButtonSizer), + wxPanel:setSizer(Dialog, DialogSizer), + + case wxDialog:showModal(Dialog) of + ?wxID_OK -> + InboundRulesVal = wxTextCtrl:getValue(InboundRulesCtrl), + Rules0 = [string:trim(Rule) || Rule <- string:split(InboundRulesVal, "\n", all)], + Rules1 = [Rule || Rule <- Rules0, not string:is_empty(Rule)], + Rules2 = [[string:trim(Elem) || Elem <- string:split(Rule, ";", all)] || Rule <- Rules1], + Rules3 = [case Rule of [Protos, Ports, Sources] -> #{ + protocols => nonempty_splitrim(Protos, ","), + target_ports => nonempty_splitrim(Ports, ","), + sources => nonempty_splitrim(Sources, ",") + } end || Rule <- Rules2], + io:format("inbound rules: ~p~n", [Rules3]), + erpc:call(Node, virtuerl_mgt, domain_create, [#{ + name => wxTextCtrl:getValue(NameCtrl), + os_type => wxChoice:getStringSelection(OsChoice), + base_image => wxChoice:getStringSelection(ImageChoice), + network_id => list_to_binary(wxChoice:getStringSelection(NetworkChoice)), + user_data => wxStyledTextCtrl:getText(UserDataCtrl), + vcpu => wxSpinCtrl:getValue(VcpuCtrl), + memory => wxSpinCtrl:getValue(MemoryCtrl), + inbound_rules => Rules3 + }]); + _ -> ok + end, + wxDialog:destroy(Dialog). + +update_domain_dialog(Node, #{id := DomainId, state := RunState, inbound_rules := InboundRules} = Domain) -> + Dialog = wxDialog:new(wx:null(), ?wxID_ANY, "Edit Domain", [{size, {1000, 500}}]), + DialogSizer = wxBoxSizer:new(?wxVERTICAL), + + % Inbound Rules + % wxBoxSizer:new(?wxHORIZONTAL), + InboundRulesInitial = string:join([ string:join([string:join(Protos, ","), string:join([case Port of + Num when is_integer(Num) -> + integer_to_list(Num); + Str when is_list(Str) orelse is_binary(Str) -> + Str + end || Port <- Ports], ","), string:join(Sources, ",")], ";") || #{protocols := Protos, target_ports := Ports, sources := Sources} <- InboundRules], "\n"), + InboundRulesCtrl = wxTextCtrl:new(Dialog, ?wxID_ANY, [{style, ?wxTE_MULTILINE}, {value, InboundRulesInitial}]), + wxSizer:add(DialogSizer, InboundRulesCtrl, [{flag, ?wxEXPAND}, {proportion, 1}]), + + ButtonSizer = wxDialog:createStdDialogButtonSizer(Dialog, ?wxOK bor ?wxCANCEL), + wxSizer:add(DialogSizer, ButtonSizer), + wxPanel:setSizer(Dialog, DialogSizer), + + case wxDialog:showModal(Dialog) of + ?wxID_OK -> + InboundRulesVal = wxTextCtrl:getValue(InboundRulesCtrl), + Rules0 = [string:trim(Rule) || Rule <- string:split(InboundRulesVal, "\n", all)], + Rules1 = [Rule || Rule <- Rules0, not string:is_empty(Rule)], + Rules2 = [[string:trim(Elem) || Elem <- string:split(Rule, ";", all)] || Rule <- Rules1], + Rules3 = [case Rule of [Protos, Ports, Sources] -> #{ + protocols => nonempty_splitrim(Protos, ","), + target_ports => nonempty_splitrim(Ports, ","), + sources => nonempty_splitrim(Sources, ",") + } end || Rule <- Rules2], + io:format("inbound rules: ~p~n", [Rules3]), + erpc:call(Node, virtuerl_mgt, domain_update, [#{ + id => DomainId, + state => RunState, + inbound_rules => Rules3 + }]); + _ -> ok + end, + wxDialog:destroy(Dialog). + + +nonempty_splitrim(Str, Delim) -> + Res = [string:trim(Elem) || Elem <- string:split(Str, Delim, all)], + [Elem || Elem <- Res, not string:is_empty(Elem)]. + + diff --git a/src/virtuerl_util.erl b/src/virtuerl_util.erl new file mode 100644 index 0000000..78717f0 --- /dev/null +++ b/src/virtuerl_util.erl @@ -0,0 +1,41 @@ +%%%------------------------------------------------------------------- +%%% @author ilya +%%% @copyright (C) 2023, +%%% @doc +%%% +%%% @end +%%% Created : 02. Sep 2023 4:05 PM +%%%------------------------------------------------------------------- +-module(virtuerl_util). +-author("ilya"). + +%% API +-export([uuid4/0, mac_to_str/1, delete_file/1, cmd/1, ends_with/2]). + +uuid4() -> + ID = string:lowercase(binary:encode_hex(<<(rand:uniform(16#FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)-1):128>>)), + <> = ID, + Uuid4 = iolist_to_binary([A, "-", B, "-", C, "-", D, "-", E]), + Uuid4. + +mac_to_str(<>) -> + <> = string:lowercase(binary:encode_hex(<>)), + <>. + +delete_file(Filename) -> + case file:delete(Filename) of + ok -> ok; + {error, enoent} -> ok; + Other -> Other + end. + +ends_with(Str, Suffix) -> + Suf = string:slice(Str, string:length(Str) - string:length(Suffix)), + string:equal(Suf, Suffix). + +cmd(Cmd) -> + Port = open_port({spawn, Cmd}, [exit_status]), + receive + {Port, {exit_status, 0}} -> ok; + {Port, {exit_signal, _}} -> error + end. diff --git a/src/virtuerl_vnc.erl b/src/virtuerl_vnc.erl new file mode 100644 index 0000000..c5be2a5 --- /dev/null +++ b/src/virtuerl_vnc.erl @@ -0,0 +1,140 @@ +-module(virtuerl_vnc). +-include_lib("wx/include/wx.hrl"). +-include_lib("wx/include/gl.hrl"). + +-export([ + init/1, handle_info/2, handle_event/2, + code_change/3, terminate/2]). +-export([start/3]). + +-behaviour(wx_object). + +-define(vncINVALID, 0). +-define(vncNONE, 1). +-define(vncVNC_AUTH, 2). +-define(vncOK, 0). +-define(vncFAILED, 1). +-define(vncMSG_TYPE_FramebufferUpdate, 0). +-define(vncMSG_TYPE_FramebufferUpdateRequest, 3). +-define(vncMSG_TYPE_KeyEvent, 4). +-define(vncMSG_TYPE_PointerEvent, 5). +-define(vncENCODING_RAW, 0). + +-record(state, {win, socket, panel, tex_id, vnc_conf, parent}). +-record(vnc_conf, {width, height}). + +start(Parent, DomainId, Node) -> + wx_object:start_link(?MODULE, [Parent, DomainId, Node], []). + +%% Init is called in the new process. +init([Parent, DomainId, Node]) -> + {Mx, My, _, _} = wxWindow:getTextExtent(Parent, "M"), +%% wxFrame:setClientSize(Frame, {60*Mx, 20*My}), + Panel = wxGLCanvas:new(Parent, [{attribList, [?WX_GL_RGBA,?WX_GL_DOUBLEBUFFER,0]}]), + Context = wxGLContext:new(Panel), +%% wxWindow:setMinSize(Panel, {VncWidth, VncHeight}), + + wxGLCanvas:connect(Panel, key_down), + wxGLCanvas:connect(Panel, key_up), + + wxGLCanvas:setCurrent(Panel, Context), + + VncProxy = virtuerl_vnc_proxy:start(DomainId, Node), + {VncWidth, VncHeight} = receive + {conf, TVncWidth, TVncHeight} -> {TVncWidth, TVncHeight} + after 1000 -> + io:format("the foock?~n"), + {700, 400} + end, + + VncProxy ! {framebuffer_update_request, 0, 0, 0, VncWidth, VncHeight}, + + gl:enable(?GL_TEXTURE_2D), + [TexId] = gl:genTextures(1), + io:format("TEXTURE ID: ~p~n", [TexId]), + gl:bindTexture(?GL_TEXTURE_2D, TexId), + gl:texImage2D(?GL_TEXTURE_2D, 0, ?GL_RGB, VncWidth, VncHeight, 0, ?GL_RGB, ?GL_UNSIGNED_BYTE, 0), + gl:texParameteri(?GL_TEXTURE_2D,?GL_TEXTURE_MIN_FILTER,?GL_LINEAR), + Err = gl:getError(), + io:format("ERROR: ~p~n", [glu:errorString(Err)]), + wxGLCanvas:connect(Panel, erase_background, [{callback, fun(_,_) -> ok end}]), + wxGLCanvas:connect(Panel, paint), + {Panel, #state{socket=VncProxy, parent = Parent, panel = Panel, tex_id = TexId, vnc_conf = {VncWidth, VncHeight}}}. + +%% Handled as in normal gen_server callbacks +handle_info({framebuffer_update, Rects}, #state{socket = Proxy, panel = Panel, tex_id = TexId, vnc_conf = {VncWidth, VncHeight}} = State) -> +%% io:format("got framebuffer_update~n"), + wx:batch(fun() -> + gl:bindTexture(?GL_TEXTURE_2D, TexId), + [gl:texSubImage2D(?GL_TEXTURE_2D, 0, X, Y, Width, Height, ?GL_BGRA, ?GL_UNSIGNED_BYTE, Data) || {X, Y, Width, Height, Data} <- Rects] + end), + gl:flush(), + wxPanel:refresh(Panel, [{eraseBackground, false}]), +%% io:format("Writing rects took ~p/~p~n", [TimeRects, TimeRes]), + + + Proxy ! {framebuffer_update_request, 1, 0, 0, VncWidth, VncHeight}, + {noreply, State}; + +handle_info(Msg, State) -> + io:format("Got Info ~p~n",[Msg]), + {noreply,State}. + +handle_event(#wx{event=#wxPaint{type = paint}, obj = _Obj}, State = #state{parent = Parent, win=Frame, panel=Panel, tex_id = TexId, vnc_conf = {Width, Height}}) -> + {W, H} = wxGLCanvas:getSize(Panel), + Wscale = W / Width, + Hscale = H / Height, + {Scale, Xt, Yt} = case Wscale > Hscale of + true -> + {Hscale, max((W - Width*Hscale) / 2, 0), 0}; + false -> + {Wscale, 0, max((H - Height*Wscale) / 2, 0)} + end, + + {ScaleX, ScaleY} = {Width * Scale / W, Height * Scale / H}, + gl:viewport(0,0,W,H), + + gl:matrixMode(?GL_MODELVIEW), + gl:loadIdentity(), + gl:scalef(ScaleX, ScaleY, 0.0), + gl:clearColor(0.2, 0.2, 0.2, 1.0), + gl:clear(?GL_COLOR_BUFFER_BIT bor ?GL_DEPTH_BUFFER_BIT), + gl:bindTexture(?GL_TEXTURE_2D, TexId), + gl:enable(?GL_TEXTURE_2D), + gl:'begin'(?GL_QUADS), + + gl:texCoord2f(0.0, 1.0), + gl:vertex2f(-1.0, -1.0), + gl:texCoord2f(0.0, 0.0), + gl:vertex2f(-1.0, 1.0), + gl:texCoord2f(1.0, 0.0), + gl:vertex2f( 1.0, 1.0), + gl:texCoord2f(1.0, 1.0), + gl:vertex2f( 1.0, -1.0), + + gl:'end'(), + gl:flush(), + wxGLCanvas:swapBuffers(Panel), + {noreply, State}; +handle_event(#wx{event=#wxKey{type = Type, keyCode = KeyCode, rawCode = Key}} = Event, State = #state{win=Frame, panel=Panel, socket = Socket}) -> + io:format("Event ~p~n", [Event]), + DownFlag = case Type of + key_down -> 1; + key_up -> 0 + end, + Socket ! {key_event, DownFlag, Key}, + {noreply, State}; +handle_event(#wx{id = ?wxID_EXIT, event = #wxCommand{type = command_menu_selected}}, State = #state{win=Frame}) -> + io:format("~p Quitting window ~n",[self()]), + ok = wxFrame:setStatusText(Frame, "Closing...",[]), + wxWindow:destroy(Frame), + {stop, normal, State}; +handle_event(Event, State) -> + io:format("Got Event ~p~n",[Event]), + {noreply, State}. + +code_change(_, _, State) -> + {stop, not_yet_implemented, State}. + +terminate(_Reason, _State) -> + ok. diff --git a/src/virtuerl_vnc_proxy.erl b/src/virtuerl_vnc_proxy.erl new file mode 100644 index 0000000..78bd607 --- /dev/null +++ b/src/virtuerl_vnc_proxy.erl @@ -0,0 +1,143 @@ +%%%------------------------------------------------------------------- +%%% @author ilya +%%% @copyright (C) 2023, +%%% @doc +%%% @end +%%%------------------------------------------------------------------- +-module(virtuerl_vnc_proxy). + +-export([start/2]). + +-define(SERVER, ?MODULE). +-define(APPLICATION, virtuerl). + +-define(vncINVALID, 0). +-define(vncNONE, 1). +-define(vncVNC_AUTH, 2). +-define(vncOK, 0). +-define(vncFAILED, 1). +-define(vncMSG_TYPE_FramebufferUpdate, 0). +-define(vncMSG_TYPE_FramebufferUpdateRequest, 3). +-define(vncMSG_TYPE_KeyEvent, 4). +-define(vncMSG_TYPE_PointerEvent, 5). +-define(vncENCODING_RAW, 0). + +-record(state, {win, socket, panel, tex_id, vnc_conf}). +-record(vnc_conf, {width, height}). + +%%%=================================================================== +%%% Spawning and gen_server implementation +%%%=================================================================== + +start(DomainId, Node) -> + VncSocketPath = filename:join([virtuerl_mgt:home_path(), "domains", DomainId, "vnc.sock"]), + Self = self(), + spawn(Node, fun() -> init(VncSocketPath, Self) end). + +init(VncSocketPath, Sender) -> + {ok, Socket} = gen_tcp:connect({local, VncSocketPath}, 0, [{active, true}, binary, local]), + receive + {tcp, Socket, <<"RFB ", Major:3/binary, ".", Minor:3/binary, "\n">>} -> + io:format("Major: ~s / Minor: ~s~n", [Major, Minor]), + ok + after 2000 -> + timeout + end, + + ok = gen_tcp:send(Socket, <<"RFB 003.008\n">>), + receive + {tcp, Socket, <>} -> + % TODO: AuthTypes is a list + io:format("AuthType: ~p~n", [AuthType]), + case AuthType of + ?vncINVALID -> {error, auth_invalid}; + ?vncNONE -> ok; + ?vncVNC_AUTH -> {error, auth_not_supported}; + _ -> io:format("Unknown auth: ~p~n", [AuthType]) + end + after 2000 -> + {error, timeout} + end, + + ok = gen_tcp:send(Socket, <>), + receive + {tcp, Socket, <>} -> + io:format("Security result: ~B~n", [SecurityResult]), + case SecurityResult of + ?vncOK -> ok; + ?vncFAILED -> error + end + after 2000 -> + {error, timeout} + end, + + ok = gen_tcp:send(Socket, <<1>>), + VncConf = receive + {tcp, Socket, <>} -> + io:format("~s: ~Bx~B~n~B/~B (BE: ~B, TC: ~B)~nR:(X>>~B)&~B G:(X>>~B)&~B B:(X>>~B)&~B~n", [Name, Width, Height, Depth, BitsPerPixel, BigEndianFlag, TrueColorFlag, RedShift, RedMax, GreenShift, GreenMax, BlueShift, BlueMax]), + #vnc_conf{width = Width, height = Height} + after 2000 -> + {error, timeout} + end, + #vnc_conf{width = VncWidth, height = VncHeight} = VncConf, + inet:setopts(Socket, [{active, once}]), + + Sender ! {conf, VncWidth, VncHeight}, + + monitor(process, Sender), + loop(Socket, Sender). + +loop(Socket, Sender) -> + receive + {tcp, Socket, <>} -> + {TimeRects, Rects} = timer:tc(fun() -> read_rects(NumRects, Rest, Socket) end), + Sender ! {framebuffer_update, Rects}, + inet:setopts(Socket, [{active, once}]), + loop(Socket, Sender); + {tcp, Socket,_Data} -> + io:format("Got data: ~p~n", [_Data]), + inet:setopts(Socket, [{active, once}]), + loop(Socket, Sender); + {framebuffer_update_request, Incremental, X, Y, Width, Height} -> + ok = gen_tcp:send(Socket, <>), + loop(Socket, Sender); + {key_event, DownFlag, Key} -> + ok = gen_tcp:send(Socket, <>), + loop(Socket, Sender); + {'DOWN', Ref, process, Pid, Reason} -> + io:format("Terminating because: ~p (~p)~n", [Reason, Pid]); + _Message -> + io:format("Got message: ~p~n", [_Message]), + loop(Socket, Sender) +end. + +read_rects(0, _Data, _Socket) -> + []; +read_rects(NumRects, Data, Socket) -> + case Data of + <> -> + case Encoding of + ?vncENCODING_RAW -> + NumBytes = Width * Height * 4, + case NumBytes > byte_size(Rest) of + true -> + BytesToFetch = NumBytes - byte_size(Rest) + (NumRects - 1) * 12, + {ok, MoreData} = gen_tcp:recv(Socket, BytesToFetch), + read_rects(NumRects, <>, Socket); + false -> + <> = Rest, + Rect = {X, Y, Width, Height, PixelBytes}, + Rects = read_rects(NumRects - 1, ActualRest, Socket), + [Rect | Rects] + end; + _ -> io:format("Unsupported encoding ~p~n", [Encoding]) + end; + _ -> + {ok, MoreData} = gen_tcp:recv(Socket, 12 - byte_size(Data) + (NumRects - 1) * 12), + read_rects(NumRects, <>, Socket) + end. diff --git a/test/ipam.erl b/test/ipam.erl new file mode 100644 index 0000000..b448f90 --- /dev/null +++ b/test/ipam.erl @@ -0,0 +1,83 @@ +-module(ipam). +-include_lib("eunit/include/eunit.hrl"). +-include_lib("khepri/src/khepri_error.hrl"). + + +simple_test() -> + ?assert(0 == 0). + +fixture_test_() -> + {setup, + fun() -> + {ok, StoreId} = khepri:start("khepri-test", test), + {ok, Pid} = virtuerl_app:start_server(StoreId), + {StoreId, Pid} + end, + fun({StoreId, Pid}) -> + virtuerl_app:stop_server(Pid), + khepri:stop(StoreId), + case file:del_dir_r("khepri-test") of + {error, enoent} -> + ok; + ok -> + ok + end + end, + fun({StoreId, Pid}) -> + [ + {"network4", fun() -> network_test_4(StoreId) end}, + {"network6", fun() -> network_test_6(StoreId) end}, + {"test-me", fun() -> test_me(StoreId) end}, + {"test-me6", fun() -> test_me6(StoreId) end} + ] + end + }. + +network_test_4(StoreId) -> + NetId = <<"aabbcc">>, + {error, network_too_small} = virtuerl_app:ipam_put_net({NetId, <<192:8, 168:8, 10:8, 0:8>>, 29}), + {error, ?khepri_error(node_not_found, _)} = khepri:get(StoreId, [network, NetId]), + ok = khepri:delete(StoreId, [network, NetId]). + +network_test_6(StoreId) -> + NetId = <<"aabbcc">>, + {error, network_too_small} = virtuerl_app:ipam_put_net({NetId, <<16#20010db8000000000000000000000000:128>>, 125}), + {error, ?khepri_error(node_not_found, _)} = khepri:get(StoreId, [network, NetId]), + ok = khepri:delete(StoreId, [network, NetId]). + +test_me(StoreId) -> + NetId = <<"aabbcc">>, + {error, network_not_found} = virtuerl_app:ipam_next_ip(NetId), + ok = virtuerl_app:ipam_put_net({NetId, <<192:8, 168:8, 10:8, 0:8>>, 28}), +%% {ok, NextIP} = virtuerl_app:ipam_next_ip(NetId), + {ok, Net} = khepri:get(StoreId, [network, NetId]), + ?debugVal(Net), + {Time, {ok, NextIP}} = timer:tc(virtuerl_app, ipam_next_ip, [NetId]), + ?debugVal(Time), + ?debugVal(NextIP), + ?assertEqual(<<192:8, 168:8, 10:8, 8:8>>, NextIP), + {TimeSecond, {ok, _}} = timer:tc(virtuerl_app, ipam_next_ip, [NetId]), + ?debugVal(TimeSecond), + [{ok, _} = virtuerl_app:ipam_next_ip(NetId) || _ <- lists:seq(1, 6)], + {TimeLast, {error, no_ip_available}} = timer:tc(virtuerl_app, ipam_next_ip, [NetId]), + ?debugVal(TimeLast), + ok = khepri:delete(StoreId, [network, NetId]). + +test_me6(StoreId) -> + NetId = <<"aabbcc">>, + {error, network_not_found} = virtuerl_app:ipam_next_ip(NetId), + ok = virtuerl_app:ipam_put_net({NetId, <<16#20010db8000000000000000000000000:128>>, 124}), +%% {ok, NextIP} = virtuerl_app:ipam_next_ip(NetId), + {ok, Net} = khepri:get(StoreId, [network, NetId]), + ?debugVal(Net), + {Time, {ok, NextIP}} = timer:tc(virtuerl_app, ipam_next_ip, [NetId]), + ?debugVal(Time), + ?debugVal(NextIP), + ?assertEqual(<<16#20010db8000000000000000000000008:128>>, NextIP), + {TimeSecond, {ok, _}} = timer:tc(virtuerl_app, ipam_next_ip, [NetId]), + ?debugVal(TimeSecond), + [{ok, _} = virtuerl_app:ipam_next_ip(NetId) || _ <- lists:seq(1, 6)], + {TimeLast, {error, no_ip_available}} = timer:tc(virtuerl_app, ipam_next_ip, [NetId]), + ?debugVal(TimeLast), + ok = khepri:delete(StoreId, [network, NetId]). + diff --git a/test/scheduler.erl b/test/scheduler.erl new file mode 100644 index 0000000..2f37601 --- /dev/null +++ b/test/scheduler.erl @@ -0,0 +1,6 @@ +-module(scheduler). +-include_lib("eunit/include/eunit.hrl"). + +simple_2_test() -> + ?assert(0==0). + diff --git a/test/virtuerl_SUITE.erl b/test/virtuerl_SUITE.erl new file mode 100644 index 0000000..7ecb3aa --- /dev/null +++ b/test/virtuerl_SUITE.erl @@ -0,0 +1,163 @@ +%%%------------------------------------------------------------------- +%%% @author ilya +%%% @copyright (C) 2023, +%%% @doc +%%% +%%% @end +%%% Created : 11. Sep 2023 9:48 PM +%%%------------------------------------------------------------------- +-module(virtuerl_SUITE). +-author("ilya"). + +%% API +-export([all/0, init_per_suite/1, end_per_suite/1]). +-export([test_domain/1, test_network/1, test_create_domain/1, test_create_domain_dualstack/1]). + +all() -> [test_create_domain, test_create_domain_dualstack]. + +init_per_suite(Config) -> + {ok, _} = application:ensure_all_started(virtuerl), + Config. + +end_per_suite(_Config) -> + application:stop(virtuerl). + +test_network(_Config) -> + NetJson = thoas:encode(#{"network" => #{"cidr4" => "192.168.111.0/24", "cidr6" => "2001:db8::/80"}}), + {ok, {{_, 201, _}, Headers, _}} = httpc:request(post, {"http://localhost:8081/networks", [], "application/json", NetJson}, [], []), + Loc = proplists:get_value("location", Headers), + NetUri = uri_string:resolve(Loc, "http://localhost:8081"), + + {ok, {{_, 200, _}, _, NetBody}} = httpc:request(get, {NetUri, []}, [], []), + {ok, #{<<"cidrs">> := [<<"192.168.111.0/24">>, <<"2001:db8::/80">>]}} = thoas:decode(NetBody), + + {ok, {{_, 204, _}, _, _}} = httpc:request(delete, {NetUri, []}, [], []). + +test_domain(_Config) -> + NetJson = thoas:encode(#{"network" => #{"cidr4" => "192.168.111.0/24", "cidr6" => "2001:db8::/80"}}), + {ok, {{_, 201, _}, NetHeaders, NetBody}} = httpc:request(post, {"http://localhost:8081/networks", [], "application/json", NetJson}, [], []), + NetLoc = proplists:get_value("location", NetHeaders), + NetUri = uri_string:resolve(NetLoc, "http://localhost:8081"), + {ok, #{<<"id">> := NetId}} = thoas:decode(NetBody), + + DomainJson = thoas:encode(#{"domain" => #{"network_id" => NetId, "ipv4_addr" => "192.168.111.39"}}), + {ok, {{_, 201, _}, DomainHeaders, DomainBody}} = httpc:request(post, {"http://localhost:8081/domains", [], "application/json", DomainJson}, [], []), + DomainLoc = proplists:get_value("location", DomainHeaders), + DomainUri = uri_string:resolve(DomainLoc, "http://localhost:8081"), + {ok, #{<<"mac_addr">> := MacAddr, <<"ipv4_addr">> := <<"192.168.111.39">>, <<"ipv6_addr">> := <<"2001:db8::8">>}} = thoas:decode(DomainBody), + <<_:6, Laa:2, _/binary>> = binary:decode_hex(MacAddr), + 2 = Laa, + +%% {ok, {{_, 200, _}, _, DomainBody1}} = httpc:request(DomainUri), +%% {ok, #{<<"mac_addr">> := MacAddr, <<"ipv4_addr">> := <<"192.168.111.39">>, <<"ipv6_addr">> := <<"2001:db8::8">>}} = thoas:decode(DomainBody1), + + {ok, {{_, 204, _}, _, _}} = httpc:request(delete, {DomainUri, []}, [], []), + {ok, {{_, 204, _}, _, _}} = httpc:request(delete, {NetUri, []}, [], []). + +test_create_domain(_Config) -> + {ok, NetID} = virtuerl_ipam:ipam_create_net([{<<192:8,168:8,17:8,0:8>>, 24}]), + {ok, #{id := DomId, ipv4_addr := <<"192.168.17.8">>}} = virtuerl_mgt:domain_create(#{name => "test_domain", vcpu => 1, memory => 512, network_id => NetID, + inbound_rules => [#{protocols => ["tcp"], target_ports => [80]}], + user_data => +"#cloud-config + +users: + - name: tester + passwd: $6$Cf1HnaIWk8TunKFs$40sITB7utYJbVL9kkmhVwzCW33vbq55IGSbpLp1AqOufng1qNxf8wyHj4fdp3xMAfr0yrGioiWwtRvbN58rlI. + lock_passwd: false + shell: /bin/bash + sudo: ALL=(ALL) NOPASSWD:ALL + ssh_authorized_keys: + - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBDCT3LrJenezXzP9T6519IgpVCP1uv6f5iQwZ+IDdFc + +apt: + primary: + - arches: [default] + search: + - http://mirror.ipb.de/debian/ + - http://security.debian.org/debian-security + +packages: + - nginx + +runcmd: + - service nginx restart +" + }), % password: asd + {ok, {{_, 200, _}, _, _}} = wait_for_http("http://192.168.17.8/", 5*60*1000), + + % make sure address is actually released and reused + virtuerl_pubsub:subscribe(), + virtuerl_mgt:domain_delete(#{id => DomId}), + receive + {domain_deleted, DomId} ->ok + end, + {ok, #{id := Dom2Id, ipv4_addr := <<"192.168.17.8">>}} = virtuerl_mgt:domain_create(#{name => "test_domain_2", vcpu => 1, memory => 512, network_id => NetID, user_data => ""}), + virtuerl_mgt:domain_delete(#{id => Dom2Id}), + receive + {domain_deleted, Dom2Id} ->ok + end, + + virtuerl_ipam:ipam_delete_net(NetID), + + ok. + +test_create_domain_dualstack(_Config) -> + {ok, NetID} = virtuerl_ipam:ipam_create_net([{<<192:8,168:8,17:8,0:8>>, 24}, {<<16#fd58:16, 16#40cb:16, 16#bddb:16, 0:80>>, 48}]), + {ok, #{id := DomId, ipv4_addr := <<"192.168.17.8">>, ipv6_addr := <<"fd58:40cb:bddb::8">>}} = virtuerl_mgt:domain_create(#{name => "test_domain", vcpu => 1, memory => 512, network_id => NetID, + inbound_rules => [#{protocols => ["tcp"], target_ports => [80]}], + user_data => +"#cloud-config + +users: + - name: tester + passwd: $6$Cf1HnaIWk8TunKFs$40sITB7utYJbVL9kkmhVwzCW33vbq55IGSbpLp1AqOufng1qNxf8wyHj4fdp3xMAfr0yrGioiWwtRvbN58rlI. + lock_passwd: false + shell: /bin/bash + sudo: ALL=(ALL) NOPASSWD:ALL + ssh_authorized_keys: + - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBDCT3LrJenezXzP9T6519IgpVCP1uv6f5iQwZ+IDdFc + +apt: + primary: + - arches: [default] + search: + - http://mirror.ipb.de/debian/ + - http://security.debian.org/debian-security + +packages: + - nginx + +runcmd: + - service nginx restart +" + }), % password: asd + {ok, {{_, 200, _}, _, _}} = wait_for_http("http://[fd58:40cb:bddb::8]/", 5*60*1000), + {ok, {{_, 200, _}, _, _}} = wait_for_http("http://192.168.17.8/", 5*60*1000), + + virtuerl_pubsub:subscribe(), + virtuerl_mgt:domain_delete(#{id => DomId}), + receive + {domain_deleted, DomId} ->ok + end, + + virtuerl_ipam:ipam_delete_net(NetID), + + ok. + +wait_for_http(Url, Timeout) -> + Deadline = erlang:system_time(millisecond) + Timeout, + do_wait_for_http(Url, Deadline). + +do_wait_for_http(Url, Deadline) -> + case httpc:request(get, {Url, []}, [{timeout, 1000}], []) of + {error, _} -> + case erlang:system_time(millisecond) > Deadline of + true -> + {error, timeout}; + false -> + timer:sleep(2000), + do_wait_for_http(Url, Deadline) + end; + Res -> Res + end. diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py deleted file mode 100644 index fa437d2..0000000 --- a/tests/integration/test_integration.py +++ /dev/null @@ -1,211 +0,0 @@ -import datetime -import io -from time import sleep -from urllib.parse import urljoin -from urllib.request import urlopen - -import grpc -import pytest -from fabric import Connection -from paramiko.ed25519key import Ed25519Key - -from minivirt import controller_pb2_grpc, domain_pb2, port_forwarding_pb2, volume_pb2 - - -@pytest.fixture -def client(): - channel = grpc.secure_channel( - "localhost:8093", - grpc.ssl_channel_credentials( - root_certificates=b"""-----BEGIN CERTIFICATE----- -MIICCjCCAY+gAwIBAgIUSl7KWjtgvG9rNMz7hhYRKy5LsB8wCgYIKoZIzj0EAwIw -RDELMAkGA1UEBhMCREUxDzANBgNVBAgMBkJlcmxpbjERMA8GA1UECgwIbWluaXZp -cnQxETAPBgNVBAMMCG1pbml2aXJ0MB4XDTIzMDcwODIzMjUyMFoXDTMzMDcwNTIz -MjUyMFowRDELMAkGA1UEBhMCREUxDzANBgNVBAgMBkJlcmxpbjERMA8GA1UECgwI -bWluaXZpcnQxETAPBgNVBAMMCG1pbml2aXJ0MHYwEAYHKoZIzj0CAQYFK4EEACID -YgAEI3nOFzsWO3w8qGLSjDSiX3OWCH7qBRcTjt/luPjXLqe3DVcFQPLYN31PaggR -o0jCjrKklxqtzHmmMdMRnyoRPbQOQPRa9N177a2s97M5ZJQVkeFL8WRUf7x1P0Cd -SZvco0IwQDAdBgNVHQ4EFgQUde9ormRTZys4Nt81qAPcSm1qQWMwDwYDVR0TAQH/ -BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwIDaQAwZgIxAJTpUOUz -RJwMLNUEa4qIgdamMuqyl5h6ghT9zX5BLsX3cFs+MqJ/J0HcKDJd81lVlQIxAPCG -hy/zgj6wy28S8GWe8KdbZ73BJtC5MyOu6hHD9EpZ1hT8K3q0VIwMEyUMmbv3Xw== ------END CERTIFICATE----- -""", - certificate_chain=b"""-----BEGIN CERTIFICATE----- -MIICeTCCAf+gAwIBAgIUVG1gWhFwRQ5kzxwRzF6V5h6D8ZYwCgYIKoZIzj0EAwIw -RDELMAkGA1UEBhMCREUxDzANBgNVBAgMBkJlcmxpbjERMA8GA1UECgwIbWluaXZp -cnQxETAPBgNVBAMMCG1pbml2aXJ0MB4XDTIzMDcwODIzMzA0MloXDTI1MDcwNzIz -MzA0MlowRDELMAkGA1UEBhMCREUxDzANBgNVBAgMBkJlcmxpbjERMA8GA1UECgwI -bWluaXZpcnQxETAPBgNVBAMMCG1pbml2aXJ0MHYwEAYHKoZIzj0CAQYFK4EEACID -YgAEHWV9eL3/egpqcgTaMDPWga2xpfTZCc66yNxkVGPsw5BWE/EXvWtuUCjDmHWo -HOdrbt7iI9lA9VnSwlC9PeIvX4lK2dXNOpn3GJlZ8JkjpZZBg0mxaUt6vQMyGSco -cOOAo4GxMIGuMB8GA1UdIwQYMBaAFHXvaK5kU2crODbfNagD3EptakFjMAwGA1Ud -EwEB/wQCMAAwDgYDVR0PAQH/BAQDAgWgMCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMB -BggrBgEFBQcDAjAsBgNVHREEJTAjgglsb2NhbGhvc3SHBH8AAAGHEAAAAAAAAAAA -AAAAAAAAAAEwHQYDVR0OBBYEFDtlzMapZ4eV0/m8ina1GM3biELeMAoGCCqGSM49 -BAMCA2gAMGUCMECXLmHsWMTaFRK+qWaBRZMLuhFNixMsSmmHHIqGlvIrWFa5MiN6 -RaZ7aTGa/HMKZAIxAPRJZ11Vp1BNBszSiswk32hsck4JP9h1hn00IMu33iK0+q22 -Jv73oZy3l4gQmLlCDg== ------END CERTIFICATE----- -""", - private_key=b"""-----BEGIN PRIVATE KEY----- -MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDCIJQuTNENeEnhjCtdV -GXoFgh69LQ+Ms5B/i0jWMSqMALL26uG6NNM4nvHgIMbChKGhZANiAAQdZX14vf96 -CmpyBNowM9aBrbGl9NkJzrrI3GRUY+zDkFYT8Re9a25QKMOYdagc52tu3uIj2UD1 -WdLCUL094i9fiUrZ1c06mfcYmVnwmSOllkGDSbFpS3q9AzIZJyhw44A= ------END PRIVATE KEY----- -""", - ), - ) - return controller_pb2_grpc.ControllerServiceStub(channel) - - -def test_create_domain_ipv4_linux(client: controller_pb2_grpc.ControllerServiceStub): - network = client.CreateNetwork( - domain_pb2.CreateNetworkRequest( - network=domain_pb2.Network( - name="restvirt", - cidr="192.168.69.0/24", - ) - ) - ) - dom = client.CreateDomain( - domain_pb2.CreateDomainRequest( - domain=domain_pb2.Domain( - name="test", - vcpu=1, - memory=512, - private_ip="192.168.69.69", - network=network.uuid, - user_data="""#cloud-config""", - ), - ) - ) - dom = client.GetDomain(domain_pb2.GetDomainRequest(uuid=dom.uuid)) - assert dom.private_ip == "192.168.69.69" - - client.DeleteDomain(domain_pb2.DeleteDomainRequest(uuid=dom.uuid)) - client.DeleteNetwork(domain_pb2.DeleteNetworkRequest(uuid=network.uuid)) - - -def test_create_domain_linux(client: controller_pb2_grpc.ControllerServiceStub): - network = client.CreateNetwork( - domain_pb2.CreateNetworkRequest( - network=domain_pb2.Network( - name="restvirt", - cidr="192.168.69.0/24", - cidr6="fd8d:dd47:05bc:5307::/64", - ) - ) - ) - dom = client.CreateDomain( - domain_pb2.CreateDomainRequest( - domain=domain_pb2.Domain( - name="test", - vcpu=1, - memory=512, - private_ip="192.168.69.69", - ipv6_address="fd8d:dd47:05bc:5307::10", - network=network.uuid, - user_data="""#cloud-config - -users: - - name: tester - shell: /bin/bash - sudo: ALL=(ALL) NOPASSWD:ALL - ssh_authorized_keys: - - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBDCT3LrJenezXzP9T6519IgpVCP1uv6f5iQwZ+IDdFc - -packages: - - nginx - -runcmd: - - service nginx restart -""", - ), - ) - ) - - dom = client.GetDomain(domain_pb2.GetDomainRequest(uuid=dom.uuid)) - assert dom.ipv6_address == "fd8d:dd47:05bc:5307::10" - - fwd = port_forwarding_pb2.PortForwarding( - protocol="tcp", - source_port=8080, - target_ip="192.168.69.69", - target_port=80, - ) - client.PutPortForwarding(port_forwarding_pb2.PutPortForwardingRequest(port_forwarding=fwd)) - - response = wait_for_http("http://192.168.69.1:8080") - assert "Welcome to nginx!" in response - - private_ssh_key = """-----BEGIN OPENSSH PRIVATE KEY----- -b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW -QyNTUxOQAAACAQwk9y6yXp3s18z/U+udfSIKVQj9br+n+YkMGfiA3RXAAAAJhEeSndRHkp -3QAAAAtzc2gtZWQyNTUxOQAAACAQwk9y6yXp3s18z/U+udfSIKVQj9br+n+YkMGfiA3RXA -AAAEC9jx0pxfKwyPq3RpOsCef7UA2pqBzAj2+bqVTix2f0fRDCT3LrJenezXzP9T6519Ig -pVCP1uv6f5iQwZ+IDdFcAAAAFWlseWFASWx5YXMtaU1hYy5sb2NhbA== ------END OPENSSH PRIVATE KEY----- -""" - with io.StringIO(private_ssh_key) as f: - pkey = Ed25519Key.from_private_key(f) - conn = Connection("192.168.69.69", "tester", connect_kwargs={"pkey": pkey}) - conn.config.run.in_stream = False - - old_root_vol_size = int(conn.run("lsblk /dev/vda -bndo SIZE").stdout) - assert old_root_vol_size == 20 * 1024**3 - - volumes = client.ListVolumes(volume_pb2.ListVolumesRequest()) - volumes = {v.name: v for v in volumes.volumes} - root_vol_name = f"{dom.name}-root.qcow2" - assert root_vol_name in volumes - - wait_until_stopped(client, dom.uuid) - client.UpdateVolume( - volume_pb2.UpdateVolumeRequest( - volume=volume_pb2.Volume(id=root_vol_name, size=30 * 1024**3) - ) - ) - - client.StartDomain(domain_pb2.StartDomainRequest(uuid=dom.uuid)) - response = wait_for_http("http://192.168.69.1:8080") - assert "Welcome to nginx!" in response - response = wait_for_http("http://[fd8d:dd47:05bc:5307::10]") - assert "Welcome to nginx!" in response - - new_root_vol_size = int(conn.run("lsblk /dev/vda -bndo SIZE").stdout) - assert new_root_vol_size == 30 * 1024**3 - - client.DeletePortForwarding( - port_forwarding_pb2.PortForwardingIdentifier(protocol="tcp", source_port=8080) - ) - client.DeleteDomain(domain_pb2.DeleteDomainRequest(uuid=dom.uuid)) - client.DeleteNetwork(domain_pb2.DeleteNetworkRequest(uuid=network.uuid)) - - -def wait_for_http(server, path="/", timeout=datetime.timedelta(seconds=180)): - end_time = datetime.datetime.now() + timeout - while True: - try: - with urlopen(urljoin(server, path)) as resp: - return resp.read().decode() - except: - now = datetime.datetime.now() - if now >= end_time: - raise Exception("Timed out") - sleep(min((end_time - now).total_seconds(), 10)) - - -def wait_until_stopped(client, domain_uuid, timeout=datetime.timedelta(seconds=180)): - end_time = datetime.datetime.now() + timeout - while True: - dom = client.GetDomain(domain_pb2.GetDomainRequest(uuid=domain_uuid)) - if dom.state == "SHUTOFF": - break - - client.StopDomain(domain_pb2.StopDomainRequest(uuid=domain_uuid)) - now = datetime.datetime.now() - if now >= end_time: - raise Exception("Timed out") - sleep(min((end_time - now).total_seconds(), 10)) diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py deleted file mode 100644 index bce86d3..0000000 --- a/tests/unit/conftest.py +++ /dev/null @@ -1,231 +0,0 @@ -import contextlib -import sys -from concurrent import futures - -import grpc -import pytest -from google.protobuf import empty_pb2 -from sqlalchemy import create_engine, event -from sqlalchemy.engine import Engine -from sqlalchemy.orm import sessionmaker -from sqlalchemy.pool import StaticPool - -from minivirt import controller_pb2_grpc, daemon_pb2_grpc, host_pb2, host_pb2_grpc -from minivirt.controller import Controller -from minivirt.dns_controller import DNSController -from minivirt.host import HostController, HostService -from minivirt.models import Base - -OPERATING_SYSTEMS = {"darwin", "linux", "windows"} - - -def pytest_addoption(parser): - parser.addoption("--pool-dir", default="/data/restvirt") - - -@pytest.fixture(scope="session") -def pool_dir(pytestconfig): - return pytestconfig.getoption("pool_dir") - - -def pytest_configure(config): - for os in OPERATING_SYSTEMS: - config.addinivalue_line("markers", f"{os}: mark tests to only run on {os} operating system") - - -def pytest_runtest_setup(item): - supported_platforms = OPERATING_SYSTEMS.intersection(mark.name for mark in item.iter_markers()) - plat = sys.platform - if supported_platforms and plat not in supported_platforms: - pytest.skip("cannot run on platform {}".format(plat)) - - -def pytest_collection_modifyitems(items): - for item in items: - os = item.nodeid.split("_")[-1] - if os in OPERATING_SYSTEMS: - item.add_marker(os) - - -@event.listens_for(Engine, "connect") -def set_sqlite_pragma(dbapi_connection, connection_record): - cursor = dbapi_connection.cursor() - cursor.execute("PRAGMA foreign_keys=ON") - cursor.close() - - -@pytest.fixture -def engine(): - engine = create_engine( - "sqlite:///:memory:", - connect_args={"check_same_thread": False}, - poolclass=StaticPool, - echo=True, - future=True, - ) - Base.metadata.create_all(engine) - yield engine - Base.metadata.drop_all(engine) - - -@pytest.fixture -def session_factory(engine): - return sessionmaker(engine, future=True) - - -@pytest.fixture -def dns_controller(session_factory): - return DNSController(session_factory) - - -class DaemonDummy(daemon_pb2_grpc.DaemonServiceServicer): - def SyncRoutes(self, request, context): - return empty_pb2.Empty() - - -@pytest.fixture -def controller_channel(session_factory, dns_controller): - server = grpc.server(futures.ThreadPoolExecutor(max_workers=10), interceptors=[]) - port = server.add_insecure_port("localhost:0") - channel = grpc.insecure_channel(f"localhost:{port}") - - host_controller = HostController(session_factory) - controller_pb2_grpc.add_ControllerServiceServicer_to_server( - Controller(session_factory, host_controller, dns_controller), - server, - ) - host_pb2_grpc.add_HostServiceServicer_to_server( - HostService(host_controller, session_factory), server - ) - - dns_controller.start() - server.start() - yield channel - server.stop(1) - dns_controller.stop() - - -@pytest.fixture -def host_client(controller_channel): - return host_pb2_grpc.HostServiceStub(controller_channel) - - -@pytest.fixture -def controller_client_dummy(controller_channel, host_client): - daemon = grpc.server(futures.ThreadPoolExecutor(max_workers=10), interceptors=[]) - daemon_port = daemon.add_insecure_port("localhost:0") - - daemon_pb2_grpc.add_DaemonServiceServicer_to_server(DaemonDummy(), daemon) - - token = host_client.CreateBootstrapToken(host_pb2.CreateBootstrapTokenRequest()).token - host_client.Register( - host_pb2.RegisterHostRequest( - token=token, - host=host_pb2.Host( - name="test", - address=f"localhost:{daemon_port}", - ), - ) - ) - - daemon.start() - yield controller_pb2_grpc.ControllerServiceStub(controller_channel) - daemon.stop(1) - - -@pytest.fixture -def controller_client(session_factory, controller_channel, host_client): - from minivirt.daemon import DaemonService - from minivirt.port_forwarding import IPTablesPortForwardingSynchronizer - from minivirt.utils import UnaryUnaryInterceptor - - daemon = grpc.server( - futures.ThreadPoolExecutor(max_workers=10), interceptors=[UnaryUnaryInterceptor()] - ) - daemon_port = daemon.add_insecure_port("localhost:0") - - daemon_service = DaemonService( - session_factory, - IPTablesPortForwardingSynchronizer( - controller_pb2_grpc.ControllerServiceStub(controller_channel), - ), - controller_channel, - ) - daemon_pb2_grpc.add_DaemonServiceServicer_to_server( - daemon_service, - daemon, - ) - daemon_service.sync() - - token = host_client.CreateBootstrapToken(host_pb2.CreateBootstrapTokenRequest()).token - host_client.Register( - host_pb2.RegisterHostRequest( - token=token, - host=host_pb2.Host( - name="test", - address=f"localhost:{daemon_port}", - ), - ) - ) - - daemon.start() - yield controller_pb2_grpc.ControllerServiceStub(controller_channel) - daemon.stop(1) - - -@contextlib.contextmanager -def dummy_controller(dns_port=53): - engine = create_engine( - "sqlite:///:memory:", - connect_args={"check_same_thread": False}, - poolclass=StaticPool, - echo=False, - future=True, - ) - Base.metadata.create_all(engine) - session_factory = sessionmaker(engine) - - server = grpc.server(futures.ThreadPoolExecutor(max_workers=10), interceptors=[]) - port = server.add_insecure_port("localhost:0") - controller_channel = grpc.insecure_channel(f"localhost:{port}") - - dns_controller = DNSController(session_factory) - - host_controller = HostController(session_factory) - controller_pb2_grpc.add_ControllerServiceServicer_to_server( - Controller(session_factory, host_controller, dns_controller), - server, - ) - host_pb2_grpc.add_HostServiceServicer_to_server( - HostService(host_controller, session_factory), server - ) - - dns_controller.start(dns_port) - server.start() - - daemon = grpc.server(futures.ThreadPoolExecutor(max_workers=10), interceptors=[]) - daemon_port = daemon.add_insecure_port("localhost:0") - - daemon_pb2_grpc.add_DaemonServiceServicer_to_server(DaemonDummy(), daemon) - - host_client = host_pb2_grpc.HostServiceStub(controller_channel) - token = host_client.CreateBootstrapToken(host_pb2.CreateBootstrapTokenRequest()).token - host_client.Register( - host_pb2.RegisterHostRequest( - token=token, - host=host_pb2.Host( - name="test", - address=f"localhost:{daemon_port}", - ), - ) - ) - - daemon.start() - - try: - yield controller_pb2_grpc.ControllerServiceStub(controller_channel) - finally: - daemon.stop(1) - server.stop(1) - dns_controller.stop() - Base.metadata.drop_all(engine) diff --git a/tests/unit/test_dns.py b/tests/unit/test_dns.py deleted file mode 100644 index e00ca7a..0000000 --- a/tests/unit/test_dns.py +++ /dev/null @@ -1,151 +0,0 @@ -import grpc -import pytest -from dnslib import CLASS, QTYPE, RCODE, RR, DNSQuestion, DNSRecord -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker -from sqlalchemy.pool import StaticPool - -from minivirt import dns_pb2, dns_pb2_grpc, models -from minivirt.dns_controller import DNSController -from minivirt.models import Base - -from .conftest import dummy_controller - - -class DNSClient: - def __init__(self, host="localhost", port=53): - self.host = host - self.port = port - - def query(self, name, qtype, qclass="IN"): - q = DNSRecord(q=DNSQuestion(name, getattr(QTYPE, qtype), getattr(CLASS, qclass))) - raw = q.send(self.host, self.port) - return DNSRecord.parse(raw) - - -@pytest.fixture -def dns_client(dns_controller): - yield DNSClient() - - -@pytest.fixture -def client(controller_client_dummy): - return controller_client_dummy - - -def test_dns_put(client: dns_pb2_grpc.DNSStub, dns_client): - client.PutDNSRecord( - dns_pb2.PutDNSRecordRequest( - dns_record=dns_pb2.DNSRecord( - name="mydomain.internal", - type="A", - ttl=60, - records=["192.168.1.1"], - ) - ) - ) - records = client.ListDNSRecords(dns_pb2.ListDNSRecordsRequest()).dns_records - assert len(records) == 1 - assert dns_client.query("mydomain.internal", "A").rr == RR.fromZone( - "mydomain.internal. 60 IN A 192.168.1.1" - ) - - client.PutDNSRecord( - dns_pb2.PutDNSRecordRequest( - dns_record=dns_pb2.DNSRecord( - name="mydomain.internal", - type="A", - ttl=120, - records=["192.168.1.2"], - ) - ) - ) - records = client.ListDNSRecords(dns_pb2.ListDNSRecordsRequest()).dns_records - assert len(records) == 1 - assert dns_client.query("mydomain.internal", "A").rr == RR.fromZone( - "mydomain.internal. 120 IN A 192.168.1.2" - ) - - -def test_dns_get(client: dns_pb2_grpc.DNSStub): - client.PutDNSRecord( - dns_pb2.PutDNSRecordRequest( - dns_record=dns_pb2.DNSRecord( - name="mydomain.internal", - type="A", - ttl=120, - records=["192.168.1.2"], - ) - ) - ) - - record = client.GetDNSRecord(dns_pb2.DNSRecordIdentifier(name="mydomain.internal", type="A")) - assert record.records == ["192.168.1.2"] - - with pytest.raises(grpc.RpcError) as e: - client.GetDNSRecord(dns_pb2.DNSRecordIdentifier(name="non-existing.internal", type="A")) - assert e.value.code() == grpc.StatusCode.NOT_FOUND - - -# def test_dns_forward(client: dns_pb2_grpc.DNSStub, dns_client): -# assert dns_client.query("google.com", "A").header.rcode == RCODE.NOERROR -# assert dns_client.query("non-existing.internal", "A").header.rcode == RCODE.NXDOMAIN - - -def test_dns_delete(client: dns_pb2_grpc.DNSStub): - client.PutDNSRecord( - dns_pb2.PutDNSRecordRequest( - dns_record=dns_pb2.DNSRecord( - name="mydomain.internal", - type="A", - ttl=120, - records=["192.168.1.2"], - ) - ) - ) - - client.DeleteDNSRecord(dns_pb2.DNSRecordIdentifier(name="mydomain.internal", type="A")) - records = client.ListDNSRecords(dns_pb2.ListDNSRecordsRequest()).dns_records - assert len(records) == 0 - - -def test_dns_delegation(): - engine = create_engine( - "sqlite:///:memory:", - connect_args={"check_same_thread": False}, - poolclass=StaticPool, - echo=False, - future=True, - ) - Base.metadata.create_all(engine) - sessfact = sessionmaker(engine) - upstream_dns = DNSController(sessfact) - with sessfact() as session: - session.merge( - models.DNSRecord(name="test.delegated.internal", type="A", records=["192.168.11.11"]) - ) - session.commit() - upstream_dns.start() - - with dummy_controller(dns_port=30053) as client: - client.PutDNSRecord( - dns_pb2.PutDNSRecordRequest( - dns_record=dns_pb2.DNSRecord( - name="delegated.internal", - type="NS", - ttl=120, - records=["127.0.0.1"], - ) - ) - ) - - dns_client = DNSClient(port=30053) - resp = dns_client.query("test.delegated.internal", "A") - assert len(resp.ar) == 0 - assert len(resp.auth) == 1 - assert resp.auth[0].rtype == QTYPE.NS - assert resp.auth[0].rname == "delegated.internal" - assert str(resp.auth[0].rdata) == "127.0.0.1." - assert not resp.header.ra - - upstream_dns.stop() diff --git a/tests/unit/test_domain.py b/tests/unit/test_domain.py deleted file mode 100644 index 3558f2e..0000000 --- a/tests/unit/test_domain.py +++ /dev/null @@ -1,110 +0,0 @@ -import datetime -from time import sleep -from urllib.parse import urljoin -from urllib.request import urlopen - -import pytest - -from minivirt import domain_pb2, domain_pb2_grpc - - -@pytest.fixture -def conn(): - import libvirt - - conn = libvirt.open("qemu:///system?socket=/var/run/libvirt/libvirt-sock") - yield conn - conn.close() - - -@pytest.fixture -def client(controller_client, pool_dir, conn): - yield controller_client - - img_pool = conn.storagePoolLookupByName("restvirtimages") - for vol in img_pool.listAllVolumes(): - vol.delete() - img_pool.destroy() - img_pool.delete() - img_pool.undefine() - - for dom in conn.listAllDomains(): - try: - dom.destroy() - except: - pass - dom.undefine() - - -def test_libvirt_linux(): - import libvirt - - libvirt.getVersion() - - -def test_network_linux(client: domain_pb2_grpc.DomainServiceStub): - network = client.CreateNetwork( - domain_pb2.CreateNetworkRequest( - host="test", - network=domain_pb2.Network( - name="restvirt", - cidr="192.168.69.0/24", - ), - ) - ) - - resp = client.GetNetwork(domain_pb2.GetNetworkRequest(host="test", uuid=network.uuid)) - assert resp.name == "restvirt" - assert resp.cidr == "192.168.69.0/24" - - client.DeleteNetwork(domain_pb2.DeleteNetworkRequest(host="test", uuid=network.uuid)) - - -# def test_create_domain_linux(client: domain_pb2_grpc.DomainServiceStub): -# network = client.CreateNetwork( -# domain_pb2.CreateNetworkRequest( -# network=domain_pb2.Network( -# name="restvirt", -# cidr="192.168.69.0/24", -# ) -# ) -# ) -# dom = client.CreateDomain( -# domain_pb2.CreateDomainRequest( -# host="test", -# domain=domain_pb2.Domain( -# name="test", -# vcpu=1, -# memory=512, -# private_ip="192.168.69.69", -# network=network.name, -# user_data="""#cloud-config -# -# packages: -# - nginx -# -# runcmd: -# - service nginx restart -# """, -# ), -# ) -# ) -# -# response = wait_for_http("http://192.168.69.69") -# assert "Welcome to nginx!" in response -# -# client.DeleteDomain(domain_pb2.DeleteDomainRequest(host="test", uuid=dom.uuid)) -# client.DeleteNetwork(domain_pb2.DeleteNetworkRequest(uuid=network.uuid)) -# -# -# def wait_for_http(server, path="/", timeout=datetime.timedelta(seconds=180)): -# end_time = datetime.datetime.now() + timeout -# while True: -# try: -# with urlopen(urljoin(server, path)) as resp: -# return resp.read().decode() -# except: -# now = datetime.datetime.now() -# if now >= end_time: -# raise Exception("Timed out") -# sleep(min((end_time - now).total_seconds(), 10)) diff --git a/tests/unit/test_host.py b/tests/unit/test_host.py deleted file mode 100644 index ab8344d..0000000 --- a/tests/unit/test_host.py +++ /dev/null @@ -1,52 +0,0 @@ -from datetime import datetime, timedelta - -import grpc -import pytest - -from minivirt import host_pb2, host_pb2_grpc - - -@pytest.fixture -def client(host_client): - return host_client - - -def test_register(client: host_pb2_grpc.HostServiceStub): - assert len(client.ListHosts(host_pb2.ListHostsRequest()).hosts) == 0 - - token = client.CreateBootstrapToken(host_pb2.CreateBootstrapTokenRequest()).token - client.Register( - host_pb2.RegisterHostRequest( - token=token, - host=host_pb2.Host( - name="yourmom", - address="localhost:8333", - ), - ) - ) - hosts = client.ListHosts(host_pb2.ListHostsRequest()).hosts - assert len(hosts) == 1 - host = hosts[0] - assert host.address == "localhost:8333" - - client.Deregister(host_pb2.Host(name="yourmom")) - assert len(client.ListHosts(host_pb2.ListHostsRequest()).hosts) == 0 - - -def test_register_expired(client: host_pb2_grpc.HostServiceStub): - token = client.CreateBootstrapToken( - host_pb2.CreateBootstrapTokenRequest( - expires_at=(datetime.utcnow() - timedelta(seconds=10)).isoformat() - ) - ).token - with pytest.raises(grpc.RpcError) as e: - client.Register( - host_pb2.RegisterHostRequest( - token=token, - host=host_pb2.Host( - name="yourmom", - address="localhost:8333", - ), - ) - ) - assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT diff --git a/tests/unit/test_image.py b/tests/unit/test_image.py deleted file mode 100644 index f445200..0000000 --- a/tests/unit/test_image.py +++ /dev/null @@ -1,35 +0,0 @@ -from ipaddress import ip_address, ip_network - -import pytest - -from minivirt.image import ( - create_cloud_config_image, - read_ip_from_cloud_config_image, - read_user_data_from_cloud_config_image, -) - - -@pytest.fixture -def data(): - return create_cloud_config_image( - domain_id="domain_id", - user_data="hello world", - mac="mac_address", - network=ip_network("192.168.1.0/24"), - network6=ip_network("fd8d:dd47:05bc:5307::/64"), - address=ip_address("192.168.1.43"), - address6=ip_address("fd8d:dd47:05bc:5307::10"), - gateway=ip_address("192.168.1.1"), - gateway6=ip_address("fd8d:dd47:05bc:5307::1"), - name="name", - ) - - -def test_read_ip(data): - ip = read_ip_from_cloud_config_image(data) - assert ip == "192.168.1.43" - - -def test_read_user_data(data): - user_data = read_user_data_from_cloud_config_image(data) - assert user_data == "hello world" diff --git a/tests/unit/test_port_forwarding.py b/tests/unit/test_port_forwarding.py deleted file mode 100644 index 8a7c9a7..0000000 --- a/tests/unit/test_port_forwarding.py +++ /dev/null @@ -1,141 +0,0 @@ -import grpc -import pytest - -from minivirt import port_forwarding_pb2, port_forwarding_pb2_grpc - - -@pytest.fixture -def client(controller_client): - return controller_client - - -def test_port_forwarding_get_linux(client: port_forwarding_pb2_grpc.PortForwardingServiceStub): - identifier = port_forwarding_pb2.PortForwardingIdentifier( - host="test", protocol="tcp", source_port=2020 - ) - with pytest.raises(grpc.RpcError) as e: - client.GetPortForwarding(identifier) - assert e.value.code() == grpc.StatusCode.NOT_FOUND - - fwd = port_forwarding_pb2.PortForwarding( - protocol="tcp", - source_port=2020, - target_ip="192.168.1.69", - target_port=2021, - ) - client.PutPortForwarding( - port_forwarding_pb2.PutPortForwardingRequest(host="test", port_forwarding=fwd) - ) - forwarding = client.GetPortForwarding(identifier) - assert forwarding.target_ip == "192.168.1.69" - assert forwarding.target_port == 2021 - - -def test_port_forwarding_put_linux(client: port_forwarding_pb2_grpc.PortForwardingServiceStub): - fwd = port_forwarding_pb2.PortForwarding( - protocol="tcp", - source_port=2020, - target_ip="192.168.1.69", - target_port=2021, - ) - client.PutPortForwarding( - port_forwarding_pb2.PutPortForwardingRequest(host="test", port_forwarding=fwd) - ) - fwds = client.ListPortForwardings( - port_forwarding_pb2.ListPortForwardingsRequest(host="test") - ).port_forwardings - assert len(fwds) == 1 - assert fwds[0].target_ip == "192.168.1.69" - - fwds = client.ListPortForwardings( - port_forwarding_pb2.ListPortForwardingsRequest(host="test") - ).port_forwardings - assert len(fwds) == 1 - assert fwds[0].target_ip == "192.168.1.69" - - fwd.target_ip = "192.168.1.70" - client.PutPortForwarding( - port_forwarding_pb2.PutPortForwardingRequest(host="test", port_forwarding=fwd) - ) - fwds = client.ListPortForwardings( - port_forwarding_pb2.ListPortForwardingsRequest(host="test") - ).port_forwardings - assert len(fwds) == 1 - assert fwds[0].target_ip == "192.168.1.70" - - -def test_port_forwarding_linux(client: port_forwarding_pb2_grpc.PortForwardingServiceStub): - import nftables - - fwd = port_forwarding_pb2.PortForwarding( - protocol="tcp", - source_port=2020, - target_ip="192.168.1.69", - target_port=2021, - ) - client.PutPortForwarding( - port_forwarding_pb2.PutPortForwardingRequest(host="test", port_forwarding=fwd) - ) - - table_name = "restvirt" - nft = nftables.Nftables() - nft.set_json_output(True) - nft.set_stateless_output(True) - - _, output, _ = nft.json_cmd( - { - "nftables": [ - { - "list": { - "table": { - "family": "inet", - "name": table_name, - } - } - } - ] - } - ) - output = output["nftables"] - all_rules = [rule["rule"] for rule in output if "rule" in rule] - - rules = [rule for rule in all_rules if rule["chain"] == "forward"] - assert len(rules) == 1 - rule = rules[0] - assert rule["expr"] == [ - { - "match": { - "left": {"payload": {"field": "dport", "protocol": "tcp"}}, - "op": "==", - "right": 2021, - } - }, - { - "match": { - "left": {"payload": {"field": "daddr", "protocol": "ip"}}, - "op": "==", - "right": "192.168.1.69", - } - }, - {"counter": None}, - {"accept": None}, - ] - - rules = [rule for rule in all_rules if rule["chain"] == "prerouting"] - assert len(rules) == 1 - rule = rules[0] - assert rule["expr"] == [ - { - "match": { - "left": {"payload": {"field": "dport", "protocol": "tcp"}}, - "op": "==", - "right": 2020, - } - }, - {"counter": None}, - {"dnat": {"addr": "192.168.1.69", "family": "ip", "port": 2021}}, - ] - - client.DeletePortForwarding( - port_forwarding_pb2.PortForwardingIdentifier(host="test", protocol="tcp", source_port=2020) - ) diff --git a/tests/unit/test_route.py b/tests/unit/test_route.py deleted file mode 100644 index 3a8f363..0000000 --- a/tests/unit/test_route.py +++ /dev/null @@ -1,153 +0,0 @@ -import pytest -from pyroute2 import IPRoute - -from minivirt import controller_pb2_grpc, route_pb2 - - -@pytest.fixture -def client(controller_client_dummy): - return controller_client_dummy - - -def test_id_generation(client: controller_pb2_grpc.ControllerServiceStub): - req = route_pb2.CreateRouteTableRequest(route_table=route_pb2.RouteTable(name="XAXAXA")) - creates = [client.CreateRouteTable(req) for _ in range(3)] - ids = [resp.id for resp in creates] - client.DeleteRouteTable(route_pb2.RouteTableIdentifier(id=ids[1])) - resp = client.CreateRouteTable(req) - assert resp.id == ids[1] - - for _ in range(98): - client.CreateRouteTable(req) - - with pytest.raises(Exception): - client.CreateRouteTable(req) - - -def test_route_update(client: controller_pb2_grpc.ControllerServiceStub): - req = route_pb2.CreateRouteTableRequest(route_table=route_pb2.RouteTable(name="XAXAXA")) - resp = client.CreateRouteTable(req) - route_table_id = resp.id - client.PutRoute( - route_pb2.PutRouteRequest( - route=route_pb2.Route( - route_table_id=route_table_id, - destination="10.6.1.0/24", - gateways=["192.168.0.1"], - ) - ) - ) - client.PutRoute( - route_pb2.PutRouteRequest( - route=route_pb2.Route( - route_table_id=route_table_id, - destination="10.6.1.0/24", - gateways=["192.168.0.2", "192.168.0.3"], - ) - ) - ) - routes = client.ListRoutes(route_pb2.ListRoutesRequest(route_table_id=route_table_id)).routes - assert len(routes) == 1 - assert sorted(routes[0].gateways) == ["192.168.0.2", "192.168.0.3"] - - -def test_route_delete(client: controller_pb2_grpc.ControllerServiceStub): - req = route_pb2.CreateRouteTableRequest(route_table=route_pb2.RouteTable(name="XAXAXA")) - resp = client.CreateRouteTable(req) - route_table_id = resp.id - route = client.PutRoute( - route_pb2.PutRouteRequest( - route=route_pb2.Route( - route_table_id=route_table_id, - destination="10.6.1.0/24", - gateways=["192.168.0.1"], - ) - ) - ) - routes = client.ListRoutes(route_pb2.ListRoutesRequest(route_table_id=route_table_id)).routes - assert len(routes) == 1 - - client.DeleteRoute( - route_pb2.RouteIdentifier( - route_table_id=route.route_table_id, - destination=route.destination, - ) - ) - routes = client.ListRoutes(route_pb2.ListRoutesRequest(route_table_id=route_table_id)).routes - assert len(routes) == 0 - - client.DeleteRoute( - route_pb2.RouteIdentifier( - route_table_id=route.route_table_id, - destination=route.destination, - ) - ) - - -def test_route_table_delete(client: controller_pb2_grpc.ControllerServiceStub): - req = route_pb2.CreateRouteTableRequest(route_table=route_pb2.RouteTable(name="XAXAXA")) - resp = client.CreateRouteTable(req) - route_table_id = resp.id - client.PutRoute( - route_pb2.PutRouteRequest( - route=route_pb2.Route( - route_table_id=route_table_id, - destination="10.6.1.0/24", - gateways=["192.168.0.1"], - ) - ) - ) - - client.DeleteRouteTable(route_pb2.RouteTableIdentifier(id=route_table_id)) - routes = client.ListRoutes(route_pb2.ListRoutesRequest(route_table_id=route_table_id)).routes - assert len(routes) == 0 - - client.DeleteRouteTable(route_pb2.RouteTableIdentifier(id=route_table_id)) - - -@pytest.fixture -def rtnl_api(): - with IPRoute() as ip: - yield ip - - -@pytest.fixture -def client_iproute(controller_client, rtnl_api): - rtnl_api.link("add", ifname="restvirtbr0", kind="dummy") - dev = rtnl_api.link_lookup(ifname="restvirtbr0")[0] - rtnl_api.addr("add", index=dev, address="10.69.69.0", mask=24) - rtnl_api.link("set", index=dev, state="up") - - yield controller_client - - rtnl_api.link("del", ifname="restvirtbr0") - - -def test_route_linux(client_iproute: controller_pb2_grpc.ControllerServiceStub, rtnl_api: IPRoute): - client = client_iproute - req = route_pb2.CreateRouteTableRequest(route_table=route_pb2.RouteTable(name="XAXAXA")) - resp = client.CreateRouteTable(req) - route_table_id = resp.id - route = client.PutRoute( - route_pb2.PutRouteRequest( - route=route_pb2.Route( - route_table_id=route_table_id, - destination="10.69.42.0/24", - gateways=["10.69.69.1"], - ) - ) - ) - - r = rtnl_api.route("get", dst="10.69.42.1")[0] - assert r.get_attr("RTA_GATEWAY") == "10.69.69.1" - - client.DeleteRoute( - route_pb2.RouteIdentifier( - route_table_id=route.route_table_id, - destination=route.destination, - ) - ) - assert len(rtnl_api.get_routes(table=route_table_id)) == 0 - - client.DeleteRouteTable(route_pb2.RouteTableIdentifier(id=route_table_id)) - assert len(rtnl_api.get_rules(priority=route_table_id)) == 0