Merge pull request #690 from coreemu/docker-updates

Docker updates
This commit is contained in:
bharnden 2022-06-08 13:57:06 -07:00 committed by GitHub
commit bb49947550
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
47 changed files with 1093 additions and 873 deletions

View file

@ -1,100 +0,0 @@
# syntax=docker/dockerfile:1
FROM ubuntu:20.04
LABEL Description="CORE Docker Image"
# define variables
ARG DEBIAN_FRONTEND=noninteractive
ARG PREFIX=/usr/local
ARG BRANCH=master
ARG CORE_TARBALL=core.tar.gz
ARG OSPF_TARBALL=ospf.tar.gz
# install system dependencies
RUN apt-get update && \
apt-get install -y --no-install-recommends \
automake \
bash \
ca-certificates \
ethtool \
gawk \
gcc \
g++ \
iproute2 \
iputils-ping \
libc-dev \
libev-dev \
libreadline-dev \
libtool \
libtk-img \
make \
nftables \
python3 \
python3-pip \
python3-tk \
pkg-config \
systemctl \
tk \
wget \
xauth \
xterm \
&& apt-get clean
# install python dependencies
RUN python3 -m pip install \
grpcio==1.27.2 \
grpcio-tools==1.27.2 \
poetry==1.1.7
# retrieve, build, and install core
RUN wget -q -O ${CORE_TARBALL} https://api.github.com/repos/coreemu/core/tarball/${BRANCH} && \
tar xf ${CORE_TARBALL} && \
cd coreemu-core* && \
./bootstrap.sh && \
./configure && \
make -j $(nproc) && \
make install && \
cd daemon && \
python3 -m poetry build -f wheel && \
python3 -m pip install dist/* && \
cp scripts/* ${PREFIX}/bin && \
mkdir /etc/core && \
cp -n data/core.conf /etc/core && \
cp -n data/logging.conf /etc/core && \
mkdir -p ${PREFIX}/share/core && \
cp -r examples ${PREFIX}/share/core && \
echo '\
[Unit]\n\
Description=Common Open Research Emulator Service\n\
After=network.target\n\
\n\
[Service]\n\
Type=simple\n\
ExecStart=/usr/local/bin/core-daemon\n\
TasksMax=infinity\n\
\n\
[Install]\n\
WantedBy=multi-user.target\
' > /lib/systemd/system/core-daemon.service && \
cd ../.. && \
rm ${CORE_TARBALL} && \
rm -rf coreemu-core*
# retrieve, build, and install ospf mdr
RUN wget -q -O ${OSPF_TARBALL} https://github.com/USNavalResearchLaboratory/ospf-mdr/tarball/master && \
tar xf ${OSPF_TARBALL} && \
cd USNavalResearchLaboratory-ospf-mdr* && \
./bootstrap.sh && \
./configure --disable-doc --enable-user=root --enable-group=root \
--with-cflags=-ggdb --sysconfdir=/usr/local/etc/quagga --enable-vtysh \
--localstatedir=/var/run/quagga && \
make -j $(nproc) && \
make install && \
cd .. && \
rm ${OSPF_TARBALL} && \
rm -rf USNavalResearchLaboratory-ospf-mdr*
# retrieve and install emane packages
RUN wget -q https://adjacentlink.com/downloads/emane/emane-1.2.7-release-1.ubuntu-20_04.amd64.tar.gz && \
tar xf emane*.tar.gz && \
cd emane-1.2.7-release-1/debs/ubuntu-20_04/amd64 && \
apt-get install -y ./emane*.deb ./python3-emane_*.deb && \
cd ../../../.. && \
rm emane-1.2.7-release-1.ubuntu-20_04.amd64.tar.gz && \
rm -rf emane-1.2.7-release-1
CMD ["systemctl", "start", "core-daemon"]

39
Dockerfile.centos Normal file
View file

@ -0,0 +1,39 @@
# syntax=docker/dockerfile:1
FROM centos:7
LABEL Description="CORE Docker CentOS Image"
# define variables
ARG PREFIX=/usr
ARG BRANCH=master
# define environment
ENV DEBIAN_FRONTEND=noninteractive
ENV LANG en_US.UTF-8
# install core
RUN yum -y update && \
yum install -y git sudo wget tzdata unzip python3
WORKDIR /root
RUN git clone https://github.com/coreemu/core
WORKDIR /root/core
RUN git checkout ${BRANCH}
RUN ./setup.sh
RUN . /root/.bashrc && inv install -v -p ${PREFIX}
# install emane packages and python bindings
WORKDIR /root
RUN wget -q https://adjacentlink.com/downloads/emane/emane-1.3.3-release-1.el7.x86_64.tar.gz && \
tar xf emane-1.3.3-release-1.el7.x86_64.tar.gz && \
cd emane-1.3.3-release-1/rpms/el7/x86_64 && \
yum install -y epel-release && \
yum install -y ./openstatistic*.rpm ./emane*.rpm ./python3-emane_*.rpm && \
cd ../../../.. && \
rm emane-1.3.3-release-1.el7.x86_64.tar.gz && \
rm -rf emane-1.3.3-release-1
RUN wget https://github.com/protocolbuffers/protobuf/releases/download/v3.7.1/protoc-3.7.1-linux-x86_64.zip && \
mkdir protoc && \
unzip protoc-3.7.1-linux-x86_64.zip -d protoc
WORKDIR /root/core
RUN . /root/.bashrc && PATH=/root/protoc/bin:$PATH inv install-emane -v -e v1.3.3
# run daemon
CMD ["core-daemon"]

37
Dockerfile.oracle Normal file
View file

@ -0,0 +1,37 @@
# syntax=docker/dockerfile:1
FROM oraclelinux:8
LABEL Description="CORE Docker Oracle Linux Image"
# define variables
ARG PREFIX=/usr
ARG BRANCH=docker-updates
# define environment
ENV DEBIAN_FRONTEND=noninteractive
ENV LANG en_US.UTF-8
# install core
RUN yum -y update && \
yum install -y git sudo wget tzdata unzip python39 which iproute-tc hostname xterm
WORKDIR /root
RUN git clone https://github.com/coreemu/core
WORKDIR /root/core
RUN git checkout ${BRANCH}
RUN PYTHON=python3.9 PYTHON_DEP=python39 ./setup.sh
RUN . /root/.bashrc && PYTHON=python3.9 PYTHON_DEP=python39 inv install -v -p ${PREFIX}
# install emane packages and python bindings
WORKDIR /root
RUN yum config-manager --set-enabled ol8_codeready_builder && yum install -y protobuf-devel
RUN wget -q https://adjacentlink.com/downloads/emane/emane-1.3.3-release-1.el8.x86_64.tar.gz && \
tar xf emane-1.3.3-release-1.el8.x86_64.tar.gz && \
cd emane-1.3.3-release-1/rpms/el8/x86_64 && \
yum install -y epel-release && \
yum install -y ./openstatistic*.rpm ./emane*.rpm ./python3-emane-1.3.3*.rpm && \
cd ../../../.. && \
rm emane-1.3.3-release-1.el8.x86_64.tar.gz && \
rm -rf emane-1.3.3-release-1
WORKDIR /root/core
RUN . /root/.bashrc && PYTHON=python3.9 PATH=/root/protoc/bin:$PATH inv install-emane -v -e v1.3.3
# run daemon
CMD ["core-daemon"]

34
Dockerfile.ubuntu Normal file
View file

@ -0,0 +1,34 @@
# syntax=docker/dockerfile:1
FROM ubuntu:20.04
LABEL Description="CORE Docker Ubuntu Image"
# define variables
ARG PREFIX=/usr/local
ARG BRANCH=master
# define environment
ENV DEBIAN_FRONTEND=noninteractive
# install core
RUN apt-get update && \
apt-get install -y git sudo wget tzdata
WORKDIR /root
RUN git clone https://github.com/coreemu/core
WORKDIR /root/core
RUN git checkout ${BRANCH}
RUN ./setup.sh
RUN . /root/.bashrc && inv install -v -p ${PREFIX}
# install emane packages and python bindings
WORKDIR /root
RUN wget -q https://adjacentlink.com/downloads/emane/emane-1.3.3-release-1.ubuntu-20_04.amd64.tar.gz && \
tar xf emane*.tar.gz && \
cd emane-1.3.3-release-1/debs/ubuntu-20_04/amd64 && \
apt-get install -y ./emane*.deb ./python3-emane_*.deb && \
cd ../../../.. && \
rm emane-1.3.3-release-1.ubuntu-20_04.amd64.tar.gz && \
rm -rf emane-1.3.3-release-1
WORKDIR /root/core
RUN . /root/.bashrc && inv install-emane -v -e v1.3.3
# run daemon
CMD ["core-daemon"]

View file

@ -17,15 +17,22 @@ from core.api.grpc.services_pb2 import (
ServiceDefaults,
)
from core.config import ConfigurableOptions
from core.emane.nodes import EmaneNet
from core.emulator.data import InterfaceData, LinkData, LinkOptions, NodeOptions
from core.emane.nodes import EmaneNet, EmaneOptions
from core.emulator.data import InterfaceData, LinkData, LinkOptions
from core.emulator.enumerations import LinkTypes, NodeTypes
from core.emulator.links import CoreLink
from core.emulator.session import Session
from core.errors import CoreError
from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility
from core.nodes.base import CoreNode, CoreNodeBase, NodeBase
from core.nodes.docker import DockerNode
from core.nodes.base import (
CoreNode,
CoreNodeBase,
CoreNodeOptions,
NodeBase,
NodeOptions,
Position,
)
from core.nodes.docker import DockerNode, DockerOptions
from core.nodes.interface import CoreInterface
from core.nodes.lxd import LxcNode
from core.nodes.network import CoreNetwork, CtrlNet, PtpNet, WlanNode
@ -55,34 +62,33 @@ class CpuUsage:
return (total_diff - idle_diff) / total_diff
def add_node_data(node_proto: core_pb2.Node) -> Tuple[NodeTypes, int, NodeOptions]:
def add_node_data(
_class: Type[NodeBase], node_proto: core_pb2.Node
) -> Tuple[Position, NodeOptions]:
"""
Convert node protobuf message to data for creating a node.
:param _class: node class to create options from
:param node_proto: node proto message
:return: node type, id, and options
"""
_id = node_proto.id
_type = NodeTypes(node_proto.type)
options = NodeOptions(
name=node_proto.name,
model=node_proto.model,
icon=node_proto.icon,
image=node_proto.image,
services=node_proto.services,
config_services=node_proto.config_services,
canvas=node_proto.canvas,
)
if node_proto.emane:
options.emane = node_proto.emane
if node_proto.server:
options.server = node_proto.server
position = node_proto.position
options.set_position(position.x, position.y)
options = _class.create_options()
options.icon = node_proto.icon
options.canvas = node_proto.canvas
if isinstance(options, CoreNodeOptions):
options.model = node_proto.model
options.services = node_proto.services
options.config_services = node_proto.config_services
if isinstance(options, EmaneOptions):
options.emane_model = node_proto.emane
if isinstance(options, DockerOptions):
options.image = node_proto.image
position = Position()
position.set(node_proto.position.x, node_proto.position.y)
if node_proto.HasField("geo"):
geo = node_proto.geo
options.set_location(geo.lat, geo.lon, geo.alt)
return _type, _id, options
position.set_geo(geo.lon, geo.lat, geo.alt)
return position, options
def link_iface(iface_proto: core_pb2.Interface) -> InterfaceData:
@ -150,9 +156,17 @@ def create_nodes(
"""
funcs = []
for node_proto in node_protos:
_type, _id, options = add_node_data(node_proto)
_type = NodeTypes(node_proto.type)
_class = session.get_node_class(_type)
args = (_class, _id, options)
position, options = add_node_data(_class, node_proto)
args = (
_class,
node_proto.id or None,
node_proto.name or None,
node_proto.server or None,
position,
options,
)
funcs.append((session.add_node, args, {}))
start = time.monotonic()
results, exceptions = utils.threadpool(funcs)

View file

@ -88,7 +88,12 @@ from core.configservice.base import ConfigServiceBootError
from core.emane.modelmanager import EmaneModelManager
from core.emulator.coreemu import CoreEmu
from core.emulator.data import InterfaceData, LinkData, LinkOptions
from core.emulator.enumerations import EventTypes, ExceptionLevels, MessageFlags
from core.emulator.enumerations import (
EventTypes,
ExceptionLevels,
MessageFlags,
NodeTypes,
)
from core.emulator.session import NT, Session
from core.errors import CoreCommandError, CoreError
from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility
@ -548,9 +553,17 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
"""
logger.debug("add node: %s", request)
session = self.get_session(request.session_id, context)
_type, _id, options = grpcutils.add_node_data(request.node)
_type = NodeTypes(request.node.type)
_class = session.get_node_class(_type)
node = session.add_node(_class, _id, options)
position, options = grpcutils.add_node_data(_class, request.node)
node = session.add_node(
_class,
request.node.id or None,
request.node.name or None,
request.node.server or None,
position,
options,
)
grpcutils.configure_node(session, request.node, node, context)
source = request.source if request.source else None
session.broadcast_node(node, MessageFlags.ADD, source)

View file

@ -382,6 +382,8 @@ class EmaneManager:
service = EmaneEventService(
self, event_net.brname, eventgroup, int(eventport)
)
if self.doeventmonitor():
service.start()
self.services[event_net.brname] = service
self.nem_service[nem_id] = service
except EventServiceException:
@ -603,7 +605,7 @@ class EmaneManager:
node = iface.node
loglevel = str(DEFAULT_LOG_LEVEL)
cfgloglevel = self.session.options.get_int("emane_log_level", 2)
realtime = self.session.options.get_bool("emane_realtime")
realtime = self.session.options.get_bool("emane_realtime", True)
if cfgloglevel:
logger.info("setting user-defined emane log level: %d", cfgloglevel)
loglevel = str(cfgloglevel)
@ -636,20 +638,13 @@ class EmaneManager:
"""
Returns boolean whether or not EMANE events will be monitored.
"""
# this support must be explicitly turned on; by default, CORE will
# generate the EMANE events when nodes are moved
return self.session.options.get_bool("emane_event_monitor", False)
def genlocationevents(self) -> bool:
"""
Returns boolean whether or not EMANE events will be generated.
"""
# By default, CORE generates EMANE location events when nodes
# are moved; this can be explicitly disabled in core.conf
tmp = self.session.options.get_bool("emane_event_generate", True)
if tmp is None:
tmp = not self.doeventmonitor()
return tmp
return self.session.options.get_bool("emane_event_generate", True)
def handlelocationevent(self, rxnemid: int, eid: int, data: str) -> None:
"""

View file

@ -5,13 +5,14 @@ share the same MAC+PHY model.
import logging
import time
from dataclasses import dataclass
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Type, Union
from core.emulator.data import InterfaceData, LinkData, LinkOptions
from core.emulator.distributed import DistributedServer
from core.emulator.enumerations import EventTypes, MessageFlags, RegisterTlvs
from core.errors import CoreCommandError, CoreError
from core.nodes.base import CoreNetworkBase, CoreNode
from core.nodes.base import CoreNetworkBase, CoreNode, NodeOptions
from core.nodes.interface import CoreInterface
logger = logging.getLogger(__name__)
@ -139,6 +140,12 @@ class TunTap(CoreInterface):
self.node.node_net_client.create_address(self.name, str(ip))
@dataclass
class EmaneOptions(NodeOptions):
emane_model: str = None
"""name of emane model to associate an emane network to"""
class EmaneNet(CoreNetworkBase):
"""
EMANE node contains NEM configuration and causes connected nodes
@ -152,11 +159,20 @@ class EmaneNet(CoreNetworkBase):
_id: int = None,
name: str = None,
server: DistributedServer = None,
options: EmaneOptions = None,
) -> None:
super().__init__(session, _id, name, server)
options = options or EmaneOptions()
super().__init__(session, _id, name, server, options)
self.conf: str = ""
self.wireless_model: Optional["EmaneModel"] = None
self.mobility: Optional[WayPointMobility] = None
model_class = self.session.emane.get_model(options.emane_model)
self.wireless_model: Optional["EmaneModel"] = model_class(self.session, self.id)
if self.session.state == EventTypes.RUNTIME_STATE:
self.session.emane.add_node(self)
@classmethod
def create_options(cls) -> EmaneOptions:
return EmaneOptions()
def linkconfig(
self, iface: CoreInterface, options: LinkOptions, iface2: CoreInterface = None

View file

@ -92,6 +92,10 @@ class NodeOptions:
image: str = None
emane: str = None
legacy: bool = False
# src, dst
binds: List[Tuple[str, str]] = field(default_factory=list)
# src, dst, unique, delete
volumes: List[Tuple[str, str, bool, bool]] = field(default_factory=list)
def set_position(self, x: float, y: float) -> None:
"""

View file

@ -14,7 +14,7 @@ import tempfile
import threading
import time
from pathlib import Path
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Type, TypeVar, Union
from typing import Callable, Dict, List, Optional, Set, Tuple, Type, TypeVar, Union
from core import constants, utils
from core.configservice.manager import ConfigServiceManager
@ -29,7 +29,6 @@ from core.emulator.data import (
LinkData,
LinkOptions,
NodeData,
NodeOptions,
)
from core.emulator.distributed import DistributedController
from core.emulator.enumerations import (
@ -44,7 +43,7 @@ from core.errors import CoreError
from core.location.event import EventLoop
from core.location.geo import GeoLocation
from core.location.mobility import BasicRangeModel, MobilityManager
from core.nodes.base import CoreNetworkBase, CoreNode, CoreNodeBase, NodeBase
from core.nodes.base import CoreNode, CoreNodeBase, NodeBase, NodeOptions, Position
from core.nodes.docker import DockerNode
from core.nodes.interface import DEFAULT_MTU, CoreInterface
from core.nodes.lxd import LxcNode
@ -476,14 +475,23 @@ class Session:
return _id
def add_node(
self, _class: Type[NT], _id: int = None, options: NodeOptions = None
self,
_class: Type[NT],
_id: int = None,
name: str = None,
server: str = None,
position: Position = None,
options: NodeOptions = None,
) -> NT:
"""
Add a node to the session, based on the provided node data.
:param _class: node class to create
:param _id: id for node, defaults to None for generated id
:param options: data to create node with
:param name: name to assign to node
:param server: distributed server for node, if desired
:param position: geo or x/y/z position to set
:param options: options to create node with
:return: created node
:raises core.CoreError: when an invalid node type is given
"""
@ -492,87 +500,31 @@ class Session:
enable_rj45 = self.options.get_int("enablerj45") == 1
if _class == Rj45Node and not enable_rj45:
start = False
# determine node id
if not _id:
_id = self.next_node_id()
# generate name if not provided
if not options:
options = NodeOptions()
options.set_position(0, 0)
name = options.name
if not name:
name = f"{_class.__name__}{_id}"
# generate options if not provided
options = options if options else _class.create_options()
# verify distributed server
server = self.distributed.servers.get(options.server)
if options.server is not None and server is None:
raise CoreError(f"invalid distributed server: {options.server}")
dist_server = None
if server is not None:
dist_server = self.distributed.servers.get(server)
if not dist_server:
raise CoreError(f"invalid distributed server: {server}")
# create node
logger.info(
"creating node(%s) id(%s) name(%s) start(%s)",
_class.__name__,
_id,
name,
start,
)
kwargs = dict(_id=_id, name=name, server=server)
if _class in CONTAINER_NODES:
kwargs["image"] = options.image
node = self.create_node(_class, start, **kwargs)
# set node attributes
node.icon = options.icon
node.canvas = options.canvas
# set node position and broadcast it
has_geo = all(i is not None for i in [options.lon, options.lat, options.alt])
if has_geo:
self.set_node_geo(node, options.lon, options.lat, options.alt)
node = self.create_node(_class, start, _id, name, dist_server, options)
# set node position
position = position or Position()
if position.has_geo():
self.set_node_geo(node, position.lon, position.lat, position.alt)
else:
self.set_node_pos(node, options.x, options.y)
# add services to needed nodes
if isinstance(node, (CoreNode, PhysicalNode)):
node.model = options.model
if options.legacy or options.services:
logger.debug("set node type: %s", node.model)
self.services.add_services(node, node.model, options.services)
# add config services
config_services = options.config_services
if not options.legacy and not config_services and not node.services:
config_services = self.services.default_services.get(node.model, [])
logger.info("setting node config services: %s", config_services)
for name in config_services:
service_class = self.service_manager.get_service(name)
node.add_config_service(service_class)
# set network mtu, if configured
mtu = self.options.get_int("mtu")
if isinstance(node, CoreNetworkBase) and mtu > 0:
node.mtu = mtu
# ensure default emane configuration
if isinstance(node, EmaneNet) and options.emane:
model_class = self.emane.get_model(options.emane)
node.wireless_model = model_class(self, node.id)
if self.state == EventTypes.RUNTIME_STATE:
self.emane.add_node(node)
# set default wlan config if needed
self.set_node_pos(node, position.x, position.y)
# setup default wlan
if isinstance(node, WlanNode):
self.mobility.set_model_config(_id, BasicRangeModel.name)
# boot nodes after runtime CoreNodes and PhysicalNodes
is_boot_node = isinstance(node, (CoreNode, PhysicalNode))
if self.state == EventTypes.RUNTIME_STATE and is_boot_node:
self.mobility.set_model_config(self.id, BasicRangeModel.name)
# boot core nodes after runtime
is_runtime = self.state == EventTypes.RUNTIME_STATE
if is_runtime and isinstance(node, CoreNode):
self.write_nodes()
self.add_remove_control_iface(node, remove=False)
self.boot_node(node)
self.sdt.add_node(node)
return node
@ -647,28 +599,6 @@ class Session:
logger.info("immediately running new state hook")
self.run_hook(hook)
def add_node_file(
self,
node_id: int,
src_path: Optional[Path],
file_path: Path,
data: Optional[str],
) -> None:
"""
Add a file to a node.
:param node_id: node to add file to
:param src_path: source file path
:param file_path: file path to add
:param data: file data
:return: nothing
"""
node = self.get_node(node_id, CoreNode)
if src_path is not None:
node.copy_file(src_path, file_path)
elif data is not None:
node.create_file(file_path, data)
def clear(self) -> None:
"""
Clear all CORE session data. (nodes, hooks, etc)
@ -978,24 +908,39 @@ class Session:
logger.exception("failed to set permission on %s", self.directory)
def create_node(
self, _class: Type[NT], start: bool, *args: Any, **kwargs: Any
self,
_class: Type[NT],
start: bool,
_id: int = None,
name: str = None,
server: str = None,
options: NodeOptions = None,
) -> NT:
"""
Create an emulation node.
:param _class: node class to create
:param start: True to start node, False otherwise
:param args: list of arguments for the class to create
:param kwargs: dictionary of arguments for the class to create
:param _id: id for node, defaults to None for generated id
:param name: name to assign to node
:param server: distributed server for node, if desired
:param options: options to create node with
:return: the created node instance
:raises core.CoreError: when id of the node to create already exists
"""
with self.nodes_lock:
node = _class(self, *args, **kwargs)
node = _class(self, _id=_id, name=name, server=server, options=options)
if node.id in self.nodes:
node.shutdown()
raise CoreError(f"duplicate node id {node.id} for {node.name}")
self.nodes[node.id] = node
logger.info(
"created node(%s) id(%s) name(%s) start(%s)",
_class.__name__,
node.id,
node.name,
start,
)
if start:
node.startup()
return node
@ -1217,7 +1162,7 @@ class Session:
funcs = []
start = time.monotonic()
for node in self.nodes.values():
if isinstance(node, (CoreNode, PhysicalNode)):
if isinstance(node, CoreNode):
self.add_remove_control_iface(node, remove=False)
funcs.append((self.boot_node, (node,), {}))
results, exceptions = utils.threadpool(funcs)
@ -1352,21 +1297,18 @@ class Session:
updown_script,
server_iface,
)
control_net = self.create_node(
CtrlNet,
start=False,
prefix=prefix,
_id=_id,
updown_script=updown_script,
serverintf=server_iface,
)
options = CtrlNet.create_options()
options.prefix = prefix
options.updown_script = updown_script
options.serverintf = server_iface
control_net = self.create_node(CtrlNet, False, _id, options=options)
control_net.brname = f"ctrl{net_index}.{self.short_session_id()}"
control_net.startup()
return control_net
def add_remove_control_iface(
self,
node: Union[CoreNode, PhysicalNode],
node: CoreNode,
net_index: int = 0,
remove: bool = False,
conf_required: bool = True,

View file

@ -1,30 +1,31 @@
from typing import List
BASH: str = "bash"
VNODED: str = "vnoded"
VCMD: str = "vcmd"
SYSCTL: str = "sysctl"
IP: str = "ip"
ETHTOOL: str = "ethtool"
TC: str = "tc"
IP: str = "ip"
MOUNT: str = "mount"
UMOUNT: str = "umount"
OVS_VSCTL: str = "ovs-vsctl"
TEST: str = "test"
NFTABLES: str = "nft"
OVS_VSCTL: str = "ovs-vsctl"
SYSCTL: str = "sysctl"
TC: str = "tc"
TEST: str = "test"
UMOUNT: str = "umount"
VCMD: str = "vcmd"
VNODED: str = "vnoded"
COMMON_REQUIREMENTS: List[str] = [
BASH,
NFTABLES,
ETHTOOL,
IP,
MOUNT,
NFTABLES,
SYSCTL,
TC,
UMOUNT,
TEST,
UMOUNT,
VCMD,
VNODED,
]
VCMD_REQUIREMENTS: List[str] = [VNODED, VCMD]
OVS_REQUIREMENTS: List[str] = [OVS_VSCTL]
@ -38,6 +39,4 @@ def get_requirements(use_ovs: bool) -> List[str]:
requirements = COMMON_REQUIREMENTS
if use_ovs:
requirements += OVS_REQUIREMENTS
else:
requirements += VCMD_REQUIREMENTS
return requirements

View file

@ -5,6 +5,7 @@ import abc
import logging
import shutil
import threading
from dataclasses import dataclass, field
from pathlib import Path
from threading import RLock
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type, Union
@ -33,6 +34,94 @@ if TYPE_CHECKING:
PRIVATE_DIRS: List[Path] = [Path("/var/run"), Path("/var/log")]
@dataclass
class Position:
"""
Helper class for Cartesian coordinate position
"""
x: float = 0.0
y: float = 0.0
z: float = 0.0
lon: float = None
lat: float = None
alt: float = None
def set(self, x: float = None, y: float = None, z: float = None) -> bool:
"""
Returns True if the position has actually changed.
:param x: x position
:param y: y position
:param z: z position
:return: True if position changed, False otherwise
"""
if self.x == x and self.y == y and self.z == z:
return False
self.x = x
self.y = y
self.z = z
return True
def get(self) -> Tuple[float, float, float]:
"""
Retrieve x,y,z position.
:return: x,y,z position tuple
"""
return self.x, self.y, self.z
def has_geo(self) -> bool:
return all(x is not None for x in [self.lon, self.lat, self.alt])
def set_geo(self, lon: float, lat: float, alt: float) -> None:
"""
Set geo position lon, lat, alt.
:param lon: longitude value
:param lat: latitude value
:param alt: altitude value
:return: nothing
"""
self.lon = lon
self.lat = lat
self.alt = alt
def get_geo(self) -> Tuple[float, float, float]:
"""
Retrieve current geo position lon, lat, alt.
:return: lon, lat, alt position tuple
"""
return self.lon, self.lat, self.alt
@dataclass
class NodeOptions:
"""
Base options for configuring a node.
"""
canvas: int = None
"""id of canvas for display within gui"""
icon: str = None
"""custom icon for display, None for default"""
@dataclass
class CoreNodeOptions(NodeOptions):
model: str = "PC"
"""model is used for providing a default set of services"""
services: List[str] = field(default_factory=list)
"""services to start within node"""
config_services: List[str] = field(default_factory=list)
"""config services to start within node"""
directory: Path = None
"""directory to define node, defaults to path under the session directory"""
legacy: bool = False
"""legacy nodes default to standard services"""
class NodeBase(abc.ABC):
"""
Base class for CORE nodes (nodes and networks)
@ -44,6 +133,7 @@ class NodeBase(abc.ABC):
_id: int = None,
name: str = None,
server: "DistributedServer" = None,
options: NodeOptions = None,
) -> None:
"""
Creates a NodeBase instance.
@ -53,26 +143,29 @@ class NodeBase(abc.ABC):
:param name: object name
:param server: remote server node
will run on, default is None for localhost
:param options: options to create node with
"""
self.session: "Session" = session
if _id is None:
_id = session.next_node_id()
self.id: int = _id
self.name: str = name or f"o{self.id}"
self.id: int = _id if _id is not None else self.session.next_node_id()
self.name: str = name or f"{self.__class__.__name__}{self.id}"
self.server: "DistributedServer" = server
self.model: Optional[str] = None
self.services: CoreServices = []
self.ifaces: Dict[int, CoreInterface] = {}
self.iface_id: int = 0
self.canvas: Optional[int] = None
self.icon: Optional[str] = None
self.position: Position = Position()
self.up: bool = False
self.lock: RLock = RLock()
self.net_client: LinuxNetClient = get_net_client(
self.session.use_ovs(), self.host_cmd
)
options = options if options else NodeOptions()
self.canvas: Optional[int] = options.canvas
self.icon: Optional[str] = options.icon
@classmethod
def create_options(cls) -> NodeOptions:
return NodeOptions()
@abc.abstractmethod
def startup(self) -> None:
@ -288,6 +381,7 @@ class CoreNodeBase(NodeBase):
_id: int = None,
name: str = None,
server: "DistributedServer" = None,
options: NodeOptions = None,
) -> None:
"""
Create a CoreNodeBase instance.
@ -298,7 +392,7 @@ class CoreNodeBase(NodeBase):
:param server: remote server node
will run on, default is None for localhost
"""
super().__init__(session, _id, name, server)
super().__init__(session, _id, name, server, options)
self.config_services: Dict[str, "ConfigService"] = {}
self.directory: Optional[Path] = None
self.tmpnodedir: bool = False
@ -460,8 +554,8 @@ class CoreNode(CoreNodeBase):
session: "Session",
_id: int = None,
name: str = None,
directory: Path = None,
server: "DistributedServer" = None,
options: CoreNodeOptions = None,
) -> None:
"""
Create a CoreNode instance.
@ -469,18 +563,37 @@ class CoreNode(CoreNodeBase):
:param session: core session instance
:param _id: object id
:param name: object name
:param directory: node directory
:param server: remote server node
will run on, default is None for localhost
:param options: options to create node with
"""
super().__init__(session, _id, name, server)
self.directory: Optional[Path] = directory
options = options or CoreNodeOptions()
super().__init__(session, _id, name, server, options)
self.directory: Optional[Path] = options.directory
self.ctrlchnlname: Path = self.session.directory / self.name
self.pid: Optional[int] = None
self._mounts: List[Tuple[Path, Path]] = []
self.node_net_client: LinuxNetClient = self.create_node_net_client(
self.session.use_ovs()
)
options = options or CoreNodeOptions()
self.model: Optional[str] = options.model
# setup services
if options.legacy or options.services:
logger.debug("set node type: %s", self.model)
self.session.services.add_services(self, self.model, options.services)
# add config services
config_services = options.config_services
if not options.legacy and not config_services and not options.services:
config_services = self.session.services.default_services.get(self.model, [])
logger.info("setting node config services: %s", config_services)
for name in config_services:
service_class = self.session.service_manager.get_service(name)
self.add_config_service(service_class)
@classmethod
def create_options(cls) -> CoreNodeOptions:
return CoreNodeOptions()
def create_node_net_client(self, use_ovs: bool) -> LinuxNetClient:
"""
@ -797,6 +910,7 @@ class CoreNetworkBase(NodeBase):
_id: int,
name: str,
server: "DistributedServer" = None,
options: NodeOptions = None,
) -> None:
"""
Create a CoreNetworkBase instance.
@ -806,9 +920,11 @@ class CoreNetworkBase(NodeBase):
:param name: object name
:param server: remote server node
will run on, default is None for localhost
:param options: options to create node with
"""
super().__init__(session, _id, name, server)
self.mtu: int = DEFAULT_MTU
super().__init__(session, _id, name, server, options)
mtu = self.session.options.get_int("mtu")
self.mtu: int = mtu if mtu > 0 else DEFAULT_MTU
self.brname: Optional[str] = None
self.linked: Dict[CoreInterface, Dict[CoreInterface, bool]] = {}
self.linked_lock: threading.Lock = threading.Lock()
@ -839,69 +955,3 @@ class CoreNetworkBase(NodeBase):
iface.net_id = None
with self.linked_lock:
del self.linked[iface]
class Position:
"""
Helper class for Cartesian coordinate position
"""
def __init__(self, x: float = None, y: float = None, z: float = None) -> None:
"""
Creates a Position instance.
:param x: x position
:param y: y position
:param z: z position
"""
self.x: float = x
self.y: float = y
self.z: float = z
self.lon: Optional[float] = None
self.lat: Optional[float] = None
self.alt: Optional[float] = None
def set(self, x: float = None, y: float = None, z: float = None) -> bool:
"""
Returns True if the position has actually changed.
:param x: x position
:param y: y position
:param z: z position
:return: True if position changed, False otherwise
"""
if self.x == x and self.y == y and self.z == z:
return False
self.x = x
self.y = y
self.z = z
return True
def get(self) -> Tuple[float, float, float]:
"""
Retrieve x,y,z position.
:return: x,y,z position tuple
"""
return self.x, self.y, self.z
def set_geo(self, lon: float, lat: float, alt: float) -> None:
"""
Set geo position lon, lat, alt.
:param lon: longitude value
:param lat: latitude value
:param alt: altitude value
:return: nothing
"""
self.lon = lon
self.lat = lat
self.alt = alt
def get_geo(self) -> Tuple[float, float, float]:
"""
Retrieve current geo position lon, lat, alt.
:return: lon, lat, alt position tuple
"""
return self.lon, self.lat, self.alt

View file

@ -1,108 +1,114 @@
import json
import logging
import shlex
from dataclasses import dataclass, field
from pathlib import Path
from tempfile import NamedTemporaryFile
from typing import TYPE_CHECKING, Callable, Dict, Optional
from typing import TYPE_CHECKING, Dict, List, Tuple
from core import utils
from core.emulator.distributed import DistributedServer
from core.errors import CoreCommandError
from core.nodes.base import CoreNode
from core.nodes.netclient import LinuxNetClient, get_net_client
from core.errors import CoreCommandError, CoreError
from core.executables import BASH
from core.nodes.base import CoreNode, CoreNodeOptions
logger = logging.getLogger(__name__)
if TYPE_CHECKING:
from core.emulator.session import Session
DOCKER: str = "docker"
class DockerClient:
def __init__(self, name: str, image: str, run: Callable[..., str]) -> None:
self.name: str = name
self.image: str = image
self.run: Callable[..., str] = run
self.pid: Optional[str] = None
def create_container(self) -> str:
self.run(
f"docker run -td --init --net=none --hostname {self.name} "
f"--name {self.name} --sysctl net.ipv6.conf.all.disable_ipv6=0 "
f"--privileged {self.image} /bin/bash"
)
self.pid = self.get_pid()
return self.pid
@dataclass
class DockerOptions(CoreNodeOptions):
image: str = "ubuntu"
"""image used when creating container"""
binds: List[Tuple[str, str]] = field(default_factory=list)
"""bind mount source and destinations to setup within container"""
volumes: List[Tuple[str, str, bool, bool]] = field(default_factory=list)
"""
volume mount source, destination, unique, delete to setup within container
def get_info(self) -> Dict:
args = f"docker inspect {self.name}"
output = self.run(args)
data = json.loads(output)
if not data:
raise CoreCommandError(1, args, f"docker({self.name}) not present")
return data[0]
unique is True for node unique volume naming
delete is True for deleting volume mount during shutdown
"""
def is_alive(self) -> bool:
try:
data = self.get_info()
return data["State"]["Running"]
except CoreCommandError:
return False
def stop_container(self) -> None:
self.run(f"docker rm -f {self.name}")
def check_cmd(self, cmd: str, wait: bool = True, shell: bool = False) -> str:
logger.info("docker cmd output: %s", cmd)
return utils.cmd(f"docker exec {self.name} {cmd}", wait=wait, shell=shell)
def create_ns_cmd(self, cmd: str) -> str:
return f"nsenter -t {self.pid} -a {cmd}"
def get_pid(self) -> str:
args = f"docker inspect -f '{{{{.State.Pid}}}}' {self.name}"
output = self.run(args)
self.pid = output
logger.debug("node(%s) pid: %s", self.name, self.pid)
return output
def copy_file(self, src_path: Path, dst_path: Path) -> str:
args = f"docker cp {src_path} {self.name}:{dst_path}"
return self.run(args)
@dataclass
class DockerVolume:
src: str
"""volume mount name"""
dst: str
"""volume mount destination directory"""
unique: bool = True
"""True to create a node unique prefixed name for this volume"""
delete: bool = True
"""True to delete the volume during shutdown"""
path: str = None
"""path to the volume on the host"""
class DockerNode(CoreNode):
"""
Provides logic for creating a Docker based node.
"""
def __init__(
self,
session: "Session",
_id: int = None,
name: str = None,
directory: str = None,
server: DistributedServer = None,
image: str = None,
options: DockerOptions = None,
) -> None:
"""
Create a DockerNode instance.
:param session: core session instance
:param _id: object id
:param name: object name
:param directory: node directory
:param _id: node id
:param name: node name
:param server: remote server node
will run on, default is None for localhost
:param image: image to start container with
:param options: options for creating node
"""
super().__init__(session, _id, name, directory, server)
self.image = image if image is not None else "ubuntu"
self.client: Optional[DockerClient] = None
options = options or DockerOptions()
super().__init__(session, _id, name, server, options)
self.image: str = options.image
self.binds: List[Tuple[str, str]] = options.binds
self.volumes: Dict[str, DockerVolume] = {}
for src, dst, unique, delete in options.volumes:
src_name = self._unique_name(src) if unique else src
self.volumes[src] = DockerVolume(src_name, dst, unique, delete)
def create_node_net_client(self, use_ovs: bool) -> LinuxNetClient:
@classmethod
def create_options(cls) -> DockerOptions:
"""
Create node network client for running network commands within the nodes
container.
Return default creation options, which can be used during node creation.
:param use_ovs: True for OVS bridges, False for Linux bridges
:return:node network client
:return: docker options
"""
return get_net_client(use_ovs, self.nsenter_cmd)
return DockerOptions()
def _create_cmd(self, args: str, shell: bool = False) -> str:
"""
Create command used to run commands within the context of a node.
:param args: command arguments
:param shell: True to run shell like, False otherwise
:return: node command
"""
if shell:
args = f"{BASH} -c {shlex.quote(args)}"
return f"nsenter -t {self.pid} -m -u -i -p -n {args}"
def _unique_name(self, name: str) -> str:
"""
Creates a session/node unique prefixed name for the provided input.
:param name: name to make unique
:return: unique session/node prefixed name
"""
return f"{self.session.id}.{self.id}.{name}"
def alive(self) -> bool:
"""
@ -110,22 +116,51 @@ class DockerNode(CoreNode):
:return: True if node is alive, False otherwise
"""
return self.client.is_alive()
try:
running = self.host_cmd(
f"{DOCKER} inspect -f '{{{{.State.Running}}}}' {self.name}"
)
return json.loads(running)
except CoreCommandError:
return False
def startup(self) -> None:
"""
Start a new namespace node by invoking the vnoded process that
allocates a new namespace. Bring up the loopback device and set
the hostname.
Create a docker container instance for the specified image.
:return: nothing
"""
with self.lock:
if self.up:
raise ValueError("starting a node that is already up")
raise CoreError(f"starting node({self.name}) that is already up")
self.makenodedir()
self.client = DockerClient(self.name, self.image, self.host_cmd)
self.pid = self.client.create_container()
binds = ""
for src, dst in self.binds:
binds += f"--mount type=bind,source={src},target={dst} "
volumes = ""
for volume in self.volumes.values():
volumes += (
f"--mount type=volume," f"source={volume.src},target={volume.dst} "
)
self.host_cmd(
f"{DOCKER} run -td --init --net=none --hostname {self.name} "
f"--name {self.name} --sysctl net.ipv6.conf.all.disable_ipv6=0 "
f"{binds} {volumes} "
f"--privileged {self.image} tail -f /dev/null"
)
self.pid = self.host_cmd(
f"{DOCKER} inspect -f '{{{{.State.Pid}}}}' {self.name}"
)
for src, dst in self.binds:
link_path = self.host_path(Path(dst), True)
self.host_cmd(f"ln -s {src} {link_path}")
for volume in self.volumes.values():
volume.path = self.host_cmd(
f"{DOCKER} volume inspect -f '{{{{.Mountpoint}}}}' {volume.src}"
)
link_path = self.host_path(Path(volume.dst), True)
self.host_cmd(f"ln -s {volume.path} {link_path}")
logger.debug("node(%s) pid: %s", self.name, self.pid)
self.up = True
def shutdown(self) -> None:
@ -139,17 +174,12 @@ class DockerNode(CoreNode):
return
with self.lock:
self.ifaces.clear()
self.client.stop_container()
self.host_cmd(f"{DOCKER} rm -f {self.name}")
for volume in self.volumes.values():
if volume.delete:
self.host_cmd(f"{DOCKER} volume rm {volume.src}")
self.up = False
def nsenter_cmd(self, args: str, wait: bool = True, shell: bool = False) -> str:
if self.server is None:
args = self.client.create_ns_cmd(args)
return utils.cmd(args, wait=wait, shell=shell)
else:
args = self.client.create_ns_cmd(args)
return self.server.remote_cmd(args, wait=wait)
def termcmdstring(self, sh: str = "/bin/sh") -> str:
"""
Create a terminal command string.
@ -157,7 +187,7 @@ class DockerNode(CoreNode):
:param sh: shell to execute command in
:return: str
"""
return f"docker exec -it {self.name} bash"
return f"{DOCKER} exec -it {self.name} {sh}"
def create_dir(self, dir_path: Path) -> None:
"""
@ -167,8 +197,7 @@ class DockerNode(CoreNode):
:return: nothing
"""
logger.debug("creating node dir: %s", dir_path)
args = f"mkdir -p {dir_path}"
self.cmd(args)
self.cmd(f"mkdir -p {dir_path}")
def mount(self, src_path: str, target_path: str) -> None:
"""
@ -201,7 +230,7 @@ class DockerNode(CoreNode):
self.cmd(f"mkdir -m {0o755:o} -p {directory}")
if self.server is not None:
self.server.remote_put(temp_path, temp_path)
self.client.copy_file(temp_path, file_path)
self.host_cmd(f"{DOCKER} cp {temp_path} {self.name}:{file_path}")
self.cmd(f"chmod {mode:o} {file_path}")
if self.server is not None:
self.host_cmd(f"rm -f {temp_path}")
@ -226,6 +255,6 @@ class DockerNode(CoreNode):
temp_path = Path(temp.name)
src_path = temp_path
self.server.remote_put(src_path, temp_path)
self.client.copy_file(src_path, dst_path)
self.host_cmd(f"{DOCKER} cp {src_path} {self.name}:{dst_path}")
if mode is not None:
self.cmd(f"chmod {mode:o} {dst_path}")

View file

@ -1,15 +1,16 @@
import json
import logging
import time
from dataclasses import dataclass, field
from pathlib import Path
from tempfile import NamedTemporaryFile
from typing import TYPE_CHECKING, Callable, Dict, Optional
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple
from core import utils
from core.emulator.data import InterfaceData, LinkOptions
from core.emulator.distributed import DistributedServer
from core.errors import CoreCommandError
from core.nodes.base import CoreNode
from core.nodes.base import CoreNode, CoreNodeOptions
from core.nodes.interface import CoreInterface
logger = logging.getLogger(__name__)
@ -66,15 +67,29 @@ class LxdClient:
self.run(args)
@dataclass
class LxcOptions(CoreNodeOptions):
image: str = "ubuntu"
"""image used when creating container"""
binds: List[Tuple[str, str]] = field(default_factory=list)
"""bind mount source and destinations to setup within container"""
volumes: List[Tuple[str, str, bool, bool]] = field(default_factory=list)
"""
volume mount source, destination, unique, delete to setup within container
unique is True for node unique volume naming
delete is True for deleting volume mount during shutdown
"""
class LxcNode(CoreNode):
def __init__(
self,
session: "Session",
_id: int = None,
name: str = None,
directory: str = None,
server: DistributedServer = None,
image: str = None,
options: LxcOptions = None,
) -> None:
"""
Create a LxcNode instance.
@ -82,15 +97,19 @@ class LxcNode(CoreNode):
:param session: core session instance
:param _id: object id
:param name: object name
:param directory: node directory
:param server: remote server node
will run on, default is None for localhost
:param image: image to start container with
:param options: option to create node with
"""
super().__init__(session, _id, name, directory, server)
self.image: str = image if image is not None else "ubuntu"
options = options or LxcOptions()
super().__init__(session, _id, name, server, options)
self.image: str = options.image
self.client: Optional[LxdClient] = None
@classmethod
def create_options(cls) -> LxcOptions:
return LxcOptions()
def alive(self) -> bool:
"""
Check if the node is alive.

View file

@ -4,6 +4,7 @@ Defines network nodes used within core.
import logging
import threading
from dataclasses import dataclass
from pathlib import Path
from typing import TYPE_CHECKING, Dict, List, Optional, Type
@ -14,7 +15,7 @@ from core.emulator.data import InterfaceData, LinkData
from core.emulator.enumerations import MessageFlags, NetworkPolicy, RegisterTlvs
from core.errors import CoreCommandError, CoreError
from core.executables import NFTABLES
from core.nodes.base import CoreNetworkBase
from core.nodes.base import CoreNetworkBase, NodeOptions
from core.nodes.interface import CoreInterface, GreTap
from core.nodes.netclient import get_net_client
@ -180,6 +181,12 @@ class NftablesQueue:
nft_queue: NftablesQueue = NftablesQueue()
@dataclass
class NetworkOptions(NodeOptions):
policy: NetworkPolicy = None
"""allows overriding the network policy, otherwise uses class defined default"""
class CoreNetwork(CoreNetworkBase):
"""
Provides linux bridge network functionality for core nodes.
@ -193,7 +200,7 @@ class CoreNetwork(CoreNetworkBase):
_id: int = None,
name: str = None,
server: "DistributedServer" = None,
policy: NetworkPolicy = None,
options: NetworkOptions = None,
) -> None:
"""
Creates a CoreNetwork instance.
@ -203,18 +210,19 @@ class CoreNetwork(CoreNetworkBase):
:param name: object name
:param server: remote server node
will run on, default is None for localhost
:param policy: network policy
:param options: options to create node with
"""
super().__init__(session, _id, name, server)
if name is None:
name = str(self.id)
if policy is not None:
self.policy: NetworkPolicy = policy
self.name: Optional[str] = name
options = options or NetworkOptions()
super().__init__(session, _id, name, server, options)
self.policy: NetworkPolicy = options.policy if options.policy else self.policy
sessionid = self.session.short_session_id()
self.brname: str = f"b.{self.id}.{sessionid}"
self.has_nftables_chain: bool = False
@classmethod
def create_options(cls) -> NetworkOptions:
return NetworkOptions()
def host_cmd(
self,
args: str,
@ -482,6 +490,20 @@ class GreTapBridge(CoreNetwork):
self.add_ips(ips)
@dataclass
class CtrlNetOptions(NetworkOptions):
prefix: str = None
"""ip4 network prefix to use for generating an address"""
updown_script: str = None
"""script to execute during startup and shutdown"""
serverintf: str = None
"""used to associate an interface with the control network bridge"""
assign_address: bool = True
"""used to determine if a specific address should be assign using hostid"""
hostid: int = None
"""used with assign address to """
class CtrlNet(CoreNetwork):
"""
Control network functionality.
@ -500,36 +522,32 @@ class CtrlNet(CoreNetwork):
def __init__(
self,
session: "Session",
prefix: str,
_id: int = None,
name: str = None,
hostid: int = None,
server: "DistributedServer" = None,
assign_address: bool = True,
updown_script: str = None,
serverintf: str = None,
options: CtrlNetOptions = None,
) -> None:
"""
Creates a CtrlNet instance.
:param session: core session instance
:param _id: node id
:param name: node namee
:param prefix: control network ipv4 prefix
:param hostid: host id
:param name: node name
:param server: remote server node
will run on, default is None for localhost
:param assign_address: assigned address
:param updown_script: updown script
:param serverintf: server interface
:return:
:param options: node options for creation
"""
self.prefix: netaddr.IPNetwork = netaddr.IPNetwork(prefix).cidr
self.hostid: Optional[int] = hostid
self.assign_address: bool = assign_address
self.updown_script: Optional[str] = updown_script
self.serverintf: Optional[str] = serverintf
super().__init__(session, _id, name, server)
options = options or CtrlNetOptions()
super().__init__(session, _id, name, server, options)
self.prefix: netaddr.IPNetwork = netaddr.IPNetwork(options.prefix).cidr
self.hostid: Optional[int] = options.hostid
self.assign_address: bool = options.assign_address
self.updown_script: Optional[str] = options.updown_script
self.serverintf: Optional[str] = options.serverintf
@classmethod
def create_options(cls) -> CtrlNetOptions:
return CtrlNetOptions()
def add_addresses(self, index: int) -> None:
"""
@ -669,7 +687,7 @@ class WlanNode(CoreNetwork):
_id: int = None,
name: str = None,
server: "DistributedServer" = None,
policy: NetworkPolicy = None,
options: NetworkOptions = None,
) -> None:
"""
Create a WlanNode instance.
@ -679,9 +697,9 @@ class WlanNode(CoreNetwork):
:param name: node name
:param server: remote server node
will run on, default is None for localhost
:param policy: wlan policy
:param options: options to create node with
"""
super().__init__(session, _id, name, server, policy)
super().__init__(session, _id, name, server, options)
# wireless and mobility models (BasicRangeModel, Ns2WaypointMobility)
self.wireless_model: Optional[WirelessModel] = None
self.mobility: Optional[WayPointMobility] = None

View file

@ -13,7 +13,7 @@ from core.emulator.distributed import DistributedServer
from core.emulator.enumerations import TransportType
from core.errors import CoreCommandError, CoreError
from core.executables import BASH, TEST, UMOUNT
from core.nodes.base import CoreNode, CoreNodeBase
from core.nodes.base import CoreNode, CoreNodeBase, CoreNodeOptions, NodeOptions
from core.nodes.interface import CoreInterface
logger = logging.getLogger(__name__)
@ -34,6 +34,7 @@ class Rj45Node(CoreNodeBase):
_id: int = None,
name: str = None,
server: DistributedServer = None,
options: NodeOptions = None,
) -> None:
"""
Create an RJ45Node instance.
@ -43,8 +44,9 @@ class Rj45Node(CoreNodeBase):
:param name: node name
:param server: remote server node
will run on, default is None for localhost
:param options: option to create node with
"""
super().__init__(session, _id, name, server)
super().__init__(session, _id, name, server, options)
self.iface: CoreInterface = CoreInterface(
self.iface_id, name, name, session.use_ovs(), node=self, server=server
)
@ -224,12 +226,12 @@ class PhysicalNode(CoreNode):
session: "Session",
_id: int = None,
name: str = None,
directory: Path = None,
server: DistributedServer = None,
options: CoreNodeOptions = None,
) -> None:
if not self.server:
raise CoreError("physical nodes must be assigned to a remote server")
super().__init__(session, _id, name, directory, server)
super().__init__(session, _id, name, server, options)
def startup(self) -> None:
with self.lock:

View file

@ -14,7 +14,7 @@ from core.emulator.data import LinkData, LinkOptions
from core.emulator.enumerations import LinkTypes, MessageFlags
from core.errors import CoreError
from core.executables import NFTABLES
from core.nodes.base import CoreNetworkBase
from core.nodes.base import CoreNetworkBase, NodeOptions
from core.nodes.interface import CoreInterface
if TYPE_CHECKING:
@ -108,8 +108,9 @@ class WirelessNode(CoreNetworkBase):
_id: int,
name: str,
server: "DistributedServer" = None,
options: NodeOptions = None,
):
super().__init__(session, _id, name, server)
super().__init__(session, _id, name, server, options)
self.bridges: Dict[int, Tuple[CoreInterface, str]] = {}
self.links: Dict[Tuple[int, int], WirelessLink] = {}
self.position_enabled: bool = CONFIG_ENABLED

View file

@ -8,12 +8,12 @@ import core.nodes.base
import core.nodes.physical
from core import utils
from core.config import Configuration
from core.emane.nodes import EmaneNet
from core.emulator.data import InterfaceData, LinkOptions, NodeOptions
from core.emane.nodes import EmaneNet, EmaneOptions
from core.emulator.data import InterfaceData, LinkOptions
from core.emulator.enumerations import EventTypes, NodeTypes
from core.errors import CoreXmlError
from core.nodes.base import CoreNodeBase, NodeBase
from core.nodes.docker import DockerNode
from core.nodes.base import CoreNodeBase, CoreNodeOptions, NodeBase, Position
from core.nodes.docker import DockerNode, DockerOptions
from core.nodes.interface import CoreInterface
from core.nodes.lxd import LxcNode
from core.nodes.network import CtrlNet, GreTapBridge, PtpNet, WlanNode
@ -802,68 +802,76 @@ class CoreXmlReader:
clazz = device_element.get("class")
image = device_element.get("image")
server = device_element.get("server")
options = NodeOptions(
name=name, model=model, image=image, icon=icon, server=server
)
canvas = get_int(device_element, "canvas")
node_type = NodeTypes.DEFAULT
if clazz == "docker":
node_type = NodeTypes.DOCKER
elif clazz == "lxc":
node_type = NodeTypes.LXC
_class = self.session.get_node_class(node_type)
options = _class.create_options()
options.icon = icon
options.canvas = canvas
# check for special options
if isinstance(options, CoreNodeOptions):
options.model = model
service_elements = device_element.find("services")
if service_elements is not None:
options.services = [x.get("name") for x in service_elements.iterchildren()]
options.services.extend(
x.get("name") for x in service_elements.iterchildren()
)
config_service_elements = device_element.find("configservices")
if config_service_elements is not None:
options.config_services = [
options.config_services.extend(
x.get("name") for x in config_service_elements.iterchildren()
]
)
if isinstance(options, DockerOptions):
options.image = image
# get position information
position_element = device_element.find("position")
position = None
if position_element is not None:
position = Position()
x = get_float(position_element, "x")
y = get_float(position_element, "y")
if all([x, y]):
options.set_position(x, y)
position.set(x, y)
lat = get_float(position_element, "lat")
lon = get_float(position_element, "lon")
alt = get_float(position_element, "alt")
if all([lat, lon, alt]):
options.set_location(lat, lon, alt)
position.set_geo(lon, lat, alt)
logger.info("reading node id(%s) model(%s) name(%s)", node_id, model, name)
self.session.add_node(_class, node_id, options)
self.session.add_node(_class, node_id, name, server, position, options)
def read_network(self, network_element: etree.Element) -> None:
node_id = get_int(network_element, "id")
name = network_element.get("name")
server = network_element.get("server")
node_type = NodeTypes[network_element.get("type")]
_class = self.session.get_node_class(node_type)
icon = network_element.get("icon")
server = network_element.get("server")
options = NodeOptions(name=name, icon=icon, server=server)
if node_type == NodeTypes.EMANE:
model = network_element.get("model")
options.emane = model
options = _class.create_options()
options.canvas = get_int(network_element, "canvas")
options.icon = network_element.get("icon")
if isinstance(options, EmaneOptions):
options.emane_model = network_element.get("model")
position_element = network_element.find("position")
position = None
if position_element is not None:
position = Position()
x = get_float(position_element, "x")
y = get_float(position_element, "y")
if all([x, y]):
options.set_position(x, y)
position.set(x, y)
lat = get_float(position_element, "lat")
lon = get_float(position_element, "lon")
alt = get_float(position_element, "alt")
if all([lat, lon, alt]):
options.set_location(lat, lon, alt)
position.set_geo(lon, lat, alt)
logger.info(
"reading node id(%s) node_type(%s) name(%s)", node_id, node_type, name
)
node = self.session.add_node(_class, node_id, options)
node = self.session.add_node(_class, node_id, name, server, position, options)
if isinstance(node, WirelessNode):
wireless_element = network_element.find("wireless")
if wireless_element:

View file

@ -1,7 +1,7 @@
import logging
from core.emulator.coreemu import CoreEmu
from core.emulator.data import IpPrefixes, NodeOptions
from core.emulator.data import IpPrefixes
from core.emulator.enumerations import EventTypes
from core.nodes.base import CoreNode
from core.nodes.network import SwitchNode
@ -11,13 +11,13 @@ if __name__ == "__main__":
# setup basic network
prefixes = IpPrefixes(ip4_prefix="10.83.0.0/16")
options = NodeOptions(model=None)
coreemu = CoreEmu()
session = coreemu.create_session()
session.set_state(EventTypes.CONFIGURATION_STATE)
switch = session.add_node(SwitchNode)
# node one
options = CoreNode.create_options()
options.config_services = ["DefaultRoute", "IPForward"]
node1 = session.add_node(CoreNode, options=options)
interface = prefixes.create_iface(node1)

View file

@ -1,7 +1,7 @@
import logging
from core.emulator.coreemu import CoreEmu
from core.emulator.data import IpPrefixes, NodeOptions
from core.emulator.data import IpPrefixes
from core.emulator.enumerations import EventTypes
from core.nodes.base import CoreNode
from core.nodes.docker import DockerNode
@ -14,9 +14,10 @@ if __name__ == "__main__":
try:
prefixes = IpPrefixes(ip4_prefix="10.83.0.0/16")
options = NodeOptions(model=None, image="ubuntu")
# create node one
options = DockerNode.create_options()
options.image = "ubuntu"
node1 = session.add_node(DockerNode, options=options)
interface1_data = prefixes.create_iface(node1)

View file

@ -1,7 +1,7 @@
import logging
from core.emulator.coreemu import CoreEmu
from core.emulator.data import IpPrefixes, NodeOptions
from core.emulator.data import IpPrefixes
from core.emulator.enumerations import EventTypes
from core.nodes.docker import DockerNode
@ -15,9 +15,10 @@ if __name__ == "__main__":
# create nodes and interfaces
try:
prefixes = IpPrefixes(ip4_prefix="10.83.0.0/16")
options = NodeOptions(model=None, image="ubuntu")
# create node one
options = DockerNode.create_options()
options.image = "ubuntu"
node1 = session.add_node(DockerNode, options=options)
interface1_data = prefixes.create_iface(node1)

View file

@ -1,9 +1,8 @@
import logging
from core.emulator.coreemu import CoreEmu
from core.emulator.data import IpPrefixes, NodeOptions
from core.emulator.data import IpPrefixes
from core.emulator.enumerations import EventTypes
from core.nodes.base import CoreNode
from core.nodes.docker import DockerNode
from core.nodes.network import SwitchNode
@ -16,12 +15,15 @@ if __name__ == "__main__":
try:
prefixes = IpPrefixes(ip4_prefix="10.83.0.0/16")
options = NodeOptions(model=None, image="ubuntu")
# create switch
switch = session.add_node(SwitchNode)
# node one
options = DockerNode.create_options()
options.image = "core"
options.binds.append(("/tmp/testbind", "/tmp/bind"))
options.volumes.append(("var.log", "/tmp/var.log", True, True))
node1 = session.add_node(DockerNode, options=options)
interface1_data = prefixes.create_iface(node1)
@ -30,16 +32,18 @@ if __name__ == "__main__":
interface2_data = prefixes.create_iface(node2)
# node three
node_three = session.add_node(CoreNode)
interface_three = prefixes.create_iface(node_three)
# node_three = session.add_node(CoreNode)
# interface_three = prefixes.create_iface(node_three)
# add links
session.add_link(node1.id, switch.id, interface1_data)
session.add_link(node2.id, switch.id, interface2_data)
session.add_link(node_three.id, switch.id, interface_three)
# session.add_link(node_three.id, switch.id, interface_three)
# instantiate
session.instantiate()
print(f"{node2.name}: {node2.volumes.values()}")
finally:
input("continue to shutdown")
coreemu.shutdown()

View file

@ -1,7 +1,7 @@
import logging
from core.emulator.coreemu import CoreEmu
from core.emulator.data import IpPrefixes, NodeOptions
from core.emulator.data import IpPrefixes
from core.emulator.enumerations import EventTypes
from core.nodes.base import CoreNode
from core.nodes.lxd import LxcNode
@ -14,9 +14,10 @@ if __name__ == "__main__":
try:
prefixes = IpPrefixes(ip4_prefix="10.83.0.0/16")
options = NodeOptions(image="ubuntu")
# create node one
options = LxcNode.create_options()
options.image = "ubuntu"
node1 = session.add_node(LxcNode, options=options)
interface1_data = prefixes.create_iface(node1)

View file

@ -1,7 +1,7 @@
import logging
from core.emulator.coreemu import CoreEmu
from core.emulator.data import IpPrefixes, NodeOptions
from core.emulator.data import IpPrefixes
from core.emulator.enumerations import EventTypes
from core.nodes.lxd import LxcNode
@ -15,9 +15,10 @@ if __name__ == "__main__":
# create nodes and interfaces
try:
prefixes = IpPrefixes(ip4_prefix="10.83.0.0/16")
options = NodeOptions(image="ubuntu:18.04")
# create node one
options = LxcNode.create_options()
options.image = "ubuntu:18.04"
node1 = session.add_node(LxcNode, options=options)
interface1_data = prefixes.create_iface(node1)

View file

@ -1,7 +1,7 @@
import logging
from core.emulator.coreemu import CoreEmu
from core.emulator.data import IpPrefixes, NodeOptions
from core.emulator.data import IpPrefixes
from core.emulator.enumerations import EventTypes
from core.nodes.base import CoreNode
from core.nodes.lxd import LxcNode
@ -16,12 +16,13 @@ if __name__ == "__main__":
try:
prefixes = IpPrefixes(ip4_prefix="10.83.0.0/16")
options = NodeOptions(image="ubuntu")
# create switch
switch = session.add_node(SwitchNode)
# node one
options = LxcNode.create_options()
options.image = "ubuntu"
node1 = session.add_node(LxcNode, options=options)
interface1_data = prefixes.create_iface(node1)

View file

@ -9,7 +9,7 @@ import logging
from core.emane.models.ieee80211abg import EmaneIeee80211abgModel
from core.emane.nodes import EmaneNet
from core.emulator.coreemu import CoreEmu
from core.emulator.data import IpPrefixes, NodeOptions
from core.emulator.data import IpPrefixes
from core.emulator.enumerations import EventTypes
from core.nodes.base import CoreNode
@ -50,11 +50,13 @@ def main(args):
session.set_state(EventTypes.CONFIGURATION_STATE)
# create local node, switch, and remote nodes
options = NodeOptions(model="mdr")
options.set_position(0, 0)
options = CoreNode.create_options()
options.model = "mdr"
node1 = session.add_node(CoreNode, options=options)
emane_net = session.add_node(EmaneNet)
session.emane.set_model(emane_net, EmaneIeee80211abgModel)
options = EmaneNet.create_options()
options.emane_model = EmaneIeee80211abgModel.name
emane_net = session.add_node(EmaneNet, options=options)
options = CoreNode.create_options()
options.server = server_name
node2 = session.add_node(CoreNode, options=options)

View file

@ -7,7 +7,7 @@ import argparse
import logging
from core.emulator.coreemu import CoreEmu
from core.emulator.data import IpPrefixes, NodeOptions
from core.emulator.data import IpPrefixes
from core.emulator.enumerations import EventTypes
from core.nodes.lxd import LxcNode
@ -42,7 +42,8 @@ def main(args):
session.set_state(EventTypes.CONFIGURATION_STATE)
# create local node, switch, and remote nodes
options = NodeOptions(image="ubuntu:18.04")
options = LxcNode.create_options()
options.image = "ubuntu:18.04"
node1 = session.add_node(LxcNode, options=options)
options.server = server_name
node2 = session.add_node(LxcNode, options=options)

View file

@ -7,7 +7,7 @@ import argparse
import logging
from core.emulator.coreemu import CoreEmu
from core.emulator.data import IpPrefixes, NodeOptions
from core.emulator.data import IpPrefixes
from core.emulator.enumerations import EventTypes
from core.nodes.base import CoreNode
@ -42,10 +42,8 @@ def main(args):
session.set_state(EventTypes.CONFIGURATION_STATE)
# create local node, switch, and remote nodes
options = NodeOptions()
node1 = session.add_node(CoreNode, options=options)
options.server = server_name
node2 = session.add_node(CoreNode, options=options)
node1 = session.add_node(CoreNode)
node2 = session.add_node(CoreNode, server=server_name)
# create node interfaces and link
interface1_data = prefixes.create_iface(node1)

View file

@ -7,7 +7,7 @@ import argparse
import logging
from core.emulator.coreemu import CoreEmu
from core.emulator.data import IpPrefixes, NodeOptions
from core.emulator.data import IpPrefixes
from core.emulator.enumerations import EventTypes
from core.nodes.base import CoreNode
from core.nodes.network import SwitchNode
@ -47,7 +47,7 @@ def main(args):
# create local node, switch, and remote nodes
node1 = session.add_node(CoreNode)
switch = session.add_node(SwitchNode)
options = NodeOptions()
options = CoreNode.create_options()
options.server = server_name
node2 = session.add_node(CoreNode, options=options)

View file

@ -2,9 +2,9 @@
from core.emane.models.ieee80211abg import EmaneIeee80211abgModel
from core.emane.nodes import EmaneNet
from core.emulator.coreemu import CoreEmu
from core.emulator.data import IpPrefixes, NodeOptions
from core.emulator.data import IpPrefixes
from core.emulator.enumerations import EventTypes
from core.nodes.base import CoreNode
from core.nodes.base import CoreNode, Position
# ip nerator for example
ip_prefixes = IpPrefixes(ip4_prefix="10.0.0.0/24")
@ -21,14 +21,20 @@ session.location.refscale = 150.0
session.set_state(EventTypes.CONFIGURATION_STATE)
# create emane
options = NodeOptions(x=200, y=200, emane=EmaneIeee80211abgModel.name)
emane = session.add_node(EmaneNet, options=options)
options = EmaneNet.create_options()
options.emane_model = EmaneIeee80211abgModel.name
position = Position(x=200, y=200)
emane = session.add_node(EmaneNet, position=position, options=options)
# create nodes
options = NodeOptions(model="mdr", x=100, y=100)
n1 = session.add_node(CoreNode, options=options)
options = NodeOptions(model="mdr", x=300, y=100)
n2 = session.add_node(CoreNode, options=options)
options = CoreNode.create_options()
options.model = "mdr"
position = Position(x=100, y=100)
n1 = session.add_node(CoreNode, position=position, options=options)
options = CoreNode.create_options()
options.model = "mdr"
position = Position(x=300, y=100)
n2 = session.add_node(CoreNode, position=position, options=options)
# configure general emane settings
config = session.emane.get_configs()

View file

@ -1,8 +1,8 @@
# required imports
from core.emulator.coreemu import CoreEmu
from core.emulator.data import IpPrefixes, NodeOptions
from core.emulator.data import IpPrefixes
from core.emulator.enumerations import EventTypes
from core.nodes.base import CoreNode
from core.nodes.base import CoreNode, Position
# ip nerator for example
ip_prefixes = IpPrefixes(ip4_prefix="10.0.0.0/24")
@ -15,10 +15,10 @@ session = coreemu.create_session()
session.set_state(EventTypes.CONFIGURATION_STATE)
# create nodes
options = NodeOptions(x=100, y=100)
n1 = session.add_node(CoreNode, options=options)
options = NodeOptions(x=300, y=100)
n2 = session.add_node(CoreNode, options=options)
position = Position(x=100, y=100)
n1 = session.add_node(CoreNode, position=position)
position = Position(x=300, y=100)
n2 = session.add_node(CoreNode, position=position)
# link nodes together
iface1 = ip_prefixes.create_iface(n1)

View file

@ -1,8 +1,8 @@
# required imports
from core.emulator.coreemu import CoreEmu
from core.emulator.data import IpPrefixes, NodeOptions
from core.emulator.data import IpPrefixes
from core.emulator.enumerations import EventTypes
from core.nodes.base import CoreNode
from core.nodes.base import CoreNode, Position
from core.nodes.network import SwitchNode
# ip nerator for example
@ -16,14 +16,14 @@ session = coreemu.create_session()
session.set_state(EventTypes.CONFIGURATION_STATE)
# create switch
options = NodeOptions(x=200, y=200)
switch = session.add_node(SwitchNode, options=options)
position = Position(x=200, y=200)
switch = session.add_node(SwitchNode, position=position)
# create nodes
options = NodeOptions(x=100, y=100)
n1 = session.add_node(CoreNode, options=options)
options = NodeOptions(x=300, y=100)
n2 = session.add_node(CoreNode, options=options)
position = Position(x=100, y=100)
n1 = session.add_node(CoreNode, position=position)
position = Position(x=300, y=100)
n2 = session.add_node(CoreNode, position=position)
# link nodes to switch
iface1 = ip_prefixes.create_iface(n1)

View file

@ -2,9 +2,9 @@
import logging
from core.emulator.coreemu import CoreEmu
from core.emulator.data import IpPrefixes, NodeOptions
from core.emulator.data import IpPrefixes
from core.emulator.enumerations import EventTypes
from core.nodes.base import CoreNode
from core.nodes.base import CoreNode, Position
from core.nodes.network import WlanNode
# enable info logging
@ -21,14 +21,18 @@ session = coreemu.create_session()
session.set_state(EventTypes.CONFIGURATION_STATE)
# create wireless
options = NodeOptions(x=200, y=200)
wireless = session.add_node(WlanNode, options=options)
position = Position(x=200, y=200)
wireless = session.add_node(WlanNode, position=position)
# create nodes
options = NodeOptions(model="mdr", x=100, y=100)
n1 = session.add_node(CoreNode, options=options)
options = NodeOptions(model="mdr", x=300, y=100)
n2 = session.add_node(CoreNode, options=options)
options = CoreNode.create_options()
options.model = "mdr"
position = Position(x=100, y=100)
n1 = session.add_node(CoreNode, position=position, options=options)
options = CoreNode.create_options()
options.model = "mdr"
position = Position(x=300, y=100)
n2 = session.add_node(CoreNode, position=position, options=options)
# link nodes to wireless
iface1 = ip_prefixes.create_iface(n1)

View file

@ -1,9 +1,9 @@
# required imports
from core.emulator.coreemu import CoreEmu
from core.emulator.data import IpPrefixes, NodeOptions
from core.emulator.data import IpPrefixes
from core.emulator.enumerations import EventTypes
from core.location.mobility import BasicRangeModel
from core.nodes.base import CoreNode
from core.nodes.base import CoreNode, Position
from core.nodes.network import WlanNode
# ip nerator for example
@ -17,14 +17,18 @@ session = coreemu.create_session()
session.set_state(EventTypes.CONFIGURATION_STATE)
# create wlan
options = NodeOptions(x=200, y=200)
wlan = session.add_node(WlanNode, options=options)
position = Position(x=200, y=200)
wlan = session.add_node(WlanNode, position=position)
# create nodes
options = NodeOptions(model="mdr", x=100, y=100)
n1 = session.add_node(CoreNode, options=options)
options = NodeOptions(model="mdr", x=300, y=100)
n2 = session.add_node(CoreNode, options=options)
options = CoreNode.create_options()
options.model = "mdr"
position = Position(x=100, y=100)
n1 = session.add_node(CoreNode, position=position, options=options)
options = CoreNode.create_options()
options.model = "mdr"
position = Position(x=300, y=100)
n2 = session.add_node(CoreNode, position=position, options=options)
# configuring wlan
session.mobility.set_model_config(

350
daemon/poetry.lock generated
View file

@ -30,7 +30,7 @@ tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>
[[package]]
name = "bcrypt"
version = "3.2.0"
version = "3.2.2"
description = "Modern password hashing for your software and your servers"
category = "main"
optional = false
@ -38,7 +38,6 @@ python-versions = ">=3.6"
[package.dependencies]
cffi = ">=1.1"
six = ">=1.4.1"
[package.extras]
tests = ["pytest (>=3.2.1,!=3.3.0)"]
@ -61,6 +60,14 @@ toml = ">=0.9.4"
[package.extras]
d = ["aiohttp (>=3.3.2)", "aiohttp-cors"]
[[package]]
name = "certifi"
version = "2021.10.8"
description = "Python package for providing Mozilla's CA Bundle."
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "cffi"
version = "1.15.0"
@ -82,7 +89,7 @@ python-versions = ">=3.6"
[[package]]
name = "click"
version = "8.0.3"
version = "8.0.4"
description = "Composable command line interface toolkit"
category = "dev"
optional = false
@ -102,7 +109,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "cryptography"
version = "36.0.1"
version = "37.0.2"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
category = "main"
optional = false
@ -117,7 +124,7 @@ docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling
pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
sdist = ["setuptools_rust (>=0.11.4)"]
ssh = ["bcrypt (>=3.1.5)"]
test = ["pytest (>=6.2.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"]
test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"]
[[package]]
name = "dataclasses"
@ -179,26 +186,29 @@ pyflakes = ">=2.2.0,<2.3.0"
[[package]]
name = "grpcio"
version = "1.27.2"
version = "1.43.0"
description = "HTTP/2-based RPC framework"
category = "main"
optional = false
python-versions = "*"
python-versions = ">=3.6"
[package.dependencies]
six = ">=1.5.2"
[package.extras]
protobuf = ["grpcio-tools (>=1.43.0)"]
[[package]]
name = "grpcio-tools"
version = "1.27.2"
version = "1.43.0"
description = "Protobuf code generator for gRPC"
category = "dev"
optional = false
python-versions = "*"
python-versions = ">=3.6"
[package.dependencies]
grpcio = ">=1.27.2"
protobuf = ">=3.5.0.post1"
grpcio = ">=1.43.0"
protobuf = ">=3.5.0.post1,<4.0dev"
[[package]]
name = "identify"
@ -325,7 +335,7 @@ test = ["pytest", "pytest-cov"]
[[package]]
name = "more-itertools"
version = "8.12.0"
version = "8.13.0"
description = "More routines for operating on iterables, beyond itertools"
category = "dev"
optional = false
@ -360,7 +370,7 @@ pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
[[package]]
name = "paramiko"
version = "2.9.2"
version = "2.10.4"
description = "SSH2 protocol library"
category = "main"
optional = false
@ -370,6 +380,7 @@ python-versions = "*"
bcrypt = ">=3.1.3"
cryptography = ">=2.5"
pynacl = ">=1.0.1"
six = "*"
[package.extras]
all = ["pyasn1 (>=0.1.7)", "pynacl (>=1.0.1)", "bcrypt (>=3.1.3)", "invoke (>=1.3)", "gssapi (>=1.4.1)", "pywin32 (>=2.1.8)"]
@ -497,11 +508,14 @@ diagrams = ["jinja2", "railroad-diagrams"]
[[package]]
name = "pyproj"
version = "2.6.1.post1"
version = "3.0.1"
description = "Python interface to PROJ (cartographic projections and coordinate transformations library)"
category = "main"
optional = false
python-versions = ">=3.5"
python-versions = ">=3.6"
[package.dependencies]
certifi = "*"
[[package]]
name = "pytest"
@ -560,7 +574,7 @@ python-versions = ">=3.6"
[[package]]
name = "virtualenv"
version = "20.13.1"
version = "20.14.1"
description = "Virtual Python Environment builder"
category = "dev"
optional = false
@ -601,7 +615,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes
[metadata]
lock-version = "1.1"
python-versions = "^3.6"
content-hash = "64ea28583e46b32b3aa2be3627ee8f68c1bbf36622ec6f575062d5059745a6f9"
content-hash = "0a739e4b479c0c2111fa22ffb1ed55a99d26583a576a1b548204c29b726e3e33"
[metadata.files]
appdirs = [
@ -617,18 +631,26 @@ attrs = [
{file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
]
bcrypt = [
{file = "bcrypt-3.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c95d4cbebffafcdd28bd28bb4e25b31c50f6da605c81ffd9ad8a3d1b2ab7b1b6"},
{file = "bcrypt-3.2.0-cp36-abi3-manylinux1_x86_64.whl", hash = "sha256:63d4e3ff96188e5898779b6057878fecf3f11cfe6ec3b313ea09955d587ec7a7"},
{file = "bcrypt-3.2.0-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:cd1ea2ff3038509ea95f687256c46b79f5fc382ad0aa3664d200047546d511d1"},
{file = "bcrypt-3.2.0-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:cdcdcb3972027f83fe24a48b1e90ea4b584d35f1cc279d76de6fc4b13376239d"},
{file = "bcrypt-3.2.0-cp36-abi3-win32.whl", hash = "sha256:a67fb841b35c28a59cebed05fbd3e80eea26e6d75851f0574a9273c80f3e9b55"},
{file = "bcrypt-3.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:81fec756feff5b6818ea7ab031205e1d323d8943d237303baca2c5f9c7846f34"},
{file = "bcrypt-3.2.0.tar.gz", hash = "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29"},
{file = "bcrypt-3.2.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:7180d98a96f00b1050e93f5b0f556e658605dd9f524d0b0e68ae7944673f525e"},
{file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:61bae49580dce88095d669226d5076d0b9d927754cedbdf76c6c9f5099ad6f26"},
{file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88273d806ab3a50d06bc6a2fc7c87d737dd669b76ad955f449c43095389bc8fb"},
{file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6d2cb9d969bfca5bc08e45864137276e4c3d3d7de2b162171def3d188bf9d34a"},
{file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b02d6bfc6336d1094276f3f588aa1225a598e27f8e3388f4db9948cb707b521"},
{file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a2c46100e315c3a5b90fdc53e429c006c5f962529bc27e1dfd656292c20ccc40"},
{file = "bcrypt-3.2.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7d9ba2e41e330d2af4af6b1b6ec9e6128e91343d0b4afb9282e54e5508f31baa"},
{file = "bcrypt-3.2.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cd43303d6b8a165c29ec6756afd169faba9396a9472cdff753fe9f19b96ce2fa"},
{file = "bcrypt-3.2.2-cp36-abi3-win32.whl", hash = "sha256:4e029cef560967fb0cf4a802bcf4d562d3d6b4b1bf81de5ec1abbe0f1adb027e"},
{file = "bcrypt-3.2.2-cp36-abi3-win_amd64.whl", hash = "sha256:7ff2069240c6bbe49109fe84ca80508773a904f5a8cb960e02a977f7f519b129"},
{file = "bcrypt-3.2.2.tar.gz", hash = "sha256:433c410c2177057705da2a9f2cd01dd157493b2a7ac14c8593a16b3dab6b6bfb"},
]
black = [
{file = "black-19.3b0-py36-none-any.whl", hash = "sha256:09a9dcb7c46ed496a9850b76e4e825d6049ecd38b611f1224857a79bd985a8cf"},
{file = "black-19.3b0.tar.gz", hash = "sha256:68950ffd4d9169716bcb8719a56c07a2f4485354fec061cdd5910aa07369731c"},
]
certifi = [
{file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
{file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"},
]
cffi = [
{file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"},
{file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"},
@ -686,34 +708,36 @@ cfgv = [
{file = "cfgv-3.0.0.tar.gz", hash = "sha256:04b093b14ddf9fd4d17c53ebfd55582d27b76ed30050193c14e560770c5360eb"},
]
click = [
{file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"},
{file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"},
{file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"},
{file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"},
]
colorama = [
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
]
cryptography = [
{file = "cryptography-36.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:73bc2d3f2444bcfeac67dd130ff2ea598ea5f20b40e36d19821b4df8c9c5037b"},
{file = "cryptography-36.0.1-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:2d87cdcb378d3cfed944dac30596da1968f88fb96d7fc34fdae30a99054b2e31"},
{file = "cryptography-36.0.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74d6c7e80609c0f4c2434b97b80c7f8fdfaa072ca4baab7e239a15d6d70ed73a"},
{file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:6c0c021f35b421ebf5976abf2daacc47e235f8b6082d3396a2fe3ccd537ab173"},
{file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d59a9d55027a8b88fd9fd2826c4392bd487d74bf628bb9d39beecc62a644c12"},
{file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a817b961b46894c5ca8a66b599c745b9a3d9f822725221f0e0fe49dc043a3a3"},
{file = "cryptography-36.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:94ae132f0e40fe48f310bba63f477f14a43116f05ddb69d6fa31e93f05848ae2"},
{file = "cryptography-36.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7be0eec337359c155df191d6ae00a5e8bbb63933883f4f5dffc439dac5348c3f"},
{file = "cryptography-36.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e0344c14c9cb89e76eb6a060e67980c9e35b3f36691e15e1b7a9e58a0a6c6dc3"},
{file = "cryptography-36.0.1-cp36-abi3-win32.whl", hash = "sha256:4caa4b893d8fad33cf1964d3e51842cd78ba87401ab1d2e44556826df849a8ca"},
{file = "cryptography-36.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:391432971a66cfaf94b21c24ab465a4cc3e8bf4a939c1ca5c3e3a6e0abebdbcf"},
{file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bb5829d027ff82aa872d76158919045a7c1e91fbf241aec32cb07956e9ebd3c9"},
{file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebc15b1c22e55c4d5566e3ca4db8689470a0ca2babef8e3a9ee057a8b82ce4b1"},
{file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:596f3cd67e1b950bc372c33f1a28a0692080625592ea6392987dba7f09f17a94"},
{file = "cryptography-36.0.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:30ee1eb3ebe1644d1c3f183d115a8c04e4e603ed6ce8e394ed39eea4a98469ac"},
{file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec63da4e7e4a5f924b90af42eddf20b698a70e58d86a72d943857c4c6045b3ee"},
{file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca238ceb7ba0bdf6ce88c1b74a87bffcee5afbfa1e41e173b1ceb095b39add46"},
{file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:ca28641954f767f9822c24e927ad894d45d5a1e501767599647259cbf030b903"},
{file = "cryptography-36.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:39bdf8e70eee6b1c7b289ec6e5d84d49a6bfa11f8b8646b5b3dfe41219153316"},
{file = "cryptography-36.0.1.tar.gz", hash = "sha256:53e5c1dc3d7a953de055d77bef2ff607ceef7a2aac0353b5d630ab67f7423638"},
{file = "cryptography-37.0.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:ef15c2df7656763b4ff20a9bc4381d8352e6640cfeb95c2972c38ef508e75181"},
{file = "cryptography-37.0.2-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3c81599befb4d4f3d7648ed3217e00d21a9341a9a688ecdd615ff72ffbed7336"},
{file = "cryptography-37.0.2-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2bd1096476aaac820426239ab534b636c77d71af66c547b9ddcd76eb9c79e004"},
{file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:31fe38d14d2e5f787e0aecef831457da6cec68e0bb09a35835b0b44ae8b988fe"},
{file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:093cb351031656d3ee2f4fa1be579a8c69c754cf874206be1d4cf3b542042804"},
{file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59b281eab51e1b6b6afa525af2bd93c16d49358404f814fe2c2410058623928c"},
{file = "cryptography-37.0.2-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:0cc20f655157d4cfc7bada909dc5cc228211b075ba8407c46467f63597c78178"},
{file = "cryptography-37.0.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:f8ec91983e638a9bcd75b39f1396e5c0dc2330cbd9ce4accefe68717e6779e0a"},
{file = "cryptography-37.0.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:46f4c544f6557a2fefa7ac8ac7d1b17bf9b647bd20b16decc8fbcab7117fbc15"},
{file = "cryptography-37.0.2-cp36-abi3-win32.whl", hash = "sha256:731c8abd27693323b348518ed0e0705713a36d79fdbd969ad968fbef0979a7e0"},
{file = "cryptography-37.0.2-cp36-abi3-win_amd64.whl", hash = "sha256:471e0d70201c069f74c837983189949aa0d24bb2d751b57e26e3761f2f782b8d"},
{file = "cryptography-37.0.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a68254dd88021f24a68b613d8c51d5c5e74d735878b9e32cc0adf19d1f10aaf9"},
{file = "cryptography-37.0.2-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:a7d5137e556cc0ea418dca6186deabe9129cee318618eb1ffecbd35bee55ddc1"},
{file = "cryptography-37.0.2-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:aeaba7b5e756ea52c8861c133c596afe93dd716cbcacae23b80bc238202dc023"},
{file = "cryptography-37.0.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95e590dd70642eb2079d280420a888190aa040ad20f19ec8c6e097e38aa29e06"},
{file = "cryptography-37.0.2-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:1b9362d34363f2c71b7853f6251219298124aa4cc2075ae2932e64c91a3e2717"},
{file = "cryptography-37.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e53258e69874a306fcecb88b7534d61820db8a98655662a3dd2ec7f1afd9132f"},
{file = "cryptography-37.0.2-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:1f3bfbd611db5cb58ca82f3deb35e83af34bb8cf06043fa61500157d50a70982"},
{file = "cryptography-37.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:419c57d7b63f5ec38b1199a9521d77d7d1754eb97827bbb773162073ccd8c8d4"},
{file = "cryptography-37.0.2-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:dc26bb134452081859aa21d4990474ddb7e863aa39e60d1592800a8865a702de"},
{file = "cryptography-37.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3b8398b3d0efc420e777c40c16764d6870bcef2eb383df9c6dbb9ffe12c64452"},
{file = "cryptography-37.0.2.tar.gz", hash = "sha256:f224ad253cc9cea7568f49077007d2263efa57396a2f2f78114066fd54b5c68e"},
]
dataclasses = [
{file = "dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf"},
@ -736,94 +760,96 @@ flake8 = [
{file = "flake8-3.8.2.tar.gz", hash = "sha256:c69ac1668e434d37a2d2880b3ca9aafd54b3a10a3ac1ab101d22f29e29cf8634"},
]
grpcio = [
{file = "grpcio-1.27.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:dbec0a3a154dbf2eb85b38abaddf24964fa1c059ee0a4ad55d6f39211b1a4bca"},
{file = "grpcio-1.27.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:1ef949b15a1f5f30651532a9b54edf3bd7c0b699a10931505fa2c80b2d395942"},
{file = "grpcio-1.27.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:ed123037896a8db6709b8ad5acc0ed435453726ea0b63361d12de369624c2ab5"},
{file = "grpcio-1.27.2-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:f9d632ce9fd485119c968ec6a7a343de698c5e014d17602ae2f110f1b05925ed"},
{file = "grpcio-1.27.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:80c3d1ce8820dd819d1c9d6b63b6f445148480a831173b572a9174a55e7abd47"},
{file = "grpcio-1.27.2-cp27-cp27m-win32.whl", hash = "sha256:07f82aefb4a56c7e1e52b78afb77d446847d27120a838a1a0489260182096045"},
{file = "grpcio-1.27.2-cp27-cp27m-win_amd64.whl", hash = "sha256:28f27c64dd699b8b10f70da5f9320c1cffcaefca7dd76275b44571bd097f276c"},
{file = "grpcio-1.27.2-cp27-cp27mu-linux_armv7l.whl", hash = "sha256:a25b84e10018875a0f294a7649d07c43e8bc3e6a821714e39e5cd607a36386d7"},
{file = "grpcio-1.27.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:57949756a3ce1f096fa2b00f812755f5ab2effeccedb19feeb7d0deafa3d1de7"},
{file = "grpcio-1.27.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:f3614dabd2cc8741850597b418bcf644d4f60e73615906c3acc407b78ff720b3"},
{file = "grpcio-1.27.2-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:25c77692ea8c0929d4ad400ea9c3dcbcc4936cee84e437e0ef80da58fa73d88a"},
{file = "grpcio-1.27.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:5dab393ab96b2ce4012823b2f2ed4ee907150424d2f02b97bd6f8dd8f17cc866"},
{file = "grpcio-1.27.2-cp35-cp35m-linux_armv7l.whl", hash = "sha256:bb2987eb3af9bcf46019be39b82c120c3d35639a95bc4ee2d08f36ecdf469345"},
{file = "grpcio-1.27.2-cp35-cp35m-macosx_10_7_intel.whl", hash = "sha256:6f328a3faaf81a2546a3022b3dfc137cc6d50d81082dbc0c94d1678943f05df3"},
{file = "grpcio-1.27.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:5ebc13451246de82f130e8ee7e723e8d7ae1827f14b7b0218867667b1b12c88d"},
{file = "grpcio-1.27.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:355bd7d7ce5ff2917d217f0e8ddac568cb7403e1ce1639b35a924db7d13a39b6"},
{file = "grpcio-1.27.2-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:d1e5563e3b7f844dbc48d709c9e4a75647e11d0387cc1fa0c861d3e9d34bc844"},
{file = "grpcio-1.27.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:1ec8fc865d8da6d0713e2092a27eee344cd54628b2c2065a0e77fff94df4ae00"},
{file = "grpcio-1.27.2-cp35-cp35m-win32.whl", hash = "sha256:706e2dea3de33b0d8884c4d35ecd5911b4ff04d0697c4138096666ce983671a6"},
{file = "grpcio-1.27.2-cp35-cp35m-win_amd64.whl", hash = "sha256:d18b4c8cacbb141979bb44355ee5813dd4d307e9d79b3a36d66eca7e0a203df8"},
{file = "grpcio-1.27.2-cp36-cp36m-linux_armv7l.whl", hash = "sha256:02aef8ef1a5ac5f0836b543e462eb421df6048a7974211a906148053b8055ea6"},
{file = "grpcio-1.27.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b78af4d42985ab3143d9882d0006f48d12f1bc4ba88e78f23762777c3ee64571"},
{file = "grpcio-1.27.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:9c0669ba9aebad540fb05a33beb7e659ea6e5ca35833fc5229c20f057db760e8"},
{file = "grpcio-1.27.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:68a149a0482d0bc697aac702ec6efb9d380e0afebf9484db5b7e634146528371"},
{file = "grpcio-1.27.2-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:a71138366d57901597bfcc52af7f076ab61c046f409c7b429011cd68de8f9fe6"},
{file = "grpcio-1.27.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:9e9cfe55dc7ac2aa47e0fd3285ff829685f96803197042c9d2f0fb44e4b39b2c"},
{file = "grpcio-1.27.2-cp36-cp36m-win32.whl", hash = "sha256:d22c897b65b1408509099f1c3334bd3704f5e4eb7c0486c57d0e212f71cb8f54"},
{file = "grpcio-1.27.2-cp36-cp36m-win_amd64.whl", hash = "sha256:c59b9280284b791377b3524c8e39ca7b74ae2881ba1a6c51b36f4f1bb94cee49"},
{file = "grpcio-1.27.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6e545908bcc2ae28e5b190ce3170f92d0438cf26a82b269611390114de0106eb"},
{file = "grpcio-1.27.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6db7ded10b82592c472eeeba34b9f12d7b0ab1e2dcad12f081b08ebdea78d7d6"},
{file = "grpcio-1.27.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4d3b6e66f32528bf43ca2297caca768280a8e068820b1c3dca0fcf9f03c7d6f1"},
{file = "grpcio-1.27.2-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:586d931736912865c9790c60ca2db29e8dc4eace160d5a79fec3e58df79a9386"},
{file = "grpcio-1.27.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:c03ce53690fe492845e14f4ab7e67d5a429a06db99b226b5c7caa23081c1e2bb"},
{file = "grpcio-1.27.2-cp37-cp37m-win32.whl", hash = "sha256:209927e65395feb449783943d62a3036982f871d7f4045fadb90b2d82b153ea8"},
{file = "grpcio-1.27.2-cp37-cp37m-win_amd64.whl", hash = "sha256:9713578f187fb1c4d00ac554fe1edcc6b3ddd62f5d4eb578b81261115802df8e"},
{file = "grpcio-1.27.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b4efde5524579a9ce0459ca35a57a48ca878a4973514b8bb88cb80d7c9d34c85"},
{file = "grpcio-1.27.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:fb62996c61eeff56b59ab8abfcaa0859ec2223392c03d6085048b576b567459b"},
{file = "grpcio-1.27.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:a22daaf30037b8e59d6968c76fe0f7ff062c976c7a026e92fbefc4c4bf3fc5a4"},
{file = "grpcio-1.27.2-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:4a0a33ada3f6f94f855f92460896ef08c798dcc5f17d9364d1735c5adc9d7e4a"},
{file = "grpcio-1.27.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:8111b61eee12d7af5c58f82f2c97c2664677a05df9225ef5cbc2f25398c8c454"},
{file = "grpcio-1.27.2-cp38-cp38-win32.whl", hash = "sha256:5121fa96c79fc0ec81825091d0be5c16865f834f41b31da40b08ee60552f9961"},
{file = "grpcio-1.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:1cff47297ee614e7ef66243dc34a776883ab6da9ca129ea114a802c5e58af5c1"},
{file = "grpcio-1.27.2.tar.gz", hash = "sha256:5ae532b93cf9ce5a2a549b74a2c35e3b690b171ece9358519b3039c7b84c887e"},
{file = "grpcio-1.43.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:a4e786a8ee8b30b25d70ee52cda6d1dbba2a8ca2f1208d8e20ed8280774f15c8"},
{file = "grpcio-1.43.0-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:af9c3742f6c13575c0d4147a8454da0ff5308c4d9469462ff18402c6416942fe"},
{file = "grpcio-1.43.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:fdac966699707b5554b815acc272d81e619dd0999f187cd52a61aef075f870ee"},
{file = "grpcio-1.43.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e463b4aa0a6b31cf2e57c4abc1a1b53531a18a570baeed39d8d7b65deb16b7e"},
{file = "grpcio-1.43.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f11d05402e0ac3a284443d8a432d3dfc76a6bd3f7b5858cddd75617af2d7bd9b"},
{file = "grpcio-1.43.0-cp310-cp310-win32.whl", hash = "sha256:c36f418c925a41fccada8f7ae9a3d3e227bfa837ddbfddd3d8b0ac252d12dda9"},
{file = "grpcio-1.43.0-cp310-cp310-win_amd64.whl", hash = "sha256:772b943f34374744f70236bbbe0afe413ed80f9ae6303503f85e2b421d4bca92"},
{file = "grpcio-1.43.0-cp36-cp36m-linux_armv7l.whl", hash = "sha256:cbc9b83211d905859dcf234ad39d7193ff0f05bfc3269c364fb0d114ee71de59"},
{file = "grpcio-1.43.0-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:fb7229fa2a201a0c377ff3283174ec966da8f9fd7ffcc9a92f162d2e7fc9025b"},
{file = "grpcio-1.43.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:17b75f220ee6923338155b4fcef4c38802b9a57bc57d112c9599a13a03e99f8d"},
{file = "grpcio-1.43.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:6620a5b751b099b3b25553cfc03dfcd873cda06f9bb2ff7e9948ac7090e20f05"},
{file = "grpcio-1.43.0-cp36-cp36m-manylinux_2_17_aarch64.whl", hash = "sha256:1898f999383baac5fcdbdef8ea5b1ef204f38dc211014eb6977ac6e55944d738"},
{file = "grpcio-1.43.0-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47b6821238d8978014d23b1132713dac6c2d72cbb561cf257608b1673894f90a"},
{file = "grpcio-1.43.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80398e9fb598060fa41050d1220f5a2440fe74ff082c36dda41ac3215ebb5ddd"},
{file = "grpcio-1.43.0-cp36-cp36m-win32.whl", hash = "sha256:0110310eff07bb69782f53b7a947490268c4645de559034c43c0a635612e250f"},
{file = "grpcio-1.43.0-cp36-cp36m-win_amd64.whl", hash = "sha256:45401d00f2ee46bde75618bf33e9df960daa7980e6e0e7328047191918c98504"},
{file = "grpcio-1.43.0-cp37-cp37m-linux_armv7l.whl", hash = "sha256:af78ac55933811e6a25141336b1f2d5e0659c2f568d44d20539b273792563ca7"},
{file = "grpcio-1.43.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:8b2b9dc4d7897566723b77422e11c009a0ebd397966b165b21b89a62891a9fdf"},
{file = "grpcio-1.43.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:77ef653f966934b3bfdd00e4f2064b68880eb40cf09b0b99edfa5ee22a44f559"},
{file = "grpcio-1.43.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:e95b5d62ec26d0cd0b90c202d73e7cb927c369c3358e027225239a4e354967dc"},
{file = "grpcio-1.43.0-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:04239e8f71db832c26bbbedb4537b37550a39d77681d748ab4678e58dd6455d6"},
{file = "grpcio-1.43.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b4a7152187a49767a47d1413edde2304c96f41f7bc92cc512e230dfd0fba095"},
{file = "grpcio-1.43.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8cc936a29c65ab39714e1ba67a694c41218f98b6e2a64efb83f04d9abc4386b"},
{file = "grpcio-1.43.0-cp37-cp37m-win32.whl", hash = "sha256:577e024c8dd5f27cd98ba850bc4e890f07d4b5942e5bc059a3d88843a2f48f66"},
{file = "grpcio-1.43.0-cp37-cp37m-win_amd64.whl", hash = "sha256:138f57e3445d4a48d9a8a5af1538fdaafaa50a0a3c243f281d8df0edf221dc02"},
{file = "grpcio-1.43.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:08cf25f2936629db062aeddbb594bd76b3383ab0ede75ef0461a3b0bc3a2c150"},
{file = "grpcio-1.43.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:01f4b887ed703fe82ebe613e1d2dadea517891725e17e7a6134dcd00352bd28c"},
{file = "grpcio-1.43.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:0aa8285f284338eb68962fe1a830291db06f366ea12f213399b520c062b01f65"},
{file = "grpcio-1.43.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:0edbfeb6729aa9da33ce7e28fb7703b3754934115454ae45e8cc1db601756fd3"},
{file = "grpcio-1.43.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:c354017819201053d65212befd1dcb65c2d91b704d8977e696bae79c47cd2f82"},
{file = "grpcio-1.43.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50cfb7e1067ee5e00b8ab100a6b7ea322d37ec6672c0455106520b5891c4b5f5"},
{file = "grpcio-1.43.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57f1aeb65ed17dfb2f6cd717cc109910fe395133af7257a9c729c0b9604eac10"},
{file = "grpcio-1.43.0-cp38-cp38-win32.whl", hash = "sha256:fa26a8bbb3fe57845acb1329ff700d5c7eaf06414c3e15f4cb8923f3a466ef64"},
{file = "grpcio-1.43.0-cp38-cp38-win_amd64.whl", hash = "sha256:ade8b79a6b6aea68adb9d4bfeba5d647667d842202c5d8f3ba37ac1dc8e5c09c"},
{file = "grpcio-1.43.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:124e718faf96fe44c98b05f3f475076be8b5198bb4c52a13208acf88a8548ba9"},
{file = "grpcio-1.43.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2f96142d0abc91290a63ba203f01649e498302b1b6007c67bad17f823ecde0cf"},
{file = "grpcio-1.43.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:31e6e489ccd8f08884b9349a39610982df48535881ec34f05a11c6e6b6ebf9d0"},
{file = "grpcio-1.43.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:0e731f660e1e68238f56f4ce11156f02fd06dc58bc7834778d42c0081d4ef5ad"},
{file = "grpcio-1.43.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:1f16725a320460435a8a5339d8b06c4e00d307ab5ad56746af2e22b5f9c50932"},
{file = "grpcio-1.43.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4b4543e13acb4806917d883d0f70f21ba93b29672ea81f4aaba14821aaf9bb0"},
{file = "grpcio-1.43.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:594aaa0469f4fca7773e80d8c27bf1298e7bbce5f6da0f084b07489a708f16ab"},
{file = "grpcio-1.43.0-cp39-cp39-win32.whl", hash = "sha256:5449ae564349e7a738b8c38583c0aad954b0d5d1dd3cea68953bfc32eaee11e3"},
{file = "grpcio-1.43.0-cp39-cp39-win_amd64.whl", hash = "sha256:bdf41550815a831384d21a498b20597417fd31bd084deb17d31ceb39ad9acc79"},
{file = "grpcio-1.43.0.tar.gz", hash = "sha256:735d9a437c262ab039d02defddcb9f8f545d7009ae61c0114e19dda3843febe5"},
]
grpcio-tools = [
{file = "grpcio-tools-1.27.2.tar.gz", hash = "sha256:845a51305af9fc7f9e2078edaec9a759153195f6cf1fbb12b1fa6f077e56b260"},
{file = "grpcio_tools-1.27.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:7a2d5fb558ac153a326e742ebfd7020eb781c43d3ffd920abd42b2e6c6fdfb37"},
{file = "grpcio_tools-1.27.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:99961156a36aae4a402d6b14c1e7efde642794b3ddbf32c51db0cb3a199e8b11"},
{file = "grpcio_tools-1.27.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:069826dd02ce1886444cf4519c4fe1b05ac9ef41491f26e97400640531db47f6"},
{file = "grpcio_tools-1.27.2-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:fae91f30dc050a8d0b32d20dc700e6092f0bd2138d83e9570fff3f0372c1b27e"},
{file = "grpcio_tools-1.27.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a14dc7a36c845991d908a7179502ca47bcba5ae1817c4426ce68cf2c97b20ad9"},
{file = "grpcio_tools-1.27.2-cp27-cp27m-win32.whl", hash = "sha256:d1a5e5fa47ba9557a7d3b31605631805adc66cdba9d95b5d10dfc52cca1fed53"},
{file = "grpcio_tools-1.27.2-cp27-cp27m-win_amd64.whl", hash = "sha256:7b54b283ec83190680903a9037376dc915e1f03852a2d574ba4d981b7a1fd3d0"},
{file = "grpcio_tools-1.27.2-cp27-cp27mu-linux_armv7l.whl", hash = "sha256:4698c6b6a57f73b14d91a542c69ff33a2da8729691b7060a5d7f6383624d045e"},
{file = "grpcio_tools-1.27.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:87e8ca2c2d2d3e09b2a2bed5d740d7b3e64028dafb7d6be543b77eec85590736"},
{file = "grpcio_tools-1.27.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bd7f59ff1252a3db8a143b13ea1c1e93d4b8cf4b852eb48b22ef1e6942f62a84"},
{file = "grpcio_tools-1.27.2-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:a8f892378b0b02526635b806f59141abbb429d19bec56e869e04f396502c9651"},
{file = "grpcio_tools-1.27.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:69c4a63919b9007e845d9f8980becd2f89d808a4a431ca32b9723ee37b521cb1"},
{file = "grpcio_tools-1.27.2-cp35-cp35m-linux_armv7l.whl", hash = "sha256:dcbc06556f3713a9348c4fce02d05d91e678fc320fb2bcf0ddf8e4bb11d17867"},
{file = "grpcio_tools-1.27.2-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:16dc3fad04fe18d50777c56af7b2d9b9984cd1cfc71184646eb431196d1645c6"},
{file = "grpcio_tools-1.27.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1de5a273eaffeb3d126a63345e9e848ea7db740762f700eb8b5d84c5e3e7687d"},
{file = "grpcio_tools-1.27.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6016c07d6566e3109a3c032cf3861902d66501ecc08a5a84c47e43027302f367"},
{file = "grpcio_tools-1.27.2-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:915a695bc112517af48126ee0ecdb6aff05ed33f3eeef28f0d076f1f6b52ef5e"},
{file = "grpcio_tools-1.27.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:ea4b3ad696d976d5eac74ec8df9a2c692113e455446ee38d5b3bd87f8e034fa6"},
{file = "grpcio_tools-1.27.2-cp35-cp35m-win32.whl", hash = "sha256:a140bf853edb2b5e8692fe94869e3e34077d7599170c113d07a58286c604f4fe"},
{file = "grpcio_tools-1.27.2-cp35-cp35m-win_amd64.whl", hash = "sha256:77e25c241e33b75612f2aa62985f746c6f6803ec4e452da508bb7f8d90a69db4"},
{file = "grpcio_tools-1.27.2-cp36-cp36m-linux_armv7l.whl", hash = "sha256:5fd7efc2fd3370bd2c72dc58f31a407a5dff5498befa145da211b2e8c6a52c63"},
{file = "grpcio_tools-1.27.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9ba88c2d99bcaf7b9cb720925e3290d73b2367d238c5779363fd5598b2dc98c7"},
{file = "grpcio_tools-1.27.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:b56caecc16307b088a431a4038c3b3bb7d0e7f9988cbd0e9fa04ac937455ea38"},
{file = "grpcio_tools-1.27.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:f8514453411d72cc3cf7d481f2b6057e5b7436736d0cd39ee2b2f72088bbf497"},
{file = "grpcio_tools-1.27.2-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:c1bb8f47d58e9f7c4825abfe01e6b85eda53c8b31d2267ca4cddf3c4d0829b80"},
{file = "grpcio_tools-1.27.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:e17b2e0936b04ced99769e26111e1e86ba81619d1b2691b1364f795e45560953"},
{file = "grpcio_tools-1.27.2-cp36-cp36m-win32.whl", hash = "sha256:520b7dafddd0f82cb7e4f6e9c6ba1049aa804d0e207870def9fe7f94d1e14090"},
{file = "grpcio_tools-1.27.2-cp36-cp36m-win_amd64.whl", hash = "sha256:ee50b0cf0d28748ef9f941894eb50fc464bd61b8e96aaf80c5056bea9b80d580"},
{file = "grpcio_tools-1.27.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:627c91923df75091d8c4d244af38d5ab7ed8d786d480751d6c2b9267fbb92fe0"},
{file = "grpcio_tools-1.27.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ef624b6134aef737b3daa4fb7e806cb8c5749efecd0b1fa9ce4f7e060c7a0221"},
{file = "grpcio_tools-1.27.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e6932518db389ede8bf06b4119bbd3e17f42d4626e72dec2b8955b20ec732cb6"},
{file = "grpcio_tools-1.27.2-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:43a1573400527a23e4174d88604fde7a9d9a69bf9473c21936b7f409858f8ebb"},
{file = "grpcio_tools-1.27.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:57f8b9e2c7f55cd45f6dd930d6de61deb42d3eb7f9788137fbc7155cf724132a"},
{file = "grpcio_tools-1.27.2-cp37-cp37m-win32.whl", hash = "sha256:2ca280af2cae1a014a238057bd3c0a254527569a6a9169a01c07f0590081d530"},
{file = "grpcio_tools-1.27.2-cp37-cp37m-win_amd64.whl", hash = "sha256:59fbeb5bb9a7b94eb61642ac2cee1db5233b8094ca76fc56d4e0c6c20b5dd85f"},
{file = "grpcio_tools-1.27.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:00c5080cfb197ed20ecf0d0ff2d07f1fc9c42c724cad21c40ff2d048de5712b1"},
{file = "grpcio_tools-1.27.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:f5450aa904e720f9c6407b59e96a8951ed6a95463f49444b6d2594b067d39588"},
{file = "grpcio_tools-1.27.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:aaa5ae26883c3d58d1a4323981f96b941fa09bb8f0f368d97c6225585280cf04"},
{file = "grpcio_tools-1.27.2-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:1266b577abe7c720fd16a83d0a4999a192e87c4a98fc9f97e0b99b106b3e155f"},
{file = "grpcio_tools-1.27.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:a3d2aec4b09c8e59fee8b0d1ed668d09e8c48b738f03f5d8401d7eb409111c47"},
{file = "grpcio_tools-1.27.2-cp38-cp38-win32.whl", hash = "sha256:8e7738a4b93842bca1158cde81a3587c9b7111823e40a1ddf73292ca9d58e08b"},
{file = "grpcio_tools-1.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:84724458c86ff9b14c29b49e321f34d80445b379f4cd4d0494c694b49b1d6f88"},
{file = "grpcio-tools-1.43.0.tar.gz", hash = "sha256:f42f1d713096808b1b0472dd2a3749b712d13f0092dab9442d9c096446e860b2"},
{file = "grpcio_tools-1.43.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:766771ef5b60ebcba0a3bdb302dd92fda988552eb8508451ff6d97371eac38e5"},
{file = "grpcio_tools-1.43.0-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:178a881db5de0f89abf3aeeb260ecfd1116cc31f88fb600a45fb5b19c3323b33"},
{file = "grpcio_tools-1.43.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:019f55929e963214471825c7a4cdab7a57069109d5621b24e4db7b428b5fe47d"},
{file = "grpcio_tools-1.43.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6c0e1d1b47554c580882d392b739df91a55b6a8ec696b2b2e1bbc127d63df2c"},
{file = "grpcio_tools-1.43.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c5c80098fa69593b828d119973744de03c3f9a6935df8a02e4329a39b7072f5"},
{file = "grpcio_tools-1.43.0-cp310-cp310-win32.whl", hash = "sha256:53f7dcaa4218df1b64b39d0fc7236a8270e8ab2db4ab8cd1d2fda0e6d4544946"},
{file = "grpcio_tools-1.43.0-cp310-cp310-win_amd64.whl", hash = "sha256:5be6d402b0cafef20ba3abb3baa37444961d9a9c4a6434d3d7c1f082f7697deb"},
{file = "grpcio_tools-1.43.0-cp36-cp36m-linux_armv7l.whl", hash = "sha256:8953fdebef6905d7ff13a5a376b21b6fecd808d18bf4f0d3990ffe4a215d56eb"},
{file = "grpcio_tools-1.43.0-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:18870dcc8369ac4c37213e6796d8dc20494ea770670204f5e573f88e69eaaf0b"},
{file = "grpcio_tools-1.43.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:010a4be6a2fccbd6741a4809c5da7f2e39a1e9e227745e6b495be567638bbeb9"},
{file = "grpcio_tools-1.43.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:426f16b6b14d533ce61249a18fbcd1a23a4fa0c71a6d7ab347b1c7f862847bb8"},
{file = "grpcio_tools-1.43.0-cp36-cp36m-manylinux_2_17_aarch64.whl", hash = "sha256:f974cb0bea88bac892c3ed16da92c6ac88cff0fea17f24bf0e1892eb4d27cd00"},
{file = "grpcio_tools-1.43.0-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:55c2e604536e06248e2f81e549737fb3a180c8117832e494a0a8a81fbde44837"},
{file = "grpcio_tools-1.43.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f97f9ffa49348fb24692751d2d4455ef2968bd07fe536d65597caaec14222629"},
{file = "grpcio_tools-1.43.0-cp36-cp36m-win32.whl", hash = "sha256:6eaf97414237b8670ae9fa623879a26eabcc4c635b550c79a81e17eb600d6ae3"},
{file = "grpcio_tools-1.43.0-cp36-cp36m-win_amd64.whl", hash = "sha256:04f100c1f6a7c72c537760c33582f6970070bd6fa6676b529bccfa31cc58bc79"},
{file = "grpcio_tools-1.43.0-cp37-cp37m-linux_armv7l.whl", hash = "sha256:9dbb6d1f58f26d88ae689f1b49de84cfaf4786c81c01b9001d3ceea178116a07"},
{file = "grpcio_tools-1.43.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:63862a441a77f6326ea9fe4bb005882f0e363441a5968d9cf8621c34d3dadc2b"},
{file = "grpcio_tools-1.43.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:6dea0cb2e79b67593553ed8662f70e4310599fa8850fc0e056b19fcb63572b7f"},
{file = "grpcio_tools-1.43.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3eb4aa5b0e578c3d9d9da8e37a2ef73654287a498b8081543acd0db7f0ec1a9c"},
{file = "grpcio_tools-1.43.0-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:09464c6b17663088144b7e6ea10e9465efdcee03d4b2ffefab39a799bd8360f8"},
{file = "grpcio_tools-1.43.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2458d6b0404f83d95aef00cec01f310d30e9719564a25be50e39b259f6a2da5d"},
{file = "grpcio_tools-1.43.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e9bb5da437364b7dcd2d3c6850747081ecbec0ba645c96c6d471f7e21fdcadb"},
{file = "grpcio_tools-1.43.0-cp37-cp37m-win32.whl", hash = "sha256:2737f749a6ab965748629e619b35f3e1cbe5820fc79e34c88f73cb99efc71dde"},
{file = "grpcio_tools-1.43.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c39cbe7b902bb92f9afaa035091f5e2b8be35acbac501fec8cb6a0be7d7cdbbd"},
{file = "grpcio_tools-1.43.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:05550ba473cff7c09e905fcfb2263fd1f7600389660194ec022b5d5a3802534b"},
{file = "grpcio_tools-1.43.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:ce13a922db8f5f95c5041d3a4cbf04d942b353f0cba9b251a674f69a31a2d3a6"},
{file = "grpcio_tools-1.43.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:f19d40690c97365c1c1bde81474e6f496d7ab76f87e6d2889c72ad01bac98f2d"},
{file = "grpcio_tools-1.43.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba3da574eb08fcaed541b3fc97ce217360fd86d954fa9ad6a604803d57a2e049"},
{file = "grpcio_tools-1.43.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:efd1eb5880001f5189cfa3a774675cc9bbc8cc51586a3e90fe796394ac8626b8"},
{file = "grpcio_tools-1.43.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:234c7a5af653357df5c616e013173eddda6193146c8ab38f3108c4784f66be26"},
{file = "grpcio_tools-1.43.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7e3662f62d410b3f81823b5fa0f79c6e0e250977a1058e4131867b85138a661"},
{file = "grpcio_tools-1.43.0-cp38-cp38-win32.whl", hash = "sha256:5f2e584d7644ef924e9e042fa151a3bb9f7c28ef1ae260ee6c9cb327982b5e94"},
{file = "grpcio_tools-1.43.0-cp38-cp38-win_amd64.whl", hash = "sha256:98dcb5b756855110fb661ccd6a93a716610b7efcd5720a3aec01358a1a892c30"},
{file = "grpcio_tools-1.43.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:61ef6cb6ccf9b9c27bb85fffc5338194bcf444df502196c2ad0ff8df4706d41e"},
{file = "grpcio_tools-1.43.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:1def9b68ac9e62674929bc6590a33d89635f1cf16016657d9e16a69f41aa5c36"},
{file = "grpcio_tools-1.43.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:b68cc0c95a0f8c757e8d69b5fa46111d5c9d887ae62af28f827649b1d1b70fe1"},
{file = "grpcio_tools-1.43.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:e956b5c3b586d7b27eae49fb06f544a26288596fe12e22ffec768109717276d1"},
{file = "grpcio_tools-1.43.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:671e61bbc91d8d568f12c3654bb5a91fce9f3fdfd5ec2cfc60c2d3a840449aa6"},
{file = "grpcio_tools-1.43.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d7173ed19854d1066bce9bdc09f735ca9c13e74a25d47a1cc5d1fe803b53bffb"},
{file = "grpcio_tools-1.43.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1adb0dbcc1c10b86dcda910b8f56e39210e401bcee923dba166ba923a5f4696a"},
{file = "grpcio_tools-1.43.0-cp39-cp39-win32.whl", hash = "sha256:ebfb94ddb454a6dc3a505d9531dc81c948e6364e181b8795bfad3f3f479974dc"},
{file = "grpcio_tools-1.43.0-cp39-cp39-win_amd64.whl", hash = "sha256:d21928b680e6e29538688cffbf53f3d5a53cff0ec8f0c33139641700045bdf1a"},
]
identify = [
{file = "identify-1.6.2-py2.py3-none-any.whl", hash = "sha256:8f9879b5b7cca553878d31548a419ec2f227d3328da92fe8202bc5e546d5cbc3"},
@ -992,8 +1018,8 @@ mock = [
{file = "mock-4.0.2.tar.gz", hash = "sha256:dd33eb70232b6118298d516bbcecd26704689c386594f0f3c4f13867b2c56f72"},
]
more-itertools = [
{file = "more-itertools-8.12.0.tar.gz", hash = "sha256:7dc6ad46f05f545f900dd59e8dfb4e84a4827b97b3cfecb175ea0c7d247f6064"},
{file = "more_itertools-8.12.0-py3-none-any.whl", hash = "sha256:43e6dd9942dffd72661a2c4ef383ad7da1e6a3e968a927ad7a6083ab410a688b"},
{file = "more-itertools-8.13.0.tar.gz", hash = "sha256:a42901a0a5b169d925f6f217cd5a190e32ef54360905b9c39ee7db5313bfec0f"},
{file = "more_itertools-8.13.0-py3-none-any.whl", hash = "sha256:c5122bffc5f104d37c1626b8615b511f3427aa5389b94d61e5ef8236bfbc3ddb"},
]
netaddr = [
{file = "netaddr-0.7.19-py2.py3-none-any.whl", hash = "sha256:56b3558bd71f3f6999e4c52e349f38660e54a7a8a9943335f73dfc96883e08ca"},
@ -1008,8 +1034,8 @@ packaging = [
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
]
paramiko = [
{file = "paramiko-2.9.2-py2.py3-none-any.whl", hash = "sha256:04097dbd96871691cdb34c13db1883066b8a13a0df2afd4cb0a92221f51c2603"},
{file = "paramiko-2.9.2.tar.gz", hash = "sha256:944a9e5dbdd413ab6c7951ea46b0ab40713235a9c4c5ca81cfe45c6f14fa677b"},
{file = "paramiko-2.10.4-py2.py3-none-any.whl", hash = "sha256:3c9ed6084f4b671ab66dc3c729092d32d96c3258f1426071301cb33654b09027"},
{file = "paramiko-2.10.4.tar.gz", hash = "sha256:3d2e650b6812ce6d160abff701d6ef4434ec97934b13e95cf1ad3da70ffb5c58"},
]
pillow = [
{file = "Pillow-8.3.2-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:c691b26283c3a31594683217d746f1dad59a7ae1d4cfc24626d7a064a11197d4"},
@ -1139,29 +1165,31 @@ pyparsing = [
{file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"},
]
pyproj = [
{file = "pyproj-2.6.1.post1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:457ad3856014ac26af1d86def6dc8cf69c1fa377b6e2fd6e97912d51cf66bdbe"},
{file = "pyproj-2.6.1.post1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:6f3f36440ea61f5f6da4e6beb365dddcbe159815450001d9fb753545affa45ff"},
{file = "pyproj-2.6.1.post1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6a212d0e5c7efa33d039f0c8b0a489e2204fcd28b56206567852ad7f5f2a653e"},
{file = "pyproj-2.6.1.post1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:451a3d1c563b672458029ebc04acbb3266cd8b3025268eb871a9176dc3638911"},
{file = "pyproj-2.6.1.post1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e015f900b4b84e908f8035ab16ebf02d67389c1c216c17a2196fc2e515c00762"},
{file = "pyproj-2.6.1.post1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a13e5731b3a360ee7fbd1e9199ec9203fafcece8ebd0b1351f16d0a90cad6828"},
{file = "pyproj-2.6.1.post1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:33c1c2968a4f4f87d517c4275a18b557e5c13907cf2609371fadea8463c3ba05"},
{file = "pyproj-2.6.1.post1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3fef83a01c1e86dd9fa99d8214f749837cfafc34d9d6230b4b0a998fa7a68a1a"},
{file = "pyproj-2.6.1.post1-cp36-cp36m-win32.whl", hash = "sha256:a6ac4861979cd05a0f5400fefa41d26c0269a5fb8237618aef7c998907db39e1"},
{file = "pyproj-2.6.1.post1-cp36-cp36m-win_amd64.whl", hash = "sha256:cbf6ccf990860b06c5262ff97c4b78e1d07883981635cd53a6aa438a68d92945"},
{file = "pyproj-2.6.1.post1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:adacb67a9f71fb54ca1b887a6ab20f32dd536fcdf2acec84a19e25ad768f7965"},
{file = "pyproj-2.6.1.post1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:e50d5d20b87758acf8f13f39a3b3eb21d5ef32339d2bc8cdeb8092416e0051df"},
{file = "pyproj-2.6.1.post1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2518d1606e2229b82318e704b40290e02a2a52d77b40cdcb2978973d6fc27b20"},
{file = "pyproj-2.6.1.post1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:33a5d1cfbb40a019422eb80709a0e270704390ecde7278fdc0b88f3647c56a39"},
{file = "pyproj-2.6.1.post1-cp37-cp37m-win32.whl", hash = "sha256:daf2998e3f5bcdd579a18faf009f37f53538e9b7d0a252581a610297d31e8536"},
{file = "pyproj-2.6.1.post1-cp37-cp37m-win_amd64.whl", hash = "sha256:a8b7c8accdc61dac8e91acab7c1f7b4590d1e102f2ee9b1f1e6399fad225958e"},
{file = "pyproj-2.6.1.post1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9f097e8f341a162438918e908be86d105a28194ff6224633b2e9616c5031153f"},
{file = "pyproj-2.6.1.post1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:d90a5d1fdd066b0e9b22409b0f5e81933469918fa04c2cf7f9a76ce84cb29dad"},
{file = "pyproj-2.6.1.post1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:f5a8015c74ec8f6508aebf493b58ba20ccb4da8168bf05f0c2a37faccb518da9"},
{file = "pyproj-2.6.1.post1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d87836be6b720fb4d9c112136aa47621b6ca09a554e645c1081561eb8e2fa1f4"},
{file = "pyproj-2.6.1.post1-cp38-cp38-win32.whl", hash = "sha256:bc2f3a15d065e206d63edd2cc4739aa0a35c05338ee276ab1dc72f56f1944bda"},
{file = "pyproj-2.6.1.post1-cp38-cp38-win_amd64.whl", hash = "sha256:93cbad7b699e8e80def7de80c350617f35e6a0b82862f8ce3c014657c25fdb3c"},
{file = "pyproj-2.6.1.post1.tar.gz", hash = "sha256:4f5b02b4abbd41610397c635b275a8ee4a2b5bc72a75572b98ac6ae7befa471e"},
{file = "pyproj-3.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f942a976ea3de6a519cf48be30a12f465e44d0ac0c38a0d820ab3acfcc0a48a6"},
{file = "pyproj-3.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:09db64a8088b23f001e574d92bcc3080bf7de44ddca152d0282a2b50c918a64a"},
{file = "pyproj-3.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:cba99e171d744969e13a865ad28fa9c949c4400b0e9c431a802cdd804f52f632"},
{file = "pyproj-3.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:81c06df20d09d621e52791c19ce3c880695fb430061e59c2472fa5467e890391"},
{file = "pyproj-3.0.1-cp36-cp36m-win32.whl", hash = "sha256:3e7e851e6d58c16ac2cd920a1bacb7fbb24758a6fcd7f234d594a88ebae04ec9"},
{file = "pyproj-3.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:aa0a2981b25145523ca17a643c5be077fe13e514fdca9b6d1c412a95d723a5a5"},
{file = "pyproj-3.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:708d6e01b9ff3d6dc62a5ad2d2ba1264a863eaa657c1a9bf713a10cc35d34553"},
{file = "pyproj-3.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:36ba436675f9dea4ab3db7d9a32d3ff11c2fbb4d6690a83454d2f3c5c0b54041"},
{file = "pyproj-3.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:489a96da87d8846c34c90da90e637544e4f4f50a13589b5aac54297f5ee1b01d"},
{file = "pyproj-3.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:4a333f3e46fe8b2eb4647a3daa3a2cec52ddc6c107c653b45880526114942ee8"},
{file = "pyproj-3.0.1-cp37-cp37m-win32.whl", hash = "sha256:9e2ef75401f17062166d3fe53c555cd62c9577697a2f5ded916b23c54e5db497"},
{file = "pyproj-3.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7bfaa34e8bb0510d4380310374deecd9e4328b9cf556925cfb45b5a94d5bbdbe"},
{file = "pyproj-3.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9666d01faf4e758ac68f2c16695c90de49c3170e3760988bf76a34aae11f4e15"},
{file = "pyproj-3.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:c658afc8a6115b58b02aa53d27bf2a67c1b00b55067edb1b7711c6c7391cfaa9"},
{file = "pyproj-3.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:fee7517bd389a1db7b8bebb18838d04dedca9eaacda01d353d98f5ee421f263e"},
{file = "pyproj-3.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:86ef2fcd584a3222bf73e2befc24b2badd139b3371f4a1e88649978ef7649540"},
{file = "pyproj-3.0.1-cp38-cp38-win32.whl", hash = "sha256:d27d40ec541ef69a5107bfcd85f40170e9e122ceb6315ce508cd44d199983d41"},
{file = "pyproj-3.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:bc70b6adcfa713d89bc561673cb57af5fb3a1718cd7d57ec537430cd1007a864"},
{file = "pyproj-3.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b845510255f9580d7e226dd3321a51c468cefb7be24e46415caf67caa4287c4"},
{file = "pyproj-3.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:7ae8e7052f18fde1884574da449010e94fa205ad27aeeaa34a097f49a1ed6a2b"},
{file = "pyproj-3.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:a3805e026a5547be205a5e322c08e3069f0a48c63bbd53dbc7a8e3499bc66d58"},
{file = "pyproj-3.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:1be7d54900eb7e2d1e637319080b3a047c70d1fb2f3c12d3400c0fa8a90cf440"},
{file = "pyproj-3.0.1-cp39-cp39-win32.whl", hash = "sha256:09bead60769e69b592e8cb3ac51b5215f75e9bb9c213ce575031961deb48d6da"},
{file = "pyproj-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:a3a8ab19232bf4f4bb2590536538881b7bd0c07df23e0c2a792402ca2476c197"},
{file = "pyproj-3.0.1.tar.gz", hash = "sha256:bfbac35490dd17f706700673506eeb8170f8a2a63fb5878171d4e6eef242d141"},
]
pytest = [
{file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"},
@ -1203,8 +1231,8 @@ typing-extensions = [
{file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"},
]
virtualenv = [
{file = "virtualenv-20.13.1-py2.py3-none-any.whl", hash = "sha256:45e1d053cad4cd453181ae877c4ffc053546ae99e7dd049b9ff1d9be7491abf7"},
{file = "virtualenv-20.13.1.tar.gz", hash = "sha256:e0621bcbf4160e4e1030f05065c8834b4e93f4fcc223255db2a823440aca9c14"},
{file = "virtualenv-20.14.1-py2.py3-none-any.whl", hash = "sha256:e617f16e25b42eb4f6e74096b9c9e37713cf10bf30168fb4a739f3fa8f898a3a"},
{file = "virtualenv-20.14.1.tar.gz", hash = "sha256:ef589a79795589aada0c1c5b319486797c03b67ac3984c48c669c0e4f50df3a5"},
]
wcwidth = [
{file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},

View file

@ -19,20 +19,20 @@ exclude = ["core/constants.py.in"]
python = "^3.6"
dataclasses = { version = "*", python = "~3.6" }
fabric = "2.5.0"
grpcio = "1.27.2"
grpcio = "1.43.0"
invoke = "1.4.1"
lxml = "4.6.5"
mako = "1.1.3"
netaddr = "0.7.19"
pillow = "8.3.2"
protobuf = "3.19.4"
pyproj = "2.6.1.post1"
pyproj = "3.0.1"
pyyaml = "5.4"
[tool.poetry.dev-dependencies]
black = "==19.3b0"
flake8 = "3.8.2"
grpcio-tools = "1.27.2"
grpcio-tools = "1.43.0"
isort = "4.3.21"
mock = "4.0.2"
pre-commit = "2.1.1"

View file

@ -28,7 +28,7 @@ from core.api.grpc.wrappers import (
Position,
)
NODE_TYPES = [x for x in NodeType if x != NodeType.PEER_TO_PEER]
NODE_TYPES = [x.name for x in NodeType if x != NodeType.PEER_TO_PEER]
def protobuf_to_json(message: Any) -> Dict[str, Any]:

View file

@ -16,10 +16,10 @@ from core.emane.models.ieee80211abg import EmaneIeee80211abgModel
from core.emane.models.rfpipe import EmaneRfPipeModel
from core.emane.models.tdma import EmaneTdmaModel
from core.emane.nodes import EmaneNet
from core.emulator.data import IpPrefixes, NodeOptions
from core.emulator.data import IpPrefixes
from core.emulator.session import Session
from core.errors import CoreCommandError, CoreError
from core.nodes.base import CoreNode
from core.nodes.base import CoreNode, Position
_EMANE_MODELS = [
EmaneIeee80211abgModel,
@ -53,19 +53,22 @@ class TestEmane:
"""
# create emane node for networking the core nodes
session.set_location(47.57917, -122.13232, 2.00000, 1.0)
options = NodeOptions()
options.set_position(80, 50)
options.emane = EmaneIeee80211abgModel.name
emane_net1 = session.add_node(EmaneNet, options=options)
options.emane = EmaneRfPipeModel.name
emane_net2 = session.add_node(EmaneNet, options=options)
options = EmaneNet.create_options()
options.emane_model = EmaneIeee80211abgModel.name
position = Position(x=80, y=50)
emane_net1 = session.add_node(EmaneNet, position=position, options=options)
options = EmaneNet.create_options()
options.emane_model = EmaneRfPipeModel.name
position = Position(x=80, y=50)
emane_net2 = session.add_node(EmaneNet, position=position, options=options)
# create nodes
options = NodeOptions(model="mdr")
options.set_position(150, 150)
node1 = session.add_node(CoreNode, options=options)
options.set_position(300, 150)
node2 = session.add_node(CoreNode, options=options)
options = CoreNode.create_options()
options.model = "mdr"
position = Position(x=150, y=150)
node1 = session.add_node(CoreNode, position=position, options=options)
position = Position(x=300, y=150)
node2 = session.add_node(CoreNode, position=position, options=options)
# create interfaces
ip_prefix1 = IpPrefixes("10.0.0.0/24")
@ -100,9 +103,10 @@ class TestEmane:
# create emane node for networking the core nodes
session.set_location(47.57917, -122.13232, 2.00000, 1.0)
options = NodeOptions(emane=model.name)
options.set_position(80, 50)
emane_network = session.add_node(EmaneNet, options=options)
options = EmaneNet.create_options()
options.emane_model = model.name
position = Position(x=80, y=50)
emane_network = session.add_node(EmaneNet, position=position, options=options)
# configure tdma
if model == EmaneTdmaModel:
@ -111,11 +115,12 @@ class TestEmane:
)
# create nodes
options = NodeOptions(model="mdr")
options.set_position(150, 150)
node1 = session.add_node(CoreNode, options=options)
options.set_position(300, 150)
node2 = session.add_node(CoreNode, options=options)
options = CoreNode.create_options()
options.model = "mdr"
position = Position(x=150, y=150)
node1 = session.add_node(CoreNode, position=position, options=options)
position = Position(x=300, y=150)
node2 = session.add_node(CoreNode, position=position, options=options)
for i, node in enumerate([node1, node2]):
node.setposition(x=150 * (i + 1), y=150)
@ -141,9 +146,10 @@ class TestEmane:
"""
# create emane node for networking the core nodes
session.set_location(47.57917, -122.13232, 2.00000, 1.0)
options = NodeOptions(emane=EmaneIeee80211abgModel.name)
options.set_position(80, 50)
emane_network = session.add_node(EmaneNet, options=options)
options = EmaneNet.create_options()
options.emane_model = EmaneIeee80211abgModel.name
position = Position(x=80, y=50)
emane_network = session.add_node(EmaneNet, position=position, options=options)
config_key = "txpower"
config_value = "10"
session.emane.set_config(
@ -151,11 +157,12 @@ class TestEmane:
)
# create nodes
options = NodeOptions(model="mdr")
options.set_position(150, 150)
node1 = session.add_node(CoreNode, options=options)
options.set_position(300, 150)
node2 = session.add_node(CoreNode, options=options)
options = CoreNode.create_options()
options.model = "mdr"
position = Position(x=150, y=150)
node1 = session.add_node(CoreNode, position=position, options=options)
position = Position(x=300, y=150)
node2 = session.add_node(CoreNode, position=position, options=options)
for i, node in enumerate([node1, node2]):
node.setposition(x=150 * (i + 1), y=150)
@ -205,14 +212,17 @@ class TestEmane:
self, session: Session, tmpdir: TemporaryFile, ip_prefixes: IpPrefixes
):
# create nodes
options = NodeOptions(model="mdr", x=50, y=50)
node1 = session.add_node(CoreNode, options=options)
options = CoreNode.create_options()
options.model = "mdr"
position = Position(x=50, y=50)
node1 = session.add_node(CoreNode, position=position, options=options)
iface1_data = ip_prefixes.create_iface(node1)
node2 = session.add_node(CoreNode, options=options)
node2 = session.add_node(CoreNode, position=position, options=options)
iface2_data = ip_prefixes.create_iface(node2)
# create emane node
options = NodeOptions(model=None, emane=EmaneRfPipeModel.name)
options = EmaneNet.create_options()
options.emane_model = EmaneRfPipeModel.name
emane_node = session.add_node(EmaneNet, options=options)
# create links
@ -255,11 +265,7 @@ class TestEmane:
assert session.get_node(node1.id, CoreNode)
assert session.get_node(node2.id, CoreNode)
assert session.get_node(emane_node.id, EmaneNet)
links = []
for node_id in session.nodes:
node = session.nodes[node_id]
links += node.links()
assert len(links) == 2
assert len(session.link_manager.links()) == 2
config = session.emane.get_config(node1.id, EmaneRfPipeModel.name)
assert config["datarate"] == datarate
@ -267,14 +273,17 @@ class TestEmane:
self, session: Session, tmpdir: TemporaryFile, ip_prefixes: IpPrefixes
):
# create nodes
options = NodeOptions(model="mdr", x=50, y=50)
node1 = session.add_node(CoreNode, options=options)
options = CoreNode.create_options()
options.model = "mdr"
position = Position(x=50, y=50)
node1 = session.add_node(CoreNode, position=position, options=options)
iface1_data = ip_prefixes.create_iface(node1)
node2 = session.add_node(CoreNode, options=options)
node2 = session.add_node(CoreNode, position=position, options=options)
iface2_data = ip_prefixes.create_iface(node2)
# create emane node
options = NodeOptions(model=None, emane=EmaneRfPipeModel.name)
options = EmaneNet.create_options()
options.emane_model = EmaneRfPipeModel.name
emane_node = session.add_node(EmaneNet, options=options)
# create links
@ -318,10 +327,6 @@ class TestEmane:
assert session.get_node(node1.id, CoreNode)
assert session.get_node(node2.id, CoreNode)
assert session.get_node(emane_node.id, EmaneNet)
links = []
for node_id in session.nodes:
node = session.nodes[node_id]
links += node.links()
assert len(links) == 2
assert len(session.link_manager.links()) == 2
config = session.emane.get_config(config_id, EmaneRfPipeModel.name)
assert config["datarate"] == datarate

View file

@ -8,7 +8,7 @@ from typing import List, Type
import pytest
from core.emulator.data import IpPrefixes, NodeOptions
from core.emulator.data import IpPrefixes
from core.emulator.session import Session
from core.errors import CoreCommandError
from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility
@ -75,8 +75,8 @@ class TestCore:
session.mobility.set_model(wlan_node, BasicRangeModel)
# create nodes
options = NodeOptions(model="mdr")
options.set_position(0, 0)
options = CoreNode.create_options()
options.model = "mdr"
node1 = session.add_node(CoreNode, options=options)
node2 = session.add_node(CoreNode, options=options)
@ -105,8 +105,8 @@ class TestCore:
session.mobility.set_model(wlan_node, BasicRangeModel)
# create nodes
options = NodeOptions(model="mdr")
options.set_position(0, 0)
options = CoreNode.create_options()
options.model = "mdr"
node1 = session.add_node(CoreNode, options=options)
node2 = session.add_node(CoreNode, options=options)

View file

@ -1,4 +1,3 @@
from core.emulator.data import NodeOptions
from core.emulator.session import Session
from core.nodes.base import CoreNode
from core.nodes.network import HubNode
@ -12,8 +11,7 @@ class TestDistributed:
# when
session.distributed.add_server(server_name, host)
options = NodeOptions(server=server_name)
node = session.add_node(CoreNode, options=options)
node = session.add_node(CoreNode, server=server_name)
session.instantiate()
# then
@ -30,8 +28,7 @@ class TestDistributed:
# when
session.distributed.add_server(server_name, host)
node1 = session.add_node(HubNode)
options = NodeOptions(server=server_name)
node2 = session.add_node(HubNode, options=options)
node2 = session.add_node(HubNode, server=server_name)
session.add_link(node1.id, node2.id)
session.instantiate()

View file

@ -34,7 +34,7 @@ from core.api.grpc.wrappers import (
)
from core.emane.models.ieee80211abg import EmaneIeee80211abgModel
from core.emane.nodes import EmaneNet
from core.emulator.data import EventData, IpPrefixes, NodeData, NodeOptions
from core.emulator.data import EventData, IpPrefixes, NodeData
from core.emulator.enumerations import EventTypes, ExceptionLevels, MessageFlags
from core.errors import CoreError
from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility
@ -350,8 +350,7 @@ class TestGrpc:
client = CoreGrpcClient()
session = grpc_server.coreemu.create_session()
session.set_state(EventTypes.CONFIGURATION_STATE)
options = NodeOptions(model="Host")
node = session.add_node(CoreNode, options=options)
node = session.add_node(CoreNode)
session.instantiate()
expected_output = "hello world"
expected_status = 0
@ -369,8 +368,7 @@ class TestGrpc:
client = CoreGrpcClient()
session = grpc_server.coreemu.create_session()
session.set_state(EventTypes.CONFIGURATION_STATE)
options = NodeOptions(model="Host")
node = session.add_node(CoreNode, options=options)
node = session.add_node(CoreNode)
session.instantiate()
# then
@ -444,10 +442,12 @@ class TestGrpc:
# given
client = CoreGrpcClient()
session = grpc_server.coreemu.create_session()
session.set_state(EventTypes.CONFIGURATION_STATE)
switch = session.add_node(SwitchNode)
node = session.add_node(CoreNode)
iface_data = ip_prefixes.create_iface(node)
iface, _ = session.add_link(node.id, switch.id, iface_data)
session.instantiate()
options = LinkOptions(bandwidth=30000)
assert iface.options.bandwidth != options.bandwidth
link = Link(node.id, switch.id, iface1=Interface(id=iface.id), options=options)
@ -535,7 +535,8 @@ class TestGrpc:
client = CoreGrpcClient()
session = grpc_server.coreemu.create_session()
session.set_location(47.57917, -122.13232, 2.00000, 1.0)
options = NodeOptions(emane=EmaneIeee80211abgModel.name)
options = EmaneNet.create_options()
options.emane_model = EmaneIeee80211abgModel.name
emane_network = session.add_node(EmaneNet, options=options)
session.emane.node_models[emane_network.id] = EmaneIeee80211abgModel.name
config_key = "bandwidth"
@ -565,7 +566,8 @@ class TestGrpc:
client = CoreGrpcClient()
session = grpc_server.coreemu.create_session()
session.set_location(47.57917, -122.13232, 2.00000, 1.0)
options = NodeOptions(emane=EmaneIeee80211abgModel.name)
options = EmaneNet.create_options()
options.emane_model = EmaneIeee80211abgModel.name
emane_network = session.add_node(EmaneNet, options=options)
session.emane.node_models[emane_network.id] = EmaneIeee80211abgModel.name
@ -685,7 +687,8 @@ class TestGrpc:
# given
client = CoreGrpcClient()
session = grpc_server.coreemu.create_session()
options = NodeOptions(legacy=True)
options = CoreNode.create_options()
options.legacy = True
node = session.add_node(CoreNode, options=options)
service_name = "DefaultRoute"
@ -932,6 +935,7 @@ class TestGrpc:
# given
client = CoreGrpcClient()
session = grpc_server.coreemu.create_session()
session.set_state(EventTypes.CONFIGURATION_STATE)
wlan = session.add_node(WlanNode)
node1 = session.add_node(CoreNode)
node2 = session.add_node(CoreNode)

View file

@ -1,6 +1,6 @@
import pytest
from core.emulator.data import InterfaceData, NodeOptions
from core.emulator.data import InterfaceData
from core.emulator.session import Session
from core.errors import CoreError
from core.nodes.base import CoreNode
@ -14,7 +14,8 @@ class TestNodes:
@pytest.mark.parametrize("model", MODELS)
def test_node_add(self, session: Session, model: str):
# given
options = NodeOptions(model=model)
options = CoreNode.create_options()
options.model = model
# when
node = session.add_node(CoreNode, options=options)

View file

@ -4,7 +4,7 @@ from xml.etree import ElementTree
import pytest
from core.emulator.data import IpPrefixes, LinkOptions, NodeOptions
from core.emulator.data import IpPrefixes, LinkOptions
from core.emulator.enumerations import EventTypes
from core.emulator.session import Session
from core.errors import CoreError
@ -116,8 +116,7 @@ class TestXml:
:param ip_prefixes: generates ip addresses for nodes
"""
# create nodes
options = NodeOptions(model="host")
node1 = session.add_node(CoreNode, options=options)
node1 = session.add_node(CoreNode)
node2 = session.add_node(CoreNode)
# link nodes to ptp net
@ -180,8 +179,8 @@ class TestXml:
session.mobility.set_model(wlan, BasicRangeModel, {"test": "1"})
# create nodes
options = NodeOptions(model="mdr")
options.set_position(0, 0)
options = CoreNode.create_options()
options.model = "mdr"
node1 = session.add_node(CoreNode, options=options)
node2 = session.add_node(CoreNode, options=options)

View file

@ -218,9 +218,9 @@ You can leverage the provided Dockerfile, to run and launch CORE within a Docker
git clone https://github.com/coreemu/core.git
cd core
# build image
sudo docker build -t core .
sudo docker build -t core.<cenots,ubuntu> -f Dockerfile.<centos,ubuntu>
# start container
sudo docker run -itd --name core -e DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix:rw --privileged core
sudo docker run -itd --name core -e DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix:rw --privileged core.<centos,ubuntu>
# enable xhost access to the root user
xhost +local:root
# launch core-gui

View file

@ -36,8 +36,10 @@ when creating interface data for nodes. Alternatively one can manually create
a `core.emulator.data.InterfaceData` class instead with appropriate information.
Manually creating interface data:
```python
from core.emulator.data import InterfaceData
# id is optional and will set to the next available id
# name is optional and will default to eth<id>
# mac is optional and will result in a randomly generated mac
@ -52,6 +54,7 @@ iface_data = InterfaceData(
```
Leveraging the interface prefixes helper class:
```python
from core.emulator.data import IpPrefixes
@ -69,6 +72,7 @@ iface_data = ip_prefixes.create_iface(
Various events that can occur within a session can be listened to.
Event types:
* session - events for changes in session state and mobility start/stop/pause
* node - events for node movements and icon changes
* link - events for link configuration changes and wireless link add/delete
@ -80,6 +84,7 @@ Event types:
def event_listener(event):
print(event)
# add an event listener to event type you want to listen to
# each handler will receive an object unique to that type
session.event_handlers.append(event_listener)
@ -95,6 +100,7 @@ session.config_handlers.append(event_listener)
Links can be configured at the time of creation or during runtime.
Currently supported configuration options:
* bandwidth (bps)
* delay (us)
* dup (%)
@ -119,12 +125,13 @@ session.update_link(n1_id, n2_id, iface1_id, iface2_id, options)
```
### Peer to Peer Example
```python
# required imports
from core.emulator.coreemu import CoreEmu
from core.emulator.data import IpPrefixes, NodeOptions
from core.emulator.data import IpPrefixes
from core.emulator.enumerations import EventTypes
from core.nodes.base import CoreNode
from core.nodes.base import CoreNode, Position
# ip nerator for example
ip_prefixes = IpPrefixes(ip4_prefix="10.0.0.0/24")
@ -137,10 +144,10 @@ session = coreemu.create_session()
session.set_state(EventTypes.CONFIGURATION_STATE)
# create nodes
options = NodeOptions(x=100, y=100)
n1 = session.add_node(CoreNode, options=options)
options = NodeOptions(x=300, y=100)
n2 = session.add_node(CoreNode, options=options)
position = Position(x=100, y=100)
n1 = session.add_node(CoreNode, position=position)
position = Position(x=300, y=100)
n2 = session.add_node(CoreNode, position=position)
# link nodes together
iface1 = ip_prefixes.create_iface(n1)
@ -158,12 +165,13 @@ session.shutdown()
```
### Switch/Hub Example
```python
# required imports
from core.emulator.coreemu import CoreEmu
from core.emulator.data import IpPrefixes, NodeOptions
from core.emulator.data import IpPrefixes
from core.emulator.enumerations import EventTypes
from core.nodes.base import CoreNode
from core.nodes.base import CoreNode, Position
from core.nodes.network import SwitchNode
# ip nerator for example
@ -177,14 +185,14 @@ session = coreemu.create_session()
session.set_state(EventTypes.CONFIGURATION_STATE)
# create switch
options = NodeOptions(x=200, y=200)
switch = session.add_node(SwitchNode, options=options)
position = Position(x=200, y=200)
switch = session.add_node(SwitchNode, position=position)
# create nodes
options = NodeOptions(x=100, y=100)
n1 = session.add_node(CoreNode, options=options)
options = NodeOptions(x=300, y=100)
n2 = session.add_node(CoreNode, options=options)
position = Position(x=100, y=100)
n1 = session.add_node(CoreNode, position=position)
position = Position(x=300, y=100)
n2 = session.add_node(CoreNode, position=position)
# link nodes to switch
iface1 = ip_prefixes.create_iface(n1)
@ -203,13 +211,14 @@ session.shutdown()
```
### WLAN Example
```python
# required imports
from core.emulator.coreemu import CoreEmu
from core.emulator.data import IpPrefixes, NodeOptions
from core.emulator.data import IpPrefixes
from core.emulator.enumerations import EventTypes
from core.location.mobility import BasicRangeModel
from core.nodes.base import CoreNode
from core.nodes.base import CoreNode, Position
from core.nodes.network import WlanNode
# ip nerator for example
@ -223,14 +232,16 @@ session = coreemu.create_session()
session.set_state(EventTypes.CONFIGURATION_STATE)
# create wlan
options = NodeOptions(x=200, y=200)
wlan = session.add_node(WlanNode, options=options)
position = Position(x=200, y=200)
wlan = session.add_node(WlanNode, position=position)
# create nodes
options = NodeOptions(model="mdr", x=100, y=100)
n1 = session.add_node(CoreNode, options=options)
options = NodeOptions(model="mdr", x=300, y=100)
n2 = session.add_node(CoreNode, options=options)
options = CoreNode.create_options()
options.model = "mdr"
position = Position(x=100, y=100)
n1 = session.add_node(CoreNode, position=position, options=options)
position = Position(x=300, y=100)
n2 = session.add_node(CoreNode, position=position, options=options)
# configuring wlan
session.mobility.set_model_config(wlan.id, BasicRangeModel.name, {
@ -263,6 +274,7 @@ For EMANE you can import and use one of the existing models and
use its name for configuration.
Current models:
* core.emane.ieee80211abg.EmaneIeee80211abgModel
* core.emane.rfpipe.EmaneRfPipeModel
* core.emane.tdma.EmaneTdmaModel
@ -281,9 +293,9 @@ will use the defaults. When no configuration is used, the defaults are used.
from core.emane.models.ieee80211abg import EmaneIeee80211abgModel
from core.emane.nodes import EmaneNet
from core.emulator.coreemu import CoreEmu
from core.emulator.data import IpPrefixes, NodeOptions
from core.emulator.data import IpPrefixes
from core.emulator.enumerations import EventTypes
from core.nodes.base import CoreNode
from core.nodes.base import CoreNode, Position
# ip nerator for example
ip_prefixes = IpPrefixes(ip4_prefix="10.0.0.0/24")
@ -300,14 +312,18 @@ session.location.refscale = 150.0
session.set_state(EventTypes.CONFIGURATION_STATE)
# create emane
options = NodeOptions(x=200, y=200, emane=EmaneIeee80211abgModel.name)
emane = session.add_node(EmaneNet, options=options)
options = EmaneNet.create_options()
options.emane_model = EmaneIeee80211abgModel.name
position = Position(x=200, y=200)
emane = session.add_node(EmaneNet, position=position, options=options)
# create nodes
options = NodeOptions(model="mdr", x=100, y=100)
n1 = session.add_node(CoreNode, options=options)
options = NodeOptions(model="mdr", x=300, y=100)
n2 = session.add_node(CoreNode, options=options)
options = CoreNode.create_options()
options.model = "mdr"
position = Position(x=100, y=100)
n1 = session.add_node(CoreNode, position=position, options=options)
position = Position(x=300, y=100)
n2 = session.add_node(CoreNode, position=position, options=options)
# configure general emane settings
config = session.emane.get_configs()
@ -338,6 +354,7 @@ session.shutdown()
```
EMANE Model Configuration:
```python
from core import utils
@ -358,6 +375,7 @@ Configuring the files of a service results in a specific hard coded script being
generated, instead of the default scripts, that may leverage dynamic generation.
The following features can be configured for a service:
* configs - files that will be generated
* dirs - directories that will be mounted unique to the node
* startup - commands to run start a service
@ -365,6 +383,7 @@ The following features can be configured for a service:
* shutdown - commands to run to stop a service
Editing service properties:
```python
# configure a service, for a node, for a given session
session.services.set_service(node_id, service_name)
@ -380,6 +399,7 @@ When editing a service file, it must be the name of `config`
file that the service will generate.
Editing a service file:
```python
# to edit the contents of a generated file you can specify
# the service, the file name, and its contents
@ -397,10 +417,12 @@ File versions of the network examples can be found
[here](https://github.com/coreemu/core/tree/master/daemon/examples/python).
## Executing Scripts from GUI
To execute a python script from a GUI you need have the following.
The builtin name check here to know it is being executed from the GUI, this can
be avoided if your script does not use a name check.
```python
if __name__ in ["__main__", "__builtin__"]:
main()

View file

@ -1,14 +1,16 @@
#!/bin/bash
# install pre-reqs using yum/apt
PYTHON="${PYTHON:=python3}"
PYTHON_DEP="${PYTHON_DEP:=python3}"
if command -v apt &> /dev/null
then
echo "setup to install CORE using apt"
sudo apt install -y python3-pip python3-venv
sudo apt install -y ${PYTHON_DEP}-pip ${PYTHON_DEP}-venv
elif command -v yum &> /dev/null
then
echo "setup to install CORE using yum"
sudo yum install -y python3-pip
sudo yum install -y ${PYTHON_DEP}-pip
else
echo "apt/yum was not found"
echo "install python3, pip, venv, pipx, and invoke to run the automated install"
@ -16,8 +18,8 @@ else
fi
# install tooling for invoke based installation
python3 -m pip install --user pipx==0.16.4
python3 -m pipx ensurepath
${PYTHON} -m pip install --user pipx==0.16.4
${PYTHON} -m pipx ensurepath
export PATH=$PATH:~/.local/bin
pipx install invoke==1.4.1
pipx install poetry==1.1.12

View file

@ -110,6 +110,14 @@ class OsInfo:
return OsInfo(os_name, os_like, version)
def get_env_python() -> str:
return os.environ.get("PYTHON", "python3")
def get_env_python_dep() -> str:
return os.environ.get("PYTHON_DEP", "python3")
def get_python(c: Context, warn: bool = False) -> str:
with c.cd(DAEMON_DIR):
r = c.run("poetry env info -p", warn=warn, hide=True)
@ -149,23 +157,27 @@ def get_os(install_type: Optional[str]) -> OsInfo:
def check_existing_core(c: Context, hide: bool) -> None:
if c.run("python -c \"import core\"", warn=True, hide=hide):
raise SystemError("existing python2 core installation detected, please remove")
if c.run("python3 -c \"import core\"", warn=True, hide=hide):
raise SystemError("existing python3 core installation detected, please remove")
python_bin = get_env_python()
if c.run(f"{python_bin} -c \"import core\"", warn=True, hide=hide):
raise SystemError(
f"existing {python_bin} core installation detected, please remove"
)
if c.run("which core-daemon", warn=True, hide=hide):
raise SystemError("core scripts found, please remove old installation")
def install_system(c: Context, os_info: OsInfo, hide: bool) -> None:
python_dep = get_env_python_dep()
if os_info.like == OsLike.DEBIAN:
c.run(
"sudo apt install -y automake pkg-config gcc libev-dev nftables "
"iproute2 ethtool tk python3-tk bash",
f"iproute2 ethtool tk {python_dep}-tk bash",
hide=hide
)
elif os_info.like == OsLike.REDHAT:
c.run(
"sudo yum install -y automake pkgconf-pkg-config gcc gcc-c++ "
"libev-devel nftables iproute python3-devel python3-tkinter "
f"libev-devel nftables iproute {python_dep}-devel {python_dep}-tkinter "
"tk ethtool make bash",
hide=hide
)
@ -180,8 +192,9 @@ def install_system(c: Context, os_info: OsInfo, hide: bool) -> None:
def install_grpcio(c: Context, hide: bool) -> None:
python_bin = get_env_python()
c.run(
"python3 -m pip install --user grpcio==1.27.2 grpcio-tools==1.27.2",
f"{python_bin} -m pip install --user grpcio==1.43.0 grpcio-tools==1.43.0",
hide=hide,
)
@ -197,13 +210,15 @@ def install_core(c: Context, hide: bool) -> None:
def install_poetry(c: Context, dev: bool, local: bool, hide: bool) -> None:
python_bin = get_env_python()
if local:
with c.cd(DAEMON_DIR):
c.run("poetry build -f wheel", hide=hide)
c.run("sudo python3 -m pip install dist/*")
c.run(f"sudo {python_bin} -m pip install dist/*")
else:
args = "" if dev else "--no-dev"
with c.cd(DAEMON_DIR):
c.run(f"poetry env use {python_bin}", hide=hide)
c.run(f"poetry install {args}", hide=hide)
if dev:
c.run("poetry run pre-commit install", hide=hide)
@ -381,12 +396,13 @@ def install_emane(c, emane_version, verbose=False, install_type=None):
p = Progress(verbose)
hide = not verbose
os_info = get_os(install_type)
python_dep = get_env_python_dep()
with p.start("installing system dependencies"):
if os_info.like == OsLike.DEBIAN:
c.run(
"sudo apt install -y gcc g++ automake libtool libxml2-dev "
"libprotobuf-dev libpcap-dev libpcre3-dev uuid-dev pkg-config "
"protobuf-compiler git python3-protobuf python3-setuptools",
f"protobuf-compiler git {python_dep}-protobuf {python_dep}-setuptools",
hide=hide,
)
elif os_info.like == OsLike.REDHAT:
@ -395,7 +411,7 @@ def install_emane(c, emane_version, verbose=False, install_type=None):
c.run(
"sudo yum install -y autoconf automake git libtool libxml2-devel "
"libpcap-devel pcre-devel libuuid-devel make gcc-c++ protobuf-compiler "
"protobuf-devel python3-setuptools",
f"protobuf-devel {python_dep}-setuptools",
hide=hide,
)
emane_dir = "../emane"
@ -404,10 +420,11 @@ def install_emane(c, emane_version, verbose=False, install_type=None):
with p.start("cloning emane"):
c.run(f"git clone {emane_url} {emane_dir}", hide=hide)
with p.start("setup emane"):
python_bin = get_env_python()
with c.cd(emane_dir):
c.run(f"git checkout {emane_version}", hide=hide)
c.run("./autogen.sh", hide=hide)
c.run("PYTHON=python3 ./configure --prefix=/usr", hide=hide)
c.run(f"PYTHON={python_bin} ./configure --prefix=/usr", hide=hide)
with p.start("build emane python bindings"):
with c.cd(str(emane_python_dir)):
c.run("make -j$(nproc)", hide=hide)
@ -449,7 +466,8 @@ def uninstall(
if local:
with p.start("uninstalling core"):
c.run("sudo python3 -m pip uninstall -y core", hide=hide)
python_bin = get_env_python()
c.run(f"sudo {python_bin} -m pip uninstall -y core", hide=hide)
else:
python = get_python(c, warn=True)
if python: