Refactor of Remote Instance client
* Moved instance client into Compute namespace * Moved ping client into Compute namespace * Added instance client base class * Simplified functionality provided by instance client * (unrelated change but necessary to test this code) Added config for splitting ephemeral disks Change-Id: I2ed044436d3c4751afa79e5ba140e160ee67edc1
This commit is contained in:
parent
0de5137a1e
commit
356cf1cde5
|
@ -0,0 +1,15 @@
|
|||
"""
|
||||
Copyright 2013 Rackspace
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
"""
|
|
@ -0,0 +1,61 @@
|
|||
"""
|
||||
Copyright 2013 Rackspace
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
"""
|
||||
|
||||
import platform
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
from IPy import IP
|
||||
|
||||
|
||||
class PingClient(object):
|
||||
|
||||
PING_IPV4_COMMAND_LINUX = 'ping -c 3'
|
||||
PING_IPV6_COMMAND_LINUX = 'ping6 -c 3'
|
||||
PING_IPV4_COMMAND_WINDOWS = 'ping'
|
||||
PING_IPV6_COMMAND_WINDOWS = 'ping -6'
|
||||
PING_PACKET_LOSS_REGEX = '(\d{1,3})\.?\d*\%.*loss'
|
||||
|
||||
@classmethod
|
||||
def ping(cls, ip):
|
||||
"""
|
||||
@summary: Ping a server with a IP
|
||||
@param ip: IP address to ping
|
||||
@type ip: string
|
||||
@return: True if the server was reachable, False otherwise
|
||||
@rtype: bool
|
||||
"""
|
||||
address = IP(ip)
|
||||
ip_address_version = address.version()
|
||||
os_type = platform.system().lower()
|
||||
ping_ipv4 = (cls.PING_IPV4_COMMAND_WINDOWS if os_type == 'windows'
|
||||
else cls.PING_IPV4_COMMAND_LINUX)
|
||||
ping_ipv6 = (cls.PING_IPV6_COMMAND_WINDOWS if os_type == 'windows'
|
||||
else cls.PING_IPV6_COMMAND_LINUX)
|
||||
ping_command = ping_ipv6 if ip_address_version == 6 else ping_ipv4
|
||||
command = '{command} {address}'.format(
|
||||
command=ping_command, address=ip)
|
||||
process = subprocess.Popen(command, shell=True,
|
||||
stdout=subprocess.PIPE)
|
||||
process.wait()
|
||||
try:
|
||||
packet_loss_percent = re.search(
|
||||
cls.PING_PACKET_LOSS_REGEX,
|
||||
process.stdout.read()).group(1)
|
||||
except Exception:
|
||||
# If there is no match, fail
|
||||
return False
|
||||
return packet_loss_percent != '100'
|
|
@ -0,0 +1,15 @@
|
|||
"""
|
||||
Copyright 2013 Rackspace
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
"""
|
|
@ -0,0 +1,59 @@
|
|||
import abc
|
||||
|
||||
from cafe.engine.clients.base import BaseClient
|
||||
|
||||
|
||||
class RemoteInstanceClient(BaseClient):
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
def get_hostname(self):
|
||||
"""Returns the machine's hostname."""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_allocated_ram(self):
|
||||
"""Returns the amount of RAM in megabytes."""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_disk_size(self, disk_path):
|
||||
"""Returns the size of the disk in gigabytes."""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_number_of_cpus(self):
|
||||
"""Returns the number of CPUs the remote machine has."""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_uptime(self):
|
||||
"""Returns the amount of time since the last reboot."""
|
||||
raise NotImplementedError
|
||||
|
||||
def create_directory(self, path):
|
||||
"""Creates a directory at the given path."""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_all_disks(self):
|
||||
"""Returns a list of the physical disks available to the server."""
|
||||
raise NotImplementedError
|
||||
|
||||
def mount_disk(self, source_path, destination):
|
||||
"""Mounts a disk to a given path."""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_directory_details(self, dir_path):
|
||||
"""Returns data about the given directory."""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_file_details(self, file_path):
|
||||
"""Returns the permissions and contents of a file."""
|
||||
raise NotImplementedError
|
||||
|
||||
def is_file_present(self, file_path):
|
||||
"""Verifies that the file at the given path exists."""
|
||||
raise NotImplementedError
|
||||
|
||||
def is_directory_present(self, dir_path):
|
||||
"""Verifies that the given directory exists."""
|
||||
raise NotImplementedError
|
||||
|
||||
def can_authenticate(self):
|
||||
"""Verifies a remote connection can be made to the server."""
|
||||
raise NotImplementedError
|
|
@ -0,0 +1,15 @@
|
|||
"""
|
||||
Copyright 2013 Rackspace
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
"""
|
|
@ -0,0 +1,15 @@
|
|||
"""
|
||||
Copyright 2013 Rackspace
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
"""
|
|
@ -0,0 +1,355 @@
|
|||
"""
|
||||
Copyright 2013 Rackspace
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
"""
|
||||
|
||||
import re
|
||||
import time
|
||||
|
||||
from cafe.common.reporting import cclogging
|
||||
from cafe.engine.clients.remote_instance.models.dir_details \
|
||||
import DirectoryDetails
|
||||
from cafe.engine.clients.remote_instance.exceptions \
|
||||
import DirectoryNotFoundException
|
||||
from cafe.engine.clients.remote_instance.models.file_details \
|
||||
import FileDetails
|
||||
from cafe.engine.clients.ssh import SSHAuthStrategy, SSHBehaviors
|
||||
from cloudcafe.compute.common.clients.ping import PingClient
|
||||
from cloudcafe.compute.common.clients.remote_instance.base_client import \
|
||||
RemoteInstanceClient
|
||||
from cloudcafe.compute.common.exceptions import FileNotFoundException, \
|
||||
ServerUnreachable, SshConnectionException
|
||||
|
||||
|
||||
class LinuxClient(RemoteInstanceClient):
|
||||
|
||||
def __init__(self, ip_address=None, username='root', password=None,
|
||||
key=None, connection_timeout=600, retry_interval=10):
|
||||
self.client_log = cclogging.getLogger(
|
||||
cclogging.get_object_namespace(self.__class__))
|
||||
|
||||
if ip_address is None:
|
||||
raise ServerUnreachable("None")
|
||||
self.ip_address = ip_address
|
||||
self.username = username
|
||||
self.password = password
|
||||
|
||||
# Verify the server can be pinged before attempting to connect
|
||||
start = int(time.time())
|
||||
reachable = False
|
||||
while not reachable:
|
||||
reachable = PingClient.ping(ip_address)
|
||||
if reachable:
|
||||
break
|
||||
time.sleep(retry_interval)
|
||||
if int(time.time()) - start >= connection_timeout:
|
||||
raise ServerUnreachable(ip_address)
|
||||
|
||||
if key is not None:
|
||||
auth_strategy = SSHAuthStrategy.KEY_STRING
|
||||
else:
|
||||
auth_strategy = SSHAuthStrategy.PASSWORD
|
||||
|
||||
self.ssh_client = SSHBehaviors(
|
||||
username=self.username, password=self.password,
|
||||
host=self.ip_address, tcp_timeout=20, auth_strategy=auth_strategy,
|
||||
look_for_keys=False, key=key)
|
||||
self.ssh_client.connect_with_timeout(
|
||||
cooldown=20, timeout=connection_timeout)
|
||||
if not self.ssh_client.is_connected():
|
||||
message = ('SSH timeout after {timeout} seconds: '
|
||||
'Could not connect to {ip_address}.')
|
||||
raise SshConnectionException(message.format(
|
||||
timeout=connection_timeout, ip_address=ip_address))
|
||||
|
||||
def can_authenticate(self):
|
||||
"""
|
||||
Verifies that a connection was made to the remote server
|
||||
|
||||
@return: Whether the connection was successful
|
||||
@rtype: bool
|
||||
"""
|
||||
|
||||
return self.ssh_client.is_connected()
|
||||
|
||||
def get_hostname(self):
|
||||
"""
|
||||
Gets the host name of the server
|
||||
|
||||
@return: The host name of the server
|
||||
@rtype: string
|
||||
"""
|
||||
|
||||
output = self.ssh_client.execute_command("hostname")
|
||||
if output:
|
||||
return output.stdout.rstrip()
|
||||
|
||||
def get_allocated_ram(self):
|
||||
"""
|
||||
Returns the amount of RAM the server has
|
||||
|
||||
@return: The RAM size in MB
|
||||
@rtype: string
|
||||
"""
|
||||
|
||||
output = self.ssh_client.execute_command('free -m | grep Mem')
|
||||
if output:
|
||||
return output.stdout.split()[1]
|
||||
|
||||
def get_disk_size(self, disk_path):
|
||||
"""
|
||||
Returns the size of a given disk
|
||||
|
||||
@return: The disk size in GB
|
||||
@rtype: int
|
||||
"""
|
||||
|
||||
disks = self.get_all_disks()
|
||||
return disks.get(disk_path)
|
||||
|
||||
def get_number_of_cpus(self):
|
||||
"""
|
||||
Return the number of CPUs assigned to the server
|
||||
|
||||
@return: The number of CPUs a server has
|
||||
@rtype: int
|
||||
"""
|
||||
|
||||
command = 'cat /proc/cpuinfo | grep processor | wc -l'
|
||||
output = self.ssh_client.execute_command(command)
|
||||
if output:
|
||||
return int(output.stdout)
|
||||
|
||||
def get_uptime(self):
|
||||
"""
|
||||
Get the uptime time of the server.
|
||||
|
||||
@return: The uptime of the server in seconds
|
||||
@rtype: int
|
||||
"""
|
||||
|
||||
result = self.ssh_client.execute_command('cat /proc/uptime')
|
||||
if result:
|
||||
uptime = float(result.stdout.split(' ')[0])
|
||||
return uptime
|
||||
|
||||
def create_file(self, file_name, file_content, file_path=None):
|
||||
"""
|
||||
Creates a new file with the provided content.
|
||||
|
||||
@param file_name: File name
|
||||
@type file_name: string
|
||||
@param file_content: File content
|
||||
@type file_content: String
|
||||
@rtype: FileDetails
|
||||
"""
|
||||
|
||||
if file_path is None:
|
||||
file_path = "/root/{file_name}".format(file_name=file_name)
|
||||
self.ssh_client.execute_command(
|
||||
'echo -n {file_content} >> {file_path}'.format(
|
||||
file_content=file_content, file_name=file_name))
|
||||
return FileDetails("644", file_content, file_path)
|
||||
|
||||
def get_file_details(self, file_path):
|
||||
"""
|
||||
Retrieves the contents of a file and its permissions.
|
||||
|
||||
@param file_path: Path to the file
|
||||
@type file_path: string
|
||||
@return: File details including permissions and content
|
||||
@rtype: FileDetails
|
||||
"""
|
||||
|
||||
command = ('[ -f {file_path} ] && echo "File exists" || '
|
||||
'echo "File does not exist"'.format(file_path=file_path))
|
||||
output = self.ssh_client.execute_command(command)
|
||||
if output is None:
|
||||
return None
|
||||
output = output.stdout
|
||||
|
||||
if not output.rstrip('\n') == 'File exists':
|
||||
raise FileNotFoundException(
|
||||
"File {file_path} not found on instance.".format(
|
||||
file_path=file_path))
|
||||
|
||||
file_permissions = self.ssh_client.execute_command(
|
||||
'stat -c %a {file_path}'.format(
|
||||
file_path=file_path)).stdout.rstrip("\n")
|
||||
file_contents = self.ssh_client.execute_command(
|
||||
'cat {file_path}'.format(file_path=file_path)).stdout
|
||||
return FileDetails(file_permissions, file_contents, file_path)
|
||||
|
||||
def is_file_present(self, file_path):
|
||||
"""
|
||||
Verifies if the given file is present.
|
||||
|
||||
@param file_path: Path to the file
|
||||
@type file_path: string
|
||||
@return: True if File exists, False otherwise
|
||||
@rtype: bool
|
||||
"""
|
||||
|
||||
command = ('[ -f {file_path} ] && echo "File exists" || '
|
||||
'echo "File does not exist"'.format(file_path=file_path))
|
||||
output = self.ssh_client.execute_command(command).stdout
|
||||
if output:
|
||||
return output.rstrip('\n') == 'File exists'
|
||||
|
||||
def mount_disk(self, source_path, destination_path):
|
||||
"""
|
||||
Mounts a disk to specified destination.
|
||||
|
||||
@param source_path: Path to file source
|
||||
@type source_path: string
|
||||
@param destination_path: Path to mount destination
|
||||
@type destination_path: string
|
||||
"""
|
||||
|
||||
self.ssh_client.execute_command(
|
||||
'mount {source_path} {destination_path}'.format(
|
||||
source_path=source_path, destination_path=destination_path))
|
||||
|
||||
def get_xen_user_metadata(self):
|
||||
"""
|
||||
Retrieves the user-metadata section from the XenStore.
|
||||
|
||||
@return: The contents of the user-metadata
|
||||
@rtype: dict
|
||||
"""
|
||||
|
||||
command = 'xenstore-ls vm-data/user-metadata'
|
||||
output = self.ssh_client.execute_command(command)
|
||||
if not output:
|
||||
return None
|
||||
|
||||
output = output.stdout
|
||||
meta_list = output.split('\n')
|
||||
meta = {}
|
||||
for item in meta_list:
|
||||
# Skip any blank lines
|
||||
if item:
|
||||
meta_item = item.split("=")
|
||||
key = meta_item[0].strip()
|
||||
value = meta_item[1].strip('" ')
|
||||
meta[key] = value
|
||||
return meta
|
||||
|
||||
def get_xenstore_disk_config_value(self):
|
||||
"""
|
||||
Returns the XenStore value for disk config.
|
||||
|
||||
@return: Whether the virtual machine uses auto disk config
|
||||
@rtype: bool
|
||||
"""
|
||||
command = 'xenstore-read vm-data/auto-disk-config'
|
||||
output = self.ssh_client.execute_command(command)
|
||||
if output is None:
|
||||
return None
|
||||
output = output.stdout
|
||||
return output.strip().lower() == 'true'
|
||||
|
||||
def create_directory(self, path):
|
||||
"""
|
||||
Creates a directory at the specified path.
|
||||
|
||||
@param path: Directory path
|
||||
@type path: string
|
||||
"""
|
||||
|
||||
command = "mkdir -p {path}".format(path=path)
|
||||
output = self.ssh_client.execute_command(command)
|
||||
if output is None:
|
||||
return None
|
||||
return output.stdout
|
||||
|
||||
def is_directory_present(self, directory_path):
|
||||
"""
|
||||
Check if given directory exists.
|
||||
|
||||
@param directory_path: Path to the directory
|
||||
@type directory_path: string
|
||||
@return: Result of directory check
|
||||
@rtype: bool
|
||||
"""
|
||||
command = ("[ -d {path} ] && echo 'Directory found'"
|
||||
"|| echo 'Directory {path} not found'".format(
|
||||
path=directory_path))
|
||||
|
||||
output = self.ssh_client.execute_command(command)
|
||||
if output is None:
|
||||
return None
|
||||
output = output.stdout
|
||||
return output.rstrip('\n') == 'Directory found'
|
||||
|
||||
def get_directory_details(self, dir_path):
|
||||
"""
|
||||
Retrieves informational data about a directory.
|
||||
|
||||
@param dir_path: Path to the directory
|
||||
@type dir_path: string
|
||||
@return: Directory details
|
||||
@rtype: DirectoryDetails
|
||||
"""
|
||||
output = self.is_directory_present(dir_path)
|
||||
if output is None:
|
||||
raise DirectoryNotFoundException(
|
||||
"Directory: {0} not found.".format(dir_path))
|
||||
dir_permissions = self.ssh_client.execute_command(
|
||||
"stat -c %a {0}".format(dir_path)).stdout.rstrip("\n")
|
||||
dir_size = float(self.ssh_client.execute_command(
|
||||
"du -s {0}".format(dir_path)).stdout.split('\t', 1)[0])
|
||||
return DirectoryDetails(dir_permissions, dir_size, dir_path)
|
||||
|
||||
def get_all_disks(self):
|
||||
"""
|
||||
Returns a list of all block devices for a server.
|
||||
|
||||
@return: The accessible block devices
|
||||
@rtype: dict
|
||||
"""
|
||||
|
||||
disks_raw = self.ssh_client.execute_command('fdisk -l')
|
||||
if disks_raw is None:
|
||||
return None
|
||||
disks_raw = disks_raw.stdout
|
||||
p = re.compile('Disk /dev/\w+: \d+.*')
|
||||
disks_list = p.findall(disks_raw)
|
||||
|
||||
disks = {}
|
||||
for disk in disks_list:
|
||||
items = disk.split()
|
||||
disk_name = items[1].replace(':', '')
|
||||
size = int(items[4])/(1 << 30)
|
||||
disks[disk_name] = size
|
||||
return disks
|
||||
|
||||
def format_disk(self, disk, filesystem_type):
|
||||
"""
|
||||
Formats a disk to the provided filesystem type.
|
||||
|
||||
@param disk: The path to the disk to be formatted
|
||||
@type disk: string
|
||||
@param filesystem_type: The filesystem type to format the disk to
|
||||
@type filesystem_type: string
|
||||
|
||||
@return: Output of command execution
|
||||
@rtype: string
|
||||
"""
|
||||
|
||||
out = self.ssh_client.execute_command('mkfs -t {type} {disk}'.format(
|
||||
type=filesystem_type, disk=disk))
|
||||
if out is None:
|
||||
return None
|
||||
return out.stdout
|
|
@ -0,0 +1,15 @@
|
|||
"""
|
||||
Copyright 2013 Rackspace
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
"""
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
import time
|
||||
|
||||
from cafe.engine.behaviors import BaseBehavior
|
||||
from cloudcafe.compute.common.clients.remote_instance.linux.linux_client import LinuxClient
|
||||
from cafe.engine.clients.remote_instance.instance_client import \
|
||||
InstanceClientFactory
|
||||
from cloudcafe.compute.common.types import InstanceAuthStrategies
|
||||
|
@ -233,13 +234,15 @@ class ServerBehaviors(BaseBehavior):
|
|||
password = server.admin_pass
|
||||
|
||||
# (TODO) dwalleck: Remove hard coding of distro
|
||||
return InstanceClientFactory.get_instance_client(
|
||||
ip_address=ip_address, username=username, password=password,
|
||||
os_distro='linux', config=config)
|
||||
return LinuxClient(ip_address=ip_address, username='root', password=password)
|
||||
#return InstanceClientFactory.get_instance_client(
|
||||
# ip_address=ip_address, username=username, password=password,
|
||||
# os_distro='linux', config=config)
|
||||
else:
|
||||
return InstanceClientFactory.get_instance_client(
|
||||
ip_address=ip_address, username=username, os_distro='linux',
|
||||
config=config, key=key)
|
||||
return LinuxClient(ip_address=ip_address, username='root', key=key)
|
||||
#return InstanceClientFactory.get_instance_client(
|
||||
# ip_address=ip_address, username=username, os_distro='linux',
|
||||
# config=config, key=key)
|
||||
|
||||
def resize_and_await(self, server_id, new_flavor):
|
||||
"""
|
||||
|
@ -329,4 +332,4 @@ class ServerBehaviors(BaseBehavior):
|
|||
assert resp.status_code is 202
|
||||
resp = self.wait_for_server_status(server_id,
|
||||
ServerStates.ACTIVE)
|
||||
return resp.entity
|
||||
return resp.entity
|
|
@ -26,6 +26,23 @@ class ServersConfig(ConfigSectionInterface):
|
|||
"""Strategy to use for authenticating to an instance (password|key)"""
|
||||
return self.get("instance_auth_strategy")
|
||||
|
||||
@property
|
||||
def split_ephemeral_disk_enabled(self):
|
||||
"""
|
||||
Enable if splitting of ephemeral disks (limiting of the disk
|
||||
size and splitting into multiple disks if necessary) is enabled.
|
||||
"""
|
||||
return self.get_boolean("split_ephemeral_disk_enabled", False)
|
||||
|
||||
@property
|
||||
def ephemeral_disk_max_size(self):
|
||||
"""
|
||||
If ephemeral disk splitting is enabled, this is the maximum
|
||||
size of an ephemeral disk. If this value is less than the
|
||||
requested ephemeral disk, multiple disks will be created.
|
||||
"""
|
||||
return int(self.get("ephemeral_disk_max_size", 0))
|
||||
|
||||
@property
|
||||
def disk_config_override(self):
|
||||
"""Optional override for the disk_config parameter (all actions)"""
|
||||
|
|
Loading…
Reference in New Issue