def add_master( self, node_id: str, network_name: str, cluster_secret: str, ip_address: str, size: int, ssh_keys: List[str], pool_id: int, public_ip_wid: int = 0, disable_default_ingress=True, datastore_endpoint="", ) -> K8s: """create a kubernetes marster workload object Args: node_id(str): node ID on which to deploy the k8s master network_name(str): name of the network to use cluster_secret(str): secret of the cluster. all the member of a same cluster must share the same secret ip_address(str): ip address of the k8s master size(int): size of the VM. ssh_keys(List[str]): list of public SSH key to authorize in the VM pool_id(int): capacity pool ID Returns: K8s: K8s Raises: Input: if size is not supported """ if size not in range(1, 19): raise Input(f"VM size {size} is not supported") master = K8s() master.info.node_id = node_id master.info.workload_type = WorkloadType.Kubernetes master.info.pool_id = pool_id node = self._nodes.get(node_id) master.cluster_secret = encrypt_for_node(self._identity, node.public_key_hex, cluster_secret).decode() master.network_id = network_name master.ipaddress = ip_address master.size = size if not isinstance(ssh_keys, list): ssh_keys = [ssh_keys] master.ssh_keys = ssh_keys master.public_ip = public_ip_wid master.disable_default_ingress = disable_default_ingress master.datastore_endpoint = datastore_endpoint return master
def _preprare_extension_pool(self, farm_name, k8s_flavor, no_nodes, duration, public_ip=False, no_extend=False): """ returns pool id after extension with enough cloud units duration in seconds """ self.vdc_deployer.info( f"Preperaring pool for kubernetes cluster extension on farm {farm_name}, flavor: {k8s_flavor}, no_nodes: {no_nodes}, duration: {duration}, public_ip: {public_ip},vdc uuid: {self.vdc_uuid}" ) k8s = K8s() k8s.size = k8s_flavor.value cloud_units = k8s.resource_units().cloud_units() cus = int(cloud_units.cu * duration * no_nodes) sus = int(cloud_units.su * duration * no_nodes) ipv4us = 0 if public_ip: ipv4us = int(duration) farm = self.explorer.farms.get(farm_name=farm_name) pool_id = None self.vdc_deployer.info(f"kubernetes cluster extension: searching for existing pool on farm {farm_name}") for pool in self.zos.pools.list(): farm_id = deployer.get_pool_farm_id(pool.pool_id, pool, self.identity.instance_name) if farm_id == farm.id: pool_id = pool.pool_id break if not pool_id: pool_info = self.vdc_deployer._retry_call( self.zos.pools.create, args=[math.ceil(cus), math.ceil(sus), ipv4us, farm_name, ["TFT"], j.core.identity.me], ) pool_id = pool_info.reservation_id self.vdc_deployer.info(f"Kubernetes cluster extension: creating a new pool {pool_id}") self.vdc_deployer.info(f"New pool {pool_info.reservation_id} for kubernetes cluster extension.") elif no_extend: return pool_id else: self.vdc_deployer.info(f"Kubernetes cluster extension: found pool {pool_id}") node_ids = [node.node_id for node in self.zos.nodes_finder.nodes_search(farm_name=farm_name)] pool_info = self.vdc_deployer._retry_call( self.zos.pools.extend, args=[pool_id, cus, sus, ipv4us], kwargs={"node_ids": node_ids} ) self.vdc_deployer.info( f"Using pool {pool_id} extension reservation: {pool_info.reservation_id} for kubernetes cluster extension." ) self.vdc_deployer.pay(pool_info) success = self.vdc_deployer.wait_pool_payment(pool_info.reservation_id) if not success: raise j.exceptions.Runtime(f"Pool {pool_info.reservation_id} resource reservation timedout") return pool_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 add_master( self, node_id: str, network_name: str, cluster_secret: str, ip_address: str, size: int, ssh_keys: List[str], pool_id: int, ) -> K8s: """create a kubernetes marster workload object Args: node_id(str): node ID on which to deploy the k8s master network_name(str): name of the network to use cluster_secret(str): secret of the cluster. all the member of a same cluster must share the same secret ip_address(str): ip address of the k8s master size(int): size of the VM. ssh_keys(List[str]): list of public SSH key to authorize in the VM pool_id(int): capacity pool ID Returns: K8s: K8s Raises: Input: if size is not supported """ if size not in [1, 2]: raise Input("size can only be 1 or 2") master = K8s() master.info.node_id = node_id master.info.workload_type = WorkloadType.Kubernetes master.info.pool_id = pool_id node = self._nodes.get(node_id) master.cluster_secret = encrypt_for_node(node.public_key_hex, cluster_secret).decode() master.network_id = network_name master.ipaddress = ip_address master.size = size if not isinstance(ssh_keys, list): ssh_keys = [ssh_keys] master.ssh_keys = ssh_keys return master
def test06_add_delete_k8s_node(self): """Test case for adding and deleting node. **Test Scenario** - Deploy VDC. - Calculate the price of added zdb and fund the provisioning wallet. - Add kubernetes node. - Check that the node has been added. - Delete this node. - Check that this node has been deleted. """ self.vdc.load_info() k8s_before_add = len(self.vdc.kubernetes) self.info( "Calculate the price of added zdb and fund the provisioning wallet." ) kubernetes = K8s() kubernetes.size = VDC_SIZE.K8SNodeFlavor.MEDIUM.value # It will be deployed for an hour. zos = j.sals.zos.get() farm_name = self.get_farm_name() farm_id = zos._explorer.farms.get(farm_name=farm_name).id price = j.tools.zos.consumption.cost( kubernetes, 60 * 60, farm_id) + TRANSACTION_FEES # transactions fees. self.vdc.transfer_to_provisioning_wallet(round(price, 6), "test_wallet") self.info("Add kubernetes node") wid = self.deployer.add_k8s_nodes("medium") self.info("Check that node has been added") self.vdc.load_info() self.assertEqual(len(self.vdc.kubernetes), k8s_before_add + 1) self.info("Delete this node") self.deployer.delete_k8s_node(wid[0]) self.info("Check that this node has been deleted") self.vdc.load_info() self.assertEqual(len(self.vdc.kubernetes), k8s_before_add)
def test_k8s_ru(): z = K8s() tt = [{ "size": 1, "cru": 1, "mru": 2, "sru": 50, "hru": 0 }, { "size": 2, "cru": 2, "mru": 4, "sru": 100, "hru": 0 }] for tc in tt: z.size = tc["size"] ru = z.resource_units() assert ru.cru == tc["cru"] assert ru.mru == tc["mru"] assert ru.sru == tc["sru"] assert ru.hru == tc["hru"]
def calculate_vdc_price(flavor, farm_name=None): """calculate the workloads price for vdcs in TFT Args: flavor (str): vdc flavor in [silver, gold, platinum, diamond] Returns: str: vdc price """ all_cus = 0 all_sus = 0 all_ipv4us = 0 # get the flavor enum for item in VDC_SIZE.VDC_FLAVORS.keys(): if flavor == item.value: flavor = item # calculate cloud units enough for a month def get_cloud_units(workload): ru = workload.resource_units() cloud_units = ru.cloud_units() return ( cloud_units.cu * 60 * 60 * 24 * 30, cloud_units.su * 60 * 60 * 24 * 30, cloud_units.ipv4u * 60 * 60 * 24 * 30, ) # get zdbs usage zdb = ZdbNamespace() zdb.size = VDC_SIZE.S3_ZDB_SIZES[VDC_SIZE.VDC_FLAVORS[flavor]["s3"] ["size"]]["sru"] zdb.disk_type = DiskType.HDD _, sus, _ = get_cloud_units(zdb) all_cus += sus # get kubernetes controller usage master_size = VDC_SIZE.VDC_FLAVORS[flavor]["k8s"]["controller_size"] k8s = K8s() k8s.size = master_size.value cus, sus, ipv4us = get_cloud_units(k8s) all_cus += cus all_sus += sus all_ipv4us += ipv4us # get kubernetes workers usage no_nodes = VDC_SIZE.VDC_FLAVORS[flavor]["k8s"]["no_nodes"] worker_size = VDC_SIZE.VDC_FLAVORS[flavor]["k8s"]["size"] k8s = K8s() k8s.size = worker_size.value cus, sus, ipv4us = get_cloud_units(k8s) all_cus += cus * (no_nodes + 1) all_sus += sus * (no_nodes + 1) # get 3bot container usage cont2 = Container() cont2.capacity.cpu = THREEBOT_CPU cont2.capacity.memory = THREEBOT_MEMORY cont2.capacity.disk_size = THREEBOT_DISK cont2.capacity.disk_type = DiskType.SSD n_cus, n_sus, _ = get_cloud_units(cont2) all_cus += n_cus all_sus += n_sus # create empty pool and get the payment amount zos = j.sals.zos.get() farm_name = farm_name or zos._explorer.farms.list()[0].name pool = zos.pools.create(round(all_cus), round(all_sus), round(all_ipv4us), farm_name) amount = pool.escrow_information.amount total_amount_dec = Decimal(amount) / Decimal(1e7) total_amount = "{0:f}".format(total_amount_dec) return total_amount
def calculate_vdc_price(flavor, farm_name=None): """calculate the workloads price for vdcs in TFT Args: flavor (str): vdc flavor in [silver, gold, platinum, diamond] Returns: str: vdc price """ all_cus = 0 all_sus = 0 all_ipv4us = 0 # get the flavor enum for item in VDC_SIZE.VDC_FLAVORS.keys(): if flavor == item.value: flavor = item # calculate cloud units enough for a month def get_cloud_units(workload): ru = workload.resource_units() cloud_units = ru.cloud_units() return ( cloud_units.cu * 60 * 60 * 24 * 30, cloud_units.su * 60 * 60 * 24 * 30, cloud_units.ipv4u * 60 * 60 * 24 * 30, ) # get zdbs usage zdb = ZdbNamespace() zdb.size = VDC_SIZE.S3_ZDB_SIZES[VDC_SIZE.VDC_FLAVORS[flavor]["s3"] ["size"]]["sru"] zdb.disk_type = DiskType.HDD _, sus, _ = get_cloud_units(zdb) all_sus += sus # get kubernetes controller usage master_size = VDC_SIZE.VDC_FLAVORS[flavor]["k8s"]["controller_size"] k8s = K8s() k8s.size = master_size.value cus, sus, ipv4us = get_cloud_units(k8s) all_cus += cus all_sus += sus all_ipv4us += 60 * 60 * 24 * 30 # hardcoded since not calculated in the explorer client # get kubernetes workers usage no_nodes = VDC_SIZE.VDC_FLAVORS[flavor]["k8s"]["no_nodes"] worker_size = VDC_SIZE.VDC_FLAVORS[flavor]["k8s"]["size"] k8s = K8s() k8s.size = worker_size.value cus, sus, ipv4us = get_cloud_units(k8s) all_cus += cus * (no_nodes) all_sus += sus * (no_nodes) # get 3bot container usage cont2 = Container() cont2.capacity.cpu = THREEBOT_CPU cont2.capacity.memory = THREEBOT_MEMORY cont2.capacity.disk_size = THREEBOT_DISK cont2.capacity.disk_type = DiskType.SSD n_cus, n_sus, _ = get_cloud_units(cont2) all_cus += n_cus all_sus += n_sus # etcd containers usage etcd_cont = Container() etcd_cont.capacity.cpu = ETCD_CPU etcd_cont.capacity.memory = ETCD_MEMORY etcd_cont.capacity.disk_size = ETCD_DISK etcd_cont.capacity.disk_type = DiskType.SSD n_cus, n_sus, _ = get_cloud_units(etcd_cont) all_cus += n_cus * ETCD_CLUSTER_SIZE all_sus += n_sus * ETCD_CLUSTER_SIZE zos = j.sals.zos.get() farm_prices = zos._explorer.farms.get_deal_for_threebot( 1, j.core.identity.me.tid)["custom_cloudunits_price"] total_amount = zos._explorer.prices.calculate(cus=all_cus, sus=all_sus, ipv4us=all_ipv4us, farm_prices=farm_prices) total_amount = round(total_amount, 6) return total_amount
def setUpClass(cls): super().setUpClass() cls._import_wallet("demos_wallet") no_deployment = "single" cls.flavor = "platinum" if cls.no_deployment == "single": cls.kube_config = cls.deploy_vdc() else: cls.kube_config = cls.deploy_vdc(hours=2) if not cls.kube_config: raise RuntimeError("VDC is not deployed") j.sals.fs.copy_file( f"{j.sals.fs.home()}/sandbox/cfg/vdc/kube/{cls.vdc.owner_tname}/{cls.vdc.vdc_name}.yaml", j.sals.fs.expanduser("~/.kube/config"), ) cls.kube_manager = j.sals.kubernetes.Manager() cls.info( "Check that resources are available and ready in 5 min maximum") cls.kube_monitor = cls.vdc.get_kubernetes_monitor() has_resource = False resources_expiry = j.data.time.now().timestamp + 300 while j.data.time.now( ).timestamp < resources_expiry and not has_resource: res = cls.kube_monitor.fetch_resource_reservations() for node in res: if node.has_enough_resources(1000, 1024): cls.info("Cluster resources are ready") has_resource = True break cls.info("Check that traefik is ready in 5 min maximum") is_traefik_ready = False traefik_expiry = j.data.time.now().timestamp + 300 while j.data.time.now( ).timestamp < traefik_expiry and not is_traefik_ready: rc, system_pods, err = cls.kube_manager._execute( f"kubectl get pods --kubeconfig {cls.kube_manager.config_path} --namespace kube-system | grep traefik" ) running = system_pods.count("Running") + system_pods.count( "Completed") all = len(system_pods.splitlines()) if running == all: cls.info("Traefik is ready") is_traefik_ready = True break assert is_traefik_ready == True, "Traefik not ready within 5 minutes" # Add tokens needed in case of extending the cluster automatically. kubernetes = K8s() kubernetes.size = VDC_SIZE.K8SNodeFlavor.MEDIUM.value # It will be deployed for an hour. zos = j.sals.zos.get() farm_name = cls.get_farm_name() farm_id = zos._explorer.farms.get(farm_name=farm_name).id price = j.tools.zos.consumption.cost( kubernetes, 60 * 60, farm_id) + TRANSACTION_FEES # transactions fees. cls.vdc.transfer_to_provisioning_wallet(round(price, 6), "test_wallet") # Timeout for any exposed solution to be reachable and certified. cls.timeout = 60 # Accept Marketplace T&C for testing identity. ChatflowsBase.tname = cls.tname ChatflowsBase.accept_terms_conditions(type_="marketplace")