vcpsdk.plugins.chameleon_ext のソースコード

#
# Chamelenon Extentsions
#

import json
import os
import subprocess
import time
import uuid
import pprint
import datetime

from vcpcli.vault import VaultClient  # noqa
from vcpsdk.plugins.ext import VcpExtResource
from vcpsdk.vcp_tools import VcpSDKError

DEFAULT_VCC_PORT = 443
DEFAULT_VAULT_PORT = 8443

# 最終的にTrue
VERIFY = True

BUILD_VERSION = "20200831"


[ドキュメント] class VcpChameleonOpenrc(VcpExtResource): """ Chamelenon Extentsions の BASEクラス (内部クラスであるためAPI上は無視してよい) """ version = BUILD_VERSION def __init__(self, provider_name, config_dir, token, verbose=0): super().__init__(provider_name, config_dir, token) # debug output self._verbose = verbose # vaultはssl 固定とする self._vcc_host = self._vcp_config["vcc"]["host"] self._vcc_port = self._vcp_config["vcc"].get("vcc_port", DEFAULT_VCC_PORT) self._vault_port = self._vcp_config["vcc"].get("vault_port", DEFAULT_VAULT_PORT) # vault client 接続 self.vault_cli = VaultClient( vcc_host=self._vcc_host, token=self._token, no_verify=False ) def verbose(self, verbose): """ Chameleon extension の verbose :param verbose: > 0 でdebug出力 """ self._verbose = verbose def set_property(self, vpn_catalog): """ Chameleon 用のコマンド実行するためのパラメータ設定 :param vpn_catalog: = vcpsdk.get_vpn_catalog() で取得できるVPNカタログ情報 """ self._auth_url = vpn_catalog["chameleon_auth_url"] self._region = vpn_catalog["chameleon_region"] # TODO; interfaceの項目と値を確認 self._interface = vpn_catalog["chameleon_interface"] self._identity_api_version = vpn_catalog["chameleon_identity_api_version"] def local_exec(self, cmd, env=None): """ Chameleon 用のコマンド実行 :param cmd: コマンドライン文字列 :param env: 環境変数 """ # TODO maskする (ストレージアカウントKey) if self._verbose: print("--- command ---") print(" ".join(cmd)) proc = subprocess.Popen( args=cmd, env=env, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) (stdout, stderr) = proc.communicate() if proc.returncode != 0: raise Exception( "stdout = %s\nstderr = %s" % (stdout.decode("utf-8"), stderr.decode("utf-8")) ) if self._verbose: try: print(json.loads(stdout)) if stderr and len(stderr) > 0: print(json.loads(stderr)) except Exception: pprint.pprint(stdout) pprint.pprint(stderr) return (stdout, stderr) # TODO: openstackでの動作確認 # blazar cli起動時の環境変数の設定 def cli_env(self): """ Chameleon 用のコマンド実行時の環境変数設定 """ child_env = os.environ.copy() child_env["OS_AUTH_URL"] = self._auth_url child_env["OS_AUTH_TYPE"] = "v3applicationcredential" child_env["OS_REGION_NAME"] = self._region child_env["OS_INTERFACE"] = self._interface child_env["OS_IDENTITY_API_VERSION"] = self._identity_api_version # vcp_config.yml から chameleon のクレデンシャル情報を取得 chameleon_config = self._vcp_config.get("chameleon") child_env[ "OS_APPLICATION_CREDENTIAL_ID" ] = self.vault_cli.read_cloud_params_value( chameleon_config["application_credential_id"] ) child_env[ "OS_APPLICATION_CREDENTIAL_SECRET" ] = self.vault_cli.read_cloud_params_value( chameleon_config["application_credential_secret"] ) return child_env def __str__(self): child_env = self.cli_env() text = """ OS_AUTH_URL: {OS_AUTH_URL} OS_INTERFACE: {OS_INTERFACE} OS_IDENTITY_API_VERSION: {OS_IDENTITY_API_VERSION} OS_APPLICATION_CREDENTIAL_ID: {OS_APPLICATION_CREDENTIAL_ID} OS_APPLICATION_CREDENTIAL_SECRET: ****""".format_map( child_env ) return text
[ドキュメント] class VcpExtResourceChameleonNetwork(VcpChameleonOpenrc): """ chameleon上でvlanセグメントを予約し、ネットワークを構築する。sharedwan1 を使用する場合は不要。 サンプルコード .. code-block:: python # vpnカタログ情報を取得 vpn_catalog_name = "default" vpn_catalog = vcpsdk.get_vpn_catalog("chameleon", catalog_name=vpn_catalog_name) # Chameleon Network の予約用 extension を生成 network_ext = vcpsdk.get_ext("chameleon_network") network_ext.setup(vpn_catalog) # 期間 %Y-%m-d %H:%M (省略可) を指定して、Chameleon Network を予約 # start_date = start_dt.strftime("%Y-%m-%d %H:%M") network_ext.create_network(start_date=start_date, end_date=end_date) """
[ドキュメント] def setup(self, vpn_catalog): """ chameleon のネットワークの予約情報を作成する :param vpn_catalog: VPNカタログ情報 """ self._vpn_catalog = vpn_catalog self.set_property(vpn_catalog) # TODO; VCコントローラID, VPNカタログ名 # network name + xxx (network_nameがほかVCコントローラとかぶらない前提) self._network_lease_name = "{}-net-lease".format(self.network_name)
[ドキュメント] def network_exists(self): self.set_property(self._vpn_catalog) network_name = self.network_name cmd = ["openstack", "network", "list", "-f", "json"] (stdout, stderr) = self.local_exec(cmd, env=self.cli_env()) network = json.loads(stdout) return network_name in [x["Name"] for x in network]
@property def network_name(self): return self._vpn_catalog.get("chameleon_network_name")
[ドキュメント] def create_network(self, start_date=None, end_date=None): """ vlanセグメントを予約し、予約できた場合にネットワーク、サブネットを作成する。 """ self.set_property(self._vpn_catalog) network_name = self.network_name if self.network_exists(): VcpSDKError("network {} already exists".format(network_name)) network_spec = "resource_type=network,network_name={}".format(network_name) # vlanの予約 # TODO: VLAN IDの固定 # network_spec = 'resource_type=network,network_name={},resource_properies["==", "$segment_id","{}"]'.format( # catalog["chameleon_network_name"], catalog["chameleon_network_segment_id"] # ) date_opt = [] if start_date is not None: date_opt.append("--start-date={}".format(start_date)) if end_date is not None: date_opt.append("--end-date={}".format(end_date)) # TODO: 予約があってネットワークがないという自体を考慮して、予約が有るかチェック? # 成功するとネットワークが作成される cmd = ( ["blazar", "lease-create", "-f", "json"] + date_opt + ["--reservation", network_spec, self._network_lease_name] ) (stdout, stderr) = self.local_exec(cmd, env=self.cli_env()) # TODO; get reservation id # TODO: networkのleaseのstatus確認 for _i in range(0, 20): cmd = ["openstack", "network", "list", "-f", "json"] (stdout, stderr) = self.local_exec(cmd, env=self.cli_env()) network = json.loads(stdout) if network_name in [x["Name"] for x in network]: break time.sleep(10) # openstackのサブネットの作成 # https://chameleoncloud.readthedocs.io/en/latest/technical/networks/networks_vlan.html?highlight=subnet%20create # "--dhcp", # TODO if enabled in catalog cmd = [ "openstack", "subnet", "create", "-f", "json", "--subnet-range", self._vpn_catalog.get("private_network_ipmask"), "--network", network_name, self._vpn_catalog.get("chameleon_subnet_name"), ] (stdout, stderr) = self.local_exec(cmd, env=self.cli_env())
# TODO; get lease_id # TODO: 作成できたかどうかを返す
[ドキュメント] class VcpExtResourceChameleonHostLeaseInfo(VcpChameleonOpenrc): """ Chameleon クラウドインスタンスの予約結果 サンプルコード .. code-block:: python # lease idを使って情報取得 info_ext = vcpsdk.get_extension("chameleon_host_lease_info") info_ext.setup(lease_info.host_lease_id, vpn_catalog) print(info_ext) print("host_lease_id is {}".format(info_ext.host_lease_id)) print("reservation_id is {}".format(info_ext.reservation_id)) print("status is {}".format(info_ext.status)) # 予約の延長 info_ext.extend() # 予約の削除 info_ext.delete() # 予約の完了まで待つ info_ext.wait() """
[ドキュメント] def setup(self, host_lease_id, vpn_catalog): """ chameleon のホストの予約情報を設定する :param host_lease_id: ホストの予約情報識別子 :param vpn_catalog: VPNカタログ情報 """ self._host_lease_id = host_lease_id self._reservation_id = None self._vpn_catalog = vpn_catalog self.set_property(vpn_catalog) self.update()
[ドキュメント] def update(self): """ ホストの予約情報を再取得する。 """ cmd = ["blazar", "lease-show", "-f", "json", self._host_lease_id] (stdout, stderr) = self.local_exec(cmd, env=self.cli_env()) info = json.loads(stdout) if self._verbose: print(json.dumps(info, indent=2)) reservation_info = json.loads(info["reservations"]) self._reservation_id = reservation_info["id"] self._start_date = info["start_date"] self._end_date = info["end_date"] self._status = info["status"] self._updated_at = info["updated_at"]
def __str__(self): return "lease_id: {}\n reservation_id: {}\n state: {}\nstart_date: {}\nend_date {}\nupdated_at:{}".format( self._host_lease_id, self._reservation_id, self._status, self._start_date, self._end_date, self._updated_at, ) # getter @property def reservation_id(self): """ reservation IDを返す。CCIのreservationに指定する。 """ return self._reservation_id # getter @property def host_lease_id(self): """ lease IDを返す。 """ return self._host_lease_id # getter @property def status(self): """ lease状態を返す :return: leaseの状態 - ``STARTING`` 予約情報を作成中 - ``PENDING`` 予約開始時刻が到来していない - ``ACTIVE`` 予約開始時刻が到来した - ``ERROR`` エラーが発生した """ return self._status
[ドキュメント] def extend(self, weeks=None, days=None, hours=None): """ 予約を延長する。 weeks, days, hours のどれか一つを選んで指定する。複数の引数が指定された場合、単位が長い方の指定を優先する。 引数を指定しない場合、1日予約を延長する chameleonの仕様により、予約の延長ができるのは予約が切れる48時間まえ。 その時間外に呼ぶと予約延長操作は失敗する。 予約に失敗した場合は例外を送出する。 :param weeks: 延長する週 :param days: 延長する日数 :param hours: 延長する時間 """ delta_str = "" if weeks is not None: delta_str = "{}w".format(weeks) elif days is not None: delta_str = "{}d".format(days) elif hours is not None: delta_str = "{}h".format(hours) else: # default delta_str = "1d" # 予約の切れる48時間前であるかチェック now = datetime.datetime.utcnow() end_date = datetime.datetime.strptime(self._end_date, "%Y-%m-%dT%H:%M:%S.%f") if end_date - datetime.timedelta(hours=48) > now: VcpSDKError( "lease update avairable at {} ".format( end_date - datetime.delta(hours=48) ) ) # blazar lease-update --prolong-for "1d" my-first-lease cmd = [ "blazar", "lease-update", "--prolong-for", delta_str, self._host_lease_id, ] self.local_exec(cmd, env=self.cli_env())
[ドキュメント] def delete(self): """ ホストの予約を削除する。 """ cmd = ["blazar", "lease-delete", self._host_lease_id] (stdout, stderr) = self.local_exec(cmd, env=self.cli_env()) self._host_lease_id = None self._reservation_id = None
[ドキュメント] def wait(self): """ leaseの状態(status)がACTIVEになるまで待つ """ if self._host_lease_id is None: raise Exception("no lease found") # TODO: config for _i in range(0, 20): self.update() if self._status == "ACTIVE": break if self._status not in ["PENDING", "STARTING"]: # reservation error # TODO: エラーの理由を出力する blazarの出力の 最終行をひろう # 例: ERROR: No hosts are available raise Exception( "host reservation is failed, unknown status {}".format(self._status) ) time.sleep(10)
[ドキュメント] class VcpExtResourceChameleon(VcpChameleonOpenrc): """ Chameleon クラウドインスタンスの予約を行うクラス サンプルコード .. code-block:: python # vpnカタログ情報を取得 vpn_catalog_name = "default" vpn_catalog = vcpsdk.get_vpn_catalog("chameleon", catalog_name=vpn_catalog_name) host_ext = vcpsdk.get_extension("chameleon") # 指定可能項目 node_type = "compute_skylake" min_instances = 1 max_instances = 3 host_ext.setup( node_type, start_date=start_date, end_date=end_date, min_instances=min_instances, max_instances=max_instances, lease_name_prefix = "S003", vpn_catalog=vpn_catalog, ) # インスタンスの予約 lease_info = host_ext.reserve_host() lease_info.wait() print("host_lease_id is {}".format(lease_info.host_lease_id)) print("reservation_id is {}".format(lease_info.reservation_id)) print("status is {}".format(lease_info.status)) """
[ドキュメント] def setup( self, node_type, start_date=None, end_date=None, min_instances=1, max_instances=1, vcpus=None, memory_mb=None, disk_gb=None, lease_name_prefix=None, vpn_catalog=None, ): """ chameleon のホストの予約情報を作成する (実際の予約処理は reserve_host関数で行う) :param node_type: chameleon 上での node_type :param start_date: 予約の開始時刻 (タイムゾーンはUTCで ``YYYY-mm-dd HH:MM`` 形式) Noneを指定した場合は、呼び出し時の時刻が使われる。 :param end_date: 予約の終了時刻 (タイムゾーンはUTCで ``YYYY-mm-dd HH:MM`` 形式) Noneを指定した場合は、1日後 :param min_instances: 最小台数 :param max_instances: 最大台数 :param vcpus: VCPU数 future use :param memory_mb: メモリ容量(MBytes) future use :param disk_gb: Disk容量(GBytes) future use :param lease_name_prefix: Chameleon のlease 名に付けるprefix :param vpn_catalog: VPNカタログ情報 .. note:: vcpus, memory_mb, disk_gb の指定は現状の実装では、無視する。 """ self._vpn_catalog = vpn_catalog self.set_property(vpn_catalog) self._node_type = node_type self._start_date = start_date self._end_date = end_date self._min_instances = min_instances self._max_instances = max_instances # TODO: 調査 self._vcpus = vcpus # TODO: 調査 self._memory_mb = memory_mb # TODO: 調査 self._disk_gb = disk_gb # TODO: VC id + unit名 でつける, 連想できたほうが良い # 予約が先になるのでVC id, Unit名はつけられない prefix = uuid.uuid4().hex[:10] if lease_name_prefix is not None: self._host_lease_name = "{}-{}-host".format(lease_name_prefix, prefix) else: self._host_lease_name = "{}-host".format(prefix) self._reservation_id = None self._host_lease_id = None
[ドキュメント] def reserve_host(self): """ クラウドインスタンスの予約を実行する。 :return: クラウドインスタンスの予約結果 ( :class:`vcpsdk.plugins.chameleon_ext.VcpExtResourceChameleonHostLeaseInfo` ) """ # TODO: hypervisor propertiesを引数で取るようにする $vcpu, $memory_mb, $disk_gb resource_spec_detail = [["=", "$node_type", self._node_type]] hypervisor_spec_detail = [] # 条件を指定しても効果が有るのか不明なためコメントアウト # if self._vcpus is not None: # # TODO; 不等号固定で良いか? # hypervisor_spec_detail.append([">=", "$vcpu", str(self._vcpu)]) # if self._memory_mb is not None: # # TODO; 不等号固定で良いか? # hypervisor_spec_detail.append([">=", "$memory_mb", str(self._memory_mb)]) # if self._disk_gb is not None: # disk_bytes = 1024 * 1024 * 1024 * self._disk_gb # resource_spec_detail.append( # [">=", "$storage_devices.0.size", str(disk_bytes)] # ) # if self._disk_gb is not None: # # TODO; 不等号固定で良いか? # spec_detail.append(['>=', '$disk_gb', str(self._disk_gb)]) # TODO: andは3つ以上の条件を取れるか確認 # TODO: 取れない場合は、木にする if len(hypervisor_spec_detail) == 0: hypervisor_spec = "" elif len(hypervisor_spec_detail) == 1: hypervisor_spec = "hypervisor_properties={},".format( json.dumps(hypervisor_spec_detail[0]) ) else: # 複数条件をandで結合する hypervisor_spec = "hypervisor_properties={},".format( json.dumps(["and"] + hypervisor_spec_detail) ) if len(resource_spec_detail) == 0: resource_spec = "" elif len(resource_spec_detail) == 1: resource_spec = "resource_properties={}".format( json.dumps(resource_spec_detail[0]) ) else: # 複数条件をandで結合する resource_spec = "resource_properties={}".format( json.dumps(["and"] + resource_spec_detail) ) chameleon_spec = "min={},max={},{}{}".format( self._min_instances, self._max_instances, hypervisor_spec, resource_spec ) date_opt = [] if self._start_date is not None: date_opt.append("--start-date={}".format(self._start_date)) if self._end_date is not None: date_opt.append("--end-date={}".format(self._end_date)) cmd = ( ["blazar", "lease-create", "-f", "json",] + date_opt + ["--physical-reservation", chameleon_spec, self._host_lease_name] ) (stdout, stderr) = self.local_exec(cmd, env=self.cli_env()) # Createdというjsonではない行(1行)を消す stdout = "\n".join(stdout.decode("utf-8").split("\n")[1:]) lease = json.loads(stdout) self._host_lease_id = lease["id"] self._host_lease_info = VcpExtResourceChameleonHostLeaseInfo( self._provider_name, self._config_dir, self._token ) self._host_lease_info.setup(self._host_lease_id, self._vpn_catalog) self._host_lease_info.update() return self._host_lease_info
def __str__(self): text = super().__str__() text += """ node_type: {node_type} start_date: {start_date} end_date: {end_date} min_instances: {min_instances} max_instances: {max_instances} vcpus: {vcpus} memory_mb: {memory_mb} MB disk_gb: {disk_gb} GB host_lease_name: {host_lease_name} reservation_id: {reservation_id} host_lease_id: {host_lease_id}""".format( node_type=self._node_type, start_date=self._start_date, end_date=self._end_date, min_instances=self._min_instances, max_instances=self._max_instances, vcpus=self._vcpus, memory_mb=self._memory_mb, disk_gb=self._disk_gb, host_lease_name=self._host_lease_name, reservation_id=self._reservation_id, host_lease_id=self._host_lease_id, ) return text