Пример #1
0
class ReservationResult(Base):
    category = fields.Enum(Category)
    workload_id = fields.String(default="")
    data_json = fields.Json()
    signature = fields.Bytes()
    state = fields.Enum(State)
    message = fields.String(default="")
    epoch = fields.DateTime()
Пример #2
0
class ZdbNamespace(Base):
    id = fields.Integer()
    node_id = fields.String(default="")
    size = fields.Integer()
    mode = fields.Enum(ZDBMode)
    password = fields.String(default="")
    disk_type = fields.Enum(DiskType)
    public = fields.Boolean(default=False)
    stats_aggregator = fields.List(fields.Object(Statsaggregator))
    info = fields.Object(ReservationInfo)
Пример #3
0
class UserThreebot(Base):
    # instance name is the f"threebot_{solution uuid}"
    solution_uuid = fields.String()
    identity_tid = fields.Integer()
    name = fields.String()
    owner_tname = fields.String(
    )  # owner's tname in ThreeFold Connect after cleaning
    farm_name = fields.String()
    state = fields.Enum(ThreebotState)
    continent = fields.String()
    explorer_url = fields.String()
    threebot_container_wid = fields.Integer()
    trc_container_wid = fields.Integer(
    )  # deprecated for embeding trc # FIXME: Remove
    reverse_proxy_wid = fields.Integer(
    )  # deprecated for embeding trc # FIXME: Remove
    subdomain_wid = fields.Integer()
    secret_hash = fields.String()
    proxy_wid = fields.Integer()

    def verify_secret(self, secret):
        if not self.secret_hash:
            return True
        return self.secret_hash == hashlib.md5(secret.encode()).hexdigest()

    def hash_secret(self, secret):
        self.secret_hash = hashlib.md5(secret.encode()).hexdigest()
Пример #4
0
class KubernetesNode(VDCHostBase):
    role = fields.Enum(KubernetesRole)
    public_ip = fields.IPRange()
    _size = fields.Integer()

    @property
    def size(self):
        return VDC_SIZE.K8SNodeFlavor(self._size)

    @classmethod
    def from_workload(cls, workload):
        node = cls()
        node.wid = workload.id
        node.ip_address = workload.ipaddress
        if workload.master_ips:
            node.role = KubernetesRole.WORKER
        else:
            node.role = KubernetesRole.MASTER
        node.node_id = workload.info.node_id
        node.pool_id = workload.info.pool_id
        if workload.public_ip:
            zos = get_zos()
            public_ip_workload = zos.workloads.get(workload.public_ip)
            address = str(netaddr.IPNetwork(public_ip_workload.ipaddress).ip)
            node.public_ip = address

        node._size = (
            VDC_SIZE.K8SNodeFlavor(workload.size).value
            if workload.size in [size.value for size in K8S_SIZES]
            else VDC_SIZE.K8SNodeFlavor.SMALL.value
        )
        return node
Пример #5
0
class TfgridSolution1(Base):
    id = fields.Integer()
    name = fields.String(default="")
    solution_type = fields.Enum(SolutionType)
    rid = fields.Integer()
    form_info = fields.Typed(dict)
    explorer = fields.String(default="")
Пример #6
0
class User(Base):
    emails = fields.List(fields.String())
    permissions = fields.List(fields.Object(Permission))
    custom_config = fields.Typed(dict)
    type = fields.Enum(UserType)
    password = fields.Secret()

    first_name = fields.String(default="")
    last_name = fields.String(default="")

    def get_full_name(self):
        name = self.first_name
        if self.last_name:
            name += " " + self.last_name
        return name

    def get_unique_name(self):
        return self.full_name.replace(" ", "") + ".user"

    full_name = fields.String(compute=get_full_name)
    unique_name = fields.String(compute=get_unique_name)

    def get_my_greeter(self):
        return Greeter(self.full_name)

    my_greeter = fields.Typed(Greeter, stored=False, compute=get_my_greeter)
    ahmed_greeter = fields.Typed(Greeter,
                                 stored=False,
                                 default=Greeter("ahmed"))
Пример #7
0
class ResourceUnitPrice(Base):
    currency = fields.Enum(Currency)
    cru = fields.Float()
    mru = fields.Float()
    hru = fields.Float()
    sru = fields.Float()
    nru = fields.Float()
Пример #8
0
class NodePublicIface(Base):
    master = fields.String(default="")
    type = fields.Enum(NicType)
    ipv4 = fields.IPRange()
    ipv6 = fields.IPRange()
    gw4 = fields.IPRange()
    gw6 = fields.IPRange()
    version = fields.Integer()
Пример #9
0
class ReservationInfo(Base):
    workload_id = fields.Integer()
    node_id = fields.String()
    pool_id = fields.Integer()
    description = fields.String(default="")
    reference = fields.String(default="")
    customer_tid = fields.Integer()
    customer_signature = fields.String()
    next_action = fields.Enum(NextAction)
    signatures_provision = fields.List(fields.Object(Signature))
    signing_request_provision = fields.Object(SigningRequest)
    signing_request_delete = fields.Object(SigningRequest)
    signatures_farmer = fields.List(fields.Object(Signature))
    signatures_delete = fields.List(fields.Object(Signature))
    epoch = fields.DateTime(default=datetime.utcnow)
    metadata = fields.String(default="")
    result = fields.Object(ReservationResult)
    workload_type = fields.Enum(WorkloadType)
Пример #10
0
class ZdbNamespace(Base):
    id = fields.Integer()
    node_id = fields.String(default="")
    size = fields.Integer()
    mode = fields.Enum(ZDBMode)
    password = fields.String(default="")
    disk_type = fields.Enum(DiskType)
    public = fields.Boolean(default=False)
    stats_aggregator = fields.List(fields.Object(Statsaggregator))
    info = fields.Object(ReservationInfo)

    def resource_units(self):
        resource_units = ResourceUnitAmount()
        if self.disk_type == DiskType.HDD:
            resource_units.hru += self.size
        elif self.disk_type == DiskType.SSD:
            resource_units.sru += self.size
        return resource_units
Пример #11
0
class APIKey(Base):
    key = fields.String(default=lambda: uuid4().hex)
    role = fields.Enum(UserRole)
    created_at = fields.Float(default=lambda: j.data.time.utcnow().timestamp)

    def to_dict(self):
        d = super().to_dict()
        d["name"] = self.instance_name
        return d
Пример #12
0
class Location(Base):
    path_url = fields.String(default="/")
    force_https = fields.Boolean(default=False)
    path_location = fields.String(default="/")
    index = fields.String(default="index.html")

    scheme = fields.String(default="http")
    host = fields.String(default="127.0.0.1")
    port = fields.Integer()
    path_dest = fields.String(default="/")
    spa = fields.Boolean(default=False)
    websocket = fields.Boolean(default=False)
    location_type = fields.Enum(LocationType)
    is_auth = fields.Boolean(default=False)
    is_admin = fields.Boolean(default=False)
    package_name = fields.String()
    is_package_authorized = fields.Boolean(default=False)
    custom_config = fields.String(default=None)
    proxy_buffering = fields.Enum(ProxyBuffering)
    proxy_buffers = fields.String()
    proxy_buffer_size = fields.String()

    @property
    def cfg_dir(self):
        return j.sals.fs.join_paths(self.parent.cfg_dir, "locations")

    @property
    def cfg_file(self):
        return j.sals.fs.join_paths(self.cfg_dir, f"{self.instance_name}.conf")

    def get_config(self):
        return render_config_template(
            "location",
            base_dir=j.core.dirs.BASEDIR,
            location=self,
            threebot_connect=j.core.config.get_config().get("threebot_connect", True),
            https_port=PORTS.HTTPS,
        )

    def configure(self):
        j.sals.fs.mkdir(self.cfg_dir)
        j.sals.fs.write_file(self.cfg_file, self.get_config())
Пример #13
0
class Reservation(Base):
    id = fields.Integer()
    json = fields.String(default="")
    data_reservation = fields.Object(ReservationData)
    customer_tid = fields.Integer()
    customer_signature = fields.String(default="")
    next_action = fields.Enum(NextAction)
    signatures_provision = fields.List(fields.Object(Signature))
    signatures_farmer = fields.List(fields.Object(Signature))
    signatures_delete = fields.List(fields.Object(Signature))
    epoch = fields.DateTime(default=datetime.utcnow)
    metadata = fields.String(default="")
    results = fields.List(fields.Object(ReservationResult))
Пример #14
0
class Volume(Base):
    id = fields.Integer()
    size = fields.Integer()
    type = fields.Enum(DiskType)
    stats_aggregator = fields.List(fields.Object(Statsaggregator))
    info = fields.Object(ReservationInfo)

    def resource_units(self):
        resource_units = ResourceUnitAmount()
        if self.type == DiskType.HDD:
            resource_units.hru += self.size
        elif self.type == DiskType.SSD:
            resource_units.sru += self.size
        return resource_units
Пример #15
0
class VDCWallet(Base):
    vdc_uuid = fields.String()
    wallet_secret = fields.String()
    wallet_network = fields.Enum(StellarNetwork)

    def _init_wallet(self, secret=None):
        wallet = j.clients.stellar.new(self.instance_name, secret=secret)
        if not secret:
            wallet.activate_through_activation_wallet()
        wallet.save()
        self.wallet_secret = wallet.secret

    @property
    def stellar_wallet(self):
        if not j.clients.stellar.find(
                self.instance_name) and self.wallet_secret:
            self._init_wallet(self.wallet_secret)
        return j.clients.stellar.get(self.instance_name)
Пример #16
0
class Location(Base):
    name = fields.String()
    path_url = fields.String()
    is_auth = fields.Boolean(default=False)
    force_https = fields.Boolean(default=False)
    path_location = fields.String()
    index = fields.String()
    ipaddr_dest = fields.String()
    port_dest = fields.Integer()
    path_dest = fields.String()
    location_type = fields.Enum(LocationType)
    scheme = fields.String()

    @property
    def path_cfg_dir(self):
        return f"{self.parent.path_cfg_dir}/{self.parent.instance_name}_locations"

    @property
    def path_cfg(self):
        return f"{self.path_cfg_dir}/{self.instance_name}.conf"

    @property
    def path_web(self):
        return self.parent.path_web

    def write_config(self, content=""):
        if not content:
            content = render_config_template(f"location_{self.location_type}",
                                             obj=self)
        j.sals.fs.write_file(self.path_cfg, content)

    def configure(self):
        """Config is a server config file of nginx (in text format)
        """
        j.sals.fs.mkdir(self.path_cfg_dir)

        if self.location_type.value in [
                LocationType.STATIC.value, LocationType.SPA.value
        ]:
            if not self.path_location.endswith("/"):
                self.path_location += "/"

        self.write_config(self.config)
Пример #17
0
class Volume(Base):
    id = fields.Integer()
    size = fields.Integer()
    type = fields.Enum(DiskType)
    stats_aggregator = fields.List(fields.Object(Statsaggregator))
    info = fields.Object(ReservationInfo)
Пример #18
0
class Car(Base):
    color = fields.Enum(Colors)
Пример #19
0
class ThreebotServer(Base):
    _package_manager = fields.Factory(PackageManager)
    domain = fields.String()
    email = fields.String()
    acme_server_type = fields.Enum(AcmeServer)
    acme_server_url = fields.URL()

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._rack = None
        self._gedis = None
        self._db = None
        self._gedis_http = None
        self._services = None
        self._packages = None
        self._started = False
        self._nginx = None
        self._redis = None
        self.rack.add(GEDIS, self.gedis)
        self.rack.add(GEDIS_HTTP, self.gedis_http.gevent_server)
        self.rack.add(SERVICE_MANAGER, self.services)

    def is_running(self):
        nginx_running = self.nginx.is_running()
        redis_running = self.redis.cmd.is_running(
        ) or j.sals.nettools.wait_connection_test("127.0.0.1", 6379, timeout=1)
        gedis_running = j.sals.nettools.wait_connection_test("127.0.0.1",
                                                             16000,
                                                             timeout=1)
        return nginx_running and redis_running and gedis_running

    @property
    def started(self):
        return self._started

    @property
    def nginx(self):
        if self._nginx is None:
            self._nginx = j.tools.nginx.get("default")
        return self._nginx

    @property
    def redis(self):
        if self._redis is None:
            self._redis = j.tools.redis.get("default")
        return self._redis

    @property
    def db(self):
        if self._db is None:
            self._db = j.core.db
        return self._db

    @property
    def rack(self):
        if self._rack is None:
            self._rack = j.servers.rack
        return self._rack

    @property
    def gedis(self):
        if self._gedis is None:
            self._gedis = j.servers.gedis.get("threebot")
        return self._gedis

    @property
    def gedis_http(self):
        if self._gedis_http is None:
            self._gedis_http = j.servers.gedis_http.get("threebot")
        return self._gedis_http

    @property
    def services(self):
        if self._services is None:
            self._services = j.tools.servicemanager.get("threebot")
        return self._services

    @property
    def chatbot(self):
        return self.gedis._loaded_actors.get("chatflows_chatbot")

    @property
    def packages(self):
        if self._packages is None:
            self._packages = self._package_manager.get(self.instance_name)
        return self._packages

    def check_dependencies(self):
        install_msg = "Visit https://github.com/threefoldtech/js-sdk/blob/development/docs/wiki/quick_start.md for installation guide"

        if not self.nginx.installed:
            raise j.exceptions.NotFound(
                f"nginx is not installed.\n{install_msg}")

        ret = shutil.which("certbot")
        if not ret:
            raise j.exceptions.NotFound(
                f"certbot is not installed.\n{install_msg}")

        rc, out, err = j.sals.process.execute("certbot plugins")
        if "* nginx" not in out:
            raise j.exceptions.NotFound(
                f"python-certbot-nginx is not installed.\n{install_msg}")

        if not self.redis.installed:
            raise j.exceptions.NotFound(
                f"redis is not installed.\n{install_msg}")

        ret = shutil.which("tmux")
        if not ret:
            raise j.exceptions.NotFound(
                f"tmux is not installed.\n{install_msg}")

        ret = shutil.which("git")
        if not ret:
            raise j.exceptions.NotFound(
                f"git is not installed.\n{install_msg}")

    def start(self, wait: bool = False):
        # start default servers in the rack
        # handle signals
        for signal_type in (signal.SIGTERM, signal.SIGINT, signal.SIGKILL):
            gevent.signal(signal_type, self.stop)

        # mark app as started
        if self.is_running():
            return

        self.check_dependencies()

        self.redis.start()
        self.nginx.start()
        self.rack.start()
        j.logger.register(f"threebot_{self.instance_name}")

        # add default packages
        for package_name in DEFAULT_PACKAGES:
            j.logger.info(f"Configuring package {package_name}")
            try:
                package = self.packages.get(package_name)
                self.packages.install(package)
            except Exception as e:
                self.stop()
                raise j.core.exceptions.Runtime(
                    f"Error happened during getting or installing {package_name} package, the detailed error is {str(e)}"
                ) from e

        # install all package
        self.packages._install_all()
        j.logger.info("Reloading nginx")
        self.nginx.reload()

        # mark server as started
        self._started = True
        j.logger.info(
            f"Threebot is running at http://localhost:{PORTS.HTTP} and https://localhost:{PORTS.HTTPS}"
        )
        self.rack.start(wait=wait)  # to keep the server running

    def stop(self):
        server_packages = self.packages.list_all()
        for package_name in server_packages:
            package = self.packages.get(package_name)
            package.stop()
        self.nginx.stop()
        # mark app as stopped, do this before stopping redis
        j.logger.unregister()
        self.redis.stop()
        self.rack.stop()
        self._started = False
Пример #20
0
class Wallet(Base):
    id = fields.String()
    address = fields.String()
    balance = fields.Float()
    currency = fields.Enum(Currency)
    tags = fields.List(fields.String())
Пример #21
0
class StartupCmd(Base):
    start_cmd = fields.String()
    ports = fields.List(fields.Integer())
    executor = fields.Enum(Executor)
    check_cmd = fields.String()
    path = fields.String(default=j.core.dirs.TMPDIR)
    stop_cmd = fields.String()
    env = fields.Typed(dict, default={})
    timeout = fields.Integer(default=60)
    process_strings = fields.List(fields.String())
    process_strings_regex = fields.List(fields.String())

    def __init__(self):
        super().__init__()
        self._process = None
        self._pid = None
        self._cmd_path = None
        self.__tmux_window = None

    def reset(self):
        self._process = None
        self._pid = None

    @property
    def pid(self):
        if not self._pid:
            pids = j.sals.process.get_pids(f"startupcmd_{self.instance_name}")
            if pids:
                self._pid = pids[0]
        return self._pid

    @property
    def cmd_path(self):
        if not self._cmd_path:
            self._cmd_path = j.sals.fs.join_paths(j.core.dirs.VARDIR, "cmds",
                                                  f"{self.instance_name}.sh")
            j.sals.fs.mkdirs(j.sals.fs.dirname(self._cmd_path))
        return self._cmd_path

    @pid.setter
    def pid(self, pid):
        self._pid = pid

    @property
    def process(self):
        if not self._process:
            if self.pid:
                self._process = j.sals.process.get_process_object(self.pid,
                                                                  die=False)
                if not self._process:
                    self.pid = None
            else:
                processes = self._get_processes_by_port_or_filter()
                if len(processes) == 1:
                    self._process = processes[0]
        return self._process

    @property
    def _tmux_window(self):
        if self.executor == Executor.TMUX:
            if self.__tmux_window is None:
                self.__tmux_window = j.core.executors.tmux.get_js_window(
                    self.instance_name)
        return self.__tmux_window

    def _get_processes_by_port_or_filter(self):
        """Uses object properties to find the corresponding process(es)

        Returns:
            list: All processes that matched
        """

        pids_done = []
        result = []

        def _add_to_result(process):
            if process and process.pid not in pids_done:
                result.append(process)
                pids_done.append(process.pid)

        for port in self.ports:
            try:
                process = j.sals.process.get_process_by_port(port)
            except Exception:
                continue

            _add_to_result(process)

        for process_string in self.process_strings:
            for pid in j.sals.process.get_filtered_pids(process_string):
                process = j.sals.process.get_process_object(pid, die=False)
                _add_to_result(process)

        for pid in j.sals.process.get_pids_filtered_by_regex(
                self.process_strings_regex):
            process = j.sals.process.get_process_object(pid, die=False)
            _add_to_result(process)

        #  We return all processes which match
        return result

    def _kill_processes_by_port_or_filter(self):
        """Kills processes that matches object properties
        """
        processes = self._get_processes_by_port_or_filter()
        self._kill_processes(processes)

    def _kill_processes(self, processes):
        """Kill processes

        Args:
            processes (list): List of processes
        """
        for process in processes:
            try:
                process.kill()
            except NoSuchProcess:
                pass  # already killed

    def _soft_kill(self):
        """Kills the poocess using `stop_cmd`

        Returns:
            bool: True if was killed
        """
        if self.stop_cmd:
            cmd = j.tools.jinja2.render_template(template_text=self.stop_cmd,
                                                 args=self._get_data())
            exit_code, _, _ = j.sals.process.execute(cmd, die=False)
            self.reset()
            return exit_code == 0
        return False

    def _hard_kill(self):
        """Force Kills the process
        """
        if self.process:
            self._kill_processes([self.process])
            self.reset()

        self._kill_processes_by_port_or_filter()

        if self.executor == Executor.TMUX:
            self._tmux_window.kill_window()
            self.__tmux_window = None

    def stop(self, force=True, wait_for_stop=True, die=True, timeout=None):
        """Stops the running command

        Args:
            force (bool, optional): If True will force kill the process. Defaults to True.
            wait_for_stop (bool, optional): If True will wait until process is stopped. Defaults to True.
            die (bool, optional): If True will raise if timeout is exceeded for stop. Defaults to True.
            timeout (int, optional): Timeout for stop wait.If not set will use `timeout` property. Defaults to None.
        """

        timeout = timeout or self.timeout

        if self.is_running():
            self._soft_kill()

        if force:
            self._hard_kill()

        if wait_for_stop:
            self.wait_for_stop(die=die, timeout=timeout)
        j.sals.process.execute(f"rm {self.cmd_path}", die=False)

    def is_running(self):
        """Checks if startup cmd is running. Will use `check_cmd` property if defined or check based on objet properties

        Returns:
            bool: True if it is running
        """
        if self.check_cmd:
            exit_code, _, _ = j.sals.process.execute(self.check_cmd, die=False)
            return exit_code == 0

        self.reset()
        if self.process:
            return self.process.is_running()
        return self._get_processes_by_port_or_filter() != []

    def _wait(self, for_running, die, timeout):
        """Wait for either start or stop to finishes

        Args:
            for_running (bool): Whether to check if it is running or stopped.
            die (bool, optional): If True will raise if timeout is exceeded for stop. Defaults to True.
            timeout (int, optional): Timeout for wait operation. Defaults to None.

        Raises:
            j.exceptions.Timeout: If timeout is exceeded.
        """
        end = j.data.time.now().timestamp + timeout

        while j.data.time.now().timestamp < end:
            if self.is_running() == for_running:
                break
            time.sleep(0.05)
        else:
            if die:
                raise j.exceptions.Timeout(
                    f"Wait operation exceeded timeout: {timeout}")

    def wait_for_stop(self, die=True, timeout=10):
        """Wait for stop to finishes

        Args:
            die (bool, optional): If True will raise if timeout is exceeded for stop. Defaults to True.
            timeout (int, optional): Timeout for wait operation. Defaults to None.

        Raises:
            j.exceptions.Timeout: If timeout is exceeded.
        """
        self._wait(False, die, timeout)

    def wait_for_running(self, die=True, timeout=10):
        """Wait for start to finishes

        Args:
            die (bool, optional): If True will raise if timeout is exceeded for stop. Defaults to True.
            timeout (int, optional): Timeout for wait operation. Defaults to None.

        Raises:
            j.exceptions.Timeout: If timeout is exceeded.
        """
        self._wait(True, die, timeout)

    def start(self):
        """Starts the process
        """
        if self.is_running():
            return

        if not self.start_cmd:
            raise j.exceptions.Value("please make sure start_cmd has been set")

        if "\n" in self.start_cmd.strip():
            command = self.start_cmd
        else:
            template_script = """
            set +ex
            {% for key,val in env.items() %}
            export {{key}}='{{val}}'
            {% endfor %}

            mkdir -p {{path}}
            cd {{path}}
            bash -c \"exec -a startupcmd_{{name}} {{start_cmd}}\"

            """

            script = j.tools.jinja2.render_template(
                template_text=template_script,
                env=self.env,
                path=self.path,
                start_cmd=self.start_cmd,
                name=self.instance_name,
            )
            j.sals.fs.write_file(self.cmd_path, script)
            j.sals.fs.chmod(self.cmd_path, 0o770)
            command = f"sh {self.cmd_path}"

        if self.executor == Executor.FOREGROUND:
            j.sals.process.execute(command)
        elif self.executor == Executor.TMUX:
            self._tmux_window.attached_pane.send_keys(command)

        self.wait_for_running(die=True, timeout=self.timeout)
Пример #22
0
class Website(Base):
    domain = fields.String()
    ssl = fields.Boolean()
    port = fields.Integer(default=PORTS.HTTP)
    locations = fields.Factory(Location, stored=False)
    includes = fields.List(fields.String())

    selfsigned = fields.Boolean(default=True)

    # keep it as letsencryptemail for compatibility
    letsencryptemail = fields.String()
    acme_server_type = fields.Enum(AcmeServer)
    acme_server_url = fields.URL()
    # in case of using existing key/certificate
    key_path = fields.String()
    cert_path = fields.String()
    fullchain_path = fields.String()

    @property
    def certbot(self):
        kwargs = dict(
            domain=self.domain,
            email=self.letsencryptemail,
            server=self.acme_server_url,
            nginx_server_root=self.parent.cfg_dir,
            key_path=self.key_path,
            cert_path=self.cert_path,
            fullchain_path=self.fullchain_path,
        )

        if self.acme_server_type == AcmeServer.LETSENCRYPT:
            certbot_type = LetsencryptCertbot
        elif self.acme_server_type == AcmeServer.ZEROSSL:
            certbot_type = ZerosslCertbot
        else:
            certbot_type = CustomCertbot

        return certbot_type(**kwargs)

    @property
    def cfg_dir(self):
        return j.sals.fs.join_paths(self.parent.cfg_dir, self.instance_name)

    @property
    def cfg_file(self):
        return j.sals.fs.join_paths(self.cfg_dir, "server.conf")

    @property
    def include_paths(self):
        paths = []
        for include in self.includes:
            ## TODO validate location name and include
            website_name, location_name = include.split(".", 1)
            website = self.parent.websites.find(website_name)
            if not website:
                continue

            paths.append(j.sals.fs.join_paths(website.cfg_dir, "locations", location_name))
        return paths

    def get_locations(self):
        for location in self.locations.list_all():
            yield self.locations.get(location)

    def get_proxy_location(self, name):
        location = self.locations.get(name)
        location.location_type = LocationType.PROXY
        return location

    def get_custom_location(self, name):
        location = self.locations.get(name)
        location.location_type = LocationType.CUSTOM
        return location

    def get_static_location(self, name):
        location = self.locations.get(name)
        location.location_type = LocationType.STATIC
        return location

    def get_config(self):
        return render_config_template("website", base_dir=j.core.dirs.BASEDIR, website=self)

    def generate_certificates(self, retries=6):
        if self.domain:
            if self.key_path and self.cert_path and self.fullchain_path:
                # only use install command if an existing key and certificate were set
                self.install_certifcate()
            else:
                self.obtain_and_install_certifcate(retries=retries)

    def install_certifcate(self):
        """Construct and Execute install certificate command
        Alternative to certbot install

        """
        cmd = self.certbot.install_cmd
        j.logger.debug(f"Execute: {' '.join(cmd)}")
        rc, out, err = j.sals.process.execute(cmd)
        if rc > 0:
            j.logger.error(f"Installing certificate failed {out}\n{err}")
        else:
            j.logger.info(f"Certificate installed successfully {out}")

    def obtain_and_install_certifcate(self, retries=6):
        """Construct and Execute run certificate command,This will issue a new certificate managed by Certbot
        Alternative to certbot run

        Args:
            retries (int, optional): Number of retries Certbot will try to install the certificate if failed. Defaults to 6.
        """
        cmd = self.certbot.run_cmd
        j.logger.debug(f"Execute: {' '.join(cmd)}")
        for _ in range(retries):
            rc, out, err = j.sals.process.execute(cmd)
            if rc > 0:
                j.logger.error(f"Generating certificate failed {out}\n{err}")
            else:
                j.logger.error(f"Certificate Generated successfully {out}")
                break

    def generate_self_signed_certificates(self):
        keypempath = f"{self.parent.cfg_dir}/key.pem"
        certpempath = f"{self.parent.cfg_dir}/cert.pem"
        if j.sals.process.is_installed("mkcert"):
            res = j.sals.process.execute(
                f"mkcert -key-file {keypempath} -cert-file {certpempath} localhost *.localhost 127.0.0.1 ::1"
            )
            if res[0] != 0:
                raise j.exceptions.JSException(f"Failed to generate self-signed certificate (using mkcert).{res}")

        else:
            if j.sals.fs.exists(f"{keypempath}") and j.sals.fs.exists(f"{certpempath}"):
                return
            res = j.sals.process.execute(
                f"openssl req -nodes -x509 -newkey rsa:4096 -keyout {keypempath} -out {certpempath} -days 365 -subj '/CN=localhost'"
            )
            if res[0] != 0:
                raise j.exceptions.JSException(f"Failed to generate self-signed certificate (using openssl).{res}")

    def configure(self, generate_certificates=True):
        j.sals.fs.mkdir(self.cfg_dir)
        needed_dirs = ("body", "client-body", "fastcgi", "proxy", "scgi", "uwsgi")
        for d in needed_dirs:
            j.sals.fs.mkdir(j.sals.fs.join_paths(self.cfg_dir, d))
        for location in self.get_locations():
            location.configure()

        j.sals.fs.write_file(self.cfg_file, self.get_config())
        if self.ssl:
            self.generate_self_signed_certificates()
        if generate_certificates and self.ssl:
            self.generate_certificates()

    def clean(self):
        j.sals.fs.rmtree(self.cfg_dir)
Пример #23
0
class UserVDC(Base):
    vdc_name = fields.String()
    owner_tname = fields.String()
    solution_uuid = fields.String(default=lambda: uuid.uuid4().hex)
    identity_tid = fields.Integer()
    s3 = fields.Object(S3)
    vmachines = fields.List(fields.Object(VMachine))
    kubernetes = fields.List(fields.Object(KubernetesNode))
    etcd = fields.List(fields.Object(ETCDNode))
    threebot = fields.Object(VDCThreebot)
    created = fields.DateTime(default=datetime.datetime.utcnow)
    expiration = fields.Float(default=lambda: j.data.time.utcnow().timestamp + 30 * 24 * 60 * 60)
    last_updated = fields.DateTime(default=datetime.datetime.utcnow)
    is_blocked = fields.Boolean(default=False)  # grace period action is applied
    explorer_url = fields.String(default=lambda: j.core.identity.me.explorer_url)
    _flavor = fields.String()
    state = fields.Enum(VDCSTATE)
    __lock = BoundedSemaphore(1)
    transaction_hashes = []

    @property
    def flavor(self):
        d = self.to_dict()
        oldflavor = d.get("flavor", "silver")
        if not self._flavor:
            flavors = {"silver": "silver", "gold": "gold", "platinum": "platinum", "diamond": "diamond"}
            self._flavor = d.get(flavors[oldflavor])
            self.save()
            # TODO: should we do a save here?
            return VDC_SIZE.VDCFlavor(self._flavor)
        else:
            return VDC_SIZE.VDCFlavor(self._flavor)

    def to_dict(self):
        d = super().to_dict()
        d["flavor"] = self._flavor
        for node in d["kubernetes"]:
            node["size"] = node["_size"]
        return d

    def is_empty(self, load_info=True):
        if load_info:
            self.load_info()
        if any([self.kubernetes, self.threebot.wid, self.threebot.domain, self.s3.minio.wid, self.s3.zdbs]):
            return False
        self.state = VDCSTATE.EMPTY
        self.save()
        return True

    def has_minimal_components(self):
        if all([self.kubernetes, self.threebot.wid, self.threebot.domain]):
            return True
        return False

    @property
    def expiration_date(self):
        expiration = self.calculate_expiration_value()
        return j.data.time.get(expiration).datetime

    @property
    def prepaid_wallet(self):
        wallet_name = f"prepaid_wallet_{self.solution_uuid}"
        wallet = j.clients.stellar.find(wallet_name)
        if not wallet:
            vdc_wallet = VDC_WALLET_FACTORY.find(wallet_name)
            if not vdc_wallet:
                vdc_wallet = VDC_WALLET_FACTORY.new(wallet_name)
                vdc_wallet.save()
            wallet = vdc_wallet.stellar_wallet
        return wallet

    @property
    def provision_wallet(self):
        wallet_name = f"provision_wallet_{self.solution_uuid}"
        wallet = j.clients.stellar.find(wallet_name)
        if not wallet:
            vdc_wallet = VDC_WALLET_FACTORY.find(wallet_name)
            if not vdc_wallet:
                vdc_wallet = VDC_WALLET_FACTORY.new(wallet_name)
                vdc_wallet.save()
            wallet = vdc_wallet.stellar_wallet
        return wallet

    @property
    def vdc_workloads(self):
        workloads = []
        workloads += self.kubernetes
        workloads += self.s3.zdbs
        workloads += self.vmachines
        if self.threebot.wid:
            workloads.append(self.threebot)
        if self.s3.minio.wid:
            workloads.append(self.s3.minio)
        return workloads

    @property
    def active_pools(self):
        my_pool_ids = [w.pool_id for w in self.vdc_workloads]
        explorer = j.core.identity.me.explorer
        active_pools = [p for p in explorer.pools.list(customer_tid=self.identity_tid) if p.pool_id in my_pool_ids]
        return active_pools

    def get_deployer(
        self,
        password=None,
        identity=None,
        bot=None,
        proxy_farm_name=None,
        deployment_logs=False,
        ssh_key_path=None,
        restore=False,
        network_farm=None,
        compute_farm=None,
    ):
        proxy_farm_name = proxy_farm_name or random.choice(PROXY_FARMS.get())
        if not password and not identity:
            identity = self._get_identity()

        return VDCDeployer(
            vdc_instance=self,
            password=password,
            bot=bot,
            proxy_farm_name=proxy_farm_name,
            identity=identity,
            deployment_logs=deployment_logs,
            ssh_key_path=ssh_key_path,
            restore=restore,
            network_farm=network_farm,
            compute_farm=compute_farm,
        )

    def get_password(self):
        identity = self._get_identity(default=False)
        if not identity:
            j.logger.error("Couldn't find identity")
            return
        password_hash = j.data.encryption.mnemonic_to_key(identity.words)
        return password_hash.decode()

    def validate_password(self, password):
        password = j.data.hash.md5(password)
        vdc_password = self.get_password()
        if not vdc_password:
            # identity was not generated for this vdc instance
            return True

        if password == vdc_password:
            return True
        return False

    def _get_identity(self, default=True):
        instance_name = f"vdc_ident_{self.solution_uuid}"
        identity = None
        if j.core.identity.find(instance_name):
            identity = j.core.identity.find(instance_name)
        elif default:
            identity = j.core.identity.me
        return identity

    def get_zdb_monitor(self):
        return ZDBMonitor(self)

    def get_kubernetes_monitor(self):
        return KubernetesMonitor(self)

    def get_snapshot_manager(self, snapshots_dir=None):
        return SnapshotManager(self, snapshots_dir)

    def get_quantumstorage_manager(self, ip_version=4):
        return QuantumStorage(self, ip_version)

    def load_info(self, load_proxy=False):
        kubernetes, s3, vmachines, etcd, threebot = self.get_vdc_workloads(load_proxy=load_proxy)

        self.__lock.acquire()
        try:
            self.kubernetes = kubernetes
            self.etcd = etcd
            self.s3 = s3
            self.vmachines = vmachines
            self.threebot = threebot
        finally:
            self.__lock.release()

    def _build_zdb_proxies(self, s3):
        proxies = self._list_socat_proxies()
        for zdb in s3.zdbs:
            zdb_proxies = proxies[zdb.ip_address]
            if not zdb_proxies:
                continue
            proxy = zdb_proxies[0]
            zdb.proxy_address = f"{proxy['ip_address']}:{proxy['listen_port']}"

    def get_public_ip(self):
        if not self.kubernetes:
            self.load_info()
        public_ip = None
        for node in self.kubernetes:
            if node.public_ip != "::/128":
                public_ip = node.public_ip
                break
        return public_ip

    def _list_socat_proxies(self, public_ip=None):
        public_ip = public_ip or self.get_public_ip()
        if not public_ip:
            raise j.exceptions.Runtime(f"Couldn't get a public ip for vdc: {self.vdc_name}")
        ssh_client = self.get_ssh_client("socat_list", public_ip, "rancher")
        result = defaultdict(list)
        rc, out, _ = ssh_client.sshclient.run(f"sudo ps -ef | grep -v grep | grep socat", warn=True)
        if rc != 0:
            return result

        for line in out.splitlines():
            # root      6659     1  0 Feb19 ?        00:00:00 /var/lib/rancher/k3s/data/current/bin/socat tcp-listen:9900,reuseaddr,fork tcp:[2a02:1802:5e:0:c46:cff:fe32:39ae]:9900
            splits = line.split("tcp-listen:")
            if len(splits) != 2:
                continue
            splits = splits[1].split(",")
            if len(splits) < 2:
                continue
            listen_port = splits[0]
            splits = line.split("tcp:")
            if len(splits) != 2:
                continue
            proxy_address = splits[1]
            splits = proxy_address.split(":")
            if len(splits) < 2:
                continue

            port = splits[-1]
            ip_address = ":".join(splits[:-1])
            if ip_address[0] == "[" and ip_address[-1] == "]":
                ip_address = ip_address[1:-1]
            result[ip_address].append({"dst_port": port, "listen_port": listen_port, "ip_address": public_ip})
        return result

    def _filter_vdc_workloads(self):
        zos = get_zos()
        user_workloads = zos.workloads.list_workloads(self.identity_tid, next_action=NextAction.DEPLOY)
        result = []
        for workload in user_workloads:
            if workload.info.workload_type not in VDC_WORKLOAD_TYPES:
                continue
            if not workload.info.description:
                continue
            try:
                description = j.data.serializers.json.loads(workload.info.description)
            except:
                continue
            if description.get("vdc_uuid") != self.solution_uuid:
                continue
            result.append(workload)
        return result

    def get_vdc_workloads(self, load_proxy=False):
        kubernetes = []
        s3 = S3()
        etcd = []
        vmachines = []
        threebot = VDCThreebot()

        proxies = []
        for workload in self._filter_vdc_workloads():
            if workload.info.workload_type == WorkloadType.Kubernetes:
                kubernetes.append(KubernetesNode.from_workload(workload))
            elif workload.info.workload_type == WorkloadType.Container:
                if "minio" in workload.flist:
                    container = S3Container.from_workload(workload)
                    s3.minio = container
                elif "js-sdk" in workload.flist:
                    container = VDCThreebot.from_workload(workload)
                    threebot = container
                elif "etcd" in workload.flist:
                    node = ETCDNode.from_workload(workload)
                    etcd.append(node)
            elif workload.info.workload_type == WorkloadType.Zdb:
                zdb = S3ZDB.from_workload(workload)
                if zdb:
                    s3.zdbs.append(zdb)
            elif workload.info.workload_type == WorkloadType.Virtual_Machine:
                vmachine = VMachine.from_workload(workload)
                vmachines.append(vmachine)
            elif workload.info.workload_type == WorkloadType.Subdomain:
                s3_domain = self._check_s3_subdomains(workload)
                if s3_domain:
                    s3.domain = workload.domain
                    s3.domain_wid = workload.id
            elif workload.info.workload_type == WorkloadType.Reverse_proxy:
                proxies.append(workload)

        threebot.domain = self._get_threebot_subdomain(proxies, threebot)
        if load_proxy:
            self._build_zdb_proxies(s3)

        return kubernetes, s3, vmachines, etcd, threebot

    def _check_s3_subdomains(self, workload):
        minio_wid = self.s3.minio.wid
        if not minio_wid:
            return

        if not workload.info.description:
            return
        try:
            desc = j.data.serializers.json.loads(workload.info.description)
        except Exception as e:
            j.logger.warning(f"Failed to load workload {workload.id} description due to error {e}")
            return
        exposed_wid = desc.get("exposed_wid")
        if exposed_wid == minio_wid:
            return True

    def _get_threebot_subdomain(self, proxy_workloads, threebot):
        threebot_wid = threebot.wid
        if not threebot_wid:
            return
        non_matching_domains = []
        for workload in proxy_workloads:
            if not workload.info.description:
                continue
            try:
                desc = j.data.serializers.json.loads(workload.info.description)
            except Exception as e:
                j.logger.warning(f"Failed to load workload {workload.id} description due to error {e}")
                continue
            exposed_wid = desc.get("exposed_wid")
            if exposed_wid == threebot_wid:
                return workload.domain
            else:
                non_matching_domains.append(workload.domain)
        if not threebot.domain and non_matching_domains:
            return non_matching_domains[-1]

    def apply_grace_period_action(self):
        self.load_info()
        j.logger.info(f"VDC: {self.solution_uuid}, GRACE_PERIOD_ACTION: initialization")
        for k8s in self.kubernetes:
            ip_address = k8s.public_ip
            if ip_address == "::/128":
                continue
            try:
                ssh_client = self.get_ssh_client(self.instance_name, user="******", ip_address=str(ip_address))
                rc, out, err = ssh_client.sshclient.run("sudo ip link set cni0 down", warn=True)
                if rc:
                    j.logger.critical(
                        f"VDC: {self.solution_uuid}, GRACE_PERIOD_ACTION: failed to shutdown cni0 wid: {k8s.wid}, rc: {rc}, out: {out}, err: {err}"
                    )
                if k8s.role == KubernetesRole.MASTER:
                    rc, out, err = ssh_client.sshclient.run(
                        "sudo iptables -A INPUT -p tcp --destination-port 6443 -j DROP", warn=True
                    )
                    if rc:
                        j.logger.critical(
                            f"VDC: {self.solution_uuid}, GRACE_PERIOD_ACTION: failed to block kubernetes API wid: {k8s.wid}, rc: {rc}, out: {out}, err: {err}"
                        )

                j.clients.sshclient.delete(self.instance_name)
            except Exception as e:
                j.logger.critical(
                    f"VDC: {self.solution_uuid}, GRACE_PERIOD_ACTION: failed to connect to kubernetes controller due to error {str(e)}"
                )
                raise e
        self.is_blocked = True
        self.save()
        j.logger.info(f"VDC: {self.solution_uuid}, GRACE_PERIOD_ACTION: applied successfully")

    def revert_grace_period_action(self):
        self.load_info()
        j.logger.info(f"VDC: {self.solution_uuid}, REVERT_GRACE_PERIOD_ACTION: intializing")
        self.is_blocked = False
        for k8s in self.kubernetes:
            ip_address = k8s.public_ip
            if ip_address == "::/128":
                continue
            try:
                ssh_client = self.get_ssh_client(self.instance_name, user="******", ip_address=str(ip_address))
                rc, out, err = ssh_client.sshclient.run("sudo ip link set cni0 up", warn=True)
                if rc:
                    j.logger.critical(
                        f"VDC: {self.solution_uuid}, GRACE_PERIOD_ACTION: failed to bring up cni0 wid: {k8s.wid}, rc: {rc}, out: {out}, err: {err}"
                    )
                if k8s.role == KubernetesRole.MASTER:
                    rc, out, err = ssh_client.sshclient.run(
                        "sudo iptables -D INPUT -p tcp --destination-port 6443 -j DROP", warn=True
                    )
                    if rc:
                        j.logger.critical(
                            f"VDC: {self.solution_uuid}, GRACE_PERIOD_ACTION: failed to unblock kubernetes API wid: {k8s.wid}, rc: {rc}, out: {out}, err: {err}"
                        )

                j.clients.sshclient.delete(self.instance_name)
            except Exception as e:
                j.logger.critical(
                    f"VDC: {self.solution_uuid}, GRACE_PERIOD_ACTION: failed to connect to kubernetes controller due to error {str(e)}"
                )
                self.is_blocked = True
                continue

        self.save()
        j.logger.info(f"VDC: {self.solution_uuid}, REVERT_GRACE_PERIOD_ACTION: reverted successfully")

    def show_vdc_payment(self, bot, expiry=5, wallet_name=None):
        discount = FARM_DISCOUNT.get()
        amount = VDC_SIZE.PRICES["plans"][self.flavor] * (1 - discount)

        payment_id, _ = j.sals.billing.submit_payment(
            amount=amount,
            wallet_name=wallet_name or self.prepaid_wallet.instance_name,
            refund_extra=False,
            expiry=expiry,
            description=j.data.serializers.json.dumps(
                {"type": "VDC_INIT", "owner": self.owner_tname, "solution_uuid": self.solution_uuid}
            ),
        )

        if amount > 0:
            notes = []
            if discount:
                notes = ["For testing purposes, we applied a discount of {:.0f}%".format(discount * 100)]
            return j.sals.billing.wait_payment(payment_id, bot=bot, notes=notes), amount, payment_id
        else:
            return True, amount, payment_id

    def show_external_node_payment(self, bot, farm_name, size, no_nodes=1, expiry=5, wallet_name=None, public_ip=False):
        discount = FARM_DISCOUNT.get()
        duration = self.calculate_expiration_value() - j.data.time.utcnow().timestamp
        month = 60 * 60 * 24 * 30
        if duration > month:
            duration = month

        zos = j.sals.zos.get()
        farm_id = zos._explorer.farms.get(farm_name=farm_name).id
        k8s = K8s()
        if isinstance(size, str):
            size = VDC_SIZE.K8SNodeFlavor[size.upper()].value
        k8s.size = size
        amount = j.tools.zos.consumption.cost(k8s, duration, farm_id) + TRANSACTION_FEES

        if public_ip:
            pub_ip = PublicIP()
            amount += j.tools.zos.consumption.cost(pub_ip, duration, farm_id)
        amount *= no_nodes

        prepaid_balance = self._get_wallet_balance(self.prepaid_wallet)
        if prepaid_balance >= amount:
            if bot:
                result = bot.single_choice(
                    f"Do you want to use your existing balance to pay {round(amount,4)} TFT? (This will impact the overall expiration of your plan)",
                    ["Yes", "No"],
                    required=True,
                )
                if result == "Yes":
                    amount = 0
            else:
                amount = 0
        elif not bot:
            # Not enough funds in prepaid wallet and no bot passed to use to view QRcode
            return False, amount, None

        payment_id, _ = j.sals.billing.submit_payment(
            amount=amount,
            wallet_name=wallet_name or self.prepaid_wallet.instance_name,
            refund_extra=False,
            expiry=expiry,
            description=j.data.serializers.json.dumps(
                {"type": "VDC_K8S_EXTEND", "owner": self.owner_tname, "solution_uuid": self.solution_uuid}
            ),
        )
        if amount > 0:
            notes = []
            if discount:
                notes = ["For testing purposes, we applied a discount of {:.0f}%".format(discount * 100)]
            return j.sals.billing.wait_payment(payment_id, bot=bot, notes=notes), amount, payment_id
        else:
            return True, amount, payment_id

    def show_external_zdb_payment(
        self, bot, farm_name, size=ZDB_STARTING_SIZE, no_nodes=1, expiry=5, wallet_name=None, disk_type=DiskType.HDD
    ):
        discount = FARM_DISCOUNT.get()
        duration = self.calculate_expiration_value() - j.data.time.utcnow().timestamp
        month = 60 * 60 * 24 * 30
        if duration > month:
            duration = month

        zos = j.sals.zos.get()
        farm_id = zos._explorer.farms.get(farm_name=farm_name).id
        zdb = ZdbNamespace()
        zdb.size = size
        zdb.disk_type = disk_type
        amount = j.tools.zos.consumption.cost(zdb, duration, farm_id) + TRANSACTION_FEES
        amount *= no_nodes

        prepaid_balance = self._get_wallet_balance(self.prepaid_wallet)
        if prepaid_balance >= amount:
            if bot:
                result = bot.single_choice(
                    f"Do you want to use your existing balance to pay {round(amount,4)} TFT? (This will impact the overall expiration of your plan)",
                    ["Yes", "No"],
                    required=True,
                )
                if result == "Yes":
                    amount = 0
            else:
                amount = 0
        elif not bot:
            # Not enough funds in prepaid wallet and no bot passed to use to view QRcode
            return False, amount, None

        payment_id, _ = j.sals.billing.submit_payment(
            amount=amount,
            wallet_name=wallet_name or self.prepaid_wallet.instance_name,
            refund_extra=False,
            expiry=expiry,
            description=j.data.serializers.json.dumps(
                {"type": "VDC_ZDB_EXTEND", "owner": self.owner_tname, "solution_uuid": self.solution_uuid}
            ),
        )
        if amount > 0:
            notes = []
            if discount:
                notes = ["For testing purposes, we applied a discount of {:.0f}%".format(discount * 100)]
            return j.sals.billing.wait_payment(payment_id, bot=bot, notes=notes), amount, payment_id
        else:
            return True, amount, payment_id

    def show_external_vmachine_payment(self, bot, farm_name, size_number, expiry=5, wallet_name=None, public_ip=False):
        discount = FARM_DISCOUNT.get()
        duration = self.calculate_expiration_value() - j.data.time.utcnow().timestamp
        month = 60 * 60 * 24 * 30
        if duration > month:
            duration = month

        zos = j.sals.zos.get()
        farm_id = zos._explorer.farms.get(farm_name=farm_name).id
        vmachine = VirtualMachine()
        vmachine.size = size_number
        amount = j.tools.zos.consumption.cost(vmachine, duration, farm_id) + TRANSACTION_FEES

        if public_ip:
            pub_ip = PublicIP()
            amount += j.tools.zos.consumption.cost(pub_ip, duration, farm_id)

        prepaid_balance = self._get_wallet_balance(self.prepaid_wallet)
        if prepaid_balance >= amount:
            if bot:
                result = bot.single_choice(
                    f"Do you want to use your existing balance to pay {round(amount,4)} TFT? (This will impact the overall expiration of your plan)",
                    ["Yes", "No"],
                    required=True,
                )
                if result == "Yes":
                    amount = 0
            else:
                amount = 0
        elif not bot:
            # Not enough funds in prepaid wallet and no bot passed to use to view QRcode
            return False, amount, None

        payment_id, _ = j.sals.billing.submit_payment(
            amount=amount,
            wallet_name=wallet_name or self.prepaid_wallet.instance_name,
            refund_extra=False,
            expiry=expiry,
            description=j.data.serializers.json.dumps(
                {"type": "VM_CREATION", "owner": self.owner_tname, "solution_uuid": self.solution_uuid}
            ),
        )
        if amount > 0:
            notes = []
            if discount:
                notes = ["For testing purposes, we applied a discount of {:.0f}%".format(discount * 100)]
            return j.sals.billing.wait_payment(payment_id, bot=bot, notes=notes), amount, payment_id
        else:
            return True, amount, payment_id

    def transfer_to_provisioning_wallet(self, amount, wallet_name=None):
        if not amount:
            return True
        if wallet_name:
            wallet = j.clients.stellar.get(wallet_name)
        else:
            wallet = self.prepaid_wallet
        return self.pay_amount(self.provision_wallet.address, amount, wallet)

    def pay_initialization_fee(self, transaction_hashes, initial_wallet_name, wallet_name=None):
        initial_wallet = j.clients.stellar.get(initial_wallet_name)
        if wallet_name:
            wallet = j.clients.stellar.get(wallet_name)
        else:
            wallet = self.provision_wallet
        # get total amount
        amount = self._calculate_initialization_fee(transaction_hashes, initial_wallet)
        if not amount:
            return True
        return self.pay_amount(initial_wallet.address, amount, wallet)

    def _calculate_initialization_fee(self, transaction_hashes, initial_wallet):
        amount = 0
        for t_hash in transaction_hashes:
            effects = initial_wallet.get_transaction_effects(t_hash)
            for effect in effects:
                amount += (
                    abs(float(effect.amount)) + TRANSACTION_FEES
                )  # transaction fees to not drain the initialization wallet
        amount = round(amount, 6)
        return amount

    def _get_wallet_balance(self, wallet):
        balances = wallet.get_balance().balances
        for b in balances:
            if b.asset_code != "TFT":
                continue
            return float(b.balance)
        return 0

    def pay_amount(self, address, amount, wallet, memo_text=""):
        j.logger.info(f"transfering amount: {amount} from wallet: {wallet.instance_name} to address: {address}")
        deadline = j.data.time.now().timestamp + 5 * 60
        a = wallet._get_asset()
        has_funds = None
        while j.data.time.now().timestamp < deadline:
            if not has_funds:
                try:
                    balances = wallet.get_balance().balances
                    for b in balances:
                        if b.asset_code != "TFT":
                            continue
                        if amount <= float(b.balance) + TRANSACTION_FEES:
                            has_funds = True
                            break
                        else:
                            has_funds = False
                            raise j.exceptions.Validation(
                                f"Not enough funds in wallet {wallet.instance_name} to pay amount: {amount}. current balance: {b.balance}"
                            )
                except Exception as e:
                    if has_funds is False:
                        j.logger.error(f"Not enough funds in wallet {wallet.instance_name} to pay amount: {amount}")
                        raise e
                    j.logger.warning(f"Failed to get wallet {wallet.instance_name} balance due to error: {str(e)}")
                    continue

            try:
                return wallet.transfer(
                    address, amount=round(amount, 6), asset=f"{a.code}:{a.issuer}", memo_text=memo_text
                )
            except Exception as e:
                j.logger.warning(f"failed to submit payment to stellar due to error {str(e)}")
        j.logger.critical(
            f"Failed to submit payment to stellar in time to: {address} amount: {amount} for wallet: {wallet.instance_name}"
        )
        raise j.exceptions.Runtime(
            f"Failed to submit payment to stellar in time to: {address} amount: {amount} for wallet: {wallet.instance_name}"
        )

    def get_pools_expiration(self):
        active_pools = self.active_pools
        if not active_pools:
            return 0
        if len(active_pools) < 2:
            return active_pools[0].empty_at
        return min(*[p.empty_at for p in active_pools])

    def get_total_funds(self):
        total_tfts = 0
        for wallet in [self.prepaid_wallet, self.provision_wallet]:
            for balance in wallet.get_balance().balances:
                if balance.asset_code == "TFT":
                    total_tfts += float(balance.balance)
                    break
        return total_tfts

    def get_current_spec(self, load_info=True):
        """
        return
        dict:
            {
                "plan": self.flavor,
                "nodes": [additional_nodes_flavors],
                "services": {
                    "services.IP": count
                }
                "no_nodes": no_nodes # that were not deployed during initial vdc deployment
            }
        """
        if load_info:
            self.load_info()
        result = {"plan": self.flavor, "nodes": [], "services": {VDC_SIZE.Services.IP: 0}}
        no_nodes = VDC_SIZE.VDC_FLAVORS[self.flavor]["k8s"]["no_nodes"]
        node_flavor = VDC_SIZE.VDC_FLAVORS[self.flavor]["k8s"]["size"]
        for k8s in self.kubernetes:
            if k8s.role == KubernetesRole.MASTER:
                continue
            metadata = self._decrypt_metadata(k8s.wid)
            if k8s.size == node_flavor and no_nodes > 0 and not metadata.get("external"):
                no_nodes -= 1
                continue
            result["nodes"].append(k8s.size)
            if k8s.public_ip != "::/128":
                result["services"][VDC_SIZE.Services.IP] += 1
        result["no_nodes"] = no_nodes
        return result

    def _decrypt_metadata(self, wid, workload=None):
        identity = self._get_identity()
        workload = workload or j.sals.zos.get(identity.instance_name).workloads.get(wid)
        metadata = j.sals.reservation_chatflow.deployer.decrypt_metadata(workload.info.metadata, identity.instance_name)
        return j.data.serializers.json.loads(metadata)

    def calculate_spec_price(self, load_info=True):
        discount = FARM_DISCOUNT.get()
        current_spec = self.get_current_spec(load_info)
        total_price = (
            VDC_SIZE.PRICES["plans"][self.flavor]
            + current_spec["services"][VDC_SIZE.Services.IP] * VDC_SIZE.PRICES["services"][VDC_SIZE.Services.IP]
        )
        for size in current_spec["nodes"]:
            total_price += VDC_SIZE.PRICES["nodes"][size]
        return total_price * (1 - discount)

    def calculate_funded_period(self, load_info=True):
        """
        return how many days can the vdc be extended with prepaid + provisioning wallet
        """
        if load_info:
            self.load_info()
        prepaid_wallet_balance = self._get_wallet_balance(self.prepaid_wallet)
        provision_wallet_balance = self._get_wallet_balance(self.provision_wallet)

        days_prepaid_can_fund = (prepaid_wallet_balance / self.calculate_spec_price(load_info)) * 30
        days_provisioning_can_fund = provision_wallet_balance / (self.calculate_active_units_price() * 60 * 60 * 24)
        return days_prepaid_can_fund + days_provisioning_can_fund

    def calculate_active_units_price(self):
        cus = sus = ipv4us = 0
        for pool in self.active_pools:
            cus += pool.active_cu
            sus += pool.active_su
            ipv4us += pool.active_ipv4
        return self._get_identity().explorer.prices.calculate(cus, sus, ipv4us)  # TFTs per second

    def calculate_expiration_value(self, load_info=True):
        funded_period = self.calculate_funded_period(load_info)
        pools_expiration = self.get_pools_expiration()
        return pools_expiration + funded_period * 24 * 60 * 60

    def fund_difference(self, funding_wallet_name, destination_wallet_name=None):
        wallet = j.clients.stellar.find(funding_wallet_name)
        destination_wallet_name = destination_wallet_name or self.provision_wallet.instance_name
        dst_wallet = j.clients.stellar.find(destination_wallet_name)
        current_balance = self._get_wallet_balance(dst_wallet)
        j.logger.info(f"current balance in {destination_wallet_name} is {current_balance}")
        vdc_cost = float(j.tools.zos.consumption.calculate_vdc_price(self.flavor.value)) / 2
        j.logger.info(f"vdc_cost: {vdc_cost}")
        if vdc_cost > current_balance:
            diff = float(vdc_cost) - float(current_balance)
            j.logger.info(f"funding diff: {diff} for vdc {self.vdc_name} from wallet: {funding_wallet_name}")
            self.pay_amount(dst_wallet.address, diff, wallet)

    def get_ssh_client(self, name, ip_address, user, private_key_path=None):
        private_key_path = (
            private_key_path or f"{j.core.dirs.CFGDIR}/vdc/keys/{self.owner_tname}/{self.vdc_name}/id_rsa"
        )
        if not j.sals.fs.exists(private_key_path):
            private_key_path = "/root/.ssh/id_rsa"
        if not j.sals.fs.exists(private_key_path):
            raise j.exceptions.Input(f"couldn't find key at default locations")
        j.logger.info(f"getting ssh_client to: {user}@{ip_address} using key: {private_key_path}")
        client_name = SSH_KEY_PREFIX + name
        j.clients.sshkey.delete(client_name)
        ssh_key = j.clients.sshkey.get(client_name)
        ssh_key.private_key_path = private_key_path
        ssh_key.load_from_file_system()
        j.clients.sshclient.delete(client_name)
        ssh_client = j.clients.sshclient.get(client_name, user=user, host=ip_address, sshkey=client_name)
        return ssh_client

    def find_worker_farm(self, flavor, farm_name=None, public_ip=False):
        if farm_name:
            return farm_name, self._check_added_worker_capacity(flavor, farm_name, public_ip)
        farms = j.config.get("NETWORK_FARMS", []) if public_ip else j.config.get("COMPUTE_FARMS", [])
        for farm in farms:
            if self._check_added_worker_capacity(flavor, farm, public_ip):
                return farm, True
        else:
            self.load_info()
            pool_id = [n for n in self.kubernetes if n.role == KubernetesRole.MASTER][-1].pool_id
            farm_name = j.sals.marketplace.deployer.get_pool_farm_name(pool_id)
            return farm_name, self._check_added_worker_capacity(flavor, farm_name, public_ip)

    def have_capacity(self, query, vdc, farm_name=None, public_ip=False):
        if public_ip:
            zos = j.sals.zos.get()
            farm = zos._explorer.farms.get(farm_name=farm_name)
            available_ips = False
            for address in farm.ipaddresses:
                if not address.reservation_id:
                    available_ips = True
                    break
            if not available_ips:
                return False

        old_node_ids = []
        vdc.load_info()
        for k8s_node in vdc.kubernetes:
            old_node_ids.append(k8s_node.node_id)
        for vmachine in vdc.vmachines:
            old_node_ids.append(vmachine.node_id)

        cc = CapacityChecker(farm_name)
        cc.exclude_nodes(*old_node_ids)

        if not cc.add_query(**query):
            return False
        return True

    def find_vmachine_farm(self, query, farm_name=None, public_ip=False):
        if farm_name:
            return farm_name, self.have_capacity(query=query, vdc=self, farm_name=farm_name, public_ip=public_ip)
        farms = j.config.get("NETWORK_FARMS", []) if public_ip else j.config.get("COMPUTE_FARMS", [])
        for farm in farms:
            if self.have_capacity(query=query, vdc=self, farm_name=farm, public_ip=public_ip):
                return farm, True
        else:
            self.load_info()
            pool_id = [n for n in self.kubernetes if n.role == KubernetesRole.MASTER][-1].pool_id
            farm_name = j.sals.marketplace.deployer.get_pool_farm_name(pool_id)
            return farm_name, self.have_capacity(query=query, vdc=self, farm_name=farm_name, public_ip=public_ip)

    def _check_added_worker_capacity(self, flavor, farm_name, public_ip=False):
        if public_ip:
            zos = j.sals.zos.get()
            farm = zos._explorer.farms.get(farm_name=farm_name)
            available_ips = False
            for address in farm.ipaddresses:
                if not address.reservation_id:
                    available_ips = True
                    break
            if not available_ips:
                return False

        old_node_ids = []
        self.load_info()
        for k8s_node in self.kubernetes:
            old_node_ids.append(k8s_node.node_id)
        cc = CapacityChecker(farm_name)
        cc.exclude_nodes(*old_node_ids)
        if isinstance(flavor, str):
            flavor = VDC_SIZE.K8SNodeFlavor[flavor.upper()]
        if not cc.add_query(**VDC_SIZE.K8S_SIZES[flavor]):
            return False
        return True
Пример #24
0
class ContainerCapacity(Base):
    cpu = fields.Integer()
    memory = fields.Integer()
    disk_size = fields.Integer()
    disk_type = fields.Enum(DiskType)
Пример #25
0
class Stellar(Client):
    network = fields.Enum(Network)
    address = fields.String()

    def secret_updated(self, value):
        self.address = stellar_sdk.Keypair.from_secret(value).public_key

    secret = fields.String(on_update=secret_updated)

    def _get_horizon_server(self):
        server_url = _HORIZON_NETWORKS[self.network.value]
        server = Server(horizon_url=server_url)
        return server

    def _get_free_balances(self, address=None):
        address = address or self.address
        balances = AccountBalances(address)
        response = self._get_horizon_server().accounts().account_id(address).call()
        for response_balance in response["balances"]:
            balances.add_balance(Balance.from_horizon_response(response_balance))
        return balances

    def load_account(self):
        horizonServer = self._get_horizon_server()
        saccount = horizonServer.load_account(self.address)
        account = Account(saccount.account_id, saccount.sequence, self)
        return account

    def _get_url(self, endpoint):
        url = _THREEFOLDFOUNDATION_TFTSTELLAR_SERVICES[self.network.value]
        if not j.sals.nettools.wait_connection_test(url, 443, 5):
            raise j.exceptions.Timeout(f"Can not connect to server {url}, connection timeout")
        endpoint = _THREEFOLDFOUNDATION_TFTSTELLAR_ENDPOINT[endpoint]
        return f"https://{url}{endpoint}"

    def _fund_transaction(self, transaction):
        data = {"transaction": transaction}
        resp = j.tools.http.post(self._get_url("FUND"), json={"args": data})
        resp.raise_for_status()
        return resp.json()

    def _create_unlockhash_transaction(self, unlock_hash, transaction_xdr):
        data = {"unlockhash": unlock_hash, "transaction_xdr": transaction_xdr}
        resp = j.tools.http.post(self._get_url("CREATE_UNLOCK"), json={"args": data})
        resp.raise_for_status()
        return resp.json()

    def _get_unlockhash_transaction(self, unlockhash):
        data = {"unlockhash": unlockhash}
        resp = j.tools.http.post(self._get_url("GET_UNLOCK"), json={"args": data})
        resp.raise_for_status()
        return resp.json()

    def _create_activation_code(self):
        data = {"address": self.address}
        resp = j.tools.http.post(self._get_url("CREATE_ACTIVATION_CODE"), json={"args": data})
        resp.raise_for_status()
        return resp.json()

    def _activation_account(self, activation_code):
        data = {"activation_code": activation_code}
        resp = j.tools.http.post(self._get_url("ACTIVATE_ACCOUNT"), json={"args": data})
        resp.raise_for_status()
        return resp.json()

    def set_unlock_transaction(self, unlock_transaction):
        """
        Adds a xdr encoded unlocktransaction
        :param unlock_transaction: xdr encoded unlocktransactionaddress of the destination.
        :type destination_address: str
        """
        txe = stellar_sdk.TransactionEnvelope.from_xdr(unlock_transaction, _NETWORK_PASSPHRASES[self.network.value])
        tx_hash = txe.hash()
        unlock_hash = stellar_sdk.strkey.StrKey.encode_pre_auth_tx(tx_hash)

        self._create_unlockhash_transaction(unlock_hash=unlock_hash, transaction_xdr=txe.to_xdr())

    def get_balance(self, address=None):
        """Gets balance for a stellar address
        """
        if address is None:
            address = self.address
        all_balances = self._get_free_balances(address)
        for account in self._find_escrow_accounts(address):
            all_balances.add_escrow_account(account)
        return all_balances

    def _find_escrow_accounts(self, address=None):
        if address is None:
            address = self.address
        escrow_accounts = []
        accounts_endpoint = self._get_horizon_server().accounts()
        accounts_endpoint.signer(address)
        old_cursor = "old"
        new_cursor = ""
        while new_cursor != old_cursor:
            old_cursor = new_cursor
            accounts_endpoint.cursor(new_cursor)
            response = accounts_endpoint.call()
            next_link = response["_links"]["next"]["href"]
            next_link_query = parse.urlsplit(next_link).query
            new_cursor = parse.parse_qs(next_link_query)["cursor"][0]
            accounts = response["_embedded"]["records"]
            for account in accounts:
                account_id = account["account_id"]
                if account_id == address:
                    continue  # Do not take the receiver's account
                all_signers = account["signers"]
                preauth_signers = [signer["key"] for signer in all_signers if signer["type"] == "preauth_tx"]
                # TODO check the tresholds and signers
                # TODO if we can merge, the amount is unlocked ( if len(preauth_signers))==0
                balances = []
                for response_balance in account["balances"]:
                    balances.append(Balance.from_horizon_response(response_balance))

                escrow_account = EscrowAccount(
                    account_id,
                    preauth_signers,
                    balances,
                    _NETWORK_PASSPHRASES[self.network.value],
                    self._get_unlockhash_transaction,
                )
                escrow_accounts.append(escrow_account)
        return escrow_accounts

    def claim_locked_funds(self):
        balances = self.get_balance()
        for locked_account in balances.escrow_accounts:
            if locked_account.can_be_unlocked():
                self._unlock_account(locked_account)

    def _unlock_account(self, escrow_account):
        submitted_unlock_transactions = 0
        for unlockhash in escrow_account.unlockhashes:
            unlockhash_transation = self._get_unlockhash_transaction(unlockhash=unlockhash)
            if unlockhash_transation is None:
                return
            j.logger.info(unlockhash_transation["transaction_xdr"])
            self._get_horizon_server().submit_transaction(unlockhash_transation["transaction_xdr"])
            submitted_unlock_transactions += 1

        if submitted_unlock_transactions == len(escrow_account.unlockhashes):
            self._merge_account(escrow_account.address)

    def _merge_account(self, address):
        server = self._get_horizon_server()
        account = server.load_account(address)
        # Increment the sequence number in case the unlock transaction was not processed before the load_account call
        # account.increment_sequence_number()
        balances = self._get_free_balances(address)
        base_fee = server.fetch_base_fee()
        transaction_builder = stellar_sdk.TransactionBuilder(
            source_account=account, network_passphrase=_NETWORK_PASSPHRASES[self.network.value], base_fee=base_fee
        )
        for balance in balances.balances:
            if balance.is_native():
                continue
            # Step 1: Transfer custom assets
            transaction_builder.append_payment_op(
                destination=self.address,
                amount=balance.balance,
                asset_code=balance.asset_code,
                asset_issuer=balance.asset_issuer,
            )
            # Step 2: Delete trustlines
            transaction_builder.append_change_trust_op(
                asset_issuer=balance.asset_issuer, asset_code=balance.asset_code, limit="0"
            )
        # Step 3: Merge account
        transaction_builder.append_account_merge_op(self.address)

        transaction_builder.set_timeout(30)
        transaction = transaction_builder.build()
        signer_kp = stellar_sdk.Keypair.from_secret(self.secret)
        transaction.sign(signer_kp)
        server.submit_transaction(transaction)

    def activate_through_friendbot(self):
        """Activates and funds a testnet account using riendbot
        """
        if self.network.value != "TEST":
            raise Exception("Account activation through friendbot is only available on testnet")

        resp = j.tools.http.get("https://friendbot.stellar.org/", params={"addr": self.address})
        resp.raise_for_status()
        j.logger.info(f"account with address {self.address} activated and  funded through friendbot")

    def activate_through_threefold_service(self):
        """
        Activate your weallet through threefold services
        """
        activationdata = self._create_activation_code()
        self._activation_account(activationdata["activation_code"])

    def activate_account(self, destination_address, starting_balance="12.50"):
        """Activates another account

        Args:
            destination_address (str): address of the destination
            starting_balance (str, optional): the balance that the destination address will start with. Must be a positive integer expressed as a string. Defaults to "12.50".
        """
        server = self._get_horizon_server()
        source_keypair = stellar_sdk.Keypair.from_secret(self.secret)

        source_account = self.load_account()

        base_fee = server.fetch_base_fee()
        transaction = (
            stellar_sdk.TransactionBuilder(
                source_account=source_account,
                network_passphrase=_NETWORK_PASSPHRASES[self.network.value],
                base_fee=base_fee,
            )
            .append_create_account_op(destination=destination_address, starting_balance=starting_balance)
            .build()
        )
        transaction.sign(source_keypair)
        try:
            response = server.submit_transaction(transaction)
            j.logger.info("Transaction hash: {}".format(response["hash"]))
        except stellar_sdk.exceptions.BadRequestError as e:
            j.logger.debug(e)

    def add_trustline(self, asset_code, issuer, secret=None):
        """Create a trustline to an asset

        Args:
            asset_code (str): code of the asset. For example: 'BTC', 'TFT', ...
            issuer (str): address of the asset issuer
            secret (str, optional): Secret to use will use instance property if empty. Defaults to None.
        """
        self._change_trustline(asset_code, issuer, secret=secret)

    def add_known_trustline(self, asset_code):
        """Will add a trustline known by threefold for chosen asset_code

        Args:
            asset_code (str): code of the asset. For example: 'BTC', 'TFT', ...
        """
        issuer = _NETWORK_KNOWN_TRUSTS.get(self.network.value, {}).get(asset_code)
        if not issuer:
            raise j.exceptions.NotFound(f"We do not provide a known issuers for {asset_code} on network {self.network}")
        self._change_trustline(asset_code, issuer)

    def delete_trustline(self, asset_code, issuer, secret=None):
        """Deletes a trustline

        Args:
            asset_code (str): code of the asset. For example: 'BTC', 'XRP', ...
            issuer (str): address of the asset issuer
            secret (str, optional): Secret to use will use instance property if empty. Defaults to None.
        """
        self._change_trustline(asset_code, issuer, limit="0", secret=secret)

    def _change_trustline(self, asset_code, issuer, limit=None, secret=None):
        """Create a trustline between you and the issuer of an asset

        Args:
            asset_code (str): code which form the asset. For example: 'BTC', 'TFT', ...
            issuer (str): address of the asset issuer
            limit ([type], optional): The limit for the asset, defaults to max int64(922337203685.4775807). If the limit is set to “0” it deletes the trustline. Defaults to None.
            secret (str, optional): Secret to use will use instance property if empty. Defaults to None.
        """
        # if no secret is provided we assume we change trustlines for this account
        secret = secret or self.secret

        server = self._get_horizon_server()
        source_keypair = stellar_sdk.Keypair.from_secret(secret)
        source_public_key = source_keypair.public_key
        source_account = server.load_account(source_public_key)

        base_fee = server.fetch_base_fee()

        transaction = (
            stellar_sdk.TransactionBuilder(
                source_account=source_account,
                network_passphrase=_NETWORK_PASSPHRASES[self.network.value],
                base_fee=base_fee,
            )
            .append_change_trust_op(asset_issuer=issuer, asset_code=asset_code, limit=limit)
            .set_timeout(30)
            .build()
        )

        transaction.sign(source_keypair)

        try:
            response = server.submit_transaction(transaction)
            j.logger.info("Transaction hash: {}".format(response["hash"]))
        except stellar_sdk.exceptions.BadRequestError as e:
            j.logger.debug(e)
            raise e

    def transfer(
        self,
        destination_address,
        amount,
        asset="XLM",
        locked_until=None,
        memo_text=None,
        memo_hash=None,
        fund_transaction=True,
        from_address=None,
        timeout=30,
        sequence_number: int = None,
        sign: bool = True,
    ):
        """Transfer assets to another address

        Args:
            destination_address (str): address of the destination
            amount (str): can be a floating point number with 7 numbers after the decimal point expressed as a string
            asset (str, optional): asset to transfer. Defaults to "XLM". if you wish to specify an asset it should be in format 'assetcode:issuer'. Where issuer is the address of the
            issuer of the asset.
            locked_until (float, optional): epoch timestamp indicating until when the tokens  should be locked. Defaults to None.
            memo_text (Union[str, bytes], optional): memo text to add to the transaction, a string encoded using either ASCII or UTF-8, up to 28-bytes long. Defaults to None.
            memo_hash (Union[str, bytes], optional): memo hash to add to the transaction, A 32 byte hash. Defaults to None.
            fund_transaction (bool, optional): use the threefoldfoundation transaction funding service. Defautls to True.
            from_address (str, optional): Use a different address to send the tokens from, useful in multisig use cases. Defaults to None.
            timeout (int,optional: Seconds from now on until when the transaction to be submitted to the stellar network
            sequence_number (int,optional): specify a specific sequence number ( will still be increased by one) instead of loading it from the account
            sign (bool,optional) : Do not sign and submit the transaction

        Raises:
            Exception: If asset not in correct format
            stellar_sdk.exceptions.BadRequestError: not enough funds for opertaion
            stellar_sdk.exceptions.BadRequestError: bad transfer authentication

        Returns:
            [type]: [description]
        """
        issuer = None
        j.logger.info(f"Sending {amount} {asset} to {destination_address}")
        if asset != "XLM":
            assetStr = asset.split(":")
            if len(assetStr) != 2:
                raise Exception("Wrong asset format should be in format 'assetcode:issuer'")
            asset = assetStr[0]
            issuer = assetStr[1]

        if locked_until is not None:
            return self._transfer_locked_tokens(
                destination_address,
                amount,
                asset,
                issuer,
                locked_until,
                memo_text=memo_text,
                memo_hash=memo_hash,
                fund_transaction=fund_transaction,
                from_address=from_address,
            )

        horizon_server = self._get_horizon_server()

        base_fee = horizon_server.fetch_base_fee()
        if from_address:
            source_account = horizon_server.load_account(from_address)
        else:
            source_account = self.load_account()

        if sequence_number:
            source_account.sequence = sequence_number

        transaction_builder = stellar_sdk.TransactionBuilder(
            source_account=source_account,
            network_passphrase=_NETWORK_PASSPHRASES[self.network.value],
            base_fee=base_fee,
        )
        transaction_builder.append_payment_op(
            destination=destination_address,
            amount=str(amount),
            asset_code=asset,
            asset_issuer=issuer,
            source=source_account.account_id,
        )
        transaction_builder.set_timeout(timeout)
        if memo_text is not None:
            transaction_builder.add_text_memo(memo_text)
        if memo_hash is not None:
            transaction_builder.add_hash_memo(memo_hash)

        transaction = transaction_builder.build()
        transaction = transaction.to_xdr()

        if asset == "TFT" or asset == "FreeTFT":
            if fund_transaction:
                transaction = self._fund_transaction(transaction=transaction)
                transaction = transaction["transaction_xdr"]

        if not sign:
            return transaction

        transaction = stellar_sdk.TransactionEnvelope.from_xdr(transaction, _NETWORK_PASSPHRASES[self.network.value])

        my_keypair = stellar_sdk.Keypair.from_secret(self.secret)
        transaction.sign(my_keypair)
        response = horizon_server.submit_transaction(transaction)
        tx_hash = response["hash"]
        j.logger.info(f"Transaction hash: {tx_hash}")
        return tx_hash

    def list_payments(self, address: str = None, asset: str = None, cursor: str = None):
        """Get the transactions for an adddress
        :param address: address of the effects.In None, the address of this wallet is taken
        :param asset: stellar asset in the code:issuer form( except for XLM, which does not need an issuer)
        :param cursor:pass a cursor to continue after the last call or an empty str to start receivibg a cursor
         if a cursor is passed, a tuple of the payments and the cursor is returned
        """
        if address is None:
            address = self.address
        tx_endpoint = self._get_horizon_server().payments()
        tx_endpoint.for_account(address)
        tx_endpoint.limit(50)
        payments = []
        old_cursor = "old"
        new_cursor = ""
        if cursor is not None:
            new_cursor = cursor
        while old_cursor != new_cursor:
            old_cursor = new_cursor
            tx_endpoint.cursor(new_cursor)
            response = tx_endpoint.call()
            next_link = response["_links"]["next"]["href"]
            next_link_query = parse.urlsplit(next_link).query
            new_cursor = parse.parse_qs(next_link_query)["cursor"][0]
            response_payments = response["_embedded"]["records"]
            for response_payment in response_payments:
                ps = PaymentSummary.from_horizon_response(response_payment, address)
                if asset:
                    split_asset = asset.split(":")
                    assetcode = split_asset[0]
                    assetissuer = None
                    if len(split_asset) > 1:
                        assetissuer = split_asset[1]
                    if ps.balance and ps.balance.asset_code == assetcode:
                        if assetissuer and assetissuer == ps.balance.asset_issuer:
                            payments.append(ps)
                else:
                    payments.append(ps)
        if cursor is not None:
            return {"payments": payments, "cursor": new_cursor}

        return payments

    def list_transactions(self, address: str = None, cursor: str = None):
        """Get the transactions for an adddres
        :param address (str, optional): address of the effects.If None, the address of this wallet is taken. Defaults to None.
        :param cursor:pass a cursor to continue after the last call or an empty str to start receivibg a cursor
         if a cursor is passed, a tuple of the payments and the cursor is returned

        Returns:
            list: list of TransactionSummary objects
            dictionary: {"transactions":list of TransactionSummary objects, "cursor":cursor}
        """
        address = address or self.address
        tx_endpoint = self._get_horizon_server().transactions()
        tx_endpoint.for_account(address)
        tx_endpoint.include_failed(True)
        transactions = []
        old_cursor = "old"
        new_cursor = ""
        if cursor is not None:
            new_cursor = cursor
        while old_cursor != new_cursor:
            old_cursor = new_cursor
            tx_endpoint.cursor(new_cursor)
            response = tx_endpoint.call()
            next_link = response["_links"]["next"]["href"]
            next_link_query = parse.urlsplit(next_link).query
            new_cursor = parse.parse_qs(next_link_query)["cursor"][0]
            response_transactions = response["_embedded"]["records"]
            for response_transaction in response_transactions:
                if response_transaction["successful"]:
                    transactions.append(TransactionSummary.from_horizon_response(response_transaction))

        if cursor is not None:
            return {"transactions": transactions, "cursor": new_cursor}
        return transactions

    def get_transaction_effects(self, transaction_hash, address=None):
        """Get the effects on an adddressfor a specific transaction

        Args:
            transaction_hash (str): hash of the transaction
            address (str, optional): address of the effects.If None, the address of this wallet is taken. Defaults to None.

        Returns:
            list: list of Effect objects
        """
        address = address or self.address
        effects = []
        endpoint = self._get_horizon_server().effects()
        endpoint.for_transaction(transaction_hash)
        old_cursor = "old"
        new_cursor = ""
        while old_cursor != new_cursor:
            old_cursor = new_cursor
            endpoint.cursor(new_cursor)
            response = endpoint.call()
            next_link = response["_links"]["next"]["href"]
            next_link_query = parse.urlsplit(next_link).query
            new_cursor = parse.parse_qs(next_link_query)["cursor"][0]
            response_effects = response["_embedded"]["records"]
            for response_effect in response_effects:
                if "account" in response_effect and response_effect["account"] == address:
                    effects.append(Effect.from_horizon_response(response_effect))
        return effects

    def _transfer_locked_tokens(
        self,
        destination_address,
        amount,
        asset_code,
        asset_issuer,
        unlock_time,
        memo_text=None,
        memo_hash=None,
        fund_transaction=True,
        from_address=None,
    ):
        """Transfer locked assets to another address

        Args:
            destination_address (str): address of the destination
            amount (str): amount, can be a floating point number with 7 numbers after the decimal point expressed as a string
            asset_code (str): asset to transfer
            asset_issuer (str): if the asset_code is different from 'XlM', the issuer address
            unlock_time (float):  an epoch timestamp indicating when the funds should be unlocked
            memo_text (Union[str, bytes], optional): memo text to add to the transaction, a string encoded using either ASCII or UTF-8, up to 28-bytes long
            memo_hash (Union[str, bytes], optional): memo hash to add to the transaction, A 32 byte hash
            fund_transaction (bool, optional): use the threefoldfoundation transaction funding service.Defaults to True.
            from_address (str, optional): Use a different address to send the tokens from, useful in multisig use cases. Defaults to None.

        Returns:
            [type]: [description]
        """

        unlock_time = math.ceil(unlock_time)

        self._log_info("Creating escrow account")
        escrow_kp = stellar_sdk.Keypair.random()

        # minimum account balance as described at https://www.stellar.org/developers/guides/concepts/fees.html#minimum-account-balance
        horizon_server = self._get_horizon_server()
        base_fee = horizon_server.fetch_base_fee()
        base_reserve = 0.5
        minimum_account_balance = (2 + 1 + 3) * base_reserve  # 1 trustline and 3 signers
        required_XLM = minimum_account_balance + base_fee * 0.0000001 * 3

        self._log_info("Activating escrow account")
        self.activate_account(escrow_kp.public_key, str(math.ceil(required_XLM)))

        if asset_code != "XLM":
            self._log_info("Adding trustline to escrow account")
            self.add_trustline(asset_code, asset_issuer, escrow_kp.secret)

        preauth_tx = self._create_unlock_transaction(escrow_kp, unlock_time)
        preauth_tx_hash = preauth_tx.hash()

        # save the preauth transaction in our unlock service
        unlock_hash = stellar_sdk.strkey.StrKey.encode_pre_auth_tx(preauth_tx_hash)
        self._create_unlockhash_transaction(unlock_hash=unlock_hash, transaction_xdr=preauth_tx.to_xdr())

        self._set_escrow_account_signers(escrow_kp.public_key, destination_address, preauth_tx_hash, escrow_kp)
        self._log_info("Unlock Transaction:")
        self._log_info(preauth_tx.to_xdr())

        self.transfer(
            escrow_kp.public_key,
            amount,
            asset_code + ":" + asset_issuer,
            memo_text=memo_text,
            memo_hash=memo_hash,
            fund_transaction=fund_transaction,
            from_address=from_address,
        )
        return preauth_tx.to_xdr()

    def _create_unlock_transaction(self, escrow_kp, unlock_time):
        server = self._get_horizon_server()
        escrow_account = server.load_account(escrow_kp.public_key)
        escrow_account.increment_sequence_number()
        tx = (
            stellar_sdk.TransactionBuilder(escrow_account)
            .append_set_options_op(master_weight=0, low_threshold=1, med_threshold=1, high_threshold=1)
            .add_time_bounds(unlock_time, 0)
            .build()
        )
        tx.sign(escrow_kp)
        return tx

    def _set_account_signers(self, address, public_key_signer, preauth_tx_hash, signer_kp):
        server = self._get_horizon_server()
        if address == self.address:
            account = self.load_account()
        else:
            account = server.load_account(address)
        tx = (
            stellar_sdk.TransactionBuilder(account)
            .append_pre_auth_tx_signer(preauth_tx_hash, 1)
            .append_ed25519_public_key_signer(public_key_signer, 1)
            .append_set_options_op(master_weight=1, low_threshold=2, med_threshold=2, high_threshold=2)
            .build()
        )

        tx.sign(signer_kp)
        response = server.submit_transaction(tx)
        j.logger.info(response)
        j.logger.info(f"Set the signers of {address} to {public_key_signer} and {preauth_tx_hash}")

    def get_signing_requirements(self, address: str = None):
        address = address or self.address
        response = self._get_horizon_server().accounts().account_id(address).call()
        signing_requirements = {}
        signing_requirements["thresholds"] = response["thresholds"]
        signing_requirements["signers"] = response["signers"]
        return signing_requirements

    def modify_signing_requirements(
        self, public_keys_signers, signature_count, low_treshold=0, high_treshold=2, master_weight=2
    ):
        """modify_signing_requirements sets to amount of signatures required for the creation of multisig account. It also adds
           the public keys of the signer to this account

        Args:
            public_keys_signers (list): list of public keys of signers
            signature_count (int): amount of signatures requires to transfer funds
            low_treshold (int, optional): amount of signatures required for low security operations (transaction processing, allow trust, bump sequence). Defaults to 1.
            high_treshold (int, optional): amount of signatures required for high security operations (set options, account merge). Defaults to 2.
            master_weight (int, optional): A number from 0-255 (inclusive) representing the weight of the master key. If the weight of the master key is updated to 0, it is effectively disabled. Defaults to 2.
        """
        server = self._get_horizon_server()
        account = self.load_account()
        source_keypair = stellar_sdk.Keypair.from_secret(self.secret)

        transaction_builder = stellar_sdk.TransactionBuilder(account)
        # set the signing options
        transaction_builder.append_set_options_op(
            low_threshold=low_treshold,
            med_threshold=signature_count,
            high_threshold=high_treshold,
            master_weight=master_weight,
        )

        # For every public key given, add it as a signer to this account
        for public_key_signer in public_keys_signers:
            transaction_builder.append_ed25519_public_key_signer(public_key_signer, 1)

        transaction_builder.set_timeout(30)
        tx = transaction_builder.build()
        tx.sign(source_keypair)

        try:
            response = server.submit_transaction(tx)
            j.logger.info(response)
            j.logger.info(f"Set the signers of {self.address} to require {signature_count} signers")
        except stellar_sdk.exceptions.BadRequestError:
            j.logger.info("Transaction need additional signatures in order to send")
            return tx.to_xdr()

    def sign(self, tx_xdr: str, submit: bool = True):
        """ sign signs a transaction xdr and optionally submits it to the network

        Args:
            tx_xdr (str): transaction to sign in xdr format
            submit (bool,optional): submit the transaction tro the Stellar network
        """

        source_keypair = stellar_sdk.Keypair.from_secret(self.secret)
        tx = stellar_sdk.TransactionEnvelope.from_xdr(tx_xdr, _NETWORK_PASSPHRASES[self.network.value])
        tx.sign(source_keypair)
        if submit:
            horizon_server = self._get_horizon_server()
            horizon_server.submit_transaction(tx)
        else:
            return tx.to_xdr()

    def sign_multisig_transaction(self, tx_xdr):
        """sign_multisig_transaction signs a transaction xdr and tries to submit it to the network

           Deprecated, use sign instead

        Args:
            tx_xdr (str): transaction to sign in xdr format
        """

        try:
            self.sign(tx_xdr)
            j.logger.info("Multisig tx signed and sent")
        except UnAuthorized as e:
            j.logger.info("Transaction needs additional signatures in order to send")
            return e.transaction_xdr

    def remove_signer(self, public_key_signer):
        """remove_signer removes a public key as a signer from the source account

        Args:
            public_key_signer (str): public key of an account
        """
        server = self._get_horizon_server()
        account = self.load_account()
        tx = stellar_sdk.TransactionBuilder(account).append_ed25519_public_key_signer(public_key_signer, 0).build()

        source_keypair = stellar_sdk.Keypair.from_secret(self.secret)

        tx.sign(source_keypair)
        try:
            response = server.submit_transaction(tx)
            j.logger.info(response)
            j.logger.info("Multisig tx signed and sent")
        except stellar_sdk.exceptions.BadRequestError:
            j.logger.info("Transaction need additional signatures in order to send")
            return tx.to_xdr()

    def get_sender_wallet_address(self, transaction_hash):
        """Get the sender's wallet address from a transaction hash

        Args:
            transaction_hash (String): Transaction hash

        Returns:
            String : Wallet Hash

        """
        server = self._get_horizon_server()
        endpoint = server.operations().for_transaction(transaction_hash)
        response = endpoint.call()
        # not possible for a transaction to have more than a source, so will take first one
        wallet_address = response["_embedded"]["records"][0]["source_account"]
        return wallet_address

    def check_is_payment_transaction(self, transaction_hash):
        """Some transactions doesn't have an amount like activating the wallet
        This helper method to help in iterating in transactions

        Args:
            transaction_hash (String): Transaction hash

        Returns:
            Bool: True if transaction has amount - False if not
        """

        server = self._get_horizon_server()
        endpoint = server.operations().for_transaction(transaction_hash)
        response = endpoint.call()
        results = response["_embedded"]["records"][0]
        return results["type"] == "payment"

    def get_asset(self, code="TFT", issuer=None) -> stellar_sdk.Asset:
        """Gets an stellar_sdk.Asset object by code.
        if the code is TFT or TFTA we quickly return the Asset object based on the code.
        if the code is native (XLM) we return the Asset object with None issuer.
        if the code isn't unknown, exception is raised to manually construct the Asset object.

        Args:
            code (str, optional): code for the asset. Defaults to "TFT".
            issuer (str, optional): issuer for the asset. Defaults to None.

        Raises:
            ValueError: empty code, In case of issuer is None and not XLM or the code isn't for TFT or TFTA.
            stellar_sdk.exceptions.AssetIssuerInvalidError: Invalid issuer
        Returns:
            stellar_sdk.Asset: Asset object.
        """
        network = self.network.value
        KNOWN_ASSETS = list(_NETWORK_KNOWN_TRUSTS[network].keys()) + ["XLM"]

        if issuer and code:
            return Asset(code, issuer)

        if not code:
            raise ValueError("need to provide code")

        if not issuer and code not in KNOWN_ASSETS:
            raise ValueError(
                f"Make sure to supply the issuer for {code}, issuer is allowed to be none only in case of {KNOWN_ASSETS}"
            )

        if not issuer and code in KNOWN_ASSETS:
            asset_issuer = _NETWORK_KNOWN_TRUSTS[network].get(code, None)
            return Asset(code, asset_issuer)

    def cancel_sell_order(
        self,
        offer_id,
        selling_asset: stellar_sdk.Asset,
        buying_asset: stellar_sdk.Asset,
        price: Union[str, decimal.Decimal],
    ):
        """Deletes a selling order for amount `amount` of `selling_asset` for `buying_asset` with the price of `price`

        Args:
            selling_asset (stellar_sdk.Asset): Selling Asset object - check wallet object.get_asset_by_code function
            buying_asset (stellar_sdk.Asset): Buying Asset object - Asset object - check wallet object.get_asset_by_code function
            offer_id (int): pass the current offer id and set the amount to 0 to cancel this offer
            price (str): order price
        """
        return self._manage_sell_order(
            selling_asset=selling_asset, buying_asset=buying_asset, amount="0", price=price, offer_id=offer_id
        )

    def _manage_sell_order(
        self,
        selling_asset: stellar_sdk.Asset,
        buying_asset: stellar_sdk.Asset,
        amount: Union[str, decimal.Decimal],
        price: Union[str, decimal.Decimal],
        timeout=30,
        offer_id=0,
    ):
        """Places/Deletes a selling order for amount `amount` of `selling_asset` for `buying_asset` with the price of `price`

        Args:
            selling_asset (stellar_sdk.Asset): Selling Asset object - check wallet object.get_asset_by_code function
            buying_asset (stellar_sdk.Asset): Buying Asset object - Asset object - check wallet object.get_asset_by_code function
            amount (Union[str, decimal.Decimal]): Amount to sell.
            price (Union[str, decimal.Decimal]): Price for selling.
            timeout (int, optional): Timeout for submitting the transaction. Defaults to 30.
            offer_id: pass the current offer id and set the amount to 0 to cancel this offer or another amount to update the offer

        Raises:
            ValueError: In case of invalid issuer.
            RuntimeError: Error happened during submission of the transaction.

        Returns:
            (dict): response as the result of sumbit the transaction
        """
        server = self._get_horizon_server()
        tb = TransactionBuilder(self.load_account(), network_passphrase=_NETWORK_PASSPHRASES[self.network.value])
        try:
            tx = (
                tb.append_manage_sell_offer_op(
                    selling_code=selling_asset.code,
                    selling_issuer=selling_asset.issuer,
                    buying_code=buying_asset.code,
                    buying_issuer=buying_asset.issuer,
                    amount=amount,
                    price=price,
                    offer_id=offer_id,
                )
                .set_timeout(timeout)
                .build()
            )
        except stellar_sdk.exceptions.AssetIssuerInvalidError as e:
            raise ValueError("invalid issuer") from e
        except Exception as e:
            raise RuntimeError(
                f"error happened for placing selling order for selling: {selling_asset}, buying: {buying_asset}, amount: {amount} price: {price}"
            ) from e
        else:
            tx.sign(self.secret)
            try:
                resp = server.submit_transaction(tx)
            except Exception as e:
                raise RuntimeError(
                    f"couldn't submit sell offer, probably wallet is unfunded. Please check the error stacktrace for more information."
                ) from e
            return resp

    place_sell_order = _manage_sell_order

    def get_created_offers(self, wallet_address: str = None):
        """Returns a list of the currently created offers

        Args:
            wallet_address (Str, optional): wallet address you want to get offers to. Defaults to self.address.

        Returns:
            list
        """
        wallet_address = wallet_address or self.address
        server = self._get_horizon_server()
        endpoint = server.offers()
        endpoint.account(wallet_address)
        response = endpoint.call()
        offers = response["_embedded"]["records"]
        return offers

    def set_data_entry(self, name: str, value: str, address: str = None):
        """Sets, modifies or deletes a data entry (name/value pair) for an account

        To delete a data entry, set the value to an empty string.

        """

        address = address or self.address
        signing_key = stellar_sdk.Keypair.from_secret(self.secret)
        horizon_server = self._get_horizon_server()
        if address == self.address:
            account = self.load_account()
        else:
            account = horizon_server.load_account(address)
        base_fee = horizon_server.fetch_base_fee()
        transaction = (
            stellar_sdk.TransactionBuilder(
                source_account=account, network_passphrase=_NETWORK_PASSPHRASES[self.network.value], base_fee=base_fee,
            )
            .append_manage_data_op(name, value)
            .set_timeout(30)
            .build()
        )

        transaction.sign(signing_key)

        try:
            response = horizon_server.submit_transaction(transaction)
            j.logger.info("Transaction hash: {}".format(response["hash"]))
        except stellar_sdk.exceptions.BadRequestError as e:
            j.logger.debug(e)
            raise e

    def get_data_entries(self, address: str = None):
        address = address or self.address
        horizon_server = self._get_horizon_server()
        response = horizon_server.accounts().account_id(address).call()
        data = {}
        for data_name, data_value in response["data"].items():
            data[data_name] = base64.b64decode(data_value).decode("utf-8")
        return data
Пример #26
0
class User(Base):
    emails = fields.List(fields.String())
    permissions = fields.List(fields.Object(Permission))
    custom_config = fields.Typed(dict)
    type = fields.Enum(UserType)
    password = fields.Secret()
Пример #27
0
class ZDBClient(Client):
    addr = fields.String(default="localhost")
    port = fields.Integer(default=9900)
    secret_ = fields.String(default="1234567")
    nsname = fields.String(default="test")
    admin = fields.Boolean(default=False)
    mode = fields.Enum(Mode)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # if not self.secret_:
        #     self.secret_ = j.core.myenv.adminsecret

        assert len(self.secret_) > 5

        if self.admin:
            self.nsname = "default"
        self.type = "ZDB"
        self._redis = None
        self.nsname = self.nsname.lower().strip()

        # if j.data.bcdb._master:
        #     self._model.trigger_add(self._update_trigger)

    def _update_trigger(self, obj, action, **kwargs):
        if action in ["save", "change"]:
            self._redis = None

    @property
    def redis(self):
        if not self._redis:
            pool = redis.ConnectionPool(
                host=self.addr,
                port=self.port,
                password=self.secret_,
                connection_class=ZDBConnection,
                namespace=self.nsname,
                namespace_password=self.secret_,
                admin=self.admin,
            )
            self._redis = _patch_redis_client(redis.Redis(connection_pool=pool))
        return self._redis

    def _key_encode(self, key):
        if self.mode.value == Mode.SEQ.value:
            if key is None:
                key = ""
            else:
                key = struct.pack("<I", key)
        return key

    def _key_decode(self, key):
        if self.mode.value == Mode.SEQ.value:
            key = struct.unpack("<I", key)[0]
        return key

    def set(self, data, key=None):
        key = key or ""
        key = self._key_encode(key)
        res = self.redis.execute_command("SET", key, data)
        if not res:
            return res
        return self._key_decode(res)

    def get(self, key):
        key = self._key_encode(key)
        return self.redis.execute_command("GET", key)

    def exists(self, key):
        key = self._key_encode(key)
        return self.redis.execute_command("EXISTS", key) == 1

    def delete(self, key):
        if not key:
            raise j.exceptions.Value("key must be provided")
        key = self._key_encode(key)
        self.redis.execute_command("DEL", key)

    def flush(self):
        """
        will remove all data from the database DANGEROUS !!!!
        This is only allowed on private and password protected namespace
        You need to select the namespace before running the command.
        :return:
        """
        if not self.nsname in ["default", "system"]:
            self.redis.execute_command("FLUSH")

    def stop(self):
        pass

    @property
    def nsinfo(self):
        cmd = self.redis.execute_command("NSINFO", self.nsname)
        return _parse_nsinfo(cmd.decode())

    def list(self, key_start=None, reverse=False):
        """
        list all the keys in the namespace

        :param key_start: if specified start to walk from that key instead of the first one, defaults to None
        :param key_start: str, optional
        :param reverse: decide how to walk the namespace
                        if False, walk from older to newer keys
                        if True walk from newer to older keys
                        defaults to False
        :param reverse: bool, optional
        :return: list of keys
        :rtype: [str]
        """
        result = []
        for key, data in self.iterate(key_start=key_start, reverse=reverse, keyonly=True):
            result.append(key)
        return result

    def iterate(self, key_start=None, reverse=False, keyonly=False):
        """
        walk over all the namespace and yield (key,data) for each entries in a namespace

        :param key_start: if specified start to walk from that key instead of the first one, defaults to None
        :param key_start: str, optional
        :param reverse: decide how to walk the namespace
                if False, walk from older to newer keys
                if True walk from newer to older keys
                defaults to False
        :param reverse: bool, optional
        :param keyonly: [description], defaults to False
        :param keyonly: bool, optional
        :raises e: [description]
        """

        next = None
        data = None

        if key_start is not None:
            next = self.redis.execute_command("KEYCUR", self._key_encode(key_start))
            if not keyonly:
                data = self.get(key_start)
            yield (key_start, data)

        CMD = "SCANX" if not reverse else "RSCAN"

        while True:
            try:
                if not next:
                    response = self.redis.execute_command(CMD)
                else:
                    response = self.redis.execute_command(CMD, next)
                # format of the response
                # see https://github.com/threefoldtech/0-db/tree/development#scan
            except redis.ResponseError as e:
                if e.args[0] == "No more data":
                    return
                raise e

            (next, results) = response
            for item in results:
                keyb, size, epoch = item
                key_new = self._key_decode(keyb)
                data = None
                if not keyonly:
                    data = self.redis.execute_command("GET", keyb)
                yield (key_new, data)

    @property
    def count(self):
        """
        :return: return the number of entries in the namespace
        :rtype: int
        """
        return self.nsinfo["entries"]

    def ping(self):
        """
        go to default namespace & ping
        :return:
        """
        return self.redis.ping()

    @property
    def next_id(self):
        """
        :return: return the next id
        :rtype: int
        """
        id_bytes = struct.pack("<I", int(self.nsinfo["next_internal_id"], 16))
        return int.from_bytes(id_bytes, byteorder="big", signed=True)
Пример #28
0
class OpenRestyServer(Base):
    status = fields.Enum(Status)
    websites = fields.Factory(Website)

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._cmd = None
        self._path_web = None
        self._path_cfg_dir = None
        self._logs_dir = None

        self.executor = "tmux"  # only tmux for now

    @property
    def path_web(self):
        if not self._path_web:
            self._path_web = j.sals.fs.join_paths(j.core.dirs.VARDIR, "web",
                                                  self.instance_name)
            j.sals.fs.mkdirs(j.sals.fs.join_paths(self._path_web, "static"))
        return self._path_web

    @property
    def path_cfg_dir(self):
        if not self._path_cfg_dir:
            self._path_cfg_dir = j.sals.fs.join_paths(j.core.dirs.CFGDIR,
                                                      "nginx",
                                                      self.instance_name)
            j.sals.fs.mkdirs(self._path_cfg_dir)
        return self._path_cfg_dir

    @property
    def path_cfg(self):
        return j.sals.fs.join_paths(self.path_cfg_dir, "nginx.conf")

    @property
    def logs_dir(self):
        if not self._logs_dir:
            self._logs_dir = j.sals.fs.join_paths(j.core.dirs.LOGDIR,
                                                  "openresty",
                                                  self.instance_name)
            j.sals.fs.mkdirs(self._logs_dir)
        return self._logs_dir

    def configure(self):
        # clean old websites config
        self.cleanup()
        """configures main nginx conf
        """
        # self.install() This is commented for now until the repo and necessary deps are handled
        configtext = j.tools.jinja2.render_template(
            template_path=j.sals.fs.join_paths(DIR_PATH, "templates",
                                               "nginx.conf"),
            logs_dir=self.logs_dir,
        )
        j.sals.fs.write_file(self.path_cfg, configtext)

    def get_from_port(self, port, domain=None, ssl=None):
        """will try to get a website listening on port, if it doesn't exist it will create one

        Args:
            port (int): port to search for
            domain (str, optional): domain. Defaults to None.
            ssl (bool, optional): Will set ssl if True. Defaults to None.

        Returns:
            Website: A new or an old website instance with the needed port
        """
        website_name = f"{self.instance_name}_website_{port}"

        website = self.websites.find(website_name)
        if website:
            return website

        website = self.websites.get(website_name)
        ssl = ssl or port == 443  # Use ssl if port is 443 if ssl in not specified

        website.domain = domain
        website.port = port
        website.ssl = ssl

        return website

    def install(self, reset=False):
        """Install required deps for openresty

        Args:
            reset (bool, optional): If true will redo the installation. Defaults to False.
        """
        if reset or self.status == "init":
            # get weblib
            weblibs_path = j.tools.git.ensure_repo(
                "https://github.com/threefoldtech/js-weblibs"  # Place holder repo might be changed
            )

            # copy the templates to the right location
            j.sals.fs.copy_tree(f"{DIR_PATH}/web_resources/",
                                self.path_cfg_dir)

            j.sals.fs.symlink(
                f"{weblibs_path}/static",
                f"{self.path_web}/static/weblibs",
                overwrite=True,
            )
            self.status = Status.INSTALLED

            self.save()

    @property
    def startup_cmd(self):
        pass

    def start(self, reset=False):
        pass

    def stop(self):
        pass

    def is_running(self):
        pass

    def reload(self):
        self.configure()
        j.sals.process.execute("lapis build", cwd=self.path_cfg_dir)

    def cleanup(self):
        j.sals.fs.rmtree(f"{self.path_cfg_dir}/servers")